mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-16 02:12:28 -06:00
refactor: huge refactor into node/graph-based movement system
This commit is contained in:
@@ -1,47 +1,41 @@
|
||||
//! This module provides a simple animation and atlas system for textures.
|
||||
use anyhow::Result;
|
||||
use sdl2::render::WindowCanvas;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::{Canvas, RenderTarget};
|
||||
|
||||
use crate::texture::sprite::AtlasTile;
|
||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||
|
||||
/// An animated texture using a texture atlas.
|
||||
#[derive(Clone)]
|
||||
pub struct AnimatedTexture {
|
||||
pub frames: Vec<AtlasTile>,
|
||||
pub ticks_per_frame: u32,
|
||||
pub ticker: u32,
|
||||
pub paused: bool,
|
||||
tiles: Vec<AtlasTile>,
|
||||
frame_duration: f32,
|
||||
current_frame: usize,
|
||||
time_bank: f32,
|
||||
}
|
||||
|
||||
impl AnimatedTexture {
|
||||
pub fn new(frames: Vec<AtlasTile>, ticks_per_frame: u32) -> Self {
|
||||
AnimatedTexture {
|
||||
frames,
|
||||
ticks_per_frame,
|
||||
ticker: 0,
|
||||
paused: false,
|
||||
pub fn new(tiles: Vec<AtlasTile>, frame_duration: f32) -> Self {
|
||||
Self {
|
||||
tiles,
|
||||
frame_duration,
|
||||
current_frame: 0,
|
||||
time_bank: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the animation by one tick, unless paused.
|
||||
pub fn tick(&mut self) {
|
||||
if self.paused || self.ticks_per_frame == 0 {
|
||||
return;
|
||||
pub fn tick(&mut self, dt: f32) {
|
||||
self.time_bank += dt;
|
||||
while self.time_bank >= self.frame_duration {
|
||||
self.time_bank -= self.frame_duration;
|
||||
self.current_frame = (self.current_frame + 1) % self.tiles.len();
|
||||
}
|
||||
|
||||
self.ticker += 1;
|
||||
}
|
||||
|
||||
pub fn current_tile(&mut self) -> &mut AtlasTile {
|
||||
if self.ticks_per_frame == 0 {
|
||||
return &mut self.frames[0];
|
||||
}
|
||||
let frame_index = (self.ticker / self.ticks_per_frame) as usize % self.frames.len();
|
||||
&mut self.frames[frame_index]
|
||||
pub fn current_tile(&self) -> &AtlasTile {
|
||||
&self.tiles[self.current_frame]
|
||||
}
|
||||
|
||||
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||
let tile = self.current_tile();
|
||||
tile.render(canvas, dest)
|
||||
pub fn render<T: RenderTarget>(&self, canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, dest: Rect) -> Result<()> {
|
||||
let mut tile = self.current_tile().clone();
|
||||
tile.render(canvas, atlas, dest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,36 @@
|
||||
//! A texture that blinks on/off for a specified number of ticks.
|
||||
use anyhow::Result;
|
||||
use sdl2::render::WindowCanvas;
|
||||
|
||||
use crate::texture::animated::AnimatedTexture;
|
||||
use crate::texture::sprite::AtlasTile;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlinkingTexture {
|
||||
pub animation: AnimatedTexture,
|
||||
pub on_ticks: u32,
|
||||
pub off_ticks: u32,
|
||||
pub ticker: u32,
|
||||
pub visible: bool,
|
||||
tile: AtlasTile,
|
||||
blink_duration: f32,
|
||||
time_bank: f32,
|
||||
is_on: bool,
|
||||
}
|
||||
|
||||
impl BlinkingTexture {
|
||||
pub fn new(animation: AnimatedTexture, on_ticks: u32, off_ticks: u32) -> Self {
|
||||
BlinkingTexture {
|
||||
animation,
|
||||
on_ticks,
|
||||
off_ticks,
|
||||
ticker: 0,
|
||||
visible: true,
|
||||
pub fn new(tile: AtlasTile, blink_duration: f32) -> Self {
|
||||
Self {
|
||||
tile,
|
||||
blink_duration,
|
||||
time_bank: 0.0,
|
||||
is_on: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the blinking state by one tick.
|
||||
pub fn tick(&mut self) {
|
||||
self.animation.tick();
|
||||
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 tick(&mut self, dt: f32) {
|
||||
self.time_bank += dt;
|
||||
if self.time_bank >= self.blink_duration {
|
||||
self.time_bank -= self.blink_duration;
|
||||
self.is_on = !self.is_on;
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the blinking texture.
|
||||
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||
if self.visible {
|
||||
self.animation.render(canvas, dest)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
pub fn is_on(&self) -> bool {
|
||||
self.is_on
|
||||
}
|
||||
|
||||
pub fn tile(&self) -> &AtlasTile {
|
||||
&self.tile
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,57 @@
|
||||
//! A texture that changes based on the direction of an entity.
|
||||
use crate::entity::direction::Direction;
|
||||
use crate::texture::sprite::AtlasTile;
|
||||
use anyhow::Result;
|
||||
use sdl2::render::WindowCanvas;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::{Canvas, RenderTarget};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::entity::direction::Direction;
|
||||
use crate::texture::animated::AnimatedTexture;
|
||||
use crate::texture::sprite::SpriteAtlas;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirectionalAnimatedTexture {
|
||||
pub up: Vec<AtlasTile>,
|
||||
pub down: Vec<AtlasTile>,
|
||||
pub left: Vec<AtlasTile>,
|
||||
pub right: Vec<AtlasTile>,
|
||||
pub ticker: u32,
|
||||
pub ticks_per_frame: u32,
|
||||
textures: HashMap<Direction, AnimatedTexture>,
|
||||
stopped_textures: HashMap<Direction, AnimatedTexture>,
|
||||
}
|
||||
|
||||
impl DirectionalAnimatedTexture {
|
||||
pub fn new(
|
||||
up: Vec<AtlasTile>,
|
||||
down: Vec<AtlasTile>,
|
||||
left: Vec<AtlasTile>,
|
||||
right: Vec<AtlasTile>,
|
||||
ticks_per_frame: u32,
|
||||
) -> Self {
|
||||
pub fn new(textures: HashMap<Direction, AnimatedTexture>, stopped_textures: HashMap<Direction, AnimatedTexture>) -> Self {
|
||||
Self {
|
||||
up,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
ticker: 0,
|
||||
ticks_per_frame,
|
||||
textures,
|
||||
stopped_textures,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
self.ticker += 1;
|
||||
pub fn tick(&mut self, dt: f32) {
|
||||
for texture in self.textures.values_mut() {
|
||||
texture.tick(dt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> {
|
||||
let frames = match direction {
|
||||
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 = &mut frames[frame_index];
|
||||
|
||||
tile.render(canvas, dest)
|
||||
pub fn render<T: RenderTarget>(
|
||||
&self,
|
||||
canvas: &mut Canvas<T>,
|
||||
atlas: &mut SpriteAtlas,
|
||||
dest: Rect,
|
||||
direction: Direction,
|
||||
) -> Result<()> {
|
||||
if let Some(texture) = self.textures.get(&direction) {
|
||||
texture.render(canvas, atlas, dest)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_stopped(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> {
|
||||
let frames = match direction {
|
||||
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 = &mut frames[1];
|
||||
|
||||
tile.render(canvas, dest)
|
||||
pub fn render_stopped<T: RenderTarget>(
|
||||
&self,
|
||||
canvas: &mut Canvas<T>,
|
||||
atlas: &mut SpriteAtlas,
|
||||
dest: Rect,
|
||||
direction: Direction,
|
||||
) -> Result<()> {
|
||||
if let Some(texture) = self.stopped_textures.get(&direction) {
|
||||
texture.render(canvas, atlas, dest)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||
|
||||
pub mod animated;
|
||||
pub mod blinking;
|
||||
pub mod directional;
|
||||
pub mod sprite;
|
||||
pub mod text;
|
||||
|
||||
pub fn get_atlas_tile(atlas: &Rc<RefCell<SpriteAtlas>>, name: &str) -> AtlasTile {
|
||||
SpriteAtlas::get_tile(atlas, name).unwrap_or_else(|| panic!("Could not find tile {name}"))
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ 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;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct AtlasMapper {
|
||||
@@ -21,26 +19,28 @@ pub struct MapperFrame {
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtlasTile {
|
||||
pub atlas: Rc<RefCell<SpriteAtlas>>,
|
||||
pub pos: U16Vec2,
|
||||
pub size: U16Vec2,
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
impl AtlasTile {
|
||||
pub fn render<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, 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<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, atlas: &mut SpriteAtlas, dest: Rect) -> Result<()> {
|
||||
let color = self.color.unwrap_or(atlas.default_color.unwrap_or(Color::WHITE));
|
||||
self.render_with_color(canvas, atlas, dest, color)
|
||||
}
|
||||
|
||||
pub fn render_with_color<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, dest: Rect, color: Color) -> Result<()> {
|
||||
pub fn render_with_color<C: RenderTarget>(
|
||||
&mut self,
|
||||
canvas: &mut Canvas<C>,
|
||||
atlas: &mut SpriteAtlas,
|
||||
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);
|
||||
|
||||
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);
|
||||
@@ -68,10 +68,8 @@ impl SpriteAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tile(atlas: &Rc<RefCell<SpriteAtlas>>, name: &str) -> Option<AtlasTile> {
|
||||
let atlas_ref = atlas.borrow();
|
||||
atlas_ref.tiles.get(name).map(|frame| AtlasTile {
|
||||
atlas: Rc::clone(atlas),
|
||||
pub fn get_tile(&self, name: &str) -> Option<AtlasTile> {
|
||||
self.tiles.get(name).map(|frame| AtlasTile {
|
||||
pos: U16Vec2::new(frame.x, frame.y),
|
||||
size: U16Vec2::new(frame.width, frame.height),
|
||||
color: None,
|
||||
@@ -87,6 +85,6 @@ impl SpriteAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn texture_to_static<'a>(texture: Texture<'a>) -> Texture<'static> {
|
||||
pub unsafe fn texture_to_static(texture: Texture) -> Texture<'static> {
|
||||
std::mem::transmute(texture)
|
||||
}
|
||||
|
||||
@@ -50,38 +50,34 @@ use anyhow::Result;
|
||||
use glam::UVec2;
|
||||
|
||||
use sdl2::render::{Canvas, RenderTarget};
|
||||
use std::cell::RefCell;
|
||||
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<RefCell<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<RefCell<SpriteAtlas>>, scale: f32) -> Self {
|
||||
pub fn new(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> {
|
||||
fn get_char_tile(&mut self, atlas: &SpriteAtlas, c: char) -> Option<AtlasTile> {
|
||||
if let Some(tile) = self.char_map.get(&c) {
|
||||
return Some(tile.clone());
|
||||
return Some(*tile);
|
||||
}
|
||||
|
||||
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());
|
||||
let tile = atlas.get_tile(&tile_name)?;
|
||||
self.char_map.insert(c, tile);
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
@@ -89,9 +85,7 @@ impl TextTexture {
|
||||
fn char_to_tile_name(&self, c: char) -> Option<String> {
|
||||
let name = match c {
|
||||
// Letters A-Z
|
||||
'A'..='Z' => format!("text/{c}.png"),
|
||||
// Numbers 0-9
|
||||
'0'..='9' => format!("text/{c}.png"),
|
||||
'A'..='Z' | '0'..='9' => format!("text/{c}.png"),
|
||||
// Special characters
|
||||
'!' => "text/!.png".to_string(),
|
||||
'-' => "text/-.png".to_string(),
|
||||
@@ -108,15 +102,21 @@ impl TextTexture {
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
pub fn render<C: RenderTarget>(
|
||||
&mut self,
|
||||
canvas: &mut Canvas<C>,
|
||||
atlas: &mut SpriteAtlas,
|
||||
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(mut tile) = self.get_char_tile(c) {
|
||||
if let Some(mut tile) = self.get_char_tile(atlas, 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)?;
|
||||
tile.render(canvas, atlas, dest)?;
|
||||
}
|
||||
// Always advance x_offset for all characters (including spaces)
|
||||
x_offset += char_width;
|
||||
@@ -136,7 +136,7 @@ impl TextTexture {
|
||||
}
|
||||
|
||||
/// Calculates the width of a string in pixels at the current scale.
|
||||
pub fn text_width(&mut self, text: &str) -> u32 {
|
||||
pub fn text_width(&self, text: &str) -> u32 {
|
||||
let char_width = (8.0 * self.scale) as u32;
|
||||
let mut width = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user