From 31bc8c76146e863f0715ca6bbbb0d5f015513dad Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Tue, 8 Oct 2019 19:54:57 -0700 Subject: [PATCH] Refactor animations (#5) - `SpriteRef` impls `Animation`, `Sprite` does not - Animation duration and delta time now lives on the struct that handles the animation "state machine" - Tweak the player animation to 100ms frame duration (was 50ms) --- simple-invaders/src/lib.rs | 46 +++++--------- simple-invaders/src/sprites.rs | 110 ++++++++++++++++----------------- 2 files changed, 71 insertions(+), 85 deletions(-) diff --git a/simple-invaders/src/lib.rs b/simple-invaders/src/lib.rs index 1729c0e..03f319c 100644 --- a/simple-invaders/src/lib.rs +++ b/simple-invaders/src/lib.rs @@ -8,7 +8,7 @@ use std::time::Duration; pub use controls::{Controls, Direction}; use loader::{load_assets, Assets}; -use sprites::{blit, Drawable, Frame, Sprite, SpriteRef}; +use sprites::{blit, Animation, Frame, Sprite, SpriteRef}; mod controls; mod loader; @@ -35,8 +35,7 @@ pub struct World { score: u32, assets: Assets, screen: Vec, - timing: Duration, - frame_count: usize, + dt: Duration, } /// A tiny position vector @@ -124,7 +123,7 @@ impl World { bounds: Bounds::default(), }; let player = Player { - sprite: SpriteRef::new(&assets, Player1), + sprite: SpriteRef::new(&assets, Player1, Duration::from_millis(100)), pos: Point::new(80, 216), last_update: 0, }; @@ -148,8 +147,7 @@ impl World { score: 0, assets, screen, - timing: Duration::default(), - frame_count: 0, + dt: Duration::default(), } } @@ -163,17 +161,16 @@ impl World { let one_frame = Duration::new(0, 16_666_667); // Advance the timer by the delta time - self.timing += dt; + self.dt += dt; // Step the invaders one by one - while self.timing >= one_frame { - self.frame_count += 1; - self.timing -= one_frame; + while self.dt >= one_frame { + self.dt -= one_frame; self.step_invaders(); } // Handle player movement and animation - self.step_player(controls); + self.step_player(controls, dt); // TODO: Handle lasers and bullets // Movements can be multiplied by the delta-time frame count, instead of looping @@ -216,34 +213,25 @@ impl World { // TODO: Move the invader // Animate the invader - invader.sprite.animate(&self.assets); + invader.sprite.step_frame(&self.assets); } - fn step_player(&mut self, controls: &Controls) { - let animate_player = match controls.direction { + fn step_player(&mut self, controls: &Controls, dt: Duration) { + match controls.direction { Direction::Left => { if self.player.pos.x > 0 { self.player.pos.x -= 1; - true - } else { - false + self.player.sprite.animate(&self.assets, dt); } } Direction::Right => { if self.player.pos.x < 224 - 16 { self.player.pos.x += 1; - true - } else { - false + self.player.sprite.animate(&self.assets, dt); } } - _ => false, - }; - - if animate_player && self.frame_count - self.player.last_update >= 3 { - self.player.last_update = self.frame_count; - self.player.sprite.animate(&self.assets); + _ => (), } } @@ -331,7 +319,7 @@ fn make_invader_grid(assets: &Assets) -> Vec>> { (0..COLS) .map(|x| { Some(Invader { - sprite: SpriteRef::new(assets, Blipjoy1), + sprite: SpriteRef::new(assets, Blipjoy1, Duration::default()), pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID, score: 10, }) @@ -342,7 +330,7 @@ fn make_invader_grid(assets: &Assets) -> Vec>> { (0..COLS) .map(|x| { Some(Invader { - sprite: SpriteRef::new(assets, Ferris1), + sprite: SpriteRef::new(assets, Ferris1, Duration::default()), pos: START + FERRIS_OFFSET + Point::new(x, y) * GRID, score: 10, }) @@ -353,7 +341,7 @@ fn make_invader_grid(assets: &Assets) -> Vec>> { (0..COLS) .map(|x| { Some(Invader { - sprite: SpriteRef::new(assets, Cthulhu1), + sprite: SpriteRef::new(assets, Cthulhu1, Duration::default()), pos: START + CTHULHU_OFFSET + Point::new(x, y) * GRID, score: 10, }) diff --git a/simple-invaders/src/sprites.rs b/simple-invaders/src/sprites.rs index f91428b..18a318d 100644 --- a/simple-invaders/src/sprites.rs +++ b/simple-invaders/src/sprites.rs @@ -1,4 +1,5 @@ use std::rc::Rc; +use std::time::Duration; use crate::loader::Assets; use crate::{Point, SCREEN_WIDTH}; @@ -26,13 +27,15 @@ pub(crate) enum Frame { // Laser2, } -/// Sprites can be drawn and animated. +/// Sprites can be drawn and procedurally generated. +/// +/// A `Sprite` owns its pixel data, and cannot be animated. Use a `SpriteRef` if you need +/// animations. #[derive(Debug)] pub(crate) struct Sprite { width: usize, height: usize, pixels: Vec, - frame: Frame, } /// SpriteRefs can be drawn and animated. @@ -44,6 +47,8 @@ pub(crate) struct SpriteRef { height: usize, pixels: Rc>, frame: Frame, + duration: Duration, + dt: Duration, } /// Drawables can be blitted to the pixel buffer and animated. @@ -51,15 +56,43 @@ pub(crate) trait Drawable { fn width(&self) -> usize; fn height(&self) -> usize; fn pixels(&self) -> &[u8]; - fn update_pixels(&mut self, pixels: Rc>); - fn frame(&self) -> &Frame; - fn update_frame(&mut self, frame: Frame); +} - fn animate(&mut self, assets: &Assets) { +pub(crate) trait Animation { + fn animate(&mut self, assets: &Assets, dt: Duration); +} + +impl Sprite { + pub(crate) fn new(assets: &Assets, frame: Frame) -> Sprite { + let (width, height, pixels) = assets.sprites().get(&frame).unwrap(); + + Sprite { + width: *width, + height: *height, + pixels: pixels.to_vec(), + } + } +} + +impl SpriteRef { + pub(crate) fn new(assets: &Assets, frame: Frame, duration: Duration) -> SpriteRef { + let (width, height, pixels) = assets.sprites().get(&frame).unwrap(); + + SpriteRef { + width: *width, + height: *height, + pixels: pixels.clone(), + frame, + duration, + dt: Duration::default(), + } + } + + pub(crate) fn step_frame(&mut self, assets: &Assets) { use Frame::*; let assets = assets.sprites(); - let (pixels, frame) = match self.frame() { + let (pixels, frame) = match self.frame { Blipjoy1 => (assets.get(&Blipjoy2).unwrap().2.clone(), Blipjoy2), Blipjoy2 => (assets.get(&Blipjoy1).unwrap().2.clone(), Blipjoy1), @@ -78,34 +111,8 @@ pub(crate) trait Drawable { // Laser2 => (assets.get(&Laser1).unwrap().2.clone(), Laser1), }; - self.update_pixels(pixels); - self.update_frame(frame); - } -} - -impl Sprite { - pub(crate) fn new(assets: &Assets, frame: Frame) -> Sprite { - let (width, height, pixels) = assets.sprites().get(&frame).unwrap(); - - Sprite { - width: *width, - height: *height, - pixels: pixels.to_vec(), - frame, - } - } -} - -impl SpriteRef { - pub(crate) fn new(assets: &Assets, frame: Frame) -> SpriteRef { - let (width, height, pixels) = assets.sprites().get(&frame).unwrap(); - - SpriteRef { - width: *width, - height: *height, - pixels: pixels.clone(), - frame, - } + self.pixels = pixels; + self.frame = frame; } } @@ -121,18 +128,6 @@ impl Drawable for Sprite { fn pixels(&self) -> &[u8] { &self.pixels } - - fn update_pixels(&mut self, pixels: Rc>) { - self.pixels = pixels.to_vec(); - } - - fn frame(&self) -> &Frame { - &self.frame - } - - fn update_frame(&mut self, frame: Frame) { - self.frame = frame; - } } impl Drawable for SpriteRef { @@ -147,17 +142,20 @@ impl Drawable for SpriteRef { fn pixels(&self) -> &[u8] { &self.pixels } +} - fn update_pixels(&mut self, pixels: Rc>) { - self.pixels = pixels; - } +impl Animation for SpriteRef { + fn animate(&mut self, assets: &Assets, dt: Duration) { + if self.duration.subsec_nanos() == 0 { + self.step_frame(assets); + } else { + self.dt += dt; - fn frame(&self) -> &Frame { - &self.frame - } - - fn update_frame(&mut self, frame: Frame) { - self.frame = frame; + while self.dt >= self.duration { + self.dt -= self.duration; + self.step_frame(assets); + } + } } }