diff --git a/src/entity/blinky.rs b/src/entity/blinky.rs index 0748b6f..7f138d9 100644 --- a/src/entity/blinky.rs +++ b/src/entity/blinky.rs @@ -16,7 +16,12 @@ pub struct Blinky { } impl Blinky { - pub fn new(starting_position: UVec2, atlas: Rc, map: Rc>, pacman: Rc>) -> Blinky { + pub fn new( + starting_position: UVec2, + atlas: Rc>, + map: Rc>, + pacman: Rc>, + ) -> Blinky { Blinky { ghost: Ghost::new(GhostType::Blinky, starting_position, atlas, map, pacman), } diff --git a/src/entity/edible.rs b/src/entity/edible.rs index 165baad..1a45833 100644 --- a/src/entity/edible.rs +++ b/src/entity/edible.rs @@ -61,22 +61,22 @@ impl Entity for Edible { impl Renderable for Edible { fn render(&mut self, canvas: &mut WindowCanvas) -> Result<()> { let pos = self.base.pixel_position; - let dest = match &self.sprite { + let dest = match &mut self.sprite { EdibleSprite::Pellet(sprite) => { - let tile = sprite.current_tile(); + let mut tile = sprite.current_tile(); let x = pos.x + ((crate::constants::CELL_SIZE as i32 - tile.size.x as i32) / 2); let y = pos.y + ((crate::constants::CELL_SIZE as i32 - tile.size.y as i32) / 2); sdl2::rect::Rect::new(x, y, tile.size.x as u32, tile.size.y as u32) } EdibleSprite::PowerPellet(sprite) => { - let tile = sprite.animation.current_tile(); + let mut tile = sprite.animation.current_tile(); let x = pos.x + ((crate::constants::CELL_SIZE as i32 - tile.size.x as i32) / 2); let y = pos.y + ((crate::constants::CELL_SIZE as i32 - tile.size.y as i32) / 2); sdl2::rect::Rect::new(x, y, tile.size.x as u32, tile.size.y as u32) } }; - match &self.sprite { + match &mut self.sprite { EdibleSprite::Pellet(sprite) => sprite.render(canvas, dest), EdibleSprite::PowerPellet(sprite) => sprite.render(canvas, dest), } diff --git a/src/entity/ghost.rs b/src/entity/ghost.rs index 7e5e682..271a660 100644 --- a/src/entity/ghost.rs +++ b/src/entity/ghost.rs @@ -76,7 +76,7 @@ impl Ghost { pub fn new( ghost_type: GhostType, starting_position: UVec2, - atlas: Rc, + atlas: Rc>, map: Rc>, pacman: Rc>, ) -> Ghost { diff --git a/src/entity/pacman.rs b/src/entity/pacman.rs index 60ac606..4d575ae 100644 --- a/src/entity/pacman.rs +++ b/src/entity/pacman.rs @@ -81,7 +81,7 @@ impl QueuedDirection for Pacman { impl Pacman { /// Creates a new `Pacman` instance. - pub fn new(starting_position: UVec2, atlas: Rc, map: Rc>) -> Pacman { + pub fn new(starting_position: UVec2, atlas: Rc>, map: Rc>) -> Pacman { let pixel_position = Map::cell_to_pixel(starting_position); let get = |name: &str| get_atlas_tile(&atlas, name); diff --git a/src/game.rs b/src/game.rs index 6f76650..c539b45 100644 --- a/src/game.rs +++ b/src/game.rs @@ -11,8 +11,6 @@ use rand::SeedableRng; use sdl2::image::LoadTexture; use sdl2::keyboard::Keycode; use sdl2::render::{Texture, TextureCreator}; -use sdl2::rwops::RWops; -use sdl2::ttf::Font; use sdl2::video::WindowContext; use sdl2::{pixels::Color, render::Canvas, video::Window}; @@ -50,8 +48,7 @@ pub struct Game { fps_10s: f64, // Rendering resources - atlas: Rc, - font: Font<'static, 'static>, + atlas: Rc>, map_texture: AtlasTile, text_texture: TextTexture, @@ -76,7 +73,7 @@ impl Game { }; let atlas_json = get_asset_bytes(Asset::AtlasJson).expect("Failed to load asset"); let atlas_mapper: AtlasMapper = serde_json::from_slice(&atlas_json).expect("Could not parse atlas JSON"); - let atlas = Rc::new(SpriteAtlas::new(atlas_texture, atlas_mapper)); + let atlas = Rc::new(RefCell::new(SpriteAtlas::new(atlas_texture, atlas_mapper))); let pacman = Rc::new(RefCell::new(Pacman::new( UVec2::new(1, 1), Rc::clone(&atlas), @@ -94,15 +91,6 @@ impl Game { ), AnimatedTexture::new(vec![get_atlas_tile(&atlas, "edible/cherry.png")], 0), ); - let font = { - let font_bytes = get_asset_bytes(Asset::FontKonami).expect("Failed to load asset").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"); - let ttf_context_static: &'static sdl2::ttf::Sdl2TtfContext = unsafe { std::mem::transmute(ttf_context) }; - ttf_context_static - .load_font_from_rwops(font_rwops, 24) - .expect("Could not load font from asset API") - }; let text_texture = TextTexture::new(Rc::clone(&atlas), 1.0); let audio = Audio::new(); Game { @@ -113,7 +101,6 @@ impl Game { score: 0, debug_mode: DebugMode::None, atlas, - font, map_texture, text_texture, audio, @@ -269,20 +256,19 @@ impl Game { /// Draws the entire game to the canvas using a backbuffer. pub fn draw(&mut self, window_canvas: &mut Canvas, backbuffer: &mut Texture) -> Result<()> { - let texture_creator = window_canvas.texture_creator(); window_canvas .with_texture_canvas(backbuffer, |texture_canvas| { let this = self as *mut Self; let this = unsafe { &mut *this }; texture_canvas.set_draw_color(Color::BLACK); texture_canvas.clear(); - this.map.borrow_mut().render(texture_canvas, &this.map_texture); + this.map.borrow_mut().render(texture_canvas, &mut this.map_texture); for edible in this.edibles.iter_mut() { let _ = edible.render(texture_canvas); } let _ = this.pacman.borrow_mut().render(texture_canvas); let _ = this.blinky.render(texture_canvas); - this.render_ui_on(texture_canvas, &texture_creator); + this.render_ui_on(texture_canvas); match this.debug_mode { DebugMode::Grid => { DebugRenderer::draw_debug_grid( @@ -312,11 +298,7 @@ impl Game { Ok(()) } - fn render_ui_on( - &mut self, - canvas: &mut sdl2::render::Canvas, - texture_creator: &TextureCreator, - ) { + fn render_ui_on(&mut self, canvas: &mut sdl2::render::Canvas) { let lives = 3; let score_text = format!("{:02}", self.score); let x_offset = 12; @@ -325,18 +307,16 @@ impl Game { let score_offset = 7 - (score_text.len() as i32); let gap_offset = 6; self.text_texture.set_scale(2.0); - self.render_text_on( + self.text_texture.render( canvas, - &*texture_creator, &format!("{lives}UP HIGH SCORE "), - IVec2::new(24 * lives_offset + x_offset, y_offset), + UVec2::new(24 * lives_offset as u32 + x_offset, y_offset), Color::WHITE, ); - self.render_text_on( + self.text_texture.render( canvas, - &*texture_creator, &score_text, - IVec2::new(24 * score_offset + x_offset, 24 + y_offset + gap_offset), + UVec2::new(24 * score_offset as u32 + x_offset, 24 + y_offset + gap_offset), Color::WHITE, ); @@ -350,17 +330,4 @@ impl Game { // Color::RGB(255, 255, 0), // Yellow color for FPS display // ); } - - fn render_text_on( - &mut self, - canvas: &mut sdl2::render::Canvas, - texture_creator: &TextureCreator, - text: &str, - position: IVec2, - color: Color, - ) { - self.text_texture - .render(canvas, text, glam::UVec2::new(position.x as u32, position.y as u32)) - .unwrap(); - } } diff --git a/src/map.rs b/src/map.rs index 9ebe93b..28e7064 100644 --- a/src/map.rs +++ b/src/map.rs @@ -161,7 +161,7 @@ impl Map { } /// Renders the map to the given canvas using the provided map texture. - pub fn render(&self, canvas: &mut Canvas, map_texture: &AtlasTile) { + pub fn render(&self, canvas: &mut Canvas, map_texture: &mut AtlasTile) { let dest = Rect::new(0, 0, CELL_SIZE * BOARD_CELL_SIZE.x, CELL_SIZE * BOARD_CELL_SIZE.y); let _ = map_texture.render(canvas, dest); } diff --git a/src/texture/animated.rs b/src/texture/animated.rs index ae9951c..40229a9 100644 --- a/src/texture/animated.rs +++ b/src/texture/animated.rs @@ -1,6 +1,6 @@ //! This module provides a simple animation and atlas system for textures. use anyhow::Result; -use sdl2::render::WindowCanvas; +use sdl2::{pixels::Color, render::WindowCanvas}; use crate::texture::sprite::AtlasTile; @@ -34,16 +34,16 @@ impl AnimatedTexture { self.ticker += 1; } - pub fn current_tile(&self) -> &AtlasTile { + pub fn current_tile(&mut self) -> &mut AtlasTile { if self.ticks_per_frame == 0 { - return &self.frames[0]; + return &mut self.frames[0]; } let frame_index = (self.ticker / self.ticks_per_frame) as usize % self.frames.len(); - &self.frames[frame_index] + &mut self.frames[frame_index] } - pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> { - let tile = self.current_tile(); + pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> { + let mut tile = self.current_tile(); tile.render(canvas, dest) } } diff --git a/src/texture/blinking.rs b/src/texture/blinking.rs index 856b06a..afdf6fe 100644 --- a/src/texture/blinking.rs +++ b/src/texture/blinking.rs @@ -38,7 +38,7 @@ impl BlinkingTexture { } /// Renders the blinking texture. - pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> { + pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> { if self.visible { self.animation.render(canvas, dest) } else { diff --git a/src/texture/directional.rs b/src/texture/directional.rs index 68d8d89..b0e9ce9 100644 --- a/src/texture/directional.rs +++ b/src/texture/directional.rs @@ -37,28 +37,28 @@ impl DirectionalAnimatedTexture { pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> { let frames = match direction { - Direction::Up => &self.up, - Direction::Down => &self.down, - Direction::Left => &self.left, - Direction::Right => &self.right, + Direction::Up => &mut self.up, + Direction::Down => &mut self.down, + Direction::Left => &mut self.left, + Direction::Right => &mut self.right, }; let frame_index = (self.ticker / self.ticks_per_frame) as usize % frames.len(); - let tile = &frames[frame_index]; + let tile = &mut frames[frame_index]; tile.render(canvas, dest) } pub fn render_stopped(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> { let frames = match direction { - Direction::Up => &self.up, - Direction::Down => &self.down, - Direction::Left => &self.left, - Direction::Right => &self.right, + Direction::Up => &mut self.up, + Direction::Down => &mut self.down, + Direction::Left => &mut self.left, + Direction::Right => &mut self.right, }; // Show the last frame (full sprite) when stopped - let tile = &frames[1]; + let tile = &mut frames[1]; tile.render(canvas, dest) } diff --git a/src/texture/mod.rs b/src/texture/mod.rs index aa61faf..a14ccbf 100644 --- a/src/texture/mod.rs +++ b/src/texture/mod.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::rc::Rc; use crate::texture::sprite::{AtlasTile, SpriteAtlas}; @@ -8,6 +9,6 @@ pub mod directional; pub mod sprite; pub mod text; -pub fn get_atlas_tile(atlas: &Rc, name: &str) -> AtlasTile { +pub fn get_atlas_tile(atlas: &Rc>, name: &str) -> AtlasTile { SpriteAtlas::get_tile(atlas, name).unwrap_or_else(|| panic!("Could not find tile {}", name)) } diff --git a/src/texture/sprite.rs b/src/texture/sprite.rs index 98d18df..d87e3cf 100644 --- a/src/texture/sprite.rs +++ b/src/texture/sprite.rs @@ -1,8 +1,10 @@ use anyhow::Result; use glam::U16Vec2; +use sdl2::pixels::Color; use sdl2::rect::Rect; use sdl2::render::{Canvas, RenderTarget, Texture}; use serde::Deserialize; +use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -21,15 +23,30 @@ pub struct MapperFrame { #[derive(Clone)] pub struct AtlasTile { - pub atlas: Rc, + pub atlas: Rc>, pub pos: U16Vec2, pub size: U16Vec2, + pub color: Option, } impl AtlasTile { - pub fn render(&self, canvas: &mut Canvas, dest: Rect) -> Result<()> { + pub fn render(&mut self, canvas: &mut Canvas, dest: Rect) -> Result<()> { + let color = self + .color + .unwrap_or(self.atlas.borrow().default_color.unwrap_or(Color::WHITE)); + self.render_with_color(canvas, dest, color) + } + + pub fn render_with_color(&mut self, canvas: &mut Canvas, dest: Rect, color: Color) -> Result<()> { let src = Rect::new(self.pos.x as i32, self.pos.y as i32, self.size.x as u32, self.size.y as u32); - canvas.copy(&self.atlas.texture, src, dest).map_err(anyhow::Error::msg)?; + + let mut atlas = self.atlas.borrow_mut(); + if atlas.last_modulation != Some(color) { + atlas.texture.set_color_mod(color.r, color.g, color.b); + atlas.last_modulation = Some(color); + } + + canvas.copy(&atlas.texture, src, dest).map_err(anyhow::Error::msg)?; Ok(()) } } @@ -37,6 +54,8 @@ impl AtlasTile { pub struct SpriteAtlas { texture: Texture<'static>, tiles: HashMap, + default_color: Option, + last_modulation: Option, } impl SpriteAtlas { @@ -44,17 +63,25 @@ impl SpriteAtlas { Self { texture, tiles: mapper.frames, + default_color: None, + last_modulation: None, } } - pub fn get_tile(atlas: &Rc, name: &str) -> Option { - atlas.tiles.get(name).map(|frame| AtlasTile { - atlas: atlas.clone(), + pub fn get_tile(atlas: &Rc>, name: &str) -> Option { + let atlas_ref = atlas.borrow(); + atlas_ref.tiles.get(name).map(|frame| AtlasTile { + atlas: Rc::clone(atlas), pos: U16Vec2::new(frame.x, frame.y), size: U16Vec2::new(frame.width, frame.height), + color: None, }) } + pub fn set_color(&mut self, color: Color) { + self.default_color = Some(color); + } + pub fn texture(&self) -> &Texture<'static> { &self.texture } diff --git a/src/texture/text.rs b/src/texture/text.rs index 643d08f..5aa1586 100644 --- a/src/texture/text.rs +++ b/src/texture/text.rs @@ -48,8 +48,10 @@ use anyhow::Result; use glam::UVec2; +use sdl2::pixels::Color; use sdl2::rect::Rect; use sdl2::render::{Canvas, RenderTarget}; +use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -57,14 +59,14 @@ use crate::texture::sprite::{AtlasTile, SpriteAtlas}; /// A text texture that renders characters from the atlas. pub struct TextTexture { - atlas: Rc, + atlas: Rc>, char_map: HashMap, scale: f32, } impl TextTexture { /// Creates a new text texture with the given atlas and scale. - pub fn new(atlas: Rc, scale: f32) -> Self { + pub fn new(atlas: Rc>, scale: f32) -> Self { Self { atlas, char_map: HashMap::new(), @@ -107,13 +109,13 @@ impl TextTexture { } /// Renders a string of text at the given position. - pub fn render(&mut self, canvas: &mut Canvas, text: &str, position: UVec2) -> Result<()> { + pub fn render(&mut self, canvas: &mut Canvas, text: &str, position: UVec2, color: Color) -> Result<()> { let mut x_offset = 0; let char_width = (8.0 * self.scale) as u32; let char_height = (8.0 * self.scale) as u32; for c in text.chars() { - if let Some(tile) = self.get_char_tile(c) { + if let Some(mut tile) = self.get_char_tile(c) { let dest = sdl2::rect::Rect::new((position.x + x_offset) as i32, position.y as i32, char_width, char_height); tile.render(canvas, dest)?; }