bevy_quickstart (soon to be bevy_new_2d
)
exposes some great basic patterns for game development. However, many folks want to use
bevy_asset_loader
rather than the built-in asset
management, whether out of habit or personal preference.
Herewith the steps for doing a manual conversion.
Note that by far the smarter way is to yoink demo
in favour of your own custom game plugin,
which might well be inspired by demo
but doesn't necessarily adhere to every last
skerrick of its goodness. What follows is
more for the curious, the tinkerers, the "what if?"-ers... y'know, my people.
cargo add bevy_asset_loader
Replace the contents of src/asset_tracking.rs
with (for example):
use bevy::prelude::*;
use bevy_asset_loader::prelude::*;
use crate::screens::Screen;
pub(super) fn plugin(app: &mut App) {
app.add_loading_state(
LoadingState::new(Screen::Loading)
.continue_to_state(Screen::Title)
.load_collection::<AudioAssets>()
.load_collection::<ImageAssets>(),
);
}
#[derive(AssetCollection, Resource)]
pub struct AudioAssets {
#[asset(path = "audio/sound_effects/button_hover.ogg")]
button_hover: Handle<AudioSource>,
#[asset(path = "audio/sound_effects/button_press.ogg")]
button_press: Handle<AudioSource>,
#[asset(path = "audio/music/Fluffing A Duck.ogg")]
fluffing_a_duck: Handle<AudioSource>,
#[asset(path = "audio/music/Monkeys Spinning Monkeys.ogg")]
monkeys_spinning_monkeys: Handle<AudioSource>,
#[asset(path = "audio/sound_effects/step1.ogg")]
path_step_1: Handle<AudioSource>,
#[asset(path = "audio/sound_effects/step2.ogg")]
path_step_2: Handle<AudioSource>,
#[asset(path = "audio/sound_effects/step3.ogg")]
path_step_3: Handle<AudioSource>,
#[asset(path = "audio/sound_effects/step4.ogg")]
path_step_4: Handle<AudioSource>,
}
#[derive(AssetCollection, Resource)]
pub struct ImageAssets {
#[asset(path = "images/ducky.png")]
pub ducky: Handle<Image>,
#[asset(path = "images/splash.png")]
pub splash: Handle<Image>,
}
Obviously, this is a 1:1 replacement of the quickstart resources, but you could omit most of 'em if you're planning to delete anyway.
Next, there's a bit of a pattern to replacing the quickstart's asset code. We'll use player.rs
as
an example, but something similar should be done for each part of the demo that does asset
management:
PlayerAssets
player_assets
in spawn_player
with image_assets: Res<ImageAssets>,
player_assets
with image_assets
app.load_resource::<PlayerAssets>()
from fn plugin
When it comes to music, we might use credits.rs
as an example:
CreditsMusic
fn play_credits_music(mut commands: Commands, audio_assets: Res<AudioAssets>) {
commands
.spawn((
AudioBundle {
source: audio_assets.monkeys_spinning_monkeys.clone(),
settings: PlaybackSettings::LOOP,
},
Music,
));
}
fn stop_music(mut commands: Commands, music: Query<Entity, With<Music>>) {
if let Ok(entity) = music.get_single() {
commands.entity(entity).despawn_recursive();
}
}
i.e. just query for the entity and despawn, instead of saving it onto CreditsMusic
. (You could also
state scope the audio, if you're not planning to do anything particularly fancy with it.
State-scoping is a lot like magic.)
In loading.rs
, remove any non-existent asset resources, and the functions
continue_to_title_screen
and all_assets_loaded
, as bevy_asset_loader
is taking care of this
for us.
Some systems are configured to run against the general Update
schedule, when we really want to be
more precise about when they are called. For example, those in AppSet::Update
probably should be
divided into AppSet::UpdateAnytime
and AppSet::UpdateGameplay
or some such. This is also true of
AppSet::RecordInput
, which currently is only used for player movement. We might do something like:
app.configure_sets(Update, (AppSet::TickTimers, AppSet::Update).chain());
app.configure_sets(
Update,
(AppSet::RecordInput, AppSet::GameplayUpdate)
.chain()
.run_if(in_state(Screen::Gameplay)),
);
// ...
#[derive(SystemSet, Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
enum AppSet {
TickTimers,
RecordInput,
Update,
/// Updates only during gameplay (when resources etc. should already exist)
GameplayUpdate,
}
The goal is just to prevent unnecessary Update
system runs from attempting to access a resource
before it exists. Here, we're delegating responsibility for asset loading, so we really just need to
know which states are safe to assume the resource will exist in!
Move your systems to the appropriate AppSet
, e.g.:
app.add_systems(
Update,
(
update_animation_timer.in_set(AppSet::TickTimers),
(
update_animation_movement,
update_animation_atlas,
trigger_step_sound_effect,
)
.chain()
.in_set(AppSet::GameplayUpdate),
),
);