From 12ee16faabceee6fab9b7caaf7c84a5aea0d8cdf Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 17 Aug 2025 23:29:43 -0500 Subject: [PATCH] docs: document many major functions, types, enums for important functionality --- src/app.rs | 25 ++++++++++++++++++++ src/asset.rs | 23 +++++++++++++++++- src/audio.rs | 23 +++++++++++++----- src/constants.rs | 32 +++++++++++++++++-------- src/events.rs | 17 ++++++++++++++ src/game.rs | 50 +++++++++++++++++++++++++++++++++++----- src/map/builder.rs | 42 ++++++++++++++++++++++++++------- src/map/parser.rs | 30 +++++++++++++++--------- src/platform/mod.rs | 18 +++++++-------- src/systems/collision.rs | 7 ++++++ src/systems/ghost.rs | 5 +--- src/systems/movement.rs | 33 ++++++++++++++++++++------ src/systems/player.rs | 12 +++++++++- src/texture/animated.rs | 18 +++++++++++++++ src/texture/sprite.rs | 17 ++++++++++++++ 15 files changed, 290 insertions(+), 62 deletions(-) diff --git a/src/app.rs b/src/app.rs index 34c0897..527222a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,6 +12,11 @@ use crate::constants::{CANVAS_SIZE, LOOP_TIME, SCALE}; use crate::game::Game; use crate::platform::get_platform; +/// Main application wrapper that manages SDL initialization, window lifecycle, and the game loop. +/// +/// Handles platform-specific setup, maintains consistent frame timing, and delegates +/// game logic to the contained `Game` instance. The app manages focus state to +/// optimize CPU usage when the window loses focus. pub struct App { pub game: Game, last_tick: Instant, @@ -20,6 +25,16 @@ pub struct App { } impl App { + /// Initializes SDL subsystems, creates the game window, and sets up the game state. + /// + /// Performs comprehensive initialization including video/audio subsystems, platform-specific + /// console setup, window creation with proper scaling, and canvas configuration. All SDL + /// resources are leaked to maintain 'static lifetimes required by the game architecture. + /// + /// # Errors + /// + /// Returns `GameError::Sdl` if any SDL initialization step fails, or propagates + /// errors from `Game::new()` during game state setup. pub fn new() -> GameResult { let sdl_context: &'static Sdl = Box::leak(Box::new(sdl2::init().map_err(|e| GameError::Sdl(e.to_string()))?)); let video_subsystem: &'static VideoSubsystem = @@ -70,6 +85,16 @@ impl App { }) } + /// Executes a single frame of the game loop with consistent timing and optional sleep. + /// + /// Calculates delta time since the last frame, runs game logic via `game.tick()`, + /// and implements frame rate limiting by sleeping for remaining time if the frame + /// completed faster than the target `LOOP_TIME`. Sleep behavior varies based on + /// window focus to conserve CPU when the game is not active. + /// + /// # Returns + /// + /// `true` if the game should continue running, `false` if the game requested exit. pub fn run(&mut self) -> bool { { let start = Instant::now(); diff --git a/src/asset.rs b/src/asset.rs index 545f96f..a76c449 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -5,17 +5,28 @@ use std::borrow::Cow; use strum_macros::EnumIter; +/// Enumeration of all game assets with cross-platform loading support. +/// +/// Each variant corresponds to a specific file that can be loaded either from +/// binary-embedded data or embedded filesystem (Emscripten). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] pub enum Asset { Wav1, Wav2, Wav3, Wav4, + /// Main sprite atlas containing all game graphics (atlas.png) AtlasImage, + /// Terminal Vector font for text rendering (TerminalVector.ttf) Font, } impl Asset { + /// Returns the relative file path for this asset within the game's asset directory. + /// + /// Paths are consistent across platforms and used by the Emscripten backend + /// for filesystem loading. Desktop builds embed assets directly and don't + /// use these paths at runtime. #[allow(dead_code)] pub fn path(&self) -> &str { use Asset::*; @@ -35,7 +46,17 @@ mod imp { use crate::error::AssetError; use crate::platform::get_platform; - /// Returns the raw bytes of the given asset. + /// Loads asset bytes using the appropriate platform-specific method. + /// + /// On desktop platforms, returns embedded compile-time data via `include_bytes!`. + /// On Emscripten, loads from the filesystem using the asset's path. The returned + /// `Cow` allows zero-copy access to embedded data while supporting owned data + /// when loaded from disk. + /// + /// # Errors + /// + /// Returns `AssetError::NotFound` if the asset file cannot be located (Emscripten only), + /// or `AssetError::Io` for filesystem I/O failures. pub fn get_asset_bytes(asset: Asset) -> Result, AssetError> { get_platform().get_asset_bytes(asset) } diff --git a/src/audio.rs b/src/audio.rs index 18d75dc..7a0d0bc 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -114,9 +114,11 @@ impl Audio { } } - /// Plays the "eat" sound effect. + /// Plays the next waka eating sound in the cycle of four variants. /// - /// If audio is disabled or muted, this function does nothing. + /// Automatically rotates through the four eating sound assets. The sound plays on channel 0 and the internal sound index + /// advances to the next variant. Silently returns if audio is disabled, muted, + /// or no sounds were loaded successfully. #[allow(dead_code)] pub fn eat(&mut self) { if self.disabled || self.muted || self.sounds.is_empty() { @@ -136,9 +138,11 @@ impl Audio { self.next_sound_index = (self.next_sound_index + 1) % self.sounds.len(); } - /// Instantly mute or unmute all channels. + /// Instantly mutes or unmutes all audio channels by adjusting their volume. /// - /// If audio is disabled, this function does nothing. + /// Sets all 4 mixer channels to zero volume when muting, or restores them to + /// their default volume (32) when unmuting. The mute state is tracked internally + /// regardless of whether audio is disabled, allowing the state to be preserved. pub fn set_mute(&mut self, mute: bool) { if !self.disabled { let channels = 4; @@ -151,12 +155,19 @@ impl Audio { self.muted = mute; } - /// Returns `true` if the audio is muted. + /// Returns the current mute state regardless of whether audio is functional. + /// + /// This tracks the user's mute preference and will return `true` if muted + /// even when the audio system is disabled due to initialization failures. pub fn is_muted(&self) -> bool { self.muted } - /// Returns `true` if the audio system is disabled. + /// Returns whether the audio system failed to initialize and is non-functional. + /// + /// Audio can be disabled due to SDL2_mixer initialization failures, missing + /// audio device, or failure to load any sound assets. When disabled, all + /// audio operations become no-ops. #[allow(dead_code)] pub fn is_disabled(&self) -> bool { self.disabled diff --git a/src/constants.rs b/src/constants.rs index 39c0236..9e51ac1 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,6 +4,11 @@ use std::time::Duration; use glam::UVec2; +/// Target frame duration for 60 FPS game loop timing. +/// +/// Calculated as 1/60th of a second (≈16.67ms). +/// +/// Written out explicitly to satisfy const-eval constraints. pub const LOOP_TIME: Duration = Duration::from_nanos((1_000_000_000.0 / 60.0) as u64); /// The size of each cell, in pixels. @@ -14,9 +19,16 @@ pub const BOARD_CELL_SIZE: UVec2 = UVec2::new(28, 31); /// The scale factor for the window (integer zoom) pub const SCALE: f32 = 2.6; -/// The offset of the game board from the top-left corner of the window, in cells. +/// Game board offset from window origin to reserve space for HUD elements. +/// +/// The 3-cell vertical offset (24 pixels) provides space at the top of the +/// screen for score display, player lives, and other UI elements. pub const BOARD_CELL_OFFSET: UVec2 = UVec2::new(0, 3); -/// The offset of the game board from the top-left corner of the window, in pixels. + +/// Pixel-space equivalent of `BOARD_CELL_OFFSET` for rendering calculations. +/// +/// Automatically calculated from the cell offset to maintain consistency +/// when the cell size changes. Used for positioning sprites and debug overlays. pub const BOARD_PIXEL_OFFSET: UVec2 = UVec2::new(BOARD_CELL_OFFSET.x * CELL_SIZE, BOARD_CELL_OFFSET.y * CELL_SIZE); /// The size of the canvas, in pixels. pub const CANVAS_SIZE: UVec2 = UVec2::new( @@ -24,22 +36,24 @@ pub const CANVAS_SIZE: UVec2 = UVec2::new( (BOARD_CELL_SIZE.y + BOARD_CELL_OFFSET.y) * CELL_SIZE, ); -/// An enum representing the different types of tiles on the map. +/// Map tile types that define gameplay behavior and collision properties. #[derive(Debug, Clone, Copy, PartialEq)] pub enum MapTile { - /// An empty tile. + /// Traversable space with no collectible items Empty, - /// A wall tile. Wall, - /// A regular pellet. + /// Small collectible. Implicitly a traversable tile. Pellet, - /// A power pellet. + /// Large collectible. Implicitly a traversable tile. PowerPellet, - /// A tunnel tile. + /// Special traversable tile that connects to tunnel portals. Tunnel, } -/// The raw layout of the game board, as a 2D array of characters. +/// ASCII art representation of the classic Pac-Man maze layout. +/// +/// Uses character symbols to define the game world. This layout is parsed by `MapTileParser` +/// to generate the navigable graph and collision geometry. pub const RAW_BOARD: [&str; BOARD_CELL_SIZE.y as usize] = [ "############################", "#............##............#", diff --git a/src/events.rs b/src/events.rs index cb821dd..4b2ba69 100644 --- a/src/events.rs +++ b/src/events.rs @@ -2,19 +2,36 @@ use bevy_ecs::{entity::Entity, event::Event}; use crate::map::direction::Direction; +/// Player input commands that trigger specific game actions. +/// +/// Commands are generated by the input system in response to keyboard events +/// and processed by appropriate game systems to modify state or behavior. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum GameCommand { + /// Request immediate game shutdown Exit, + /// Set Pac-Man's movement direction MovePlayer(Direction), + /// Cycle through debug visualization modes ToggleDebug, + /// Toggle audio mute state MuteAudio, + /// Restart the current level with fresh entity positions and items ResetLevel, + /// Pause or resume game ticking logic TogglePause, } +/// Global events that flow through the ECS event system to coordinate game behavior. +/// +/// Events enable loose coupling between systems - input generates commands, collision +/// detection reports overlaps, and various systems respond appropriately without +/// direct dependencies. #[derive(Event, Clone, Copy, Debug, PartialEq, Eq)] pub enum GameEvent { + /// Player input command to be processed by relevant game systems Command(GameCommand), + /// Physical overlap detected between two entities requiring gameplay response Collision(Entity, Entity), } diff --git a/src/game.rs b/src/game.rs index fa150b5..3719b9d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -48,16 +48,37 @@ use crate::{ texture::sprite::{AtlasMapper, SpriteAtlas}, }; -/// The `Game` struct is the main entry point for the game. +/// Core game state manager built on the Bevy ECS architecture. /// -/// It contains the game's state and logic, and is responsible for -/// handling user input, updating the game state, and rendering the game. +/// Orchestrates all game systems through a centralized `World` containing entities, +/// components, and resources, while a `Schedule` defines system execution order. +/// Handles initialization of graphics resources, entity spawning, and per-frame +/// game logic coordination. SDL2 resources are stored as `NonSend` to respect +/// thread safety requirements while integrating with the ECS. pub struct Game { pub world: World, pub schedule: Schedule, } impl Game { + /// Initializes the complete game state including ECS world, graphics, and entity spawning. + /// + /// Performs extensive setup: creates render targets and debug textures, loads and parses + /// the sprite atlas, renders the static map to a cached texture, builds the navigation + /// graph from the board layout, spawns Pac-Man with directional animations, creates + /// all four ghosts with their AI behavior, and places collectible items throughout + /// the maze. Registers event types and configures the system execution schedule. + /// + /// # Arguments + /// + /// * `canvas` - SDL2 rendering context with static lifetime for ECS storage + /// * `texture_creator` - SDL2 texture factory for creating render targets + /// * `event_pump` - SDL2 event polling interface for input handling + /// + /// # Errors + /// + /// Returns `GameError` for SDL2 failures, asset loading problems, atlas parsing + /// errors, or entity initialization issues. pub fn new( canvas: &'static mut Canvas, texture_creator: &'static mut TextureCreator, @@ -289,7 +310,12 @@ impl Game { Ok(Game { world, schedule }) } - /// Spowns all four ghosts at their starting positions with appropriate textures. + /// Creates and spawns all four ghosts with unique AI personalities and directional animations. + /// + /// # Errors + /// + /// Returns `GameError::Texture` if any ghost sprite cannot be found in the atlas, + /// typically indicating missing or misnamed sprite files. fn spawn_ghosts(world: &mut World) -> GameResult<()> { // Extract the data we need first to avoid borrow conflicts let ghost_start_positions = { @@ -394,9 +420,21 @@ impl Game { Ok(()) } - /// Ticks the game state. + /// Executes one frame of game logic by running all scheduled ECS systems. /// - /// Returns true if the game should exit. + /// Updates the world's delta time resource and runs the complete system pipeline: + /// input processing, entity movement, collision detection, item collection, + /// audio playback, animation updates, and rendering. Each system operates on + /// relevant entities and modifies world state, with the schedule ensuring + /// proper execution order and data dependencies. + /// + /// # Arguments + /// + /// * `dt` - Frame delta time in seconds for time-based animations and movement + /// + /// # Returns + /// + /// `true` if the game should terminate (exit command received), `false` to continue pub fn tick(&mut self, dt: f32) -> bool { self.world.insert_resource(DeltaTime(dt)); diff --git a/src/map/builder.rs b/src/map/builder.rs index 1b2d993..cbdd0f0 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -11,25 +11,37 @@ use tracing::debug; use crate::error::{GameResult, MapError}; -/// The starting positions of the entities in the game. +/// Predefined spawn locations for all game entities within the navigation graph. +/// +/// These positions are determined during map parsing and graph construction. pub struct NodePositions { + /// Pac-Man's starting position in the lower section of the maze pub pacman: NodeId, + /// Blinky starts at the ghost house entrance pub blinky: NodeId, + /// Pinky starts in the left area of the ghost house pub pinky: NodeId, + /// Inky starts in the right area of the ghost house pub inky: NodeId, + /// Clyde starts in the center of the ghost house pub clyde: NodeId, } -/// The main map structure containing the game board and navigation graph. +/// Complete maze representation combining visual layout with navigation pathfinding. +/// +/// Transforms the ASCII board layout into a fully connected navigation graph +/// while preserving tile-based collision and rendering data. The graph enables +/// smooth entity movement with proper pathfinding, while the grid mapping allows +/// efficient spatial queries and debug visualization. #[derive(Resource)] pub struct Map { - /// The node map for entity movement. + /// Connected graph of navigable positions. pub graph: Graph, - /// A mapping from grid positions to node IDs. + /// Bidirectional mapping between 2D grid coordinates and graph node indices. pub grid_to_node: HashMap, - /// A mapping of the starting positions of the entities. + /// Predetermined spawn locations for all game entities pub start_positions: NodePositions, - /// The raw tile data for the map. + /// 2D array of tile types for collision detection and rendering tiles: [[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize], } @@ -162,7 +174,18 @@ impl Map { }) } - /// Builds the house structure in the graph. + /// Constructs the ghost house area with restricted access and internal navigation. + /// + /// Creates a multi-level ghost house with entrance control, internal movement + /// areas, and starting positions for each ghost. The house entrance uses + /// ghost-only traversal flags to prevent Pac-Man from entering while allowing + /// ghosts to exit. Internal nodes are arranged in vertical lines to provide + /// distinct starting areas for each ghost character. + /// + /// # Returns + /// + /// Tuple of node IDs: (house_entrance, left_center, center_center, right_center) + /// representing the four key positions within the ghost house structure. fn build_house( graph: &mut Graph, grid_to_node: &HashMap, @@ -296,7 +319,10 @@ impl Map { )) } - /// Builds the tunnel connections in the graph. + /// Creates horizontal tunnel portals for instant teleportation across the maze. + /// + /// Establishes the tunnel system that allows entities to instantly travel from the left edge of the maze to the right edge. + /// Creates hidden intermediate nodes beyond the visible tunnel entrances and connects them with zero-distance edges for instantaneous traversal. fn build_tunnels( graph: &mut Graph, grid_to_node: &HashMap, diff --git a/src/map/parser.rs b/src/map/parser.rs index 2f80e2c..0633167 100644 --- a/src/map/parser.rs +++ b/src/map/parser.rs @@ -4,16 +4,21 @@ use crate::constants::{MapTile, BOARD_CELL_SIZE}; use crate::error::ParseError; use glam::IVec2; -/// Represents the parsed data from a raw board layout. +/// Structured representation of parsed ASCII board layout with extracted special positions. +/// +/// Contains the complete board state after character-to-tile conversion, along with +/// the locations of special gameplay elements that require additional processing +/// during graph construction. Special positions are extracted during parsing to +/// enable proper map builder initialization. #[derive(Debug)] pub struct ParsedMap { - /// The parsed tile layout. + /// 2D array of tiles converted from ASCII characters pub tiles: [[MapTile; BOARD_CELL_SIZE.y as usize]; BOARD_CELL_SIZE.x as usize], - /// The positions of the house door tiles. + /// Two positions marking the ghost house entrance (represented by '=' characters) pub house_door: [Option; 2], - /// The positions of the tunnel end tiles. + /// Two positions marking tunnel portals for wraparound teleportation ('T' characters) pub tunnel_ends: [Option; 2], - /// Pac-Man's starting position. + /// Starting position for Pac-Man (marked by 'X' character in the layout) pub pacman_start: Option, } @@ -21,15 +26,18 @@ pub struct ParsedMap { pub struct MapTileParser; impl MapTileParser { - /// Parses a single character into a map tile. + /// Converts ASCII characters from the board layout into corresponding tile types. /// - /// # Arguments + /// Interprets the character-based maze representation: walls (`#`), collectible + /// pellets (`.` and `o`), traversable spaces (` `), tunnel entrances (`T`), + /// ghost house doors (`=`), and entity spawn markers (`X`). Special characters + /// that don't represent tiles in the final map (like spawn markers) are + /// converted to `Empty` tiles while their positions are tracked separately. /// - /// * `c` - The character to parse + /// # Errors /// - /// # Returns - /// - /// The parsed map tile, or an error if the character is unknown. + /// Returns `ParseError::UnknownCharacter` for any character not defined + /// in the game's ASCII art vocabulary. pub fn parse_character(c: char) -> Result { match c { '#' => Ok(MapTile::Wall), diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 4466e4f..a7580b8 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -10,29 +10,29 @@ mod desktop; #[cfg(target_os = "emscripten")] mod emscripten; -/// Platform abstraction trait that defines cross-platform functionality. +/// Cross-platform abstraction layer providing unified APIs for platform-specific operations. pub trait CommonPlatform { - /// Sleep for the specified duration using platform-appropriate method. + /// Platform-specific sleep function (required due to Emscripten's non-standard sleep requirements). + /// + /// Provides access to current window focus state, useful for changing sleep algorithm conditionally. fn sleep(&self, duration: Duration, focused: bool); - /// Get the current time in seconds since some reference point. - /// This is available for future use in timing and performance monitoring. #[allow(dead_code)] fn get_time(&self) -> f64; - /// Initialize platform-specific console functionality. + /// Configures platform-specific console and debugging output capabilities. fn init_console(&self) -> Result<(), PlatformError>; - /// Get canvas size for platforms that need it (e.g., Emscripten). - /// This is available for future use in responsive design. + /// Retrieves the actual display canvas dimensions. #[allow(dead_code)] fn get_canvas_size(&self) -> Option<(u32, u32)>; - /// Load asset bytes using platform-appropriate method. + /// Loads raw asset data using the appropriate platform-specific method. + fn get_asset_bytes(&self, asset: Asset) -> Result, AssetError>; } -/// Get the current platform implementation. +/// Returns the appropriate platform implementation based on compile-time target. #[allow(dead_code)] pub fn get_platform() -> &'static dyn CommonPlatform { #[cfg(not(target_os = "emscripten"))] diff --git a/src/systems/collision.rs b/src/systems/collision.rs index dc3c180..572a2e6 100644 --- a/src/systems/collision.rs +++ b/src/systems/collision.rs @@ -9,6 +9,13 @@ use crate::map::builder::Map; use crate::systems::components::{Collider, ItemCollider, PacmanCollider}; use crate::systems::movement::Position; +/// Detects overlapping entities and generates collision events for gameplay systems. +/// +/// Performs distance-based collision detection between Pac-Man and collectible items +/// using each entity's position and collision radius. When entities overlap, emits +/// a `GameEvent::Collision` for the item system to handle scoring and removal. +/// Collision detection accounts for both entities being in motion and supports +/// circular collision boundaries for accurate gameplay feel. pub fn collision_system( map: Res, pacman_query: Query<(Entity, &Position, &Collider), With>, diff --git a/src/systems/ghost.rs b/src/systems/ghost.rs index 60aaf47..8ed50f9 100644 --- a/src/systems/ghost.rs +++ b/src/systems/ghost.rs @@ -14,10 +14,7 @@ use crate::{ }, }; -/// Ghost AI system that handles randomized movement decisions. -/// -/// This system runs on all ghosts and makes periodic decisions about -/// which direction to move in when they reach intersections. +/// Autonomous ghost AI system implementing randomized movement with backtracking avoidance. pub fn ghost_movement_system( map: Res, delta_time: Res, diff --git a/src/systems/movement.rs b/src/systems/movement.rs index e5c3767..1d398ea 100644 --- a/src/systems/movement.rs +++ b/src/systems/movement.rs @@ -4,7 +4,10 @@ use crate::map::graph::Graph; use bevy_ecs::component::Component; use glam::Vec2; -/// A unique identifier for a node, represented by its index in the graph's storage. +/// Zero-based index identifying a specific node in the navigation graph. +/// +/// Nodes represent discrete movement targets in the maze. The index directly corresponds to the node's position in the +/// graph's internal storage arrays. pub type NodeId = usize; /// A component that represents the speed and cardinal direction of an entity. @@ -24,15 +27,19 @@ pub enum BufferedDirection { Some { direction: Direction, remaining_time: f32 }, } -/// Pure spatial position component - works for both static and dynamic entities. +/// Entity position state that handles both stationary entities and moving entities. +/// +/// Supports precise positioning during movement between discrete navigation nodes. +/// When moving, entities smoothly interpolate along edges while tracking exact distance remaining to the target node. #[derive(Component, Debug, Copy, Clone, PartialEq)] pub enum Position { - Stopped { - node: NodeId, - }, + /// Entity is stationary at a specific graph node. + Stopped { node: NodeId }, + /// Entity is traveling between two nodes. Moving { from: NodeId, to: NodeId, + /// Distance remaining to reach the target node. remaining_distance: f32, }, } @@ -82,9 +89,21 @@ impl Position { )) } - /// Moves the position by a given distance towards it's current target node. + /// Advances movement progress by the specified distance with overflow handling. /// - /// Returns the overflow distance, if any. + /// For moving entities, decreases the remaining distance to the target node. + /// If the distance would overshoot the target, the entity transitions to + /// `Stopped` state and returns the excess distance for chaining movement + /// to the next edge in the same frame. + /// + /// # Arguments + /// + /// * `distance` - Distance to travel this frame (typically speed × delta_time) + /// + /// # Returns + /// + /// `Some(overflow)` if the target was reached with distance remaining, + /// `None` if still moving or already stopped. pub fn tick(&mut self, distance: f32) -> Option { if distance <= 0.0 || self.is_at_node() { return None; diff --git a/src/systems/player.rs b/src/systems/player.rs index fac3e18..610cc7f 100644 --- a/src/systems/player.rs +++ b/src/systems/player.rs @@ -17,7 +17,12 @@ use crate::{ }, }; -// Handles player input and control +/// Processes player input commands and updates game state accordingly. +/// +/// Handles keyboard-driven commands like movement direction changes, debug mode +/// toggling, audio muting, and game exit requests. Movement commands are buffered +/// to allow direction changes before reaching intersections, improving gameplay +/// responsiveness. Non-movement commands immediately modify global game state. pub fn player_control_system( mut events: EventReader, mut state: ResMut, @@ -69,6 +74,11 @@ fn can_traverse(entity_type: EntityType, edge: Edge) -> bool { edge.traversal_flags.contains(entity_flags) } +/// Executes frame-by-frame movement for Pac-Man. +/// +/// Handles movement logic including buffered direction changes, edge traversal validation, and continuous movement between nodes. +/// When stopped, prioritizes buffered directions for responsive controls, falling back to current direction. +/// Supports movement chaining within a single frame when traveling at high speeds. pub fn player_movement_system( map: Res, delta_time: Res, diff --git a/src/texture/animated.rs b/src/texture/animated.rs index 8020797..c5cc52a 100644 --- a/src/texture/animated.rs +++ b/src/texture/animated.rs @@ -1,11 +1,19 @@ use crate::error::{AnimatedTextureError, GameError, GameResult, TextureError}; use crate::texture::sprite::AtlasTile; +/// Frame-based animation system for cycling through multiple sprite tiles. +/// +/// Manages automatic frame progression based on elapsed time. +/// Uses a time banking system to ensure consistent animation speed regardless of frame rate variations. #[derive(Debug, Clone)] pub struct AnimatedTexture { + /// Sequence of sprite tiles that make up the animation frames tiles: Vec, + /// Duration each frame should be displayed (in seconds) frame_duration: f32, + /// Index of the currently active frame in the tiles vector current_frame: usize, + /// Accumulated time since the last frame change (for smooth timing) time_bank: f32, } @@ -25,6 +33,16 @@ impl AnimatedTexture { }) } + /// Advances the animation by the specified time delta with automatic frame cycling. + /// + /// Accumulates time in the time bank and progresses through frames when enough + /// time has elapsed. Supports frame rates independent of game frame rate by + /// potentially advancing multiple frames in a single call if `dt` is large. + /// Animation loops automatically when reaching the final frame. + /// + /// # Arguments + /// + /// * `dt` - Time elapsed since the last tick (typically frame delta time) pub fn tick(&mut self, dt: f32) { self.time_bank += dt; while self.time_bank >= self.frame_duration { diff --git a/src/texture/sprite.rs b/src/texture/sprite.rs index d3ec876..afd7acd 100644 --- a/src/texture/sprite.rs +++ b/src/texture/sprite.rs @@ -8,8 +8,10 @@ use std::collections::HashMap; use crate::error::TextureError; +/// Atlas frame mapping data loaded from JSON metadata files. #[derive(Clone, Debug, Deserialize)] pub struct AtlasMapper { + /// Mapping from sprite name to frame bounds within the atlas texture pub frames: HashMap, } @@ -72,10 +74,19 @@ impl AtlasTile { } } +/// High-performance sprite atlas providing fast texture region lookups and rendering. +/// +/// Combines a single large texture with metadata mapping to enable efficient +/// sprite rendering without texture switching. Caches color modulation state +/// to minimize redundant SDL2 calls and supports both named sprite lookups +/// and optional default color modulation configuration. pub struct SpriteAtlas { + /// The combined texture containing all sprite frames texture: Texture<'static>, + /// Mapping from sprite names to their pixel coordinates within the texture tiles: HashMap, default_color: Option, + /// Cached color modulation state to avoid redundant SDL2 calls last_modulation: Option, } @@ -89,6 +100,12 @@ impl SpriteAtlas { } } + /// Retrieves a sprite tile by name from the atlas with fast HashMap lookup. + /// + /// Returns an `AtlasTile` containing the texture coordinates and dimensions + /// for the named sprite, or `None` if the sprite name is not found in the + /// atlas. The returned tile can be used for immediate rendering or stored + /// for repeated use in animations and entity sprites. pub fn get_tile(&self, name: &str) -> Option { self.tiles.get(name).map(|frame| AtlasTile { pos: U16Vec2::new(frame.x, frame.y),