mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-15 04:12:34 -06:00
feat: improved emscripten-compatible asset loading api
This commit is contained in:
71
src/asset.rs
Normal file
71
src/asset.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
//! Cross-platform asset loading abstraction.
|
||||||
|
//! On desktop, assets are embedded using include_bytes!; on Emscripten, assets are loaded from the filesystem.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AssetError {
|
||||||
|
Io(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for AssetError {
|
||||||
|
fn from(e: io::Error) -> Self {
|
||||||
|
AssetError::Io(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Asset {
|
||||||
|
Wav1,
|
||||||
|
Wav2,
|
||||||
|
Wav3,
|
||||||
|
Wav4,
|
||||||
|
Pacman,
|
||||||
|
Pellet,
|
||||||
|
Energizer,
|
||||||
|
Map,
|
||||||
|
FontKonami,
|
||||||
|
GhostBody,
|
||||||
|
GhostEyes,
|
||||||
|
// Add more as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
macro_rules! asset_bytes_enum {
|
||||||
|
( $asset:expr ) => {
|
||||||
|
match $asset {
|
||||||
|
Asset::Wav1 => Cow::Borrowed(include_bytes!("../assets/wav/1.ogg")),
|
||||||
|
Asset::Wav2 => Cow::Borrowed(include_bytes!("../assets/wav/2.ogg")),
|
||||||
|
Asset::Wav3 => Cow::Borrowed(include_bytes!("../assets/wav/3.ogg")),
|
||||||
|
Asset::Wav4 => Cow::Borrowed(include_bytes!("../assets/wav/4.ogg")),
|
||||||
|
Asset::Pacman => Cow::Borrowed(include_bytes!("../assets/32/pacman.png")),
|
||||||
|
Asset::Pellet => Cow::Borrowed(include_bytes!("../assets/24/pellet.png")),
|
||||||
|
Asset::Energizer => Cow::Borrowed(include_bytes!("../assets/24/energizer.png")),
|
||||||
|
Asset::Map => Cow::Borrowed(include_bytes!("../assets/map.png")),
|
||||||
|
Asset::FontKonami => Cow::Borrowed(include_bytes!("../assets/font/konami.ttf")),
|
||||||
|
Asset::GhostBody => Cow::Borrowed(include_bytes!("../assets/32/ghost_body.png")),
|
||||||
|
Asset::GhostEyes => Cow::Borrowed(include_bytes!("../assets/32/ghost_eyes.png")),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
|
Ok(asset_bytes_enum!(asset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "emscripten")]
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
|
||||||
|
let path = Path::new("assets").join(asset.path());
|
||||||
|
let bytes = fs::read(&path)?;
|
||||||
|
Ok(Cow::Owned(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use imp::get_asset_bytes;
|
||||||
20
src/audio.rs
20
src/audio.rs
@@ -1,16 +1,11 @@
|
|||||||
//! This module handles the audio playback for the game.
|
//! This module handles the audio playback for the game.
|
||||||
|
use crate::asset::{get_asset_bytes, Asset};
|
||||||
use sdl2::{
|
use sdl2::{
|
||||||
mixer::{self, Chunk, InitFlag, LoaderRWops, DEFAULT_FORMAT},
|
mixer::{self, Chunk, InitFlag, LoaderRWops, DEFAULT_FORMAT},
|
||||||
rwops::RWops,
|
rwops::RWops,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SOUND_1_DATA: &[u8] = include_bytes!("../assets/wav/1.ogg");
|
const SOUND_ASSETS: [Asset; 4] = [Asset::Wav1, Asset::Wav2, Asset::Wav3, Asset::Wav4];
|
||||||
const SOUND_2_DATA: &[u8] = include_bytes!("../assets/wav/2.ogg");
|
|
||||||
const SOUND_3_DATA: &[u8] = include_bytes!("../assets/wav/3.ogg");
|
|
||||||
const SOUND_4_DATA: &[u8] = include_bytes!("../assets/wav/4.ogg");
|
|
||||||
|
|
||||||
/// An array of all the sound effect data.
|
|
||||||
const SOUND_DATA: [&[u8]; 4] = [SOUND_1_DATA, SOUND_2_DATA, SOUND_3_DATA, SOUND_4_DATA];
|
|
||||||
|
|
||||||
/// The audio system for the game.
|
/// The audio system for the game.
|
||||||
///
|
///
|
||||||
@@ -39,13 +34,16 @@ impl Audio {
|
|||||||
|
|
||||||
let mixer_context = mixer::init(InitFlag::OGG).expect("Failed to initialize SDL2_mixer");
|
let mixer_context = mixer::init(InitFlag::OGG).expect("Failed to initialize SDL2_mixer");
|
||||||
|
|
||||||
let sounds: Vec<Chunk> = SOUND_DATA
|
let sounds: Vec<Chunk> = SOUND_ASSETS
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, data)| {
|
.map(|(i, asset)| {
|
||||||
let rwops = RWops::from_bytes(data)
|
let data = get_asset_bytes(*asset).expect("Failed to load sound asset");
|
||||||
|
let rwops = RWops::from_bytes(&data)
|
||||||
.unwrap_or_else(|_| panic!("Failed to create RWops for sound {}", i + 1));
|
.unwrap_or_else(|_| panic!("Failed to create RWops for sound {}", i + 1));
|
||||||
rwops.load_wav().unwrap_or_else(|_| panic!("Failed to load sound {} from embedded data", i + 1))
|
rwops
|
||||||
|
.load_wav()
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to load sound {} from asset API", i + 1))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
65
src/game.rs
65
src/game.rs
@@ -12,6 +12,7 @@ use sdl2::video::WindowContext;
|
|||||||
use sdl2::{pixels::Color, render::Canvas, video::Window};
|
use sdl2::{pixels::Color, render::Canvas, video::Window};
|
||||||
|
|
||||||
use crate::animation::AtlasTexture;
|
use crate::animation::AtlasTexture;
|
||||||
|
use crate::asset::{get_asset_bytes, Asset};
|
||||||
use crate::audio::Audio;
|
use crate::audio::Audio;
|
||||||
use crate::constants::RAW_BOARD;
|
use crate::constants::RAW_BOARD;
|
||||||
use crate::debug::{DebugMode, DebugRenderer};
|
use crate::debug::{DebugMode, DebugRenderer};
|
||||||
@@ -22,17 +23,6 @@ use crate::ghosts::blinky::Blinky;
|
|||||||
use crate::map::Map;
|
use crate::map::Map;
|
||||||
use crate::pacman::Pacman;
|
use crate::pacman::Pacman;
|
||||||
|
|
||||||
// Embed texture data directly into the executable
|
|
||||||
static PACMAN_TEXTURE_DATA: &[u8] = include_bytes!("../assets/32/pacman.png");
|
|
||||||
static PELLET_TEXTURE_DATA: &[u8] = include_bytes!("../assets/24/pellet.png");
|
|
||||||
static POWER_PELLET_TEXTURE_DATA: &[u8] = include_bytes!("../assets/24/energizer.png");
|
|
||||||
static MAP_TEXTURE_DATA: &[u8] = include_bytes!("../assets/map.png");
|
|
||||||
static FONT_DATA: &[u8] = include_bytes!("../assets/font/konami.ttf");
|
|
||||||
|
|
||||||
// Add ghost texture data
|
|
||||||
static GHOST_BODY_TEXTURE_DATA: &[u8] = include_bytes!("../assets/32/ghost_body.png");
|
|
||||||
static GHOST_EYES_TEXTURE_DATA: &[u8] = include_bytes!("../assets/32/ghost_eyes.png");
|
|
||||||
|
|
||||||
/// The main game state.
|
/// The main game state.
|
||||||
///
|
///
|
||||||
/// This struct contains all the information necessary to run the game, including
|
/// This struct contains all the information necessary to run the game, including
|
||||||
@@ -69,10 +59,11 @@ impl<'a> Game<'a> {
|
|||||||
) -> Game<'a> {
|
) -> Game<'a> {
|
||||||
let map = Rc::new(RefCell::new(Map::new(RAW_BOARD)));
|
let map = Rc::new(RefCell::new(Map::new(RAW_BOARD)));
|
||||||
|
|
||||||
// Load Pacman texture from embedded data
|
// Load Pacman texture from asset API
|
||||||
|
let pacman_bytes = get_asset_bytes(Asset::Pacman).expect("Failed to load asset");
|
||||||
let pacman_atlas = texture_creator
|
let pacman_atlas = texture_creator
|
||||||
.load_texture_bytes(PACMAN_TEXTURE_DATA)
|
.load_texture_bytes(&pacman_bytes)
|
||||||
.expect("Could not load pacman texture from embedded data");
|
.expect("Could not load pacman texture from asset API");
|
||||||
let pacman = Rc::new(RefCell::new(Pacman::new(
|
let pacman = Rc::new(RefCell::new(Pacman::new(
|
||||||
(1, 1),
|
(1, 1),
|
||||||
pacman_atlas,
|
pacman_atlas,
|
||||||
@@ -80,12 +71,14 @@ impl<'a> Game<'a> {
|
|||||||
)));
|
)));
|
||||||
|
|
||||||
// Load ghost textures
|
// Load ghost textures
|
||||||
|
let ghost_body_bytes = get_asset_bytes(Asset::GhostBody).expect("Failed to load asset");
|
||||||
let ghost_body = texture_creator
|
let ghost_body = texture_creator
|
||||||
.load_texture_bytes(GHOST_BODY_TEXTURE_DATA)
|
.load_texture_bytes(&ghost_body_bytes)
|
||||||
.expect("Could not load ghost body texture from embedded data");
|
.expect("Could not load ghost body texture from asset API");
|
||||||
|
let ghost_eyes_bytes = get_asset_bytes(Asset::GhostEyes).expect("Failed to load asset");
|
||||||
let ghost_eyes = texture_creator
|
let ghost_eyes = texture_creator
|
||||||
.load_texture_bytes(GHOST_EYES_TEXTURE_DATA)
|
.load_texture_bytes(&ghost_eyes_bytes)
|
||||||
.expect("Could not load ghost eyes texture from embedded data");
|
.expect("Could not load ghost eyes texture from asset API");
|
||||||
|
|
||||||
// Create Blinky
|
// Create Blinky
|
||||||
let blinky = Blinky::new(
|
let blinky = Blinky::new(
|
||||||
@@ -96,38 +89,48 @@ impl<'a> Game<'a> {
|
|||||||
Rc::clone(&pacman),
|
Rc::clone(&pacman),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load pellet texture from embedded data
|
// Load pellet texture from asset API
|
||||||
|
let pellet_bytes = get_asset_bytes(Asset::Pellet).expect("Failed to load asset");
|
||||||
let pellet_texture = Rc::new(AtlasTexture::new(
|
let pellet_texture = Rc::new(AtlasTexture::new(
|
||||||
texture_creator
|
texture_creator
|
||||||
.load_texture_bytes(PELLET_TEXTURE_DATA)
|
.load_texture_bytes(&pellet_bytes)
|
||||||
.expect("Could not load pellet texture from embedded data"),
|
.expect("Could not load pellet texture from asset API"),
|
||||||
1,
|
1,
|
||||||
24,
|
24,
|
||||||
24,
|
24,
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
|
let power_pellet_bytes = get_asset_bytes(Asset::Energizer).expect("Failed to load asset");
|
||||||
let power_pellet_texture = Rc::new(AtlasTexture::new(
|
let power_pellet_texture = Rc::new(AtlasTexture::new(
|
||||||
texture_creator
|
texture_creator
|
||||||
.load_texture_bytes(POWER_PELLET_TEXTURE_DATA)
|
.load_texture_bytes(&power_pellet_bytes)
|
||||||
.expect("Could not load power pellet texture from embedded data"),
|
.expect("Could not load power pellet texture from asset API"),
|
||||||
1,
|
1,
|
||||||
24,
|
24,
|
||||||
24,
|
24,
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Load font from embedded data
|
// Load font from asset API
|
||||||
let font_rwops = RWops::from_bytes(FONT_DATA).expect("Failed to create RWops for font");
|
let font = {
|
||||||
let font = ttf_context
|
let font_bytes = get_asset_bytes(Asset::FontKonami)
|
||||||
.load_font_from_rwops(font_rwops, 24)
|
.expect("Failed to load asset")
|
||||||
.expect("Could not load font from embedded data");
|
.into_owned();
|
||||||
|
let font_bytes_static: &'static [u8] = Box::leak(font_bytes.into_boxed_slice());
|
||||||
|
let font_rwops =
|
||||||
|
RWops::from_bytes(font_bytes_static).expect("Failed to create RWops for font");
|
||||||
|
ttf_context
|
||||||
|
.load_font_from_rwops(font_rwops, 24)
|
||||||
|
.expect("Could not load font from asset API")
|
||||||
|
};
|
||||||
|
|
||||||
let audio = Audio::new();
|
let audio = Audio::new();
|
||||||
|
|
||||||
// Load map texture from embedded data
|
// Load map texture from asset API
|
||||||
|
let map_bytes = get_asset_bytes(Asset::Map).expect("Failed to load asset");
|
||||||
let mut map_texture = texture_creator
|
let mut map_texture = texture_creator
|
||||||
.load_texture_bytes(MAP_TEXTURE_DATA)
|
.load_texture_bytes(&map_bytes)
|
||||||
.expect("Could not load map texture from embedded data");
|
.expect("Could not load map texture from asset API");
|
||||||
map_texture.set_color_mod(0, 0, 255);
|
map_texture.set_color_mod(0, 0, 255);
|
||||||
|
|
||||||
let edibles = reconstruct_edibles(
|
let edibles = reconstruct_edibles(
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ mod helper;
|
|||||||
mod map;
|
mod map;
|
||||||
mod modulation;
|
mod modulation;
|
||||||
mod pacman;
|
mod pacman;
|
||||||
|
mod asset;
|
||||||
|
|
||||||
/// The main entry point of the application.
|
/// The main entry point of the application.
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user