Closed15

BevyEngineを触ってみたい(0.14)

komemlkomeml

今回の作業環境
OS: Windows
cargo: 1.80.1
bevy: 0.14

※注意
ECSに関する詳しい内容は記述しません。
あくまで簡単な利用方法を確認して記録するものです。
あと、導入方法を日本語で記録する目的

komemlkomeml

手順

  1. 作業用ディレクトリでコマンドを開いてcargo new bevy_testでプロジェクト作成
  2. cd bevy_testでディレクトリを移動
  3. cargo add bevyでBevyEngineをプロジェクトに追加
komemlkomeml

最適化

シンプルなプロジェクトならそこまで気にはならないが規模が大きくなると重くなるみたいなので対策

  1. Cargo.tomlに以下の内容を追加する
# 少しだけ最適化を有効にする
[profile.dev]
opt-level = 1

# 依存関係に対して強めの最適化を有効にする
[profile.dev.package."*"]
opt-level = 3
  1. LLDリンク
    以下のコマンドでLLDをリンクする
cargo install -f cargo-binutils
rustup component add llvm-tools-preview

プロジェクト/.cargo/config.tomlに以下の内容を追加する

[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"

Windowsには対応していないがMoldというリンカーのほうが早いらしい。
Linuxの人はこちらを使ったほうが良いかも。
Macの人はsoldを利用するとよい。
Windowsも一応soldが対応する予定らしい(https://github.com/bluewhalesystems/sold/issues/8)

komemlkomeml
  • main関数を記述していく
use bevy::prelude::*;

fn main() {
    App::new().run();
}

これだとだた空のシェルを実行しただけなので何も起きない
println("Hello World");とか書けば一応ターミナルに表示される

komemlkomeml

Appの仕組み

  • world
    Entity、Component、Resourceやこれらに関連するメタデータに対する操作を保存している。
  • schedule
    登録されたシステムを特定の順序で実行するために必要なデータ
  • runner
    scheduleの内容を解釈していい感じに動かしてくれる
komemlkomeml

ECS

  • Component
    データ
#[derive(Component)]
struct Position {
    x: f32,
    y: f32,
}
  • System
    処理部分
fn print_position_system(query: Query<&Position>) {
    for position in &query {
        println!("position: {} {}", position.x, position.y);
    }
}
  • Entutue
    複数のコンポーネントを紐づける一意の数値
struct Entity(u64);
komemlkomeml

Component、Systemの追加方法

  • コンポーネント実装方法
    ComponentをDeriveすることでコンポーネントとして定義することができる
#[derive(Component)]
struct Person;

#[derive(Component)]
struct Name(String);
  • Worldにコンポーネントを追加する方法
    Commandを利用してWorldに追加することができる
    この段階で自動的にEntityが作成される
fn add_people(mut commands: Commands) {
    commands.spawn((Person, Neta("Elaina Proctor".to_string())));
    commands.spawn((Person, Neta("Renzo Hume".to_string())));
    commands.spawn((Person, Neta("Zayna Nieves".to_string())));
}
  • 作成した処理をWorldに追加する方法
    add_systemsを利用して処理を追加することができる
    Startupはゲーム起動時に一度だけ実行される
fn main() {
    App::new()
        .add_systems(Startup, add_people)
        .run();
}
komemlkomeml

add_systems

  • add_systems(ScheduleLabel, IntoSystemConfigs<M>)
    • ScheduleLabel
      実行するタイミングを指定する引数
    • IntoSystemConfigs<M>
      ScheduleLabelで指定したタイミングで実行する処理
      特殊な型になっているが関数入れればよし

ScheduleLabel種類

参考:https://docs.rs/bevy/latest/bevy/app/struct.Main.html

  • 最初に一度だけ呼ばれる

    1. PreStartup
    2. Startup
    3. PostStartup
  • ゲームループ

    1. First
    2. PreUpdate
    3. Update
    4. PostUpdate
    5. Last
  • 固定フレーム(デフォルトは64Hzでループする)

    1. FixedFirst
    2. FixedPreUpdate
    3. FixedUpdate
    4. FixedPostUpdate
    5. FixedLast
komemlkomeml

Query

参考:https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html

Queryを利用することで登録したComponentを取得することができる
下のQueryの場合Personコンポーネントも含むEntityに含まれるNameを取得して反復処理する処理になります。

// イミュータブルの場合
fn greet_people(query: Query<&Name, With<Person>>) {
    for name in query.iter() {
        println!("hello {}!", name.0);
    }
}

// ミュータブルの場合
fn update_people(mut query: Query<&mut Name, With<Person>>) {
    for mut name in &mut query {
        if name.0 == "Elaina Proctor" {
            name.0 = "Elaina Hume".to_string();
            break; // We don't need to change any other names.
        }
    }
}

フィルターの種類

参考:https://docs.rs/bevy/latest/bevy/ecs/query/trait.QueryFilter.html

  • コンポーネントフィルター
    • With<T>
      Tで指定されたコンポーネントを持っているEntityのフィルター
    • Without<T>
       Withの否定版
  • 変更検出フィルター
    • Added
      追加後の最初にのみ保持されるコンポーネントのフィルタ
      一回限り初期化などに利用します
    • Changed
      コマンドで発行されたエンティティの修正(エンティティの作成、エンティティコンポーネントの追加や削除など)のフィルタです
  • タプルでフィルターの指定
    Query<&ComponentA, (With<ComponentB>, Without<ComponentC>)>といった感じでタプルを利用すると一つのQueryでフィルターを複数定義できる
  • フィルタの論理演算子
    • Or
      Query<&ComponentA, Or<(With<ComponentB>, Without<ComponentC>)>>といった感じにOrの中にタプルで定義すると与えられたフィルタのいずれかが適用されるかどうかテストするフィルタになる

QueryFilter

QueryFilterを利用することで独自のフィルターを作成することができる。
参考:https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html

komemlkomeml

Chain

処理A、処理Bの順番で実行したい場合は.chain()を利用する
下のコードだと(system_a, system_b).chain()と書くことでsystem_asystem_bの順番で実行される
hello_worldはUpdate開始時にすぐ呼ばれる

fn main() {
    App::new()
        .add_systems(Startup, startup_system)
+       .add_systems(Update, (hello_world, (system_a, system_b).chain()))
        .run();
}
komemlkomeml

Plugin

参考:https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html
準備された機能を一つ登録するだけで利用できるようにする機能
UIを使いたければUiPlugin、レンダリングをしたければRenderPluginを追加すればより複雑なシステムでも簡単に利用できるようになる。
また、DefaultPluginsを追加することでゲーム開発をするうえで必要なシステムを利用することができる

fn main() {
    App::new()
+       .add_plugins(DefaultPlugins)
        .add_systems(Startup, add_person)
        .add_systems(Update, (hello_world, greet_people, change_name))
        .add_systems(PostUpdate, (change_name))
        .run();
}
komemlkomeml

オリジナルプラグイン

  • Pluginトレイトを利用することでプラグインとして認識させることができます。
+ pub struct HelloPlugin;
+ 
+ impl Plugin for HelloPlugin {
+    fn build(&self, app: &mut App) {
+         // ここにアプリを追加する
+     }
+ }

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
+       .add_plugins(HelloPlugin)
        .add_systems(Startup, add_person)
        .add_systems(Update, (hello_world, greet_people, change_name))
        .add_systems(PostUpdate, (change_name))
        .run();
}
  • いろいろ作成したsystemsをHelloPluginに移動させた場合です。
impl Plugin for HelloPlugin {
    fn build(&self, app: &mut App) {
+      app.add_systems(Startup, add_people);
+      app.add_systems(Update, (hello_world, (update_people, greet_people).chain()));
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
+       .add_plugins(HelloPlugin)
        .run();
}
komemlkomeml

Resource

Resouce「グローバルで一意な」データを扱うことができる機能です。
経過時間やアセット(サウンド、テクスチャ、メッシュ)などが管理できます。

アクセス方法

リソースへのアクセス方法はRes<T>で受け取ることができます。
ミュータブルの場合はResMut<T>で受け取れます。
TimeDefaultPluginを追加することで利用することができる前フレームから今フレームまでの時間を受け取ることができます。

+ fn greet_people(time: Res<Time>, query: Query<&Name, With<Person>>) {
    for name in &query {
        println!("hello {}!", name.0);
    }
}

リソース作り方

今回は2秒に一回実行されるタイマーを追加したいと思います。
ResourceトレイトをDeriveすればリソースとして認識されます。
Appにリソースを追加する際insert_resourceで追加すれば登録できます。

+ #[derive(Resource)]
+ struct GreetTimer(Timer);

pub struct HelloPlugin;

+ fn greet_people(time: Res<Time>, mut timer: ResMut<GreetTimer>, query: Query<&Name, With<Person>>) {
+   // タイマーを毎フレーム更新してカウントしたらtrueを返す
+   if timer.0.tick(time.delta()).just_finished() {
        for name in query.iter() {
            println!("hello {}!", name.0);
        }
+   }
}

impl Plugin for HelloPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Startup, add_person)
+          .insert_resource(GreetTimer(Timer::from_seconds(2.0, TimerMode::Repeating)))
            .add_systems(Update, (hello_world, (greet_people, change_name).chain()))
            .add_systems(PostUpdate, (change_name));
    }
}
komemlkomeml

もっと詳しく知りたい場合

このスクラップは13日前にクローズされました