mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 15:15:48 -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",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -184,6 +190,8 @@ dependencies = [
|
|||||||
"pathfinding",
|
"pathfinding",
|
||||||
"rand",
|
"rand",
|
||||||
"sdl2",
|
"sdl2",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"spin_sleep",
|
"spin_sleep",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -304,6 +312,12 @@ version = "2.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sdl2"
|
name = "sdl2"
|
||||||
version = "0.38.0"
|
version = "0.38.0"
|
||||||
@@ -335,6 +349,38 @@ version = "1.0.26"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
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]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ once_cell = "1.21.3"
|
|||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
glam = "0.30.4"
|
glam = "0.30.4"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.141"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -1,34 +1,24 @@
|
|||||||
//! This module provides a simple animation and atlas system for textures.
|
//! This module provides a simple animation and atlas system for textures.
|
||||||
|
use anyhow::Result;
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use sdl2::{
|
use sdl2::render::WindowCanvas;
|
||||||
render::{Canvas, Texture},
|
|
||||||
video::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::entity::direction::Direction;
|
use crate::texture::sprite::AtlasTile;
|
||||||
use crate::texture::atlas::AtlasTexture;
|
|
||||||
use crate::texture::FrameDrawn;
|
|
||||||
|
|
||||||
/// An animated texture using a texture atlas.
|
/// An animated texture using a texture atlas.
|
||||||
pub struct AnimatedAtlasTexture {
|
#[derive(Clone)]
|
||||||
pub atlas: AtlasTexture,
|
pub struct AnimatedTexture {
|
||||||
|
pub frames: Vec<AtlasTile>,
|
||||||
pub ticks_per_frame: u32,
|
pub ticks_per_frame: u32,
|
||||||
pub ticker: u32,
|
pub ticker: u32,
|
||||||
pub reversed: bool,
|
pub reversed: bool,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatedAtlasTexture {
|
impl AnimatedTexture {
|
||||||
pub fn new(
|
pub fn new(frames: Vec<AtlasTile>, ticks_per_frame: u32) -> Self {
|
||||||
texture: Texture<'static>,
|
AnimatedTexture {
|
||||||
ticks_per_frame: u32,
|
frames,
|
||||||
frame_count: u32,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
offset: Option<IVec2>,
|
|
||||||
) -> Self {
|
|
||||||
AnimatedAtlasTexture {
|
|
||||||
atlas: AtlasTexture::new(texture, frame_count, width, height, offset),
|
|
||||||
ticks_per_frame,
|
ticks_per_frame,
|
||||||
ticker: 0,
|
ticker: 0,
|
||||||
reversed: false,
|
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.
|
/// Advances the animation by one tick, unless paused.
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
if self.paused {
|
if self.paused || self.ticks_per_frame == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if self.reversed {
|
|
||||||
if self.ticker > 0 {
|
self.ticker += 1;
|
||||||
self.ticker -= 1;
|
}
|
||||||
}
|
|
||||||
if self.ticker == 0 {
|
pub fn current_tile(&self) -> &AtlasTile {
|
||||||
self.reversed = !self.reversed;
|
if self.ticks_per_frame == 0 {
|
||||||
}
|
return &self.frames[0];
|
||||||
} else {
|
|
||||||
self.ticker += 1;
|
|
||||||
if self.ticker + 1 == self.ticks_per_frame * self.atlas.frame_count {
|
|
||||||
self.reversed = !self.reversed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let frame_index = (self.ticker / self.ticks_per_frame) as usize % self.frames.len();
|
||||||
|
&self.frames[frame_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) {
|
pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||||
self.atlas.set_color_modulation(r, g, b);
|
let tile = self.current_tile();
|
||||||
}
|
tile.render(canvas, dest)
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
//! A texture that blinks on/off for a specified number of ticks.
|
||||||
|
use anyhow::Result;
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use sdl2::{
|
use sdl2::render::WindowCanvas;
|
||||||
render::{Canvas, Texture},
|
|
||||||
video::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::texture::atlas::AtlasTexture;
|
use crate::texture::animated::AnimatedTexture;
|
||||||
use crate::texture::FrameDrawn;
|
|
||||||
use crate::{entity::direction::Direction, texture::atlas::texture_to_static};
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BlinkingTexture {
|
pub struct BlinkingTexture {
|
||||||
pub atlas: AtlasTexture,
|
pub animation: AnimatedTexture,
|
||||||
pub on_ticks: u32,
|
pub on_ticks: u32,
|
||||||
pub off_ticks: u32,
|
pub off_ticks: u32,
|
||||||
pub ticker: u32,
|
pub ticker: u32,
|
||||||
@@ -18,17 +15,9 @@ pub struct BlinkingTexture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlinkingTexture {
|
impl BlinkingTexture {
|
||||||
pub fn new(
|
pub fn new(animation: AnimatedTexture, on_ticks: u32, off_ticks: u32) -> Self {
|
||||||
texture: Texture<'_>,
|
|
||||||
frame_count: u32,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
offset: Option<IVec2>,
|
|
||||||
on_ticks: u32,
|
|
||||||
off_ticks: u32,
|
|
||||||
) -> Self {
|
|
||||||
BlinkingTexture {
|
BlinkingTexture {
|
||||||
atlas: AtlasTexture::new(unsafe { texture_to_static(texture) }, frame_count, width, height, offset),
|
animation,
|
||||||
on_ticks,
|
on_ticks,
|
||||||
off_ticks,
|
off_ticks,
|
||||||
ticker: 0,
|
ticker: 0,
|
||||||
@@ -38,6 +27,7 @@ impl BlinkingTexture {
|
|||||||
|
|
||||||
/// Advances the blinking state by one tick.
|
/// Advances the blinking state by one tick.
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
|
self.animation.tick();
|
||||||
self.ticker += 1;
|
self.ticker += 1;
|
||||||
if self.visible && self.ticker >= self.on_ticks {
|
if self.visible && self.ticker >= self.on_ticks {
|
||||||
self.visible = false;
|
self.visible = false;
|
||||||
@@ -48,15 +38,12 @@ impl BlinkingTexture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_color_modulation(&mut self, r: u8, g: u8, b: u8) {
|
/// Renders the blinking texture.
|
||||||
self.atlas.set_color_modulation(r, g, b);
|
pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameDrawn for BlinkingTexture {
|
|
||||||
fn render(&self, canvas: &mut Canvas<Window>, position: IVec2, direction: Direction, frame: Option<u32>) {
|
|
||||||
if self.visible {
|
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 glam::IVec2;
|
||||||
use sdl2::{render::Canvas, video::Window};
|
use sdl2::{render::Canvas, video::Window};
|
||||||
|
|
||||||
use crate::entity::direction::Direction;
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// Trait for drawable atlas-based textures
|
use crate::entity::direction::Direction;
|
||||||
pub trait FrameDrawn {
|
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||||
fn render(&self, canvas: &mut Canvas<Window>, position: IVec2, direction: Direction, frame: Option<u32>);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod animated;
|
pub mod animated;
|
||||||
pub mod atlas;
|
|
||||||
pub mod blinking;
|
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