Bevyを使った個人ゲーム開発を行なっている登尾(のぼりお)です。今回はBevyでのシーン切り替えを解説します。
例えばPhaser.jsなどでゲーム開発の経験があるとシーンごとの管理を特定のクラスに閉じ込め、切り替え自体も、エンジン側でやってくれます。
そういった経験からBevyに移行すると、シーン切り替えの方法に戸惑ってしまうかもしれません。
というのも、BevyはECS(Entity Component System)というデータ駆動なゲームエンジンなので、そうでないモデルのゲームエンジンの経験と考え方が異なるためです。
ECSについて詳しくは解説しませんが、シーンを切り替えるという文脈において以下の考え方を持つとシーンを切り替えるという実装が可能になります。
今回は4つのステップで解説していきます。
1) どのシーンかを表す状態
例えば、タイトル画面、プレイ中の画面という2つのシーンがあると考えた時以下のようにStateを定義します。
use bevy::prelude::*;
#[derive(States, Debug, Clone, Eq, PartialEq, Hash, Default)]
enum SceneState {
#[default]
Title,
Play,
}
2) init_stateでSceneStateを設定
SceneStateをBevyのAppにinit_stateによって設定します。
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<SceneState>()
.run();
}
これによってBevyのアプリ内において今どのStateSceneなのか、StateSceneを切り替える、という二つの機能が呼べるようになります。(その呼び出し方は後述。)
SceneStateには、#[default]のアトリビュートによってTitleがデフォルトであると指定しているため、StateSceneを取り出すとTitleが返ってきます。
3) シーンが切り替わった時にだけ実行されるシステム
Appへadd_systemsする書き方は、前回の記事で以下のような例を書きました。
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup_clear_color)
.add_systems(Update, change_clear_color)
.run();
}
上記は、一度だけ(Startup)あるいは、常に(Update)の場合ですが、先ほどinit_stateで設定した SceneStateがどの値に切り替わったか
という条件の時だけ、実行できるシステムを書くことができます。
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<SceneState>()
.add_systems(OnEnter(SceneState::Title), enter_title)
.add_systems(OnEnter(SceneState::Play), enter_play)
.run();
}
このようにOnEnterの中にStateを渡すと、その状態に変わった時のみ呼び出される関数を定義できます。
- enter_title関数: ゲーム開始時に呼ばれる(SceneStateはTitleなので)、あるいは再度SceneStateがTitleに切り替わった時
- enter_play関数: SceneStateがPlayに切り替わった時
実際に切り替わった時のenter_title関数、enter_play関数はシンプルに背景が変わるだけの例ですが、以下の通りです。
fn enter_title(mut clear: ResMut<ClearColor>) {
clear.0 = Color::srgb(0.5, 0.0, 0.0)
}
fn enter_play(mut clear: ResMut<ClearColor>) {
clear.0 = Color::srgb(0.0, 0.0, 0.5)
}
4) 実際のシーン切り替え
さて、最後に上記までのコードを実行しても、最初のTitleシーンのままなので、TitleからPlay、PlayからTitleに切り替えてみましょう。
スペースキーを押すと交互に切り替える関数は以下のとおりです。
fn toggle_scene_on_space(
keys: Res<ButtonInput<KeyCode>>,
state: Res<State<SceneState>>,
mut next: ResMut<NextState<SceneState>>,
) {
if keys.just_pressed(KeyCode::Space) {
match *state.get() {
SceneState::Title => next.set(SceneState::Play),
SceneState::Play => next.set(SceneState::Title),
}
}
}
今がどのシーンかはstateで分かり、切り替える場合にはnextを使うというのが直感的に理解していただけると思います。
ここまでのコードによってスペースキーを押すと色が変わることでシーンが切り替わる様子を観察できます。
おしまい
今回のコードは以下の個人リポジトリで公開しています。
cloneした後に、
% cargo run --example scnes
で挙動を起動できますので、そちらも参考にしてみてください。