6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Pyxel】Python向けレトロゲームエンジンをRustで動かす

Last updated at Posted at 2024-03-14

はじめに

こんにちは! 中2のAwashAmityOakです! 今回は、PythonのレトロゲームエンジンであるPyxelを、Rustで動かす方法をまとめます。

Pyxelとは?

 
pyxel_message.png
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.18pyxel-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に、

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します。

main.rs
use pyxel::PyxelCallback;

2. App構造体の作成

PythonコードでのAppクラスにあたるものです。

main.rs
struct App {}

今回は特に作成すべきフィールドがないので、空の構造体を作ります。

3. init()メソッドの実装

implを使って、App構造体にinit()メソッドを実装していきます。

main.rs
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トレイトを用いて実装します。

main.rs
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トレイトを用いて実装します。

main.rs
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()というところに対応します。

main.rs
fn main() {
    App::init();
}
ソースコードすべて
main.rs
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.gif
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-corecrateのCargo.tomlを編集します。具体的には、

./crates/pyxel-core/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.tomlpyxel-coreを、ローカルのパスで指定します。

Cargo.toml
  [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アカウントのフォローなどもしてくれると泣いて喜びます!

最後まで読んでくださり、ありがとうございました!

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?