mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-08 02:07:56 -06:00
feat: atlas decoding & frame acquisition
This commit is contained in:
46
Cargo.lock
generated
46
Cargo.lock
generated
@@ -108,6 +108,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -184,6 +190,8 @@ dependencies = [
|
||||
"pathfinding",
|
||||
"rand",
|
||||
"sdl2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spin_sleep",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
@@ -304,6 +312,12 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.38.0"
|
||||
@@ -335,6 +349,38 @@ version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
|
||||
@@ -18,6 +18,8 @@ once_cell = "1.21.3"
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
glam = "0.30.4"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.141"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
//! This module provides a simple animation and atlas system for textures.
|
||||
use anyhow::Result;
|
||||
use glam::IVec2;
|
||||
use sdl2::{
|
||||
render::{Canvas, Texture},
|
||||
video::Window,
|
||||
};
|
||||
use sdl2::render::WindowCanvas;
|
||||
|
||||
use crate::entity::direction::Direction;
|
||||
use crate::texture::atlas::AtlasTexture;
|
||||
use crate::texture::FrameDrawn;
|
||||
use crate::texture::sprite::AtlasTile;
|
||||
|
||||
/// An animated texture using a texture atlas.
|
||||
pub struct AnimatedAtlasTexture {
|
||||
pub atlas: AtlasTexture,
|
||||
#[derive(Clone)]
|
||||
pub struct AnimatedTexture {
|
||||
pub frames: Vec<AtlasTile>,
|
||||
pub ticks_per_frame: u32,
|
||||
pub ticker: u32,
|
||||
pub reversed: bool,
|
||||
pub paused: bool,
|
||||
}
|
||||
|
||||
impl AnimatedAtlasTexture {
|
||||
pub fn new(
|
||||
texture: Texture<'static>,
|
||||
ticks_per_frame: u32,
|
||||
frame_count: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
offset: Option<IVec2>,
|
||||
) -> Self {
|
||||
AnimatedAtlasTexture {
|
||||
atlas: AtlasTexture::new(texture, frame_count, width, height, offset),
|
||||
impl AnimatedTexture {
|
||||
pub fn new(frames: Vec<AtlasTile>, ticks_per_frame: u32) -> Self {
|
||||
AnimatedTexture {
|
||||
frames,
|
||||
ticks_per_frame,
|
||||
ticker: 0,
|
||||
reversed: false,
|
||||
@@ -36,38 +26,25 @@ impl AnimatedAtlasTexture {
|
||||
}
|
||||
}
|
||||
|
||||
fn current_frame(&self) -> u32 {
|
||||
self.ticker / self.ticks_per_frame
|
||||
}
|
||||
|
||||
/// Advances the animation by one tick, unless paused.
|
||||
pub fn tick(&mut self) {
|
||||
if self.paused {
|
||||
if self.paused || self.ticks_per_frame == 0 {
|
||||
return;
|
||||
}
|
||||
if self.reversed {
|
||||
if self.ticker > 0 {
|
||||
self.ticker -= 1;
|
||||
}
|
||||
if self.ticker == 0 {
|
||||
self.reversed = !self.reversed;
|
||||
}
|
||||
} else {
|
||||
|
||||
self.ticker += 1;
|
||||
if self.ticker + 1 == self.ticks_per_frame * self.atlas.frame_count {
|
||||
self.reversed = !self.reversed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) {
|
||||
self.atlas.set_color_modulation(r, g, b);
|
||||
pub fn current_tile(&self) -> &AtlasTile {
|
||||
if self.ticks_per_frame == 0 {
|
||||
return &self.frames[0];
|
||||
}
|
||||
let frame_index = (self.ticker / self.ticks_per_frame) as usize % self.frames.len();
|
||||
&self.frames[frame_index]
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameDrawn for AnimatedAtlasTexture {
|
||||
fn render(&self, canvas: &mut Canvas<Window>, position: IVec2, direction: Direction, frame: Option<u32>) {
|
||||
let frame = frame.unwrap_or_else(|| self.current_frame());
|
||||
self.atlas.render(canvas, position, direction, Some(frame));
|
||||
pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||
let tile = self.current_tile();
|
||||
tile.render(canvas, dest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
use glam::IVec2;
|
||||
use sdl2::{
|
||||
rect::Rect,
|
||||
render::{Canvas, Texture},
|
||||
video::Window,
|
||||
};
|
||||
|
||||
use crate::{entity::direction::Direction, texture::FrameDrawn};
|
||||
|
||||
/// Unsafely converts a Texture with any lifetime to a 'static lifetime.
|
||||
/// Only use this if you guarantee the renderer/context will never be dropped!
|
||||
pub unsafe fn texture_to_static<'a>(texture: Texture<'a>) -> Texture<'static> {
|
||||
std::mem::transmute::<Texture<'a>, Texture<'static>>(texture)
|
||||
}
|
||||
|
||||
/// A texture atlas abstraction for static (non-animated) rendering.
|
||||
pub struct AtlasTexture {
|
||||
pub raw_texture: Texture<'static>,
|
||||
pub offset: IVec2,
|
||||
pub frame_count: u32,
|
||||
pub frame_width: u32,
|
||||
pub frame_height: u32,
|
||||
}
|
||||
|
||||
impl AtlasTexture {
|
||||
pub fn new(texture: Texture<'static>, frame_count: u32, frame_width: u32, frame_height: u32, offset: Option<IVec2>) -> Self {
|
||||
AtlasTexture {
|
||||
raw_texture: texture,
|
||||
frame_count,
|
||||
frame_width,
|
||||
frame_height,
|
||||
offset: offset.unwrap_or(IVec2::new(0, 0)).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_frame_rect(&self, frame: u32) -> Option<Rect> {
|
||||
if frame >= self.frame_count {
|
||||
return None;
|
||||
}
|
||||
Some(Rect::new(
|
||||
frame as i32 * self.frame_width as i32,
|
||||
0,
|
||||
self.frame_width,
|
||||
self.frame_height,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) {
|
||||
self.raw_texture.set_color_mod(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameDrawn for AtlasTexture {
|
||||
fn render(&self, canvas: &mut Canvas<Window>, position: IVec2, direction: Direction, frame: Option<u32>) {
|
||||
let texture_source_frame_rect = self.get_frame_rect(frame.unwrap_or(0));
|
||||
let canvas_destination_rect = Rect::new(
|
||||
position.x + self.offset.x,
|
||||
position.y + self.offset.y,
|
||||
self.frame_width,
|
||||
self.frame_height,
|
||||
);
|
||||
canvas
|
||||
.copy_ex(
|
||||
&self.raw_texture,
|
||||
texture_source_frame_rect,
|
||||
Some(canvas_destination_rect),
|
||||
direction.angle(),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.expect("Could not render texture on canvas");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
//! A texture that blinks on/off for a specified number of ticks.
|
||||
use anyhow::Result;
|
||||
use glam::IVec2;
|
||||
use sdl2::{
|
||||
render::{Canvas, Texture},
|
||||
video::Window,
|
||||
};
|
||||
use sdl2::render::WindowCanvas;
|
||||
|
||||
use crate::texture::atlas::AtlasTexture;
|
||||
use crate::texture::FrameDrawn;
|
||||
use crate::{entity::direction::Direction, texture::atlas::texture_to_static};
|
||||
use crate::texture::animated::AnimatedTexture;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlinkingTexture {
|
||||
pub atlas: AtlasTexture,
|
||||
pub animation: AnimatedTexture,
|
||||
pub on_ticks: u32,
|
||||
pub off_ticks: u32,
|
||||
pub ticker: u32,
|
||||
@@ -18,17 +15,9 @@ pub struct BlinkingTexture {
|
||||
}
|
||||
|
||||
impl BlinkingTexture {
|
||||
pub fn new(
|
||||
texture: Texture<'_>,
|
||||
frame_count: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
offset: Option<IVec2>,
|
||||
on_ticks: u32,
|
||||
off_ticks: u32,
|
||||
) -> Self {
|
||||
pub fn new(animation: AnimatedTexture, on_ticks: u32, off_ticks: u32) -> Self {
|
||||
BlinkingTexture {
|
||||
atlas: AtlasTexture::new(unsafe { texture_to_static(texture) }, frame_count, width, height, offset),
|
||||
animation,
|
||||
on_ticks,
|
||||
off_ticks,
|
||||
ticker: 0,
|
||||
@@ -38,6 +27,7 @@ impl BlinkingTexture {
|
||||
|
||||
/// 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;
|
||||
@@ -48,15 +38,12 @@ impl BlinkingTexture {
|
||||
}
|
||||
}
|
||||
|
||||
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>) {
|
||||
/// Renders the blinking texture.
|
||||
pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||
if self.visible {
|
||||
self.atlas.render(canvas, position, direction, frame);
|
||||
self.animation.render(canvas, dest)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
src/texture/directional.rs
Normal file
52
src/texture/directional.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! 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 glam::IVec2;
|
||||
use sdl2::render::WindowCanvas;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
impl DirectionalAnimatedTexture {
|
||||
pub fn new(
|
||||
up: Vec<AtlasTile>,
|
||||
down: Vec<AtlasTile>,
|
||||
left: Vec<AtlasTile>,
|
||||
right: Vec<AtlasTile>,
|
||||
ticks_per_frame: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
up,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
ticker: 0,
|
||||
ticks_per_frame,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
self.ticker += 1;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let frame_index = (self.ticker / self.ticks_per_frame) as usize % frames.len();
|
||||
let tile = &frames[frame_index];
|
||||
|
||||
tile.render(canvas, dest)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
use glam::IVec2;
|
||||
use sdl2::{render::Canvas, video::Window};
|
||||
|
||||
use crate::entity::direction::Direction;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Trait for drawable atlas-based textures
|
||||
pub trait FrameDrawn {
|
||||
fn render(&self, canvas: &mut Canvas<Window>, position: IVec2, direction: Direction, frame: Option<u32>);
|
||||
}
|
||||
use crate::entity::direction::Direction;
|
||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||
|
||||
pub mod animated;
|
||||
pub mod atlas;
|
||||
pub mod blinking;
|
||||
pub mod directional;
|
||||
pub mod sprite;
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
61
src/texture/sprite.rs
Normal file
61
src/texture/sprite.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use anyhow::Result;
|
||||
use glam::U16Vec2;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::{Texture, WindowCanvas};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct AtlasMapper {
|
||||
pub frames: HashMap<String, MapperFrame>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
pub struct MapperFrame {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AtlasTile {
|
||||
pub atlas: Rc<SpriteAtlas>,
|
||||
pub pos: U16Vec2,
|
||||
pub size: U16Vec2,
|
||||
}
|
||||
|
||||
impl AtlasTile {
|
||||
pub fn render(&self, canvas: &mut WindowCanvas, 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(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpriteAtlas {
|
||||
texture: Texture<'static>,
|
||||
tiles: HashMap<String, MapperFrame>,
|
||||
}
|
||||
|
||||
impl SpriteAtlas {
|
||||
pub fn new(texture: Texture<'static>, mapper: AtlasMapper) -> Self {
|
||||
Self {
|
||||
texture,
|
||||
tiles: mapper.frames,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tile(atlas: &Rc<SpriteAtlas>, name: &str) -> Option<AtlasTile> {
|
||||
atlas.tiles.get(name).map(|frame| AtlasTile {
|
||||
atlas: atlas.clone(),
|
||||
pos: U16Vec2::new(frame.x, frame.y),
|
||||
size: U16Vec2::new(frame.width, frame.height),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn texture_to_static<'a>(texture: Texture<'a>) -> Texture<'static> {
|
||||
std::mem::transmute(texture)
|
||||
}
|
||||
Reference in New Issue
Block a user