今回はBevyのイベントという機能について解説していきます。
イベントとは
Bevyではシステムの集合としてゲームを定義しますが、複数のシステムで使われる共通の処理というものもあります。このとき、同一部分のコードはもちろん関数として切り出せばいいわけですが、ただの関数として共通部分を切り分けるのには欠点があります。たとえ共通の処理を関数としてくくりだしたとしても、システムパラメータは呼び出し元のシステムから受け取らなければならないのです。
たとえば、効果音を再生する関数を定義したとしましょう。この関数は魔法攻撃を行うシステムからも使われますし、UIのシステムでボタンを押したときなど、さまざまなシステムから使われます。効果音再生関数は音声データのリソースや、現在の音量を表すリソースなどを引数で受け取るため、呼び出し元のシステムにはシステムパラメータの追加も必要です。システムに機能を追加すると、この調子でシステムパラメータもどんどん増えていきます。そうすると、どのシステムパラメータが何の処理のために使われているのかわかりづらくなってしまいます。
このようなとき、効果音の再生機能をただの関数ではなくイベントをトリガーとする別システムとして定義すると、効果音の再生を別のシステムに切り出すことができます。魔法攻撃のシステムから効果音再生システムを呼び出すというイメージです。
イベントの定義
イベントは具体的にはEvent
を導出した構造体です。次のように新たなイベントSEEvent
を定義してみます。
#[derive(Event, Clone, Copy)]
pub struct SEEvent {
se: SE,
position: Option<Vec2>,
}
あとはapp_event
でイベントを登録しておきます。
app.add_event::<SEEvent>();
イベントの送信
イベントを送信するには、EventWriter
というシステムパラメータを参照します。たとえば、キャラクターが魔法を使うときのfire_bullet
というシステムは、次のようにEventWriter<SEEvent>
というシステムパラメータをとります。
pub fn fire_bullet(
...
mut se_writer: EventWriter<SEEvent>,
...
) {
...
}
あとは、効果音を鳴らしたいときにsend
を呼ぶだけです。
se_writer.send(SEEvent::pos(SE::Fire, spawn.position));
これで、send
を呼ぶたびにイベントキューにイベントが溜まっていきます。
イベントの受信
イベントキューにたまったイベントを取り出すには、EventReader
というシステムパラメータを参照します。それでread
を呼べば、イベントキューに溜まったイベントをひとつづつ処理できます。ここで実際に効果音を再生する処理を行えばいいわけです。
fn se_events(
...
mut reader: EventReader<SEEvent>,
...
) {
for event in reader.read() {
...
}
}
これで、効果音を鳴らしたい側のシステムは、EventWriter<SEEvent>
というシステムパラメータだけを追加すれば効果音を鳴らせるようになります。
もしここでイベントではなくただの関数で共通コードをまとめようとすると、効果音を鳴らしたいシステムは、音声データを格納するリソースや、音量設定を格納するリソース、カメラを取得するクエリなどの他の複数のシステムパラメータが必要でした。それがEventWriter<SEEvent>
ひとつだけを追加すればよくなり、コードの見通しがよくなりました。
効果音を再生するのになぜカメラのクエリをするのか?と思うかもしれませんが、カメラの位置から遠い場所で発生した出来事の効果音は小さい音量で再生するようになっているからです。それは効果音再生システムの実装の都合でしかありませんが、イベントを使うことでこのあたりの実装の詳細も隠蔽できるわけです。
参考文献
今回書いた内容はcheatbookにも同じようなことが書いてあります。