🏎️

Rust & Wasmでミニカートゲームを作る

に公開

はじめに

今回は、Cnavas上で動作するカートゲームを作りました。昔、初めて"Basic Magazime"を読んでタイピングして遊んだのが、本ミニゲームのような、障害物を避けながらゴールを目指すようなゲームでした。GWに、当時の記事見たさに、青梅市にある”マイコン博物館"に行ってみたのですが、残念ながら、当時の記事を見つけることはできませんでした😢。画面のイメージは、障害物が"#"で、"●"のヘビのように下から上にスクロールして...というような感じのゲームだったと思います。
今回も、興味を持って頂いた方は、パソコンからお試しください🙂。

ミニカートゲーム

ミニレーシングゲーム

お試しURL

https://myurioka.github.io/cart/

操作

カーソル左/右: 左右移動
カーソル上: 速度UP
カーソル下: 左右の移動をなくし、機体をまっすぐにします。
スペース: ゲーム開始/再開
スペース(プレー中):ブレーキ

※今回はSP版に対応できていません。理由は、"カーソル下"の操作をどうやってTouchで表現すれば良いのか思いつかなかったため。

ソースコード

https://github.com/myurioka/cart

ステートマシンの実装について

ゲームの流れは、最初にオープニング画面が表示されます。スタートすると、画面中の車が、カーソルからの入力に従い移動し、障害物の壁も車の速度に合わせて画面の上から下に向けて移動します。車がゴールまで辿り付くとゲームクリアーの文字が表示され、途中、障害物の壁と交差すると、車が故障したイメージに変わり移動が停止し、ゲームオーバーの文字が表示されます。この後、"スペース"を押すと、再度、ゲームが始まります。今回は、これらのゲームの流れを、関数の中で、STATUSをmatch文で分岐するのではなく、「RustとWebAssemblyによるゲーム開発※」で紹介されている"ステートマシン"を使った実装にしています。以下の表が車の状態遷移表ですが、transition()関数に集約され実装しています。TypeScriptなどの他の言語でも同様な実装は、もちろんできます。ただ、rustのmatchが"タプルマッチング: (state, event)のような複数値の同時マッチングができる"ことと、From/Intoトレイトの簡潔さが、rustにおいて、強力にステートマシン実装を推していると思います。

NO 遷移前の状態 イベント 遷移後の状態 処理
01 Idle Update Update スタンディング
02 Idle Run(veloity) Running スタート
03 Running Update Running 走行中
04 Running Run(veloity) Running 加速中
05 Running Knocked Knocked クラッシュ

そのまま実装できます。

fn transition(self, event: Event) -> Self {
    match (self.clone(), event) {
        (CartStateMachine::Idle(state), Event::Update) => state.update().into(),
        (CartStateMachine::Idle(state), Event::Run(velocity)) => state.run(velocity).into(),
        (CartStateMachine::Running(state), Event::Update) => state.update().into(),
        (CartStateMachine::Running(state), Event::Run(velocity)) => {
    state.run(velocity).into()
        }
        (CartStateMachine::Running(state), Event::Knocked) => state.knocked().into(),
         _ => self,
    }
}

これまでWasmから呼び出したことがあるブラウザAPIと使い方

Wasmからは、直接ブラウザーのAPIにアクセスできないため、wasm-bindgen + web_sys を仲介して、ファイルの読込や、イベント、外部APIへのアクセスを実現します。ただその実装方法は、直接呼び出せるものもあれば、Closureから呼び出さなければならない場合もあり、使いたいAPIによって異なります。いざ実装する時に、毎回、調べるのも少し大変なので、

実際のAPI一覧
https://docs.rs/web-sys/latest/web_sys/
ここで、例えばファイルを読み込む関数の"fetch_with_str"を検索窓に入力すれば、使い方はわかりますが、Rustで組み込んでみてと言われると...戸惑います。

そこで、これまで呼び出したAPIだけでも、実装時に参考になるようにまとめておきたいと思います。今時は、このような一覧も不要かもと思いますが...。

項目 内容
NO 01
目的 ゲームループ作成
関数名 browser::request_animation_frame
分類 イベントリスナー
特徴 自身を再帰的に呼び出してGameLoopを実現
実装例 https://github.com/myurioka/cart/blob/main/src/wasm/src/engine.rs#L118-L130
NO 02
目的 キーイベント
関数名 web_sys::KeyboardEvent
分類 イベントリスナー
特徴 forget()を呼び出し、関数のスコープを抜けた後でも、イベントを受け取れるようにする
実装例 https://github.com/myurioka/cart/blob/main/src/wasm/src/engine.rs#L188-L198
NO 03
目的 ファイル(wave,mp3,png...)読込
関数名 browser::fetch_array_buffer
分類 非同期処理結果待ち
特徴 非同期処理が完了するまで、await()を呼び出しコードの実行を一時停止させることが必要
実装例 https://github.com/myurioka/cart/blob/main/src/wasm/src/engine.rs#L225-L232
NO 04
目的 マウスイベント
関数名 web_sys::MouseEvent
分類 イベントリスナー
特徴 forget()を呼び出し、関数のスコープを抜けた後でも、イベントを受け取れるようにする
実装例 https://github.com/myurioka/barragerust/blob/main/src/wasm/src/lib.rs#L384-L394
NO 05
目的 マイク入力
関数名 web_sys::SpeechRecognitionResultList
分類 イベントリスナー
特徴 マイクから入力されたデータを文字列にしてinput_textにセットする
実装例 https://github.com/myurioka/adventure/blob/main/src/wasm/src/lib.rs#L86-L142
NO 06
目的 外部API呼び出し
関数名 web_sys::XmlHttpRequest
分類 イベントリスナー
特徴 ブラウザーAPIを通して外部APIにPOSTし、レスポンスを受信するとWasm側の受信処理をコールバックする
実装例 https://github.com/myurioka/adventure/blob/main/src/wasm/src/lib.rs#L231-L318

最後に

昔は、雑誌に掲載されたプログラムを書き写していました。その中でも、機械語で書かれたプログラムをタイピングするのは大変だっと記憶があります。時が経ち、今では、自動でコーディングされる時代になりました。プログラミングの楽しさもだいぶ変わってきました。
今回も、興味を持って頂いた方は、パソコンからお試しください😄。

Discussion