ユーザ用ツール

サイト用ツール


youtube:python-mixing-010

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
youtube:python-mixing-010 [2024/02/28 18:26] – 削除 - 外部編集 (不明な日付) 127.0.0.1youtube:python-mixing-010 [2024/02/29 11:56] (現在) – [PyO3でLÖVEもどきを作る (3)] freemikan
行 1: 行 1:
 +====== PyO3でLÖVEもどきを作る (3) ======
 +
 +作成日: 2023-08-16 (水)
 +
 +[[https://youtu.be/gaUlLdn3csU|第10回 (最終回) PyO3でLÖVEもどきを作る (3)]]
 +
 +===== プロジェクト pyove =====
 +
 +==== Cargo.toml ====
 +
 +<file toml>
 +[package]
 +name = "pyove"
 +version = "0.1.0"
 +edition = "2021"
 +
 +[dependencies]
 +sdl2 = "0.35"
 +
 +[dependencies.pyo3]
 +version = "0.19.2"
 +features = ["auto-initialize", "macros"]
 +</file>
 +
 +==== src/main.rs ====
 +
 +<file rust>
 +pub mod graphics;
 +pub mod pyovemodule;
 +pub mod sdl2global;
 +
 +use pyo3::prelude::*;
 +use pyo3::types::PyDict;
 +use sdl2::event::Event;
 +use sdl2global::*;
 +use std::time::{Duration, Instant};
 +use std::{env, path::Path, process::exit};
 +
 +fn main() -> PyResult<()> {
 +    //
 +    // コマンドライン引数のパース
 +    //
 +    let args: Vec<String> = env::args().collect();
 +    if args.len() != 2 {
 +        // コマンドライン引数がただ1つだけ与えられていなければ終了する
 +        eprintln!("Usage: {} GAMEDIR", args[0]);
 +        exit(1);
 +    }
 +
 +    let gamedir = Path::new(&args[1]);
 +    let main_file = gamedir.join("main.py");
 +    if !main_file.exists() {
 +        // main.pyが存在しなければ終了する
 +        eprintln!("Error: {} not exists.", main_file.to_str().unwrap());
 +        exit(2);
 +    }
 +
 +    // main.pyの内容を読み込む
 +    let main_code = std::fs::read_to_string(main_file)?;
 +
 +    // activate Python interpreter
 +    Python::with_gil(|py| {
 +        // ゲームディレクトリをインポートパスに追加する
 +        let sys = PyModule::import(py, "sys")?;
 +        sys.getattr("path")?
 +            .getattr("append")?
 +            .call1((gamedir.to_str().unwrap(),))?;
 +
 +        // pyoveをモジュールとして登録
 +        let pyove = pyovemodule::create_pyove(py)?;
 +        let py_modules: &PyDict = sys.getattr("modules")?.downcast()?;
 +        py_modules.set_item("pyove", pyove)?;
 +
 +        // main.pyを実行
 +        py.run(&main_code, None, None)?;
 +
 +        // これら3つの関数はPython側のコードで置き換え可能
 +        let user_load = pyove.getattr("load")?;
 +        let user_update = pyove.getattr("update")?;
 +        let user_draw = pyove.getattr("draw")?;
 +
 +        user_load.call0()?;
 +
 +        // ゲームループの準備
 +        let mut event_pump = sdl_event_pump();
 +        let nanosecs_per_update = Duration::from_nanos(1_000_000_000 / 60);
 +        let clock = Instant::now();
 +        let mut last_update = clock.elapsed();
 +
 +        // ゲームループ
 +        'gameloop: loop {
 +            let start = clock.elapsed();
 +
 +            // SDLイベント処理
 +            for event in event_pump.poll_iter() {
 +                match event {
 +                    Event::Quit { .. } => break 'gameloop,
 +                    _ => {}
 +                }
 +            }
 +
 +            // アップデート
 +            let delta_time = (start - last_update).as_secs_f32();
 +            user_update.call1((delta_time,))?;
 +
 +            // 描画
 +            sdl_render_set_draw_color((0, 0, 0));
 +            sdl_render_clear();
 +            user_draw.call0()?;
 +            sdl_render_present();
 +
 +            // スリープでフレームレートを同期を強制
 +            let passed = clock.elapsed() - start;
 +            if passed < nanosecs_per_update {
 +                std::thread::sleep(nanosecs_per_update - passed);
 +            }
 +            last_update = start;
 +        }
 +
 +        Ok(())
 +    })
 +}
 +</file>
 +
 +==== src/pyovemodule.rs ====
 +
 +<file rust>
 +use crate::graphics;
 +use pyo3::prelude::*;
 +
 +#[pyfunction]
 +fn load() {
 +    println!("default load");
 +}
 +
 +#[pyfunction]
 +fn draw() {
 +    println!("default draw");
 +}
 +
 +#[pyfunction]
 +fn update(dt: f32) {
 +    println!("default update {}", dt);
 +}
 +
 +pub fn create_pyove(py: Python<'_>) -> PyResult<&PyModule> {
 +    let m = PyModule::new(py, "pyove")?;
 +    m.add_function(wrap_pyfunction!(load, m)?)?;
 +    m.add_function(wrap_pyfunction!(draw, m)?)?;
 +    m.add_function(wrap_pyfunction!(update, m)?)?;
 +    graphics::register_graphics_module(py, m)?;
 +    Ok(m)
 +}
 +</file>
 +
 +==== src/graphics.rs ====
 +
 +<file rust>
 +//
 +// このプロジェクトではset_colorとrectangle関数のみを提供する
 +//
 +use crate::sdl2global::*;
 +use pyo3::prelude::*;
 +use sdl2::rect::Rect;
 +
 +#[pyfunction]
 +#[pyo3(name = "setColor")]
 +//#[pyo3(pass_module)]
 +fn set_color(r: f32, g: f32, b: f32) {
 +    // extract normalized color values
 +    let ri = (r * 255.0) as u8;
 +    let gi = (g * 255.0) as u8;
 +    let bi = (b * 255.0) as u8;
 +    sdl_render_set_draw_color((ri, gi, bi));
 +}
 +
 +#[pyfunction]
 +fn rectangle(mode: &str, x: i32, y: i32, w: u32, h: u32) {
 +    let rect = Rect::new(x, y, w, h);
 +    if mode == "fill" {
 +        sdl_render_fill_rect(rect);
 +    } else if mode == "line" {
 +        sdl_render_draw_rect(rect);
 +    } else {
 +        eprintln!("Warning: unknown draw mode {}", mode);
 +    }
 +}
 +
 +pub fn register_graphics_module(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> {
 +    // see: https://pyo3.rs/v0.19.2/module#python-submodules
 +    // サブモジュールとしてgraphicsを登録する
 +    // graphicsに、setColorとrectangleを登録する
 +    let m = PyModule::new(py, "graphics")?;
 +    m.add_function(wrap_pyfunction!(set_color, m)?)?;
 +    m.add_function(wrap_pyfunction!(rectangle, m)?)?;
 +    parent_module.add_submodule(m)?;
 +    Ok(())
 +}
 +</file>
 +
 +==== src/sdl2global.rs ====
 +
 +<file rust>
 +use sdl2::{pixels::Color, rect::Rect, render::WindowCanvas, EventPump, Sdl};
 +use std::cell::RefCell;
 +
 +thread_local! {
 +    static SDL_CONTEXT: RefCell<Sdl> = init_sdl().unwrap();
 +    static CANVAS: RefCell<WindowCanvas> = init_canvas().unwrap();
 +}
 +
 +fn init_sdl() -> Result<RefCell<Sdl>, String> {
 +    let sdl_context = sdl2::init()?;
 +    Ok(RefCell::new(sdl_context))
 +}
 +
 +fn init_canvas() -> Result<RefCell<WindowCanvas>, String> {
 +    let video_subsystem = SDL_CONTEXT.with(|sdl| sdl.borrow().video())?;
 +
 +    let window = video_subsystem
 +        .window("PYÖVE Example", 800, 600)
 +        .position_centered()
 +        .opengl()
 +        .build()
 +        .map_err(|e| e.to_string())?;
 +
 +    let canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
 +
 +    Ok(RefCell::new(canvas))
 +}
 +
 +pub fn sdl_event_pump() -> EventPump {
 +    SDL_CONTEXT.with(|sdl| sdl.borrow().event_pump()).unwrap()
 +}
 +
 +pub fn sdl_render_clear() {
 +    CANVAS.with(|canvas| canvas.borrow_mut().clear());
 +}
 +
 +pub fn sdl_render_present() {
 +    CANVAS.with(|canvas| canvas.borrow_mut().present());
 +}
 +
 +pub fn sdl_render_set_draw_color<C>(color: C)
 +where
 +    C: Into<Color>,
 +{
 +    CANVAS.with(|canvas| canvas.borrow_mut().set_draw_color(color));
 +}
 +
 +pub fn sdl_render_draw_rect(rect: Rect) {
 +    CANVAS
 +        .with(|canvas| canvas.borrow_mut().draw_rect(rect))
 +        .unwrap();
 +}
 +
 +pub fn sdl_render_fill_rect(rect: Rect) {
 +    CANVAS
 +        .with(|canvas| canvas.borrow_mut().fill_rect(rect))
 +        .unwrap();
 +}
 +</file>
 +
 +==== example/main.py ====
 +
 +<file python>
 +import pyove
 +#import foo # test import availability
 +
 +# Python requires global scoped variable definitions
 +x, y, w, h = 0, 0, 0, 0
 +
 +def load():
 +    global x, y, w, h
 +    x, y, w, h = 20, 20, 60, 20
 +
 +def update(dt):
 +    global w, h
 +    w += 1
 +    h += 1
 +
 +def draw():
 +    pyove.graphics.setColor(0, 0.4, 0.4)
 +    pyove.graphics.rectangle("fill", x, y, w, h)
 +
 +# overwrite default module functions
 +pyove.load = load
 +pyove.draw = draw
 +pyove.update = update
 +</file>
 +
 +=== 参考 ===
 +
 +オリジナルの動作は、こちらを参考にしてください。
 +
 +  * https://love2d.org/wiki/love
  

特に明示されていない限り、本Wikiの内容は次のライセンスに従います: CC0 1.0 Universal
CC0 1.0 Universal Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki