====== PyO3でLÖVEもどきを作る (3) ====== 作成日: 2023-08-16 (水) [[https://youtu.be/gaUlLdn3csU|第10回 (最終回) PyO3でLÖVEもどきを作る (3)]] ===== プロジェクト pyove ===== ==== Cargo.toml ==== [package] name = "pyove" version = "0.1.0" edition = "2021" [dependencies] sdl2 = "0.35" [dependencies.pyo3] version = "0.19.2" features = ["auto-initialize", "macros"] ==== src/main.rs ==== 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 = 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(()) }) } ==== src/pyovemodule.rs ==== 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) } ==== src/graphics.rs ==== // // このプロジェクトでは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(()) } ==== src/sdl2global.rs ==== use sdl2::{pixels::Color, rect::Rect, render::WindowCanvas, EventPump, Sdl}; use std::cell::RefCell; thread_local! { static SDL_CONTEXT: RefCell = init_sdl().unwrap(); static CANVAS: RefCell = init_canvas().unwrap(); } fn init_sdl() -> Result, String> { let sdl_context = sdl2::init()?; Ok(RefCell::new(sdl_context)) } fn init_canvas() -> Result, 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(color: C) where C: Into, { 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(); } ==== example/main.py ==== 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 === 参考 === オリジナルの動作は、こちらを参考にしてください。 * https://love2d.org/wiki/love