mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 15:15:48 -06:00
fix: proper font loading, cross platform assets, better platform independent trait implementation, conditional modules
This commit is contained in:
@@ -11,7 +11,8 @@ pub enum Asset {
|
||||
Wav2,
|
||||
Wav3,
|
||||
Wav4,
|
||||
Atlas,
|
||||
AtlasImage,
|
||||
Font,
|
||||
}
|
||||
|
||||
impl Asset {
|
||||
@@ -23,7 +24,8 @@ impl Asset {
|
||||
Wav2 => "sound/waka/2.ogg",
|
||||
Wav3 => "sound/waka/3.ogg",
|
||||
Wav4 => "sound/waka/4.ogg",
|
||||
Atlas => "atlas.png",
|
||||
AtlasImage => "atlas.png",
|
||||
Font => "TerminalVector.ttf",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::systems::{
|
||||
AudioState, Collider, DeltaTime, DirectionalAnimated, EntityType, Ghost, GhostBundle, GhostCollider, GlobalState,
|
||||
ItemBundle, ItemCollider, PacmanCollider, PlayerBundle, PlayerControlled, RenderDirty, Renderable, ScoreResource,
|
||||
},
|
||||
debug::{debug_render_system, DebugState, DebugTextureResource},
|
||||
debug::{debug_render_system, DebugFontResource, DebugState, DebugTextureResource},
|
||||
ghost::ghost_movement_system,
|
||||
input::input_system,
|
||||
item::item_system,
|
||||
@@ -28,17 +28,14 @@ use crate::systems::{
|
||||
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
|
||||
};
|
||||
use crate::texture::animated::AnimatedTexture;
|
||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||
use bevy_ecs::system::NonSendMut;
|
||||
use bevy_ecs::{
|
||||
event::EventRegistry,
|
||||
observer::Trigger,
|
||||
schedule::Schedule,
|
||||
system::{Res, ResMut},
|
||||
world::World,
|
||||
};
|
||||
use bevy_ecs::event::EventRegistry;
|
||||
use bevy_ecs::observer::Trigger;
|
||||
use bevy_ecs::schedule::Schedule;
|
||||
use bevy_ecs::system::{NonSendMut, Res, ResMut};
|
||||
use bevy_ecs::world::World;
|
||||
use sdl2::image::LoadTexture;
|
||||
use sdl2::render::{Canvas, ScaleMode, TextureCreator};
|
||||
use sdl2::rwops::RWops;
|
||||
use sdl2::video::{Window, WindowContext};
|
||||
use sdl2::EventPump;
|
||||
|
||||
@@ -70,6 +67,7 @@ impl Game {
|
||||
) -> GameResult<Game> {
|
||||
let mut world = World::default();
|
||||
let mut schedule = Schedule::default();
|
||||
let ttf_context = Box::leak(Box::new(sdl2::ttf::init().map_err(|e| GameError::Sdl(e.to_string()))?));
|
||||
|
||||
EventRegistry::register_event::<GameError>(&mut world);
|
||||
EventRegistry::register_event::<GameEvent>(&mut world);
|
||||
@@ -92,11 +90,18 @@ impl Game {
|
||||
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||
debug_texture.set_scale_mode(ScaleMode::Nearest);
|
||||
|
||||
let font_data = get_asset_bytes(Asset::Font)?;
|
||||
let static_font_data: &'static [u8] = Box::leak(font_data.to_vec().into_boxed_slice());
|
||||
let font_asset = RWops::from_bytes(static_font_data).map_err(|_| GameError::Sdl("Failed to load font".to_string()))?;
|
||||
let debug_font = ttf_context
|
||||
.load_font_from_rwops(font_asset, 12)
|
||||
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||
|
||||
// Initialize audio system
|
||||
let audio = crate::audio::Audio::new();
|
||||
|
||||
// Load atlas and create map texture
|
||||
let atlas_bytes = get_asset_bytes(Asset::Atlas)?;
|
||||
let atlas_bytes = get_asset_bytes(Asset::AtlasImage)?;
|
||||
let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| {
|
||||
if e.to_string().contains("format") || e.to_string().contains("unsupported") {
|
||||
GameError::Texture(crate::error::TextureError::InvalidFormat(format!(
|
||||
@@ -187,6 +192,7 @@ impl Game {
|
||||
world.insert_non_send_resource(BackbufferResource(backbuffer));
|
||||
world.insert_non_send_resource(MapTextureResource(map_texture));
|
||||
world.insert_non_send_resource(DebugTextureResource(debug_texture));
|
||||
world.insert_non_send_resource(DebugFontResource(debug_font));
|
||||
world.insert_non_send_resource(AudioResource(audio));
|
||||
|
||||
world.insert_resource(map);
|
||||
@@ -207,8 +213,7 @@ impl Game {
|
||||
}
|
||||
},
|
||||
);
|
||||
schedule.add_systems(
|
||||
(
|
||||
schedule.add_systems((
|
||||
profile(SystemId::Input, input_system),
|
||||
profile(SystemId::PlayerControls, player_control_system),
|
||||
profile(SystemId::PlayerMovement, player_movement_system),
|
||||
@@ -238,9 +243,7 @@ impl Game {
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
));
|
||||
|
||||
// Spawn player
|
||||
world.spawn(player);
|
||||
@@ -288,7 +291,7 @@ impl Game {
|
||||
Ok(Game { world, schedule })
|
||||
}
|
||||
|
||||
/// Spawns all four ghosts at their starting positions with appropriate textures.
|
||||
/// Spowns all four ghosts at their starting positions with appropriate textures.
|
||||
fn spawn_ghosts(world: &mut World) -> GameResult<()> {
|
||||
// Extract the data we need first to avoid borrow conflicts
|
||||
let ghost_start_positions = {
|
||||
|
||||
@@ -5,12 +5,12 @@ use std::time::Duration;
|
||||
|
||||
use crate::asset::Asset;
|
||||
use crate::error::{AssetError, PlatformError};
|
||||
use crate::platform::Platform;
|
||||
use crate::platform::CommonPlatform;
|
||||
|
||||
/// Desktop platform implementation.
|
||||
pub struct DesktopPlatform;
|
||||
pub struct Platform;
|
||||
|
||||
impl Platform for DesktopPlatform {
|
||||
impl CommonPlatform for Platform {
|
||||
fn sleep(&self, duration: Duration, focused: bool) {
|
||||
if focused {
|
||||
spin_sleep::sleep(duration);
|
||||
@@ -75,7 +75,8 @@ impl Platform for DesktopPlatform {
|
||||
Asset::Wav2 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/2.ogg"))),
|
||||
Asset::Wav3 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/3.ogg"))),
|
||||
Asset::Wav4 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/4.ogg"))),
|
||||
Asset::Atlas => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))),
|
||||
Asset::AtlasImage => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))),
|
||||
Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ use std::time::Duration;
|
||||
|
||||
use crate::asset::Asset;
|
||||
use crate::error::{AssetError, PlatformError};
|
||||
use crate::platform::Platform;
|
||||
use crate::platform::CommonPlatform;
|
||||
|
||||
/// Emscripten platform implementation.
|
||||
pub struct EmscriptenPlatform;
|
||||
pub struct Platform;
|
||||
|
||||
impl Platform for EmscriptenPlatform {
|
||||
impl CommonPlatform for Platform {
|
||||
fn sleep(&self, duration: Duration, _focused: bool) {
|
||||
unsafe {
|
||||
emscripten_sleep(duration.as_millis() as u32);
|
||||
|
||||
@@ -5,11 +5,13 @@ use crate::error::{AssetError, PlatformError};
|
||||
use std::borrow::Cow;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod desktop;
|
||||
pub mod emscripten;
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
mod desktop;
|
||||
#[cfg(target_os = "emscripten")]
|
||||
mod emscripten;
|
||||
|
||||
/// Platform abstraction trait that defines cross-platform functionality.
|
||||
pub trait Platform {
|
||||
pub trait CommonPlatform {
|
||||
/// Sleep for the specified duration using platform-appropriate method.
|
||||
fn sleep(&self, duration: Duration, focused: bool);
|
||||
|
||||
@@ -32,17 +34,14 @@ pub trait Platform {
|
||||
|
||||
/// Get the current platform implementation.
|
||||
#[allow(dead_code)]
|
||||
pub fn get_platform() -> &'static dyn Platform {
|
||||
static DESKTOP: desktop::DesktopPlatform = desktop::DesktopPlatform;
|
||||
static EMSCRIPTEN: emscripten::EmscriptenPlatform = emscripten::EmscriptenPlatform;
|
||||
|
||||
pub fn get_platform() -> &'static dyn CommonPlatform {
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
{
|
||||
&DESKTOP
|
||||
&desktop::Platform
|
||||
}
|
||||
|
||||
#[cfg(target_os = "emscripten")]
|
||||
{
|
||||
&EMSCRIPTEN
|
||||
&emscripten::Platform
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use glam::{IVec2, UVec2, Vec2};
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::{Canvas, Texture, TextureCreator};
|
||||
use sdl2::ttf::Font;
|
||||
use sdl2::video::{Window, WindowContext};
|
||||
|
||||
#[derive(Resource, Default, Debug, Copy, Clone, PartialEq)]
|
||||
@@ -36,6 +37,9 @@ impl DebugState {
|
||||
/// Resource to hold the debug texture for persistent rendering
|
||||
pub struct DebugTextureResource(pub Texture<'static>);
|
||||
|
||||
/// Resource to hold the debug font
|
||||
pub struct DebugFontResource(pub Font<'static, 'static>);
|
||||
|
||||
/// Transforms a position from logical canvas coordinates to output canvas coordinates (with board offset)
|
||||
fn transform_position_with_offset(pos: Vec2, scale: f32) -> IVec2 {
|
||||
((pos + BOARD_PIXEL_OFFSET.as_vec2()) * scale).as_ivec2()
|
||||
@@ -46,13 +50,8 @@ fn render_timing_display(
|
||||
canvas: &mut Canvas<Window>,
|
||||
texture_creator: &mut TextureCreator<WindowContext>,
|
||||
timings: &SystemTimings,
|
||||
font: &Font,
|
||||
) {
|
||||
// Get TTF context
|
||||
let ttf_context = sdl2::ttf::init().unwrap();
|
||||
|
||||
// Load font
|
||||
let font = ttf_context.load_font("assets/site/TerminalVector.ttf", 12).unwrap();
|
||||
|
||||
// Format timing information using the formatting module
|
||||
let lines = timings.format_timing_display();
|
||||
let line_height = 14; // Approximate line height for 12pt font
|
||||
@@ -104,6 +103,7 @@ pub fn debug_render_system(
|
||||
mut canvas: NonSendMut<&mut Canvas<Window>>,
|
||||
backbuffer: NonSendMut<BackbufferResource>,
|
||||
mut debug_texture: NonSendMut<DebugTextureResource>,
|
||||
debug_font: NonSendMut<DebugFontResource>,
|
||||
debug_state: Res<DebugState>,
|
||||
timings: Res<SystemTimings>,
|
||||
map: Res<Map>,
|
||||
@@ -130,6 +130,7 @@ pub fn debug_render_system(
|
||||
|
||||
// Get texture creator before entering the closure to avoid borrowing conflicts
|
||||
let mut texture_creator = canvas.texture_creator();
|
||||
let font = &debug_font.0;
|
||||
|
||||
let cursor_world_pos = match *cursor {
|
||||
CursorPosition::None => None,
|
||||
@@ -192,8 +193,6 @@ pub fn debug_render_system(
|
||||
let node = map.graph.get_node(closest_node_id).unwrap();
|
||||
let pos = transform_position_with_offset(node.position, scale);
|
||||
|
||||
let ttf_context = sdl2::ttf::init().unwrap();
|
||||
let font = ttf_context.load_font("assets/site/TerminalVector.ttf", 12).unwrap();
|
||||
let surface = font.render(&closest_node_id.to_string()).blended(Color::WHITE).unwrap();
|
||||
let texture = texture_creator.create_texture_from_surface(&surface).unwrap();
|
||||
let dest = Rect::new(pos.x + 10, pos.y - 5, texture.query().width, texture.query().height);
|
||||
@@ -217,7 +216,7 @@ pub fn debug_render_system(
|
||||
}
|
||||
|
||||
// Render timing information in the top-left corner
|
||||
render_timing_display(debug_canvas, &mut texture_creator, &timings);
|
||||
render_timing_display(debug_canvas, &mut texture_creator, &timings, font);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn setup_sdl() -> Result<(Canvas<Window>, TextureCreator<WindowContext>, Sdl
|
||||
|
||||
pub fn create_atlas(canvas: &mut sdl2::render::Canvas<sdl2::video::Window>) -> SpriteAtlas {
|
||||
let texture_creator = canvas.texture_creator();
|
||||
let atlas_bytes = get_asset_bytes(Asset::Atlas).unwrap();
|
||||
let atlas_bytes = get_asset_bytes(Asset::AtlasImage).unwrap();
|
||||
|
||||
let texture = texture_creator.load_texture_bytes(&atlas_bytes).unwrap();
|
||||
let texture: Texture<'static> = unsafe { std::mem::transmute(texture) };
|
||||
|
||||
19
web.build.ts
19
web.build.ts
@@ -1,7 +1,7 @@
|
||||
import { $ } from "bun";
|
||||
import { existsSync, promises as fs } from "fs";
|
||||
import { platform } from "os";
|
||||
import { dirname, join, relative, resolve } from "path";
|
||||
import { basename, dirname, join, relative, resolve } from "path";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { configure, getConsoleSink, getLogger } from "@logtape/logtape";
|
||||
|
||||
@@ -79,16 +79,19 @@ async function build(release: boolean, env: Record<string, string> | null) {
|
||||
|
||||
// The files to copy into 'dist'
|
||||
const files = [
|
||||
...["index.html", "favicon.ico", "build.css", "TerminalVector.ttf"].map(
|
||||
(file) => ({
|
||||
src: join(siteFolder, file),
|
||||
dest: join(dist, file),
|
||||
...[
|
||||
"index.html",
|
||||
"favicon.ico",
|
||||
"build.css",
|
||||
"../game/TerminalVector.ttf",
|
||||
].map((file) => ({
|
||||
src: resolve(join(siteFolder, file)),
|
||||
dest: join(dist, basename(file)),
|
||||
optional: false,
|
||||
})
|
||||
),
|
||||
})),
|
||||
...["pacman.wasm", "pacman.js", "deps/pacman.data"].map((file) => ({
|
||||
src: join(outputFolder, file),
|
||||
dest: join(dist, file.split("/").pop() || file),
|
||||
dest: join(dist, basename(file)),
|
||||
optional: false,
|
||||
})),
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user