はじめに
こんにちは! 中2のAwashAmityOakです! 今回は、PythonのレトロゲームエンジンであるPyxelを、Rustで動かす方法をまとめます。
Pyxelとは?
https://github.com/kitao/pyxel/blob/main/docs/images/pyxel_message.png より |
Pyxelは、Python向けのレトロゲームエンジンです。日本人のkitaoさんという方が作られたので、日本語ドキュメントが完備されています。2023年12月に、バージョン2.0.0を迎え、その後もアップデートが続いています。
Pyxelのライブラリのコア部分は、Rustで書かれていて、crates.ioにも公開されているので、Cargo.toml
に依存関係を書き込むことで、Rustで使用することができます。
動作環境
- OS: macOS 10.15.7
- CPU: Intel Core i7
- メモリ: 8GB
Homebrew 4.2.11
SDL2 2.30.1
rustc 1.76.0
cargo 1.76.0
手順
今回は、Rustで、PyxelのexamplesのHello, Pyxel!
を動かすことを目標にします。(Pythonのソースコードはこちらです。)
RustはPCにインストール済みであることを前提とします。インストールしていない場合は、公式ページから、インストールしてください。(ググればインストールについての情報はたくさん出てきます。)
(2024年3月18日追記)
macOS 11より古いバージョンをお使いの方は、記事後半の「動かない場合は」を一度お読みください。
1. プロジェクト作成
普通のRustプロジェクトと同じように、cargo new
を使用して、プロジェクトを作成します。
$ cargo new hello_pyxel
$ cd hello_pyxel
ディレクトリ構成は以下のようになります。
hello_pyxel
├── .gitignore
├── Cargo.toml
└── src
└── main.rs
2. 依存関係の解決
Pyxelのコア部分は、v1.8.2
からv1.9.18
はpyxel-core
というcrateで管理されてます。また、v1.5.1
からv1.8.2
、そしてv2.0.0
以降はpyxel-engine
というcrateで管理されています。
私の環境では、最新のpyxel-engine
をコンパイルするときに、
ld: symbol(s) not found for architecture x86_64
というエラーが発生してしまったので、この記事では旧バージョンのコアであるpyxel-core
を使っていきます。
1. SDL2のインストール
macOSで、pyxel-core
を使う場合には、SDL2が必要となります。今回は、Homebrewを用いて、インストールしていきます。(macOS以外の環境については、Rust-SDL2公式ドキュメントを参照してください。)
$ brew install sdl2
を実行し終わった後、
$ brew info sdl2
と実行して、出力の最初の行に
==> sdl2: stable 2.30.1, HEAD
などと表示されていればOKです。
2. Cargo.toml
の編集
Cargo.toml
に、
pyxel-core = "1.9.18"
と追記します。
もしくは、cargo add
を使用して、
$ cargo add pyxel-core
とCLIで実行しても良いです。
3. 画像ファイルの用意
PyxelのGitHubにあるロゴ画像をダウンロードします。ダウンロードが完了したら、
hello_pyxel
├── .gitignore
├── Cargo.toml
├── assets
│ └── pyxel_logo_38x16.png
└── src
└── main.rs
となるように、./assets/pyxel_logo_38x16.png
に配置してください。
4. プログラムの記述
1. 必要なAPIのuse宣言
pyxel::PyxelCallback
というトレイトをuseします。
use pyxel::PyxelCallback;
2. App
構造体の作成
PythonコードでのApp
クラスにあたるものです。
struct App {}
今回は特に作成すべきフィールドがないので、空の構造体を作ります。
3. init()
メソッドの実装
impl
を使って、App
構造体にinit()
メソッドを実装していきます。
impl App {
fn init() {
pyxel::init(
160,
120,
Some("Hello Pyxel"),
None,
None,
None,
None,
None,
);
pyxel::image(0).lock().load(0, 0, "assets/pyxel_logo_38x16.png");
let app = App {};
pyxel::run(app);
}
}
Pythonでのpyxel.init()
関数には、デフォルト引数があり、特に指定しない場合は省略できました。しかし、Rustのpyxel::init()
関数では、ちゃんとすべての引数にNone
を渡してあげる必要があります。
イメージのロードについても、Pythonと少し異なる点があります。Rustでのpyxel::image()
の戻り値は、ミュータブルな値を指す、参照カウント形式のスマートポインタです。そのため、Rustの特徴の一つであるスレッド安全性を確保するために、lock()
メソッドを実行する必要があります。これは、他のタイルマップやサウンド、ミュージックについても同様です。(難しい話なので、わからない人は、「イメージやサウンドを扱うときは、lock()
をつけなければいけないんだな」くらいに思っておいてください。)
4. update()
メソッドの実装
update()
メソッドは、先ほどuse
宣言したPyxelCallback
トレイトを用いて実装します。
impl PyxelCallback for App {
fn update(&mut self) {
if pyxel::btnp(pyxel::KEY_Q, None, None) {
pyxel::quit();
}
}
// ここには`draw()`メソッドの実装が入ります。
)
pyxel::btnp()
関数にも、None
を渡してあげなければいけない引数があります。
5. draw()
メソッドの実装
draw()
メソッドも、update()
メソッドと同様に、PyxelCallback
トレイトを用いて実装します。
impl PyxelCallback for App {
// ここには`update()`メソッドの実装が入ります。
fn draw(&mut self) {
pyxel::cls(0);
pyxel::text(55.0, 41.0, "Hello, Pyxel!", (pyxel::frame_count() % 16).try_into().unwrap());
pyxel::blt(0.0, 0.0, 0, 0.0, 0.0, 8.0, 8.0, None);
}
)
pyxel::text()
に渡す引数の型変換に気をつけてください。
6. main()
関数の実装
Pythonコードでの最後のApp()
というところに対応します。
fn main() {
App::init();
}
ソースコードすべて
use pyxel::PyxelCallback;
pub struct App {}
impl App {
fn init() {
pyxel::init(
160,
120,
Some("Hello Pyxel"),
None,
None,
None,
None,
None,
);
pyxel::image(0).lock().load(0, 0, "assets/pyxel_logo_38x16.png");
let app = App {};
pyxel::run(app);
}
}
impl PyxelCallback for App {
fn update(&mut self) {
if pyxel::btnp(pyxel::KEY_Q, None, None) {
pyxel::quit();
}
}
fn draw(&mut self) {
pyxel::cls(0);
pyxel::text(55.0, 41.0, "Hello, Pyxel!", (pyxel::frame_count() % 16).try_into().unwrap());
pyxel::blt(61.0, 66.0, 0, 0.0, 0.0, 38.0, 16.0, None);
}
}
fn main() {
App::init();
}
5. コンパイルと実行
$ cargo run
を走らせて、プログラムをコンパイル&実行します!
こんなHello, Pyxel!
が表示されましたか?
Hello, Pyxel! の実行結果 |
動かない場合は(2024年3月18日追記)
古いバージョンのmacOSだと、リンクが上手く働かずに、コンパイルできない場合があるみたいです。ここでは、若干ゴリ押し気味ではありますが、対処法をお伝えしようと思います。
長いので折り畳みました
1. Pyxel v1.9.18をクローン
適当なディレクトリ内で、
$ git clone https://github.com/kitao/pyxel.git -b v1.9.18
「ブランチ作ってないよ〜」みたいな警告が出るので、気になる人はgit switch -c [ブランチ名]
で作ってあげてください。(ここからコミットするつもりはないので、私は特に気にしてません。)
2. ./crates/pyxel-core/Cargo.toml
を書き換える
pyxel-core
crateのCargo.toml
を編集します。具体的には、
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
sdl2 = { version = "0.35", default-features = false, features = [
"bundled",
- "static-link",
+ # "static-link",
"unsafe_textures",
] }
のように、"static-link",
というfeatureを無効にします。
コマンドでやる場合は、pyxel
ディレクトリに移動した後、こちらを実行してください。
$ sed 's/"static-link",/# "static-link",/' crates/pyxel-core/Cargo.toml > crates/pyxel-core/Cargo.toml.tmp && mv crates/pyxel-core/Cargo.toml.tmp crates/pyxel-core/Cargo.toml
3. 元のhello_world
プロジェクトのCargo.tomlも書き換える
元のプロジェクトに戻って、Cargo.toml
のpyxel-core
を、ローカルのパスで指定します。
[package]
name = "hello_pyxel"
version = "0.1.0"
edition = "2021"
[dependencies]
- pyxel-core = "1.9.18"
+ pyxel-core = { path = "../pyxel/crates/pyxel-core" }
../pyxel/crates/pyxel-core
の部分は、クローンしたpyxel-core
があるパスに、適宜書き換えてください。
これで完成です! すでに手順5まで進めている場合は、cargo run
を実行してみてください!
おわりに
Pyxelは、公式でRustに対応しているわけではありませんが、コア部分が使える状態で公開されているので、この記事のようにRustでプログラミングすることができてしまいます。
Pyxelを触ったことがある+Rust勉強中の人は、この機会に、自分が過去にPyxelで作ったゲームのソースコードを、Rustで書き直してみるのはいかがですか?
この記事が好評であれば、RustでPyxelを使うときのチートシートのような記事も書いてみようと思います。
誤字・脱字などあれば、コメントか編集リクエストで教えていただけるとありがたいです。
この記事が良いと思ったら、ぜひいいねとフォローをよろしくお願いします! Xアカウントのフォローなどもしてくれると泣いて喜びます!
最後まで読んでくださり、ありがとうございました!