From 62b2c607a9936f7e02522fb159a548d78fa92244 Mon Sep 17 00:00:00 2001 From: Xevion Date: Fri, 18 Jul 2025 20:07:50 -0500 Subject: [PATCH] feat: ttf score rendering, konami font --- assets/font/konami.ttf | Bin 0 -> 32440 bytes src/game.rs | 118 +++++++++++++++++++++++++++++++++++++++-- src/main.rs | 11 +--- 3 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 assets/font/konami.ttf diff --git a/assets/font/konami.ttf b/assets/font/konami.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f4ed662355c97d2f80cf535c518af6659abc066a GIT binary patch literal 32440 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 a`CkA0z4!m?y*Iuw^RGGF_ji5`k^O%+c3uMj literal 0 HcmV?d00001 diff --git a/src/game.rs b/src/game.rs index 25787af..483b798 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use sdl2::image::LoadTexture; use sdl2::keyboard::Keycode; use sdl2::render::{Texture, TextureCreator}; +use sdl2::ttf::{Font, FontStyle}; use sdl2::video::WindowContext; use sdl2::{pixels::Color, render::Canvas, video::Window}; @@ -15,15 +16,20 @@ use crate::pacman::Pacman; pub struct Game<'a> { canvas: &'a mut Canvas, map_texture: Texture<'a>, + pellet_texture: Texture<'a>, + power_pellet_texture: Texture<'a>, + font: Font<'a, 'static>, pacman: Pacman<'a>, map: Rc, debug: bool, + score: u32, } impl Game<'_> { pub fn new<'a>( canvas: &'a mut Canvas, texture_creator: &'a TextureCreator, + ttf_context: &'a sdl2::ttf::Sdl2TtfContext, ) -> Game<'a> { let map = Rc::new(Map::new(RAW_BOARD)); let pacman_atlas = texture_creator @@ -31,6 +37,17 @@ impl Game<'_> { .expect("Could not load pacman texture"); let pacman = Pacman::new((1, 1), pacman_atlas, Rc::clone(&map)); + let pellet_texture = texture_creator + .load_texture("assets/24/pellet.png") + .expect("Could not load pellet texture"); + let power_pellet_texture = texture_creator + .load_texture("assets/24/energizer.png") + .expect("Could not load power pellet texture"); + + let font = ttf_context + .load_font("assets/font/konami.ttf", 24) + .expect("Could not load font"); + Game { canvas, pacman: pacman, @@ -38,7 +55,11 @@ impl Game<'_> { map: map, map_texture: texture_creator .load_texture("assets/map.png") - .expect("Could not load pacman texture"), + .expect("Could not load map texture"), + pellet_texture, + power_pellet_texture, + font, + score: 0, } } @@ -51,6 +72,15 @@ impl Game<'_> { if keycode == Keycode::Space { self.debug = !self.debug; } + + // Test score increase + if keycode == Keycode::S { + self.add_score(10); + } + } + + pub fn add_score(&mut self, points: u32) { + self.score += points; } pub fn tick(&mut self) { @@ -62,16 +92,21 @@ impl Game<'_> { self.canvas.set_draw_color(Color::RGB(0, 0, 0)); self.canvas.clear(); - - // Render the map + // Render the map self.canvas .copy(&self.map_texture, None, None) .expect("Could not render texture on canvas"); + // Render pellets + self.render_pellets(); + // Render the pacman self.pacman.render(self.canvas); - // Draw a grid + // Render score + self.render_score(); + + // Draw the debug grid if self.debug { for x in 0..BOARD_WIDTH { for y in 0..BOARD_HEIGHT { @@ -110,6 +145,7 @@ impl Game<'_> { fn draw_cell(&mut self, cell: (u32, u32), color: Color) { let position = Map::cell_to_pixel(cell); + self.canvas.set_draw_color(color); self.canvas .draw_rect(sdl2::rect::Rect::new( @@ -120,4 +156,78 @@ impl Game<'_> { )) .expect("Could not draw rectangle"); } + + fn render_pellets(&mut self) { + for x in 0..BOARD_WIDTH { + for y in 0..BOARD_HEIGHT { + let tile = self + .map + .get_tile((x as i32, y as i32)) + .unwrap_or(MapTile::Empty); + + match tile { + MapTile::Pellet => { + let position = Map::cell_to_pixel((x, y)); + let dst_rect = sdl2::rect::Rect::new(position.0, position.1, 24, 24); + self.canvas + .copy(&self.pellet_texture, None, Some(dst_rect)) + .expect("Could not render pellet"); + } + MapTile::PowerPellet => { + let position = Map::cell_to_pixel((x, y)); + let dst_rect = sdl2::rect::Rect::new(position.0, position.1, 24, 24); + self.canvas + .copy(&self.power_pellet_texture, None, Some(dst_rect)) + .expect("Could not render power pellet"); + } + _ => {} + } + } + } + } + + fn render_score(&mut self) { + let score = 0; + let lives = 3; + let score_text = format!("{:02}", score); + + let x_offset = 12; + let y_offset = 2; + let lives_offset = 3; + let score_offset = 7 - (score_text.len() as i32); + let gap_offset = 6; + + self.render_text( + &format!("{}UP HIGH SCORE ", lives), + (24 * lives_offset + x_offset, y_offset), + Color::WHITE, + ); + self.render_text( + &score_text, + (24 * score_offset + x_offset, 24 + y_offset + gap_offset), + Color::WHITE, + ); + } + + fn render_text(&mut self, text: &str, position: (i32, i32), color: Color) { + let surface = self + .font + .render(text) + .blended(color) + .expect("Could not render text surface"); + + let texture_creator = self.canvas.texture_creator(); + let texture = texture_creator + .create_texture_from_surface(&surface) + .expect("Could not create texture from surface"); + + let query = texture.query(); + + let dst_rect = + sdl2::rect::Rect::new(position.0, position.1, query.width + 4, query.height + 4); + + self.canvas + .copy(&texture, None, Some(dst_rect)) + .expect("Could not render text texture"); + } } diff --git a/src/main.rs b/src/main.rs index 5983232..4e06388 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ mod pacman; pub fn main() { let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); + let ttf_context = sdl2::ttf::init().unwrap(); // Setup tracing let subscriber = tracing_subscriber::fmt() @@ -46,7 +47,7 @@ pub fn main() { .expect("Could not set logical size"); let texture_creator = canvas.texture_creator(); - let mut game = Game::new(&mut canvas, &texture_creator); + let mut game = Game::new(&mut canvas, &texture_creator, &ttf_context); let mut event_pump = sdl_context .event_pump() @@ -150,14 +151,6 @@ pub fn main() { let average_sleep = sleep_time / PERIOD; let average_process = loop_time - average_sleep; - event!( - tracing::Level::DEBUG, - "Timing Averages [fps={}] [sleep={:?}] [process={:?}]", - average_fps, - average_sleep, - average_process - ); - sleep_time = Duration::ZERO; last_averaging_time = Instant::now(); }