ゲームであるからには、終わりと始まりが必要です。ゲームを起動するといきなりレベル画面に放り込まれるのもダメというわけではないのですが、タイトル画面を用意するとグッとゲームらしくなったような気がします。また、ゲームをクリアしたらエンディングというかリザルト画面みたいなものを用意するとクリアした感がでて良さそうです。
シーンを定義する
ここでは、それぞれの「画面」を Scene
と呼ぶことにします。この『シーン』は互いに独立しており、ゲーム内では必ずシーンがひとつだけ保持されて表示されます。たとえば、ゲームのレベル画面(プレイヤーキャラクターを実際に操作して遊ぶ画面)は次のような構造体で定義されています。
#[derive(Clone)]
pub struct GameScene {
rng: Rng,
frame_count: u32,
player: Body,
player_lookup: i32,
fruits: std::vec::Vec<Body>,
world: World,
debug: bool,
score: f32,
vibration: i32,
}
それから、この構造体にコンストラクタみたいなのとupdate
メソッドを生やしておきます。
impl GameScene {
pub fn new() -> Self {
...
}
pub fn update(&mut self, inputs: &Inputs) -> Option<Scene> {
...
}
}
ここで、update
関数はOption<Scene>
を返すようになっていますが、ここでSome
を返した場合はそのシーンが次のシーンとして切り替わるようにしたいと思います。
このような構造体を、タイトル画面とリザルト画面のぶんも定義します。
シーンを切り替える
TypeScriptのような言語であれば、Scene
インターフェイスを用意してstart
とupdate
メソッドを定義し、各シーンではこのインターフェイスを実装するというような仕組みを考えます。オブジェクトはヒープに入れて常にポインタ越し(参照越し)に扱う言語であればそれでいいのですが、今回は言語がRustなのでシーンを表すオブジェクトはスタックに入れたいと思います。その場合、各シーンはenum
に入れて保持することになります。
#[derive(Clone)]
pub enum Scene {
TitleScene(TitleScene),
GameScene(GameScene),
EndingScene(EndingScene),
}
あとはこれをゲーム全体を表す構造体に入れて、このenum
からシーンの構造体を取り出してはupdate
を呼ぶということを繰り返すだけです。
pub fn update(&mut self) {
...
let result = match { &mut self.scene } {
Scene::TitleScene(t) => t.update(&inputs),
Scene::GameScene(g) => g.update(&inputs),
Scene::EndingScene(e) => e.update(&inputs),
};
match result {
Option::None => {}
Option::Some(next) => self.scene = next,
}
...
}
画像がデカすぎる
ちなみに、この画面全体を覆うような大きい画像を使うと、2 bits * 160 pixel * 160 pixel bits = 6.4 kbytes
で、なんとカートリッジサイズの1/10をタイトル画面の画像1枚が占めるということになってしまいます。画像を圧縮する手も考えたのですが、よく考えたら画像を描画するときに結局メモリ上に展開することになり、そちらも64キロバイトしかないので余計に容量を食うことになるだけです。あんまり演出に凝るな、いいからゲームの完成を優先しろ、ということですね。
次回予告
次回はちょっとしたお休み回で、他のWASM-4製ゲームを触ってみて、みんなどんなゲームを作っているのか調査してみます。