diff --git a/src/entity/blinky.rs b/src/entity/blinky.rs index fcfef47..ae35382 100644 --- a/src/entity/blinky.rs +++ b/src/entity/blinky.rs @@ -11,18 +11,18 @@ use crate::entity::{Entity, Moving, Renderable, StaticEntity}; use crate::map::Map; use glam::{IVec2, UVec2}; -pub struct Blinky<'a> { - ghost: Ghost<'a>, +pub struct Blinky { + ghost: Ghost, } -impl<'a> Blinky<'a> { +impl Blinky { pub fn new( starting_position: UVec2, - body_texture: Texture<'a>, - eyes_texture: Texture<'a>, + body_texture: Texture<'_>, + eyes_texture: Texture<'_>, map: Rc>, - pacman: Rc>>, - ) -> Blinky<'a> { + pacman: Rc>, + ) -> Blinky { Blinky { ghost: Ghost::new(GhostType::Blinky, starting_position, body_texture, eyes_texture, map, pacman), } @@ -44,19 +44,19 @@ impl<'a> Blinky<'a> { } } -impl<'a> Entity for Blinky<'a> { +impl Entity for Blinky { fn base(&self) -> &StaticEntity { self.ghost.base.base() } } -impl<'a> Renderable for Blinky<'a> { +impl Renderable for Blinky { fn render(&self, canvas: &mut Canvas) { self.ghost.render(canvas); } } -impl<'a> Moving for Blinky<'a> { +impl Moving for Blinky { fn move_forward(&mut self) { self.ghost.move_forward(); } @@ -81,15 +81,15 @@ impl<'a> Moving for Blinky<'a> { } // Allow direct access to ghost fields -impl<'a> std::ops::Deref for Blinky<'a> { - type Target = Ghost<'a>; +impl std::ops::Deref for Blinky { + type Target = Ghost; fn deref(&self) -> &Self::Target { &self.ghost } } -impl<'a> std::ops::DerefMut for Blinky<'a> { +impl std::ops::DerefMut for Blinky { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.ghost } diff --git a/src/entity/direction.rs b/src/entity/direction.rs index 0127200..0c9e00c 100644 --- a/src/entity/direction.rs +++ b/src/entity/direction.rs @@ -1,5 +1,6 @@ //! This module defines the `Direction` enum, which is used to represent the //! direction of an entity. +use glam::IVec2; use sdl2::keyboard::Keycode; /// An enum representing the direction of an entity. @@ -23,12 +24,12 @@ impl Direction { } /// Returns the offset of the direction as a tuple of (x, y). - pub fn offset(&self) -> (i32, i32) { + pub fn offset(&self) -> IVec2 { match self { - Direction::Right => (1, 0), - Direction::Down => (0, 1), - Direction::Left => (-1, 0), - Direction::Up => (0, -1), + Direction::Right => IVec2::new(1, 0), + Direction::Down => IVec2::new(0, 1), + Direction::Left => IVec2::new(-1, 0), + Direction::Up => IVec2::new(0, -1), } } diff --git a/src/entity/edible.rs b/src/entity/edible.rs index 93ecb97..f12e4ee 100644 --- a/src/entity/edible.rs +++ b/src/entity/edible.rs @@ -17,14 +17,14 @@ pub enum EdibleKind { Fruit(FruitType), } -pub struct Edible<'a> { +pub struct Edible { pub base: StaticEntity, pub kind: EdibleKind, - pub sprite: Rc>, + pub sprite: Rc, } -impl<'a> Edible<'a> { - pub fn new(kind: EdibleKind, cell_position: UVec2, sprite: Rc>) -> Self { +impl Edible { + pub fn new(kind: EdibleKind, cell_position: UVec2, sprite: Rc) -> Self { let pixel_position = Map::cell_to_pixel(cell_position); Edible { base: StaticEntity::new(pixel_position, cell_position), @@ -39,26 +39,26 @@ impl<'a> Edible<'a> { } } -impl<'a> Entity for Edible<'a> { +impl Entity for Edible { fn base(&self) -> &StaticEntity { &self.base } } -impl<'a> Renderable for Edible<'a> { +impl Renderable for Edible { fn render(&self, canvas: &mut Canvas) { let pos = self.base.pixel_position; - self.sprite.render(canvas, (pos.x, pos.y), Direction::Right, Some(0)); + self.sprite.render(canvas, pos, Direction::Right, Some(0)); } } /// Reconstruct all edibles from the original map layout -pub fn reconstruct_edibles<'a>( +pub fn reconstruct_edibles( map: Rc>, - pellet_sprite: Rc>, - power_pellet_sprite: Rc>, - _fruit_sprite: Rc>, -) -> Vec> { + pellet_sprite: Rc, + power_pellet_sprite: Rc, + _fruit_sprite: Rc, +) -> Vec { let mut edibles = Vec::new(); for x in 0..BOARD_WIDTH { for y in 0..BOARD_HEIGHT { diff --git a/src/entity/ghost.rs b/src/entity/ghost.rs index 4363462..bdc79c0 100644 --- a/src/entity/ghost.rs +++ b/src/entity/ghost.rs @@ -9,6 +9,7 @@ use crate::entity::{Entity, MovableEntity, Moving, Renderable}; use crate::map::Map; use crate::modulation::{SimpleTickModulator, TickModulator}; use crate::texture::animated::AnimatedAtlasTexture; +use crate::texture::atlas::{texture_to_static, AtlasTexture}; use crate::texture::FrameDrawn; use glam::{IVec2, UVec2}; use sdl2::pixels::Color; @@ -53,7 +54,7 @@ impl GhostType { } /// Base ghost struct that contains common functionality -pub struct Ghost<'a> { +pub struct Ghost { /// Shared movement and position fields. pub base: MovableEntity, /// The current mode of the ghost @@ -61,23 +62,30 @@ pub struct Ghost<'a> { /// The type/personality of this ghost pub ghost_type: GhostType, /// Reference to Pac-Man for targeting - pub pacman: Rc>>, - pub body_sprite: AnimatedAtlasTexture<'a>, - pub eyes_sprite: AnimatedAtlasTexture<'a>, + pub pacman: Rc>, + pub body_sprite: AnimatedAtlasTexture, + pub eyes_sprite: AnimatedAtlasTexture, } -impl Ghost<'_> { +impl Ghost { /// Creates a new ghost instance - pub fn new<'a>( + pub fn new( ghost_type: GhostType, starting_position: UVec2, - body_texture: Texture<'a>, - eyes_texture: Texture<'a>, + body_texture: Texture<'_>, + eyes_texture: Texture<'_>, map: Rc>, - pacman: Rc>>, - ) -> Ghost<'a> { + pacman: Rc>, + ) -> Ghost { let color = ghost_type.color(); - let mut body_sprite = AnimatedAtlasTexture::new(body_texture, 8, 2, 32, 32, Some((-4, -4))); + let mut body_sprite = AnimatedAtlasTexture::new( + unsafe { texture_to_static(body_texture) }, + 8, + 2, + 32, + 32, + Some(IVec2::new(-4, -4)), + ); body_sprite.set_color_modulation(color.r, color.g, color.b); let pixel_position = Map::cell_to_pixel(starting_position); Ghost { @@ -93,7 +101,14 @@ impl Ghost<'_> { ghost_type, pacman, body_sprite, - eyes_sprite: AnimatedAtlasTexture::new(eyes_texture, 1, 4, 32, 32, Some((-4, -4))), + eyes_sprite: AnimatedAtlasTexture::new( + unsafe { texture_to_static(eyes_texture) }, + 1, + 4, + 32, + 32, + Some((-4, -4).into()), + ), } } @@ -181,8 +196,8 @@ impl Ghost<'_> { } } for dir in &[Direction::Up, Direction::Down, Direction::Left, Direction::Right] { - let (dx, dy) = dir.offset(); - let next_p = IVec2::new(p.x as i32 + dx, p.y as i32 + dy); + let offset = dir.offset(); + let next_p = IVec2::new(p.x as i32 + offset.x, p.y as i32 + offset.y); if let Some(tile) = map.get_tile(next_p) { if tile == MapTile::Wall { continue; @@ -267,7 +282,7 @@ impl Ghost<'_> { } } -impl<'a> Moving for Ghost<'a> { +impl Moving for Ghost { fn move_forward(&mut self) { self.base.move_forward(); } @@ -291,10 +306,10 @@ impl<'a> Moving for Ghost<'a> { } } -impl<'a> Renderable for Ghost<'a> { +impl Renderable for Ghost { fn render(&self, canvas: &mut sdl2::render::Canvas) { let pos = self.base.base.pixel_position; - self.body_sprite.render(canvas, (pos.x, pos.y), Direction::Right, None); + self.body_sprite.render(canvas, pos, Direction::Right, None); // Inline the eye_frame logic here let eye_frame = if self.mode == GhostMode::Frightened { 4 // Frightened frame @@ -306,7 +321,6 @@ impl<'a> Renderable for Ghost<'a> { Direction::Down => 3, } }; - self.eyes_sprite - .render(canvas, (pos.x, pos.y), Direction::Right, Some(eye_frame)); + self.eyes_sprite.render(canvas, pos, Direction::Right, Some(eye_frame)); } } diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 3c876d3..9361544 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -114,7 +114,7 @@ impl Moving for MovableEntity { ); } fn next_cell(&self, direction: Option) -> IVec2 { - let (x, y) = direction.unwrap_or(self.direction).offset(); + let IVec2 { x, y } = direction.unwrap_or(self.direction).offset(); IVec2::new(self.base.cell_position.x as i32 + x, self.base.cell_position.y as i32 + y) } fn is_wall_ahead(&self, direction: Option) -> bool { diff --git a/src/entity/pacman.rs b/src/entity/pacman.rs index ec2ad70..8dea9d6 100644 --- a/src/entity/pacman.rs +++ b/src/entity/pacman.rs @@ -18,23 +18,23 @@ use crate::{ use glam::{IVec2, UVec2}; /// The Pac-Man entity. -pub struct Pacman<'a> { +pub struct Pacman { /// Shared movement and position fields. pub base: MovableEntity, /// The next direction of Pac-Man, which will be applied when Pac-Man is next aligned with the grid. pub next_direction: Option, /// Whether Pac-Man is currently stopped. pub stopped: bool, - pub sprite: AnimatedAtlasTexture<'a>, + pub sprite: AnimatedAtlasTexture, } -impl<'a> Entity for Pacman<'a> { +impl Entity for Pacman { fn base(&self) -> &StaticEntity { &self.base.base } } -impl<'a> Moving for Pacman<'a> { +impl Moving for Pacman { fn move_forward(&mut self) { self.base.move_forward(); } @@ -58,9 +58,9 @@ impl<'a> Moving for Pacman<'a> { } } -impl Pacman<'_> { +impl Pacman { /// Creates a new `Pacman` instance. - pub fn new<'a>(starting_position: UVec2, atlas: Texture<'a>, map: Rc>) -> Pacman<'a> { + pub fn new(starting_position: UVec2, atlas: Texture<'_>, map: Rc>) -> Pacman { let pixel_position = Map::cell_to_pixel(starting_position); Pacman { base: MovableEntity::new( @@ -73,7 +73,14 @@ impl Pacman<'_> { ), next_direction: None, stopped: false, - sprite: AnimatedAtlasTexture::new(atlas, 2, 3, 32, 32, Some((-4, -4))), + sprite: AnimatedAtlasTexture::new( + unsafe { crate::texture::atlas::texture_to_static(atlas) }, + 2, + 3, + 32, + 32, + Some(IVec2::new(-4, -4)), + ), } } @@ -119,14 +126,14 @@ impl Pacman<'_> { } } -impl Renderable for Pacman<'_> { +impl Renderable for Pacman { fn render(&self, canvas: &mut Canvas) { let pos = self.base.base.pixel_position; let dir = self.base.direction; if self.stopped { - self.sprite.render(canvas, (pos.x, pos.y), dir, Some(2)); + self.sprite.render(canvas, pos, dir, Some(2)); } else { - self.sprite.render(canvas, (pos.x, pos.y), dir, None); + self.sprite.render(canvas, pos, dir, None); } } } diff --git a/src/game.rs b/src/game.rs index 73708b2..7daa3ce 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,7 +3,7 @@ use std::cell::RefCell; use std::ops::Not; use std::rc::Rc; -use glam::UVec2; +use glam::{IVec2, UVec2}; use rand::rngs::SmallRng; use rand::seq::IteratorRandom; use rand::SeedableRng; @@ -25,7 +25,7 @@ use crate::entity::edible::{reconstruct_edibles, Edible, EdibleKind}; use crate::entity::pacman::Pacman; use crate::entity::Renderable; use crate::map::Map; -use crate::texture::atlas::AtlasTexture; +use crate::texture::atlas::{texture_to_static, AtlasTexture}; /// The main game state. /// @@ -34,16 +34,16 @@ use crate::texture::atlas::AtlasTexture; pub struct Game<'a> { canvas: &'a mut Canvas, map_texture: Texture<'a>, - pellet_texture: Rc>, - power_pellet_texture: Rc>, + pellet_texture: Rc, + power_pellet_texture: Rc, font: Font<'a, 'static>, - pacman: Rc>>, + pacman: Rc>, map: Rc>, debug_mode: DebugMode, score: u32, pub audio: Audio, - blinky: Blinky<'a>, - edibles: Vec>, + blinky: Blinky, + edibles: Vec, } impl<'a> Game<'a> { @@ -91,20 +91,28 @@ impl<'a> Game<'a> { // Load pellet texture from asset API let pellet_bytes = get_asset_bytes(Asset::Pellet).expect("Failed to load asset"); + let power_pellet_bytes = get_asset_bytes(Asset::Energizer).expect("Failed to load asset"); let pellet_texture = Rc::new(AtlasTexture::new( - texture_creator - .load_texture_bytes(&pellet_bytes) - .expect("Could not load pellet texture from asset API"), + unsafe { + texture_to_static( + texture_creator + .load_texture_bytes(&pellet_bytes) + .expect("Could not load pellet texture from asset API"), + ) + }, 1, 24, 24, None, )); - let power_pellet_bytes = get_asset_bytes(Asset::Energizer).expect("Failed to load asset"); let power_pellet_texture = Rc::new(AtlasTexture::new( - texture_creator - .load_texture_bytes(&power_pellet_bytes) - .expect("Could not load power pellet texture from asset API"), + unsafe { + texture_to_static( + texture_creator + .load_texture_bytes(&power_pellet_bytes) + .expect("Could not load power pellet texture from asset API"), + ) + }, 1, 24, 24, @@ -340,18 +348,18 @@ impl<'a> Game<'a> { // Render the score and high score self.render_text( &format!("{lives}UP HIGH SCORE "), - (24 * lives_offset + x_offset, y_offset), + IVec2::new(24 * lives_offset + x_offset, y_offset), Color::WHITE, ); self.render_text( &score_text, - (24 * score_offset + x_offset, 24 + y_offset + gap_offset), + IVec2::new(24 * score_offset + x_offset, 24 + y_offset + gap_offset), Color::WHITE, ); } /// Renders text to the screen at the given position. - fn render_text(&mut self, text: &str, position: (i32, i32), color: Color) { + fn render_text(&mut self, text: &str, position: IVec2, color: Color) { let surface = self.font.render(text).blended(color).expect("Could not render text surface"); let texture_creator = self.canvas.texture_creator(); @@ -360,7 +368,7 @@ impl<'a> Game<'a> { .expect("Could not create texture from surface"); let query = texture.query(); - let dst_rect = sdl2::rect::Rect::new(position.0, position.1, query.width, query.height); + let dst_rect = sdl2::rect::Rect::new(position.x, position.y, query.width, query.height); self.canvas .copy(&texture, None, Some(dst_rect)) diff --git a/src/texture/animated.rs b/src/texture/animated.rs index 6156aba..cbcca5c 100644 --- a/src/texture/animated.rs +++ b/src/texture/animated.rs @@ -1,4 +1,5 @@ //! This module provides a simple animation and atlas system for textures. +use glam::IVec2; use sdl2::{ render::{Canvas, Texture}, video::Window, @@ -9,22 +10,22 @@ use crate::texture::atlas::AtlasTexture; use crate::texture::FrameDrawn; /// An animated texture using a texture atlas. -pub struct AnimatedAtlasTexture<'a> { - pub atlas: AtlasTexture<'a>, +pub struct AnimatedAtlasTexture { + pub atlas: AtlasTexture, pub ticks_per_frame: u32, pub ticker: u32, pub reversed: bool, pub paused: bool, } -impl<'a> AnimatedAtlasTexture<'a> { +impl AnimatedAtlasTexture { pub fn new( - texture: Texture<'a>, + texture: Texture<'static>, ticks_per_frame: u32, frame_count: u32, width: u32, height: u32, - offset: Option<(i32, i32)>, + offset: Option, ) -> Self { AnimatedAtlasTexture { atlas: AtlasTexture::new(texture, frame_count, width, height, offset), @@ -64,8 +65,8 @@ impl<'a> AnimatedAtlasTexture<'a> { } } -impl<'a> FrameDrawn for AnimatedAtlasTexture<'a> { - fn render(&self, canvas: &mut Canvas, position: (i32, i32), direction: Direction, frame: Option) { +impl FrameDrawn for AnimatedAtlasTexture { + fn render(&self, canvas: &mut Canvas, position: IVec2, direction: Direction, frame: Option) { let frame = frame.unwrap_or_else(|| self.current_frame()); self.atlas.render(canvas, position, direction, Some(frame)); } diff --git a/src/texture/atlas.rs b/src/texture/atlas.rs index 7838d39..0d9976c 100644 --- a/src/texture/atlas.rs +++ b/src/texture/atlas.rs @@ -1,3 +1,4 @@ +use glam::IVec2; use sdl2::{ rect::Rect, render::{Canvas, Texture}, @@ -6,23 +7,29 @@ use sdl2::{ use crate::{entity::direction::Direction, texture::FrameDrawn}; +/// Unsafely converts a Texture with any lifetime to a 'static lifetime. +/// Only use this if you guarantee the renderer/context will never be dropped! +pub unsafe fn texture_to_static<'a>(texture: Texture<'a>) -> Texture<'static> { + std::mem::transmute::, Texture<'static>>(texture) +} + /// A texture atlas abstraction for static (non-animated) rendering. -pub struct AtlasTexture<'a> { - pub raw_texture: Texture<'a>, - pub offset: (i32, i32), +pub struct AtlasTexture { + pub raw_texture: Texture<'static>, + pub offset: IVec2, pub frame_count: u32, pub frame_width: u32, pub frame_height: u32, } -impl<'a> AtlasTexture<'a> { - pub fn new(texture: Texture<'a>, frame_count: u32, frame_width: u32, frame_height: u32, offset: Option<(i32, i32)>) -> Self { +impl AtlasTexture { + pub fn new(texture: Texture<'static>, frame_count: u32, frame_width: u32, frame_height: u32, offset: Option) -> Self { AtlasTexture { raw_texture: texture, frame_count, frame_width, frame_height, - offset: offset.unwrap_or((0, 0)), + offset: offset.unwrap_or(IVec2::new(0, 0)).into(), } } @@ -43,12 +50,12 @@ impl<'a> AtlasTexture<'a> { } } -impl<'a> FrameDrawn for AtlasTexture<'a> { - fn render(&self, canvas: &mut Canvas, position: (i32, i32), direction: Direction, frame: Option) { +impl FrameDrawn for AtlasTexture { + fn render(&self, canvas: &mut Canvas, position: IVec2, direction: Direction, frame: Option) { let texture_source_frame_rect = self.get_frame_rect(frame.unwrap_or(0)); let canvas_destination_rect = Rect::new( - position.0 + self.offset.0, - position.1 + self.offset.1, + position.x + self.offset.x, + position.y + self.offset.y, self.frame_width, self.frame_height, ); diff --git a/src/texture/mod.rs b/src/texture/mod.rs index e7ea1ed..75c34b0 100644 --- a/src/texture/mod.rs +++ b/src/texture/mod.rs @@ -1,10 +1,11 @@ +use glam::IVec2; use sdl2::{render::Canvas, video::Window}; use crate::entity::direction::Direction; /// Trait for drawable atlas-based textures pub trait FrameDrawn { - fn render(&self, canvas: &mut Canvas, position: (i32, i32), direction: Direction, frame: Option); + fn render(&self, canvas: &mut Canvas, position: IVec2, direction: Direction, frame: Option); } pub mod animated;