Skip to content

Commit

Permalink
Web builds (#91)
Browse files Browse the repository at this point in the history
Issue:
==============
Closes #3 
Closes #27 

What was done:
==============
* Added configuration to be able to run the project on a browser locally
by using `wasm-server-runner`.
* Added configuration to be able to build itch.io-compatible builds with
[Trunk](https://trunkrs.dev/).
* Embeds assets into the executable to avoid distributing a separate
assets folder.
* Refactored all asset loading modules to work for both wasm and
non-wasm builds.
* Added title and fit to canvas properties to the Window.
* Set tile size to 16x16 (and doubled the grid size to keep the same
level dimensions).
  • Loading branch information
mnmaita authored Nov 28, 2023
1 parent c590f99 commit e21591b
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 48 deletions.
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# will have compiled files and executables
debug/
target/
dist/

# These are backup files generated by rustfmt
**/*.rs.bk
Expand Down
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ bevy_particle_systems = "0.11.1"
noise = "0.8.2"
rand = "0.8.5"
pathfinding = "4.3.3"
bevy_embedded_assets = "0.9.1"

[profile.dev.package."*"]
opt-level = 3
Expand Down
2 changes: 2 additions & 0 deletions Trunk.itch.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
public_url = "./"
Empty file added index.html
Empty file.
157 changes: 135 additions & 22 deletions src/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ impl Plugin for AudioPlugin {
app.add_event::<PlaySoundEffectEvent>();
app.add_event::<StopMusicEvent>();
app.init_resource::<AudioLoadStates>();
app.init_resource::<MusicFolderHandle>();
app.init_resource::<SoundEffectFolderHandle>();
app.init_resource::<MusicHandles>();
app.init_resource::<SoundEffectHandles>();
app.add_systems(Startup, (load_music_files, load_sound_effect_files));
app.add_systems(
Update,
Expand All @@ -38,33 +38,64 @@ impl Plugin for AudioPlugin {
}
}

#[derive(PartialEq)]
enum AudioLoadState {
NotLoaded,
Loading,
Loaded,
Failed,
}

impl Default for AudioLoadState {
fn default() -> Self {
Self::NotLoaded
}
}

impl From<RecursiveDependencyLoadState> for AudioLoadState {
fn from(value: RecursiveDependencyLoadState) -> Self {
match value {
RecursiveDependencyLoadState::NotLoaded => Self::NotLoaded,
RecursiveDependencyLoadState::Loading => Self::Loading,
RecursiveDependencyLoadState::Loaded => Self::Loaded,
RecursiveDependencyLoadState::Failed => Self::Failed,
}
}
}

#[derive(Resource, PartialEq)]
struct AudioLoadStates {
sound_effects_load_state: RecursiveDependencyLoadState,
music_load_state: RecursiveDependencyLoadState,
sound_effects_load_state: AudioLoadState,
music_load_state: AudioLoadState,
}

impl Default for AudioLoadStates {
fn default() -> Self {
Self {
sound_effects_load_state: RecursiveDependencyLoadState::NotLoaded,
music_load_state: RecursiveDependencyLoadState::NotLoaded,
sound_effects_load_state: AudioLoadState::NotLoaded,
music_load_state: AudioLoadState::NotLoaded,
}
}
}

impl AudioLoadStates {
pub const LOADED: Self = Self {
music_load_state: RecursiveDependencyLoadState::Loaded,
sound_effects_load_state: RecursiveDependencyLoadState::Loaded,
music_load_state: AudioLoadState::Loaded,
sound_effects_load_state: AudioLoadState::Loaded,
};
}

#[derive(Resource, Default, Deref, DerefMut)]
struct MusicFolderHandle(Handle<LoadedFolder>);
struct MusicHandles(
#[cfg(not(target_family = "wasm"))] Handle<LoadedFolder>,
#[cfg(target_family = "wasm")] Vec<Handle<AudioSource>>,
);

#[derive(Resource, Default, Deref, DerefMut)]
struct SoundEffectFolderHandle(Handle<LoadedFolder>);
struct SoundEffectHandles(
#[cfg(not(target_family = "wasm"))] Handle<LoadedFolder>,
#[cfg(target_family = "wasm")] Vec<Handle<AudioSource>>,
);

#[derive(Event)]
pub struct PlayMusicEvent {
Expand Down Expand Up @@ -149,7 +180,7 @@ fn handle_play_music_events(
spatial_transform,
} = event;
let settings = settings.unwrap_or(PlaybackSettings::DESPAWN);
let path = format!("{ASSET_FOLDER_MUSIC}/{file_name}");
let path = format_music_file_name(file_name);
let source = asset_server.get_handle(path).unwrap_or_default();
let mut entity = commands.spawn((BackgroundMusic, AudioBundle { source, settings }));

Expand Down Expand Up @@ -183,7 +214,7 @@ fn handle_play_sound_effect_events(
spatial_transform,
} = event;
let settings = settings.unwrap_or(PlaybackSettings::DESPAWN);
let path = format!("{ASSET_FOLDER_SFX}/{file_name}");
let path = format_sfx_file_name(file_name);
let source = asset_server.get_handle(path).unwrap_or_default();
let mut entity = commands.spawn((AudioBundle { source, settings }, SoundEffect));

Expand All @@ -194,33 +225,115 @@ fn handle_play_sound_effect_events(
}

fn load_music_files(mut commands: Commands, asset_server: Res<AssetServer>) {
let music_folder_handle = asset_server.load_folder(ASSET_FOLDER_MUSIC);
commands.insert_resource(MusicFolderHandle(music_folder_handle));
let music_handles = {
#[cfg(not(target_family = "wasm"))]
{
asset_server.load_folder(ASSET_FOLDER_MUSIC)
}

#[cfg(target_family = "wasm")]
{
let asset_music_list = [
format_music_file_name("theme1.ogg"),
format_music_file_name("theme2.ogg"),
format_music_file_name("theme3.ogg"),
];
asset_music_list
.iter()
.map(|path| asset_server.load::<AudioSource>(path))
.collect::<Vec<Handle<AudioSource>>>()
}
};

commands.insert_resource(MusicHandles(music_handles));
}

fn load_sound_effect_files(mut commands: Commands, asset_server: Res<AssetServer>) {
let sound_effects_folder_handle = asset_server.load_folder(ASSET_FOLDER_SFX);
commands.insert_resource(SoundEffectFolderHandle(sound_effects_folder_handle));
let sound_effect_handles = {
#[cfg(not(target_family = "wasm"))]
{
asset_server.load_folder(ASSET_FOLDER_SFX)
}

#[cfg(target_family = "wasm")]
{
let asset_sfx_list = [
format_sfx_file_name("breathend.ogg"),
format_sfx_file_name("breathloop.ogg"),
format_sfx_file_name("breathstart.ogg"),
];
asset_sfx_list
.iter()
.map(|path| asset_server.load::<AudioSource>(path))
.collect::<Vec<Handle<AudioSource>>>()
}
};

commands.insert_resource(SoundEffectHandles(sound_effect_handles));
}

fn update_music_assets_load_state(
mut audio_load_states: ResMut<AudioLoadStates>,
music_folder_handle: Res<MusicFolderHandle>,
music_handles: Res<MusicHandles>,
asset_server: Res<AssetServer>,
) {
audio_load_states.music_load_state =
asset_server.recursive_dependency_load_state(music_folder_handle.id());
audio_load_states.music_load_state = {
#[cfg(not(target_family = "wasm"))]
{
asset_server
.recursive_dependency_load_state(music_handles.id())
.into()
}
#[cfg(target_family = "wasm")]
{
let all_loaded = music_handles.iter().all(|handle| {
asset_server.recursive_dependency_load_state(handle.id())
== RecursiveDependencyLoadState::Loaded
});
if all_loaded {
RecursiveDependencyLoadState::Loaded.into()
} else {
RecursiveDependencyLoadState::NotLoaded.into()
}
}
};
}

fn update_sound_effect_assets_load_state(
mut audio_load_states: ResMut<AudioLoadStates>,
sound_effect_folder_handle: Res<SoundEffectFolderHandle>,
sound_effect_handles: Res<SoundEffectHandles>,
asset_server: Res<AssetServer>,
) {
audio_load_states.sound_effects_load_state =
asset_server.recursive_dependency_load_state(sound_effect_folder_handle.id());
audio_load_states.sound_effects_load_state = {
#[cfg(not(target_family = "wasm"))]
{
asset_server
.recursive_dependency_load_state(sound_effect_handles.id())
.into()
}
#[cfg(target_family = "wasm")]
{
let all_loaded = sound_effect_handles.iter().all(|handle| {
asset_server.recursive_dependency_load_state(handle.id())
== RecursiveDependencyLoadState::Loaded
});
if all_loaded {
RecursiveDependencyLoadState::Loaded.into()
} else {
RecursiveDependencyLoadState::NotLoaded.into()
}
}
};
}

pub fn audio_assets_loaded() -> impl Condition<()> {
IntoSystem::into_system(resource_equals(AudioLoadStates::LOADED))
}

fn format_music_file_name(file_name: &str) -> String {
format!("{ASSET_FOLDER_MUSIC}/{file_name}")
}

fn format_sfx_file_name(file_name: &str) -> String {
format!("{ASSET_FOLDER_SFX}/{file_name}")
}
75 changes: 64 additions & 11 deletions src/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct FontsPlugin;
impl Plugin for FontsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<FontsLoadState>();
app.init_resource::<FontsFolderHandle>();
app.init_resource::<FontHandles>();
app.add_systems(Startup, load_fonts);
app.add_systems(
Update,
Expand All @@ -20,34 +20,87 @@ impl Plugin for FontsPlugin {
}

#[derive(Resource, PartialEq)]
struct FontsLoadState(RecursiveDependencyLoadState);
enum FontsLoadState {
NotLoaded,
Loading,
Loaded,
Failed,
}

impl Default for FontsLoadState {
fn default() -> Self {
Self(RecursiveDependencyLoadState::NotLoaded)
Self::NotLoaded
}
}

impl FontsLoadState {
pub const LOADED: Self = Self(RecursiveDependencyLoadState::Loaded);
impl From<RecursiveDependencyLoadState> for FontsLoadState {
fn from(value: RecursiveDependencyLoadState) -> Self {
match value {
RecursiveDependencyLoadState::NotLoaded => Self::NotLoaded,
RecursiveDependencyLoadState::Loading => Self::Loading,
RecursiveDependencyLoadState::Loaded => Self::Loaded,
RecursiveDependencyLoadState::Failed => Self::Failed,
}
}
}

#[derive(Resource, Default, Deref, DerefMut)]
struct FontsFolderHandle(Handle<LoadedFolder>);
struct FontHandles(
#[cfg(not(target_family = "wasm"))] Handle<LoadedFolder>,
#[cfg(target_family = "wasm")] Vec<Handle<Font>>,
);

fn load_fonts(mut commands: Commands, asset_server: Res<AssetServer>) {
let fonts_folder_handle = asset_server.load_folder(ASSET_FOLDER_FONTS);
commands.insert_resource(FontsFolderHandle(fonts_folder_handle));
let font_handles = {
#[cfg(not(target_family = "wasm"))]
{
asset_server.load_folder(ASSET_FOLDER_FONTS)
}

#[cfg(target_family = "wasm")]
{
let asset_font_list = [
format!("{ASSET_FOLDER_FONTS}/MorrisRoman-Black.ttf"),
format!("{ASSET_FOLDER_FONTS}/MorrisRomanAlternate-Black.ttf"),
format!("{ASSET_FOLDER_FONTS}/Prince Valiant.ttf"),
];
asset_font_list
.iter()
.map(|path| asset_server.load::<Font>(path))
.collect::<Vec<Handle<Font>>>()
}
};

commands.insert_resource(FontHandles(font_handles));
}

fn update_font_assets_load_state(
mut fonts_load_state: ResMut<FontsLoadState>,
fonts_folder_handle: Res<FontsFolderHandle>,
font_handles: Res<FontHandles>,
asset_server: Res<AssetServer>,
) {
fonts_load_state.0 = asset_server.recursive_dependency_load_state(fonts_folder_handle.id());
*fonts_load_state = {
#[cfg(not(target_family = "wasm"))]
{
asset_server
.recursive_dependency_load_state(font_handles.id())
.into()
}
#[cfg(target_family = "wasm")]
{
let all_loaded = font_handles.iter().all(|handle| {
asset_server.recursive_dependency_load_state(handle.id())
== RecursiveDependencyLoadState::Loaded
});
if all_loaded {
RecursiveDependencyLoadState::Loaded.into()
} else {
RecursiveDependencyLoadState::NotLoaded.into()
}
}
};
}

pub fn font_assets_loaded() -> impl Condition<()> {
IntoSystem::into_system(resource_equals(FontsLoadState::LOADED))
IntoSystem::into_system(resource_equals(FontsLoadState::Loaded))
}
Loading

0 comments on commit e21591b

Please sign in to comment.