BevyEngineを触ってみたい(0.14)
今回の作業環境
OS: Windows
cargo: 1.80.1
bevy: 0.14
※注意
ECSに関する詳しい内容は記述しません。
あくまで簡単な利用方法を確認して記録するものです。
あと、導入方法を日本語で記録する目的
インストール
以下のサイトを参考にしながら進めます。
手順
- 作業用ディレクトリでコマンドを開いて
cargo new bevy_test
でプロジェクト作成 -
cd bevy_test
でディレクトリを移動 -
cargo add bevy
でBevyEngineをプロジェクトに追加
最適化
シンプルなプロジェクトならそこまで気にはならないが規模が大きくなると重くなるみたいなので対策
- Cargo.tomlに以下の内容を追加する
# 少しだけ最適化を有効にする
[profile.dev]
opt-level = 1
# 依存関係に対して強めの最適化を有効にする
[profile.dev.package."*"]
opt-level = 3
- 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)
- main関数を記述していく
use bevy::prelude::*;
fn main() {
App::new().run();
}
これだとだた空のシェルを実行しただけなので何も起きない
println("Hello World");とか書けば一応ターミナルに表示される
Appの仕組み
- world
Entity、Component、Resourceやこれらに関連するメタデータに対する操作を保存している。 - schedule
登録されたシステムを特定の順序で実行するために必要なデータ - runner
scheduleの内容を解釈していい感じに動かしてくれる
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);
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();
}
add_systems
- add_systems(ScheduleLabel, IntoSystemConfigs<M>)
- ScheduleLabel
実行するタイミングを指定する引数 - IntoSystemConfigs<M>
ScheduleLabelで指定したタイミングで実行する処理
特殊な型になっているが関数入れればよし
- ScheduleLabel
ScheduleLabel種類
参考:https://docs.rs/bevy/latest/bevy/app/struct.Main.html
-
最初に一度だけ呼ばれる
- PreStartup
- Startup
- PostStartup
-
ゲームループ
- First
- PreUpdate
- Update
- PostUpdate
- Last
-
固定フレーム(デフォルトは64Hzでループする)
- FixedFirst
- FixedPreUpdate
- FixedUpdate
- FixedPostUpdate
- FixedLast
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の否定版
- With<T>
- 変更検出フィルター
- Added
追加後の最初にのみ保持されるコンポーネントのフィルタ
一回限り初期化などに利用します - Changed
コマンドで発行されたエンティティの修正(エンティティの作成、エンティティコンポーネントの追加や削除など)のフィルタです
- Added
- タプルでフィルターの指定
Query<&ComponentA, (With<ComponentB>, Without<ComponentC>)>
といった感じでタプルを利用すると一つのQueryでフィルターを複数定義できる - フィルタの論理演算子
- Or
Query<&ComponentA, Or<(With<ComponentB>, Without<ComponentC>)>>
といった感じにOrの中にタプルで定義すると与えられたフィルタのいずれかが適用されるかどうかテストするフィルタになる
- Or
QueryFilter
QueryFilterを利用することで独自のフィルターを作成することができる。
参考:https://docs.rs/bevy/latest/bevy/ecs/system/struct.Query.html
Chain
処理A、処理Bの順番で実行したい場合は.chain()
を利用する
下のコードだと(system_a, system_b).chain()
と書くことでsystem_a
、system_b
の順番で実行される
hello_worldはUpdate開始時にすぐ呼ばれる
fn main() {
App::new()
.add_systems(Startup, startup_system)
+ .add_systems(Update, (hello_world, (system_a, system_b).chain()))
.run();
}
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();
}
オリジナルプラグイン
- 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();
}
Resource
Resouce「グローバルで一意な」データを扱うことができる機能です。
経過時間やアセット(サウンド、テクスチャ、メッシュ)などが管理できます。
アクセス方法
リソースへのアクセス方法はRes<T>
で受け取ることができます。
ミュータブルの場合はResMut<T>
で受け取れます。
Time
はDefaultPlugin
を追加することで利用することができる前フレームから今フレームまでの時間を受け取ることができます。
+ 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));
}
}
もっと詳しく知りたい場合
- Example:https://github.com/bevyengine/bevy/tree/latest/examples#examples
サンプルプログラムがたくさん入っています。 - ドキュメント:https://docs.rs/bevy/latest/bevy/
- アセット:https://bevyengine.org/assets/
Bevyで利用できるプラグインがいろいろある - チートブック:https://bevy-cheatbook.github.io/
もっと細かな仕様に関する解説が乗っている。
昔読んだときは少し情報が古かったことがある。
今はどうか知らない。