2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Bevyデバッグのすすめ

Posted at

はじめに

本記事では、Rust製ゲームエンジンBevyを使用時に役立つデバッグ設定を紹介します。

本記事はBevyやECSの紹介・解説ではありません。Bevyについて知りたい方は、以下のリンクを参照してください。

https://bevyengine.org/learn/quick-start/introduction/
https://bevyengine.org/examples/

環境

bevy: 0.15.2

devモードを作成

開発中のみ使用したい機能がある場合、devfeatureを定義します。cfgアトリビュートを活用して、デバッグ設定を簡単にできるようにします。

Cargo.toml
[features]
default = ["dev"]
dev = [
    # release時には追加したくないfeatureをここに
    "bevy/dynamic_linking",
    "bevy/bevy_dev_tools",
]

自身のクレートのログレベルを設定

自身のクレートのみ、デバッグレベルのログを表示させます。

main.rs
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のようなメソッドを追加しても良いかもしれません。

utils.rs
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を用いて自動で名前を付けるユーティリティを書いてみました。

utils.rs
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>);

メイン例:

main.rs
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"
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?