🦀Rustを書きたい(書けるとは言っていない)
WASM-4は、以下の10のプログラミング言語でゲーム開発ができます。
- AssemblyScript
- C/C++
- D
- Go
- Nelua
- Nim
- Odin
- Rust
- WAT
- Zig
なんかまったく聞いたことのない言語もありますね。NeluaやOdinというのは私はWASM-4で初めて知りました。どういう言語なんでしょうか?
このシリーズでは、プログラミング言語に Rust を使っていこうと思います。これは私が単純に一番使ってみたかった言語が Rust だからです。ちなみにシリーズ開始時点での筆者の現在のRust力は、HelloWorldだけやったことがあるくらいです。
このシリーズの記事ではRustがどういった言語なのかについての解説はしませんが、そのかわりにRust初見プレイの感想については率直に語っていきたいと思います。なお、Rustはやや難しい部類に入るプログラミング言語だそうなので、単純にゲームを作りたいだけの人は別の言語を使ったほうがいいかもしれません。
🛠️開発環境の構築
WASM-4のツールキットはnpm install -g wasm4
で簡単にインストールできます。これをインストールするとWASM-4の開発サーバーを起動したりビルドしたりするw4
コマンドが使えるようになります。
プロジェクトの作成もw4 new --rust hello-world
のようなコマンドで簡単にできます。
開発時の開発サーバーを起動するには、ターミナルから w4 watch
コマンドを実行するだけです。w4 watch
はファイルを編集するたびに自動で再ビルドとアプリの再読み込みまでしてくれます。ただし、記事執筆時点でやってみるとw4 watch
はtarget\wasm32-unknown-unknown\debug\cart.wasm
というファイルを開こうとしており、これが見つからないのでエラーになってしまっていました。これは既知の問題のようです (GithHubのIssue)。ひとまず name = "cart"
とすることで起動できました。
[package]
edition = "2021"
name = "cart" # ←コレ
version = "0.1.0"
🗿update
関数とstart
関数
チュートリアルのコードで、エントリポイントとなる lib.rs
の中身を覗いてみます。これから作るゲームの『カートリッジ』はRustのライブラリクレートとなっていて、ライブラリクレートは lib.rs
がライブラリの入口となるクレートルートファイルになるんだそうです。
WASM-4で最低限定義が必要な関数は update
だけです。update
はいわゆる「ゲームループ」で、ゲームが起動するとupdate
が1秒間に60回呼び出されて、そこで自由に状態変更や描画をしていきます。それで、ゲームの状態をどこかに保存しなければいけないわけですが、static
な変数を定義して参照しようとすると、unsafeだなんだとかいう謎のコンパイルエラーが出ます……。(※筆者はRust初心者です)
w4 new
で作られるプロジェクトはあまりに簡素すぎて私のようなRust初心者にはつらそうだったので、こちらのチュートリアルのサンプルゲームを参考にしてみます。
このプロジェクトのupdate
関数のところを見てみると、こんな感じになっています。
lazy_static! {
static ref GAME: Mutex<Game> = Mutex::new(Game::new());
}
#[no_mangle]
fn update() {
GAME.lock().expect("game_state").update();
}
……何これ? #[no_mangle]
はまあ想像がつくとしても、lazy_static!
という謎マクロにRust初心者の私は困惑です。しかもなぜMutex
……?なんかいきなりゴツい仕掛けが出てきてさらに困惑ですが、どうもRustのグローバル変数は制約が多く、動的な値で初期化できなかったり、unsafe
になってしまうなど面倒くさいようです。これがRustの洗礼か……。まあなんかよくわかりませんが、このGame
がnew
でインスタンス化されてupdate
で更新されているということはわかるので、細かいことは気にしないことにします。
また、ゲームを起動したときに一度だけ呼ばれる start
関数も定義できます。この関数でカラーパレットを指定したりするといいようですが、ゲーム自体の初期化は今回はGame::new
でやるので、この関数を使う機会はあまりなさそうです。
#[no_mangle]
fn start() {
palette::set_palette([0xfff6d3, 0xf9a875, 0xeb6b6f, 0x7c3f58]);
}
ちなみに、このあと最近のRustはlazy_staticよりonce_cellのほうがイケてる!みたいな記事も見かけたので、そちらも使ってみました。確かにマクロを使わなくて若干オシャレになる気がします。まあこういう細かいことはいいんだ、先に進もう……。
static GAME: Lazy<Mutex<Game>> = Lazy::new(|| Mutex::new(Game::new()));
#[no_mangle]
fn update() {
GAME.lock().expect("game_state").update();
}
🖍️試しになんか描いてみる
まあ何はともあれ何か画面に表示してみましょう。適当に四角形を描いてみます。WASM-4で矩形を書く関数は rect
です。これを update
メソッド内で呼び出してみます。
pub struct Game {
}
impl Game {
pub fn new() -> Self {
Self { }
}
pub fn update(&mut self) {
wasm4::rect(10, 10, 100, 100);
}
}
struct
とかはまあC/C++の構造体だと思えば意味がわかりますね。impl Game
のところは、Game
にメソッドを追加しているんだそうです。「メソッド」とはいかにもオブジェクト指向っぽい用語ではありますが、ここ最近の「ここしばらくオブジェクト指向の流行りでデータと振る舞いを『クラス』としてひとつにまとめてきたけど、よく考えたらやっぱりデータと振る舞いは別モノなんじゃね?」「とはいいつつも、object.method
と打ったときにエディタで補間が出てくるのは捨てがたいよね……」という、界隈の近年の反省が生かされている気がします。
さて、これを実行するには w4 watch
で開発サーバーを起動するのが便利です。実行すると勝手にブラウザが開き、このように表示されます。
160ピクセルx160ピクセルの画面のうえに、100ピクセルx100ピクセルの矩形が描かれましたね。
📛カラーパレットレジスタと描画色レジスタ
WASM-4では、ゲームボーイっぽい緑がかったカラーパレットがデフォルトで設定されています。このカラーパレットは PALETTE
レジスタで設定でき、同時発色する4色を自由に選ぶことができます。例えば公式ドキュメントでは次のようなコードが紹介されています。
unsafe {
*PALETTE = [
0xfff6d3,
0xf9a875,
0xeb6b6f,
0x7c3f58,
];
}
また、rect
などの関数で描画するときの色は DRAW_COLORS
レジスタで制御できます。たとえば、次のようなコードだと、塗りつぶしの色にカラーパレットの3
つめの色、枠線にカラーパレットの4
つめの色を指定しています。
unsafe { *DRAW_COLORS = 0x42 }
rect(10, 10, 100, 100);
矩形の塗りつぶしの色が明るくなり、枠線もつきました。
こんな感じで、WASM-4のAPIは極めてシンプルで、使うのも簡単です。まあ使うの簡単だけど……いや……レジスタってさあ……。私はあまり低レベルプログラミングの経験がないので、生メモリとか生レジスタを直接触る経験がありません。でもWASM-4は非常に簡素な組み込みハードウェアを模していて、生のフレームバッファとかを直接触れて生のメモリ破壊とかが普通に起こります。JavaVMやJavaScriptランタイムの温室で育ったワイには過酷だよ……。このあたりのWASM-4の生っぷりは後々紹介していきたいと思います。
🔜次回予告
次回はどんなゲームを作っていくのかという計画を立てていきたいと思います。計画を立てて常に完成を視野に入れながら動かないと、だらだらと開発を続けることになってしまうので……。個人ゲーム開発では「何を作るか」より「何を作らないか」のほうが大事かもしれません。もちろん時間と能力と根気が許すなら商業AAAゲームにも劣らない神ゲーを作りたいですが、個人ゲーム開発では何もかもが足りないのです。