作成日: 2023-08-16 (水)
[package]
name = "pyove"
version = "0.1.0"
edition = "2021"
[dependencies]
sdl2 = "0.35"
[dependencies.pyo3]
version = "0.19.2"
features = ["auto-initialize", "macros"]
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(())
})
}
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)
}
//
// このプロジェクトでは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(())
}
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();
}
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
オリジナルの動作は、こちらを参考にしてください。