Bevyを使った個人ゲーム開発を行なっている登尾(のぼりお)です。今回はRust×Bevyゲーム開発レシピ: シーンを切り替えようから発展させた、シーンの切り替え方法を解説します。
前回の記事を見ていただいた上で、もう少しだけ現実に即した形でシーンを切り替える場合のポイントについて見ていきましょう。
タグ用コンポーネントにMesh2Dを紐付けよう
今回の例ではどのシーンかを表す状態を以下のように定義します。
#[derive(States, Debug, Clone, Eq, PartialEq, Hash, Default)]
enum SceneState {
#[default]
A,
B,
}
これによって二つのシーンを行ったり来たりするための状態ができますが、それぞれのシーンごとにタグ用のコンポーネントを用意します。
#[derive(Component)]
struct ARoot;
#[derive(Component)]
struct BRoot;
この二つのコンポーネントを親として今回は図形を描画し、さらにそのシーン表示が必要なくなったタイミングでそれぞれのタグ用コンポーネントを削除することで、不要な図形が新しいシーンに残らないような後始末を行います。
これから実装していくことを箇条書きすると以下のような形になりますので、その点を押さえていただき次の項目で具体的に実装していきましょう。
- シーンAに入ったタイミングでARootというタグ用コンポーネントにたくさんの丸を描く
- スペースキーが押され、シーンをAからBに切り替える
- シーンがAではなくなるので、ARootに紐づいていたMesh2Dの後片付け
- シーンBに入ったので、BRootというタグ用コンポーネントにたくさんの線を描く
- 今度はスペースキーが押され、シーンをBからAに切り替える
- シーンがBではなくなるので、BRootに紐づいていたMesh2Dの後片付け
- (1に戻る)
Appにシステムを紐付けよう
シーンAに入った時、シーンAから出た時、シーンBに入った時、シーンBからでた時というシステムを一度に登録します。
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup_camera)
.init_state::<SceneState>()
.add_systems(OnEnter(SceneState::A), enter_a)
.add_systems(OnExit(SceneState::A), cleanup_a)
.add_systems(OnEnter(SceneState::B), enter_b)
.add_systems(OnExit(SceneState::B), cleanup_b)
.add_systems(Update, toggle_scene_on_space)
.run();
}
setup_cameraについてはCamera2Dをspawnするだけの関数です。
fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2d);
}
enter_aの中で、たくさんの丸を描き、シーンAにいることが視覚的に分かるよう実装しましょう。この際にMesh2Dを使い、かつ表示させる位置と色をランダムにするようrandクレートを使っています。
fn enter_a(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn((Transform::default(), ARoot)).with_children(|p| {
let mut r = rand::rng();
for _n in 1..25 {
let circle = meshes.add(Circle::new(80.0));
p.spawn((
Mesh2d(circle),
MeshMaterial2d(materials.add(Color::srgba(
r.random_range(0.0..1.0),
r.random_range(0.0..1.0),
r.random_range(0.0..1.0),
0.5,
))),
Transform::from_xyz(
r.random_range(-240.0..240.0),
r.random_range(-240.0..240.0),
r.random_range(-240.0..240.0),
),
));
}
});
}
こなれた書き方ができていない自信はありますので、うまくリファクタリングして見てください。
ここまでの状態で一度起動させると以下のような感じでたくさんの丸が描画されます。
(ただしこの時点ではcleanup_aや、enter_bがまだ用意されていないので、適当に空の関数を用意してください。)
タグ用コンポーネントに紐づいたMesh2Dの後片付けをしよう
ここからがこの記事のポイントになります。
シーンAを表すARootに紐づいたMesh2Dを一気に後片付けする部分です。
fn cleanup_a(
mut commands: Commands, q: Query<Entity, With<ARoot>>
) {
for root in &q {
commands.entity(root).despawn();
}
}
このように書くことで、ARootに紐づく子の要素も後片付けされます。以前のBevyのバージョンではdespanは再起的に後片付けをしていなかったようなのですが、0.1では再起的なお掃除が行われます。
シーンを切り替えるための仕組みの説明としてはここまでで終わるのですが、残っているシーンB側の実装と、スペースキーが押された時にシーンを変えるという実装を最後に紹介して終わりましょう。
fn enter_b(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn((Transform::default(), BRoot)).with_children(|p| {
let mut r = rand::rng();
for _n in 1..25 {
let line = meshes.add(Rectangle::new(300.0, 4.0));
p.spawn((
Mesh2d(line),
MeshMaterial2d(materials.add(Color::srgba(
r.random_range(0.0..1.0),
r.random_range(0.0..1.0),
r.random_range(0.0..1.0),
0.5,
))),
Transform::from_xyz(
r.random_range(-240.0..240.0),
r.random_range(-240.0..240.0),
r.random_range(-240.0..240.0),
).with_rotation(Quat::from_rotation_z((r.random_range(0.0f32..180.0f32)).to_radians()))
));
}
});
}
fn cleanup_b(
mut commands: Commands, q: Query<Entity, With<BRoot>>
) {
for root in &q {
commands.entity(root).despawn();
}
}
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::A => next.set(SceneState::B),
SceneState::B => next.set(SceneState::A),
}
}
}
ここまでの実装によって、スペースキーを押すたびに以下のように切り替わる様子を見ることができます。
おしまい
今回のコードは以下の個人リポジトリで公開しています。
cloneした後に、
% cargo run --example scenes_with_mesh
で挙動を起動できますので、そちらも参考にしてみてください。
シーンを切り替えるという点で解説しましたので、Mesh2Dなどについてはほとんど触れませんでしたが、この辺は見ればなんとなく分かっていただけるのではと思います。
ぜひ楽しいBevyライフを送ってください。