2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🦀ひとりRustとBevyでゲーム開発🕊️Advent Calendar 2024

Day 16

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

Last updated at Posted at 2024-12-15

今回は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を通じてアセットを取り扱うのがお勧めです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?