今回はBevyにおける アセット 、つまり画像や音声などのデータの取り扱いについて解説しておきます。
アセットサーバー
以前も簡単に触れましたが、画像などを読み込むときの最も基本的な方法はアセットサーバーを使うことです。アセットサーバーはシステムパラメータのひとつとしてアクセスできます。例えば画像を表示したければ、以下のようにasset_server: Res<AssetServer>
という引数を定義して、あとはasset_server.load("branding/bevy_bird_dark.png")
のようにload
を呼びだすだけです。
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn(Sprite::from_image(
asset_server.load("branding/bevy_bird_dark.png"),
));
}
デフォルトのアセットパスはassets
ですので、上のコードでassets/branding/bevy_bird_dark.png
にある画像ファイルが読み取られて表示されます。load
が返すのは画像のハンドルで、実際の画像データは非同期に読み取られて、読み取りが完了した時点で描画されます。開発者が非同期処理について意識する必要はありません。
bevy_asset_loader
さて、先ほどのコードでも動くのですが、上記のコードだと読み込みが完了していない状態でゲームの実行が進むので、一瞬だけ遅れて画像が表示されることがあります。WASMにコンパイルしてブラウザで実行したときなどは、画像はネットワーク越しに送信されるので遅延が目立つようになります。
そんなわけで実用的には必要なデータの読み取りが完了してから実行を開始したほうが見栄えがいいわけですが、そういうときに便利なのがbevy_asset_loaderです。
bevy_asset_loaderでは、以下のように構造体のフィールドとしてアセットを列挙しておきます。
#[derive(AssetCollection, Resource)]
struct AudioAssets {
#[asset(path = "audio/background.ogg")]
background: Handle<AudioSource>,
}
そして、add_loading_state
を呼びだして、読み込み中のステートと、読み込み完了後に遷移するステートを指定します。
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<MyStates>()
.add_loading_state(
LoadingState::new(MyStates::AssetLoading)
.continue_to_state(MyStates::Next)
.load_collection::<AudioAssets>(),
)
.add_systems(OnEnter(MyStates::Next), start_background_audio)
.run();
}
これで、読み込み中はMyStates::AssetLoading
、読み込み完了後にMyStates::Next
になります。読み取りが完了したデータのハンドルはRes<AudioAssets>
から取得できますので、 あとはMyStates::Next
になったときに各種のエンティティを生成します。
fn start_background_audio(mut commands: Commands, audio_assets: Res<AudioAssets>) {
commands.spawn((AudioPlayer(audio_assets.background.clone()), PlaybackSettings::LOOP));
}
また、アセットサーバーを直接使う場合は文字列でアセットのパスを指定しますが、このパス文字列がコードのあちこちに分散してしまうと、あとでパスを変更したくなったときに厄介です。bevy_asset_loaderならアセットが構造体のフィールドをして定義されるので、パスを書き間違えたりする恐れがないですし、インテリセンスでアセット名を補完できるので便利です。
bevy_embedded_assets
ビルドした実行可能ファイルを配布するときには、アセットのファイル群も一緒に配布する必要があります。それでも構わないのですが、ユーザーがassets
フォルダを覗くとアセットのファイルが丸見えになってしまうのはちょっと不都合です。そこで、bevy_embedded_assetsを使うと実行可能ファイルにすべてのアセットを埋め込んでしまうことができます。
使い方は簡単で、EmbeddedAssetPlugin
を設定するだけです。
fn main() {
App::new().add_plugins((EmbeddedAssetPlugin::default(), DefaultPlugins));
}
ただしWASMでビルドする場合、すべてのアセットをWASMに含めるとGitHub Pagesにアップロードできる最大サイズを超えていまいかねないのd、WASMビルドの場合はbevy_embedded_assetsを使わないようにマクロで切り替えています。また、デフォルトのモードでは埋め込みのアセットは読み込むときのパスが変わるのですが、PluginMode::ReplaceDefault
を指定すると埋め込んだアセットも通常と同じパスで読み込めるので、デスクトップ版とWASM版とでパスを切り替える必要がなくなります。
#[cfg(all(not(debug_assertions), not(target_arch = "wasm32")))]
app.add_plugins(EmbeddedAssetPlugin {
mode: PluginMode::ReplaceDefault,
});
これで、実行可能ファイルにすべてのアセットが詰め込まれて、このファイルひとつで起動できるようになります。というわけで、Bevyのサンプルコードではアセットサーバーでアセットを読み込むように書いてありますが、実用上はアセットサーバーを直接使う必要はまったくなくて、常にbevy_asset_loaderとbevy_embedded_assetsを通じてアセットを取り扱うのがお勧めです。