【Rustのまほう2】#16 アセットの読み込み

今回はBevyにおける **アセット** 、つまり画像や音声などのデータの取り扱いについて解説しておきます。

# アセットサーバー

以前も簡単に触れましたが、画像などを読み込むときの最も基本的な方法は**アセットサーバー**を使うことです。アセットサーバーはシステムパラメータのひとつとしてアクセスできます。例えば画像を表示したければ、以下のように`asset_server: Res<AssetServer>`という引数を定義して、あとは`asset_server.load("branding/bevy_bird_dark.png")`のように`load`を呼びだすだけです。

```rust
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です。

https://github.com/NiklasEi/bevy_asset_loader

bevy_asset_loaderでは、以下のように構造体のフィールドとしてアセットを列挙しておきます。

```rust
#[derive(AssetCollection, Resource)]
struct AudioAssets {
    #[asset(path = "audio/background.ogg")]
    background: Handle<AudioSource>,
}
```

そして、`add_loading_state`を呼びだして、読み込み中のステートと、読み込み完了後に遷移するステートを指定します。

```rust
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`になったときに各種のエンティティを生成します。

```rust
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を使うと実行可能ファイルにすべてのアセットを埋め込んでしまうことができます。

https://github.com/vleue/bevy_embedded_assets

使い方は簡単で、`EmbeddedAssetPlugin`を設定するだけです。

```rust
fn main() {
    App::new().add_plugins((EmbeddedAssetPlugin::default(), DefaultPlugins));
}
```

ただしWASMでビルドする場合、すべてのアセットをWASMに含めるとGitHub Pagesにアップロードできる最大サイズを超えていまいかねないのｄ、WASMビルドの場合はbevy_embedded_assetsを使わないようにマクロで切り替えています。また、デフォルトのモードでは埋め込みのアセットは読み込むときのパスが変わるのですが、`PluginMode::ReplaceDefault`を指定すると埋め込んだアセットも通常と同じパスで読み込めるので、デスクトップ版とWASM版とでパスを切り替える必要がなくなります。

```rust
#[cfg(all(not(debug_assertions), not(target_arch = "wasm32")))]
app.add_plugins(EmbeddedAssetPlugin {
    mode: PluginMode::ReplaceDefault,
});
```

これで、実行可能ファイルにすべてのアセットが詰め込まれて、このファイルひとつで起動できるようになります。というわけで、Bevyのサンプルコードではアセットサーバーでアセットを読み込むように書いてありますが、実用上はアセットサーバーを直接使う必要はまったくなくて、常にbevy_asset_loaderとbevy_embedded_assetsを通じてアセットを取り扱うのがお勧めです。