feat: non-ttf text rendering using original sprite text, remove black bg from assets

This commit is contained in:
2025-07-26 14:43:25 -05:00
parent 8e5ec9fef0
commit fa5b406909
47 changed files with 174 additions and 16 deletions

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/unpacked/text/!.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
assets/unpacked/text/-.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

BIN
assets/unpacked/text/0.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
assets/unpacked/text/1.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

BIN
assets/unpacked/text/2.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

BIN
assets/unpacked/text/3.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
assets/unpacked/text/4.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

BIN
assets/unpacked/text/5.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

BIN
assets/unpacked/text/6.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
assets/unpacked/text/7.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
assets/unpacked/text/8.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

BIN
assets/unpacked/text/9.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
assets/unpacked/text/A.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
assets/unpacked/text/B.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

BIN
assets/unpacked/text/C.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

BIN
assets/unpacked/text/D.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
assets/unpacked/text/E.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

BIN
assets/unpacked/text/F.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
assets/unpacked/text/G.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
assets/unpacked/text/H.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
assets/unpacked/text/I.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

BIN
assets/unpacked/text/J.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
assets/unpacked/text/K.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
assets/unpacked/text/L.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

BIN
assets/unpacked/text/M.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

BIN
assets/unpacked/text/N.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

BIN
assets/unpacked/text/O.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

BIN
assets/unpacked/text/P.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

BIN
assets/unpacked/text/Q.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

BIN
assets/unpacked/text/R.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

BIN
assets/unpacked/text/S.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

BIN
assets/unpacked/text/T.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

BIN
assets/unpacked/text/U.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

BIN
assets/unpacked/text/V.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
assets/unpacked/text/W.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

BIN
assets/unpacked/text/X.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

BIN
assets/unpacked/text/Y.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

BIN
assets/unpacked/text/Z.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 B

View File

@@ -29,6 +29,7 @@ use crate::map::Map;
use crate::texture::animated::AnimatedTexture;
use crate::texture::blinking::BlinkingTexture;
use crate::texture::sprite::{AtlasMapper, AtlasTile, SpriteAtlas};
use crate::texture::text::TextTexture;
use crate::texture::{get_atlas_tile, sprite};
/// The main game state.
@@ -52,6 +53,7 @@ pub struct Game {
atlas: Rc<SpriteAtlas>,
font: Font<'static, 'static>,
map_texture: AtlasTile,
text_texture: TextTexture,
// Audio
pub audio: Audio,
@@ -67,11 +69,10 @@ impl Game {
let map = Rc::new(RefCell::new(Map::new(RAW_BOARD)));
let atlas_bytes = get_asset_bytes(Asset::Atlas).expect("Failed to load asset");
let atlas_texture = unsafe {
sprite::texture_to_static(
texture_creator
.load_texture_bytes(&atlas_bytes)
.expect("Could not load atlas texture from asset API"),
)
let texture = texture_creator
.load_texture_bytes(&atlas_bytes)
.expect("Could not load atlas texture from asset API");
sprite::texture_to_static(texture)
};
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");
@@ -102,6 +103,7 @@ impl Game {
.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 {
pacman,
@@ -113,6 +115,7 @@ impl Game {
atlas,
font,
map_texture,
text_texture,
audio,
fps_1s: 0.0,
fps_10s: 0.0,
@@ -321,6 +324,7 @@ impl Game {
let lives_offset = 3;
let score_offset = 7 - (score_text.len() as i32);
let gap_offset = 6;
self.text_texture.set_scale(2.0);
self.render_text_on(
canvas,
&*texture_creator,
@@ -355,14 +359,8 @@ impl Game {
position: IVec2,
color: Color,
) {
let surface = self.font.render(text).blended(color).expect("Could not render text surface");
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.x, position.y, query.width, query.height);
canvas
.copy(&texture, None, Some(dst_rect))
.expect("Could not render text texture");
self.text_texture
.render(canvas, text, glam::UVec2::new(position.x as u32, position.y as u32))
.unwrap();
}
}

View File

@@ -6,6 +6,7 @@ pub mod animated;
pub mod blinking;
pub mod directional;
pub mod sprite;
pub mod text;
pub fn get_atlas_tile(atlas: &Rc<SpriteAtlas>, name: &str) -> AtlasTile {
SpriteAtlas::get_tile(atlas, name).unwrap_or_else(|| panic!("Could not find tile {}", name))

View File

@@ -1,7 +1,7 @@
use anyhow::Result;
use glam::U16Vec2;
use sdl2::rect::Rect;
use sdl2::render::{Texture, WindowCanvas};
use sdl2::render::{Canvas, RenderTarget, Texture};
use serde::Deserialize;
use std::collections::HashMap;
use std::rc::Rc;
@@ -27,7 +27,7 @@ pub struct AtlasTile {
}
impl AtlasTile {
pub fn render(&self, canvas: &mut WindowCanvas, dest: Rect) -> Result<()> {
pub fn render<C: RenderTarget>(&self, canvas: &mut Canvas<C>, dest: Rect) -> 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)?;
Ok(())
@@ -54,6 +54,10 @@ impl SpriteAtlas {
size: U16Vec2::new(frame.width, frame.height),
})
}
pub fn texture(&self) -> &Texture<'static> {
&self.texture
}
}
pub unsafe fn texture_to_static<'a>(texture: Texture<'a>) -> Texture<'static> {

155
src/texture/text.rs Normal file
View File

@@ -0,0 +1,155 @@
//! This module provides text rendering using the texture atlas.
//!
//! The TextTexture system renders text from the atlas using character mapping.
//! It supports a subset of characters with special handling for characters that
//! can't be used in filenames.
//!
//! # Example Usage
//!
//! ```rust
//! use crate::texture::text::TextTexture;
//! use std::rc::Rc;
//!
//! // Create a text texture with 1.0 scale (8x8 pixels per character)
//! let mut text_renderer = TextTexture::new(atlas.clone(), 1.0);
//!
//! // Render text at position (100, 50)
//! text_renderer.render(canvas, "PAC-MAN", glam::UVec2::new(100, 50))?;
//!
//! // Change scale for larger text
//! text_renderer.set_scale(2.0);
//! text_renderer.render(canvas, "SCORE: 1000", glam::UVec2::new(50, 100))?;
//!
//! // Calculate text width for positioning
//! let width = text_renderer.text_width("GAME OVER");
//! let height = text_renderer.text_height();
//! ```
//!
//! # Supported Characters
//!
//! - Letters: A-Z, a-z
//! - Numbers: 0-9
//! - Common symbols: ! ? . , : ; - _ ( ) [ ] { } < > = + * / \ | & @ # $ % ^ ~ ` ' "
//! - Space character
//!
//! # Character Mapping
//!
//! Most characters use their literal name (e.g., "A.png", "1.png").
//! Special characters use alternative names:
//! - `"` → "text/_double_quote.png"
//! - `'` → "text/_single_quote.png"
//! - `\` → "text/\\backslash.png"
//! - ` ` (space) → "text/space.png"
//!
//! # Memory Optimization
//!
//! The system caches character tiles in a HashMap to avoid repeated
//! atlas lookups. Only tiles for used characters are stored in memory.
use anyhow::Result;
use glam::UVec2;
use sdl2::rect::Rect;
use sdl2::render::{Canvas, RenderTarget};
use std::collections::HashMap;
use std::rc::Rc;
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
/// A text texture that renders characters from the atlas.
pub struct TextTexture {
atlas: Rc<SpriteAtlas>,
char_map: HashMap<char, AtlasTile>,
scale: f32,
}
impl TextTexture {
/// Creates a new text texture with the given atlas and scale.
pub fn new(atlas: Rc<SpriteAtlas>, scale: f32) -> Self {
Self {
atlas,
char_map: HashMap::new(),
scale,
}
}
/// Maps a character to its atlas tile, handling special characters.
fn get_char_tile(&mut self, c: char) -> Option<AtlasTile> {
if let Some(tile) = self.char_map.get(&c) {
return Some(tile.clone());
}
let tile_name = self.char_to_tile_name(c)?;
let tile = SpriteAtlas::get_tile(&self.atlas, &tile_name)?;
self.char_map.insert(c, tile.clone());
Some(tile)
}
/// Converts a character to its tile name in the atlas.
fn char_to_tile_name(&self, c: char) -> Option<String> {
let name = match c {
// Letters A-Z
'A'..='Z' => format!("text/{}.png", c),
// Numbers 0-9
'0'..='9' => format!("text/{}.png", c),
// Special characters
'!' => "text/!.png".to_string(),
'-' => "text/-.png".to_string(),
'"' => "text/_double_quote.png".to_string(),
'/' => "text/_forward_slash.png".to_string(),
// Skip spaces for now - they don't have a tile
' ' => return None,
// Unsupported character
_ => return None,
};
Some(name)
}
/// Renders a string of text at the given position.
pub fn render<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, text: &str, position: UVec2) -> 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) {
let dest = sdl2::rect::Rect::new((position.x + x_offset) as i32, position.y as i32, char_width, char_height);
tile.render(canvas, dest)?;
}
// Always advance x_offset for all characters (including spaces)
x_offset += char_width;
}
Ok(())
}
/// Sets the scale for text rendering.
pub fn set_scale(&mut self, scale: f32) {
self.scale = scale;
}
/// Gets the current scale.
pub fn scale(&self) -> f32 {
self.scale
}
/// Calculates the width of a string in pixels at the current scale.
pub fn text_width(&mut self, text: &str) -> u32 {
let char_width = (8.0 * self.scale) as u32;
let mut width = 0;
for c in text.chars() {
if self.char_to_tile_name(c).is_some() {
width += char_width;
}
}
width
}
/// Calculates the height of text in pixels at the current scale.
pub fn text_height(&self) -> u32 {
(8.0 * self.scale) as u32
}
}