feat: shared blinking textures for power pellets

This commit is contained in:
2025-07-24 16:23:09 -05:00
parent 61ca537909
commit 9b441fa35c
4 changed files with 106 additions and 27 deletions

View File

@@ -4,6 +4,7 @@ use crate::entity::direction::Direction;
use crate::entity::{Entity, Renderable, StaticEntity};
use crate::map::Map;
use crate::texture::atlas::AtlasTexture;
use crate::texture::blinking::BlinkingTexture;
use crate::texture::FrameDrawn;
use glam::{IVec2, UVec2};
use sdl2::{render::Canvas, video::Window};
@@ -17,19 +18,32 @@ pub enum EdibleKind {
Fruit(FruitType),
}
pub enum EdibleSprite {
Pellet(Rc<Box<dyn FrameDrawn>>),
PowerPellet(Rc<RefCell<BlinkingTexture>>),
}
pub struct Edible {
pub base: StaticEntity,
pub kind: EdibleKind,
pub sprite: Rc<AtlasTexture>,
pub sprite: EdibleSprite,
}
impl Edible {
pub fn new(kind: EdibleKind, cell_position: UVec2, sprite: Rc<AtlasTexture>) -> Self {
pub fn new_pellet(cell_position: UVec2, sprite: Rc<Box<dyn FrameDrawn>>) -> Self {
let pixel_position = Map::cell_to_pixel(cell_position);
Edible {
base: StaticEntity::new(pixel_position, cell_position),
kind,
sprite,
kind: EdibleKind::Pellet,
sprite: EdibleSprite::Pellet(sprite),
}
}
pub fn new_power_pellet(cell_position: UVec2, sprite: Rc<RefCell<BlinkingTexture>>) -> Self {
let pixel_position = Map::cell_to_pixel(cell_position);
Edible {
base: StaticEntity::new(pixel_position, cell_position),
kind: EdibleKind::PowerPellet,
sprite: EdibleSprite::PowerPellet(sprite),
}
}
@@ -48,16 +62,19 @@ impl Entity for Edible {
impl Renderable for Edible {
fn render(&self, canvas: &mut Canvas<Window>) {
let pos = self.base.pixel_position;
self.sprite.render(canvas, pos, Direction::Right, Some(0));
match &self.sprite {
EdibleSprite::Pellet(sprite) => sprite.render(canvas, pos, Direction::Right, Some(0)),
EdibleSprite::PowerPellet(sprite) => sprite.borrow().render(canvas, pos, Direction::Right, Some(0)),
}
}
}
/// Reconstruct all edibles from the original map layout
pub fn reconstruct_edibles(
map: Rc<RefCell<Map>>,
pellet_sprite: Rc<AtlasTexture>,
power_pellet_sprite: Rc<AtlasTexture>,
_fruit_sprite: Rc<AtlasTexture>,
pellet_sprite: Rc<Box<dyn FrameDrawn>>,
power_pellet_sprite: Rc<RefCell<BlinkingTexture>>,
_fruit_sprite: Rc<Box<dyn FrameDrawn>>,
) -> Vec<Edible> {
let mut edibles = Vec::new();
for x in 0..BOARD_WIDTH {
@@ -65,14 +82,10 @@ pub fn reconstruct_edibles(
let tile = map.borrow().get_tile(IVec2::new(x as i32, y as i32));
match tile {
Some(MapTile::Pellet) => {
edibles.push(Edible::new(EdibleKind::Pellet, UVec2::new(x, y), Rc::clone(&pellet_sprite)));
edibles.push(Edible::new_pellet(UVec2::new(x, y), Rc::clone(&pellet_sprite)));
}
Some(MapTile::PowerPellet) => {
edibles.push(Edible::new(
EdibleKind::PowerPellet,
UVec2::new(x, y),
Rc::clone(&power_pellet_sprite),
));
edibles.push(Edible::new_power_pellet(UVec2::new(x, y), Rc::clone(&power_pellet_sprite)));
}
// Fruits can be added here if you have fruit positions
_ => {}

View File

@@ -26,6 +26,8 @@ use crate::entity::pacman::Pacman;
use crate::entity::Renderable;
use crate::map::Map;
use crate::texture::atlas::{texture_to_static, AtlasTexture};
use crate::texture::blinking::BlinkingTexture;
use crate::texture::FrameDrawn;
/// The main game state.
///
@@ -34,8 +36,8 @@ use crate::texture::atlas::{texture_to_static, AtlasTexture};
pub struct Game {
canvas: &'static mut Canvas<Window>,
map_texture: Texture<'static>,
pellet_texture: Rc<AtlasTexture>,
power_pellet_texture: Rc<AtlasTexture>,
pellet_texture: Rc<Box<dyn FrameDrawn>>,
power_pellet_texture: Rc<RefCell<BlinkingTexture>>,
font: Font<'static, 'static>,
pacman: Rc<RefCell<Pacman>>,
map: Rc<RefCell<Map>>,
@@ -92,7 +94,7 @@ impl Game {
// 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(
let pellet_texture: Rc<Box<dyn FrameDrawn>> = Rc::new(Box::new(AtlasTexture::new(
unsafe {
texture_to_static(
texture_creator
@@ -104,20 +106,18 @@ impl Game {
24,
24,
None,
));
let power_pellet_texture = Rc::new(AtlasTexture::new(
unsafe {
texture_to_static(
)));
let power_pellet_texture = Rc::new(RefCell::new(BlinkingTexture::new(
texture_creator
.load_texture_bytes(&power_pellet_bytes)
.expect("Could not load power pellet texture from asset API"),
)
},
1,
24,
24,
None,
));
30, // on_ticks
9, // off_ticks
)));
// Load map texture from asset API
let map_bytes = get_asset_bytes(Asset::Map).expect("Failed to load asset");
@@ -262,6 +262,9 @@ impl Game {
self.blinky.body_sprite.tick();
self.blinky.eyes_sprite.tick();
// Advance blinking for power pellets
self.power_pellet_texture.borrow_mut().tick();
let pacman = self.pacman.borrow();
let mut eaten_indices = vec![];
for (i, edible) in self.edibles.iter().enumerate() {

62
src/texture/blinking.rs Normal file
View File

@@ -0,0 +1,62 @@
//! A texture that blinks on/off for a specified number of ticks.
use glam::IVec2;
use sdl2::{
render::{Canvas, Texture},
video::Window,
};
use crate::texture::atlas::AtlasTexture;
use crate::texture::FrameDrawn;
use crate::{entity::direction::Direction, texture::atlas::texture_to_static};
pub struct BlinkingTexture {
pub atlas: AtlasTexture,
pub on_ticks: u32,
pub off_ticks: u32,
pub ticker: u32,
pub visible: bool,
}
impl BlinkingTexture {
pub fn new(
texture: Texture<'_>,
frame_count: u32,
width: u32,
height: u32,
offset: Option<IVec2>,
on_ticks: u32,
off_ticks: u32,
) -> Self {
BlinkingTexture {
atlas: AtlasTexture::new(unsafe { texture_to_static(texture) }, frame_count, width, height, offset),
on_ticks,
off_ticks,
ticker: 0,
visible: true,
}
}
/// Advances the blinking state by one tick.
pub fn tick(&mut self) {
self.ticker += 1;
if self.visible && self.ticker >= self.on_ticks {
self.visible = false;
self.ticker = 0;
} else if !self.visible && self.ticker >= self.off_ticks {
self.visible = true;
self.ticker = 0;
}
}
pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) {
self.atlas.set_color_modulation(r, g, b);
}
}
impl FrameDrawn for BlinkingTexture {
fn render(&self, canvas: &mut Canvas<Window>, position: IVec2, direction: Direction, frame: Option<u32>) {
if self.visible {
self.atlas.render(canvas, position, direction, frame);
}
}
}

View File

@@ -10,3 +10,4 @@ pub trait FrameDrawn {
pub mod animated;
pub mod atlas;
pub mod blinking;