はじめに
本記事では、Rust製ゲームエンジンBevyを使用時に役立つデバッグ設定を紹介します。
本記事はBevyやECSの紹介・解説ではありません。Bevyについて知りたい方は、以下のリンクを参照してください。
https://bevyengine.org/learn/quick-start/introduction/
https://bevyengine.org/examples/
環境
bevy: 0.15.2
dev
モードを作成
開発中のみ使用したい機能がある場合、dev
featureを定義します。cfgアトリビュートを活用して、デバッグ設定を簡単にできるようにします。
[features]
default = ["dev"]
dev = [
# release時には追加したくないfeatureをここに
"bevy/dynamic_linking",
"bevy/bevy_dev_tools",
]
自身のクレートのログレベルを設定
自身のクレートのみ、デバッグレベルのログを表示させます。
use bevy::prelude::*;
#[cfg(feature = "dev")]
const LOG_FILTER: &str = "wgpu=error,my_crate=debug";
#[cfg(not(feature = "dev")]
const LOG_FILTER: &str = "wgpu=error";
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(LogPlugin {
filter: LOG_FILTER.to_string(),
..default()
}))
.add_systems(Startup, test_debug_message)
.run();
}
fn test_debug_message() {
debug!("some useful debug messages");
}
bevy-inspector-egui
の導入
bevy-inspector-eguiを導入することで、Entityなどのデバッグが容易になります。また、コンポーネントの値をランタイムで調整することができるので、スタイルやカメラなどの調整にも便利。
bevy_remote_inspector
bevy-inspector-egui
のオルタナティブとして、bevy_remote_inspector
も同様の機能を持ちます。好きな方を使ってください。
各Entityに名前を付ける
Entityをスポーンする際は、できるだけNameコンポーネントを追加しましょう。
bevy-inspector-egui
でEntityリストを表示するとき、Name
コンポーネントの文字列が、ラベルとして表示されます。Name
コンポーネントを追加しないと、無名のEntityが大量に存在することになり、デバッグ時に困ります。
Entityを識別する目的でName
コンポーネントを使わないでください。Entityを識別したい場合、Markerコンポーネントパターンを使います。
spawn
関数を拡張して、Name
を確実に設定する
Commands
に対する拡張トレイトとして、spawn_named
のようなメソッドを追加しても良いかもしれません。
use std::borrow::Cow;
use bevy::prelude::*;
pub trait SpawnNamedEntityExt {
fn spawn_named<N, B>(&mut self, name: N, components: B) -> EntityCommands<'_>
where
N: Into<Cow<'static, str>>,
B: Bundle;
}
impl SpawnNamedEntityExt for Commands<'_, '_> {
fn spawn_named<N, B>(&mut self, name: N, components: B) -> EntityCommands<'_>
where
N: Into<Cow<'static, str>>,
B: Bundle,
{
self.spawn((Name::new(name), components))
}
}
Observer
にも名前を付ける
Observerは、特定のイベントのトリガーを観測するシステムを保持するコンポーネントの一種です。add_observerを実行すると、Observer
コンポーネントを持つEntityがスポーンします。
Observer
はBevy0.14で追加された比較的新しいAPIであり、将来的に破壊的変更が行われます。本記事は、Bevy0.15.2のAPIを基準に書かれています。
Observer
はコンポーネントですが、その中身はTriggerを第一引数に取る関数です。したがって、Observer
を持つEntityには、関数へのパスがわかる情報を追加するとデバッグ時に役立ちます。std::any::type_nameを用いて自動で名前を付けるユーティリティを書いてみました。
use bevy::{ecs::system::IntoObserverSystem, prelude::*};
use std::borrow::Cow;
pub trait AddObserverExt {
fn add_observer_named<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self;
}
impl AddObserverExt for App {
fn add_observer_named<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
let name_components = observer_name_components(&observer);
self.world_mut()
.spawn((Observer::new(observer), name_components));
self
}
}
/// Observer関数の型名をもとに、デバッグ情報を生成する
fn observer_name_components<T>(observer: &T) -> impl Bundle {
let type_name = std::any::type_name_of_val(observer);
// エンティティ名は簡潔に
let name = type_name.split("::").last().unwrap();
(
Name::new(name),
// フルパスも保持しておく
ObserverSystemTypeName(Cow::Borrowed(type_name)),
)
}
/// Observer関数へのフルパス
#[derive(Debug, Component, Reflect)]
struct ObserverSystemTypeName(Cow<'static, str>);
メイン例:
use bevy::prelude::*;
mod utils;
use utils::*;
fn main() {
App::new()
.add_plugins((
// ここではLogPluginの設定を省略しています
DefaultPlugins,
#[cfg(feature = "dev")]
bevy_inspector_egui::quick::WorldInspectorPlugin::new(),
))
.add_systems(Startup, setup)
.add_observer_named(on_click)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn_named("Camera2d", Camera2d);
commands.spawn_named(
"Interaction",
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
..default()
},
);
}
fn on_click(
trigger: Trigger<Pointer<Click>>,
query: Query<(&Name, &ObserverSystemTypeName)>
) {
let observer_entity = trigger.observer();
let (name, path) = query.get(observer_entity).unwrap();
debug!("name: {}", name); // "on_click"
debug!("path: {}", path.0); // "my_crate::on_click"
}