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)
This commit is contained in:
Jay Oster 2019-10-08 19:54:57 -07:00 committed by GitHub
parent d0a16f9a71
commit 31bc8c7614
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 85 deletions

View file

@ -8,7 +8,7 @@ use std::time::Duration;
pub use controls::{Controls, Direction}; pub use controls::{Controls, Direction};
use loader::{load_assets, Assets}; use loader::{load_assets, Assets};
use sprites::{blit, Drawable, Frame, Sprite, SpriteRef}; use sprites::{blit, Animation, Frame, Sprite, SpriteRef};
mod controls; mod controls;
mod loader; mod loader;
@ -35,8 +35,7 @@ pub struct World {
score: u32, score: u32,
assets: Assets, assets: Assets,
screen: Vec<u8>, screen: Vec<u8>,
timing: Duration, dt: Duration,
frame_count: usize,
} }
/// A tiny position vector /// A tiny position vector
@ -124,7 +123,7 @@ impl World {
bounds: Bounds::default(), bounds: Bounds::default(),
}; };
let player = Player { let player = Player {
sprite: SpriteRef::new(&assets, Player1), sprite: SpriteRef::new(&assets, Player1, Duration::from_millis(100)),
pos: Point::new(80, 216), pos: Point::new(80, 216),
last_update: 0, last_update: 0,
}; };
@ -148,8 +147,7 @@ impl World {
score: 0, score: 0,
assets, assets,
screen, screen,
timing: Duration::default(), dt: Duration::default(),
frame_count: 0,
} }
} }
@ -163,17 +161,16 @@ impl World {
let one_frame = Duration::new(0, 16_666_667); let one_frame = Duration::new(0, 16_666_667);
// Advance the timer by the delta time // Advance the timer by the delta time
self.timing += dt; self.dt += dt;
// Step the invaders one by one // Step the invaders one by one
while self.timing >= one_frame { while self.dt >= one_frame {
self.frame_count += 1; self.dt -= one_frame;
self.timing -= one_frame;
self.step_invaders(); self.step_invaders();
} }
// Handle player movement and animation // Handle player movement and animation
self.step_player(controls); self.step_player(controls, dt);
// TODO: Handle lasers and bullets // TODO: Handle lasers and bullets
// Movements can be multiplied by the delta-time frame count, instead of looping // Movements can be multiplied by the delta-time frame count, instead of looping
@ -216,34 +213,25 @@ impl World {
// TODO: Move the invader // TODO: Move the invader
// Animate the invader // Animate the invader
invader.sprite.animate(&self.assets); invader.sprite.step_frame(&self.assets);
} }
fn step_player(&mut self, controls: &Controls) { fn step_player(&mut self, controls: &Controls, dt: Duration) {
let animate_player = match controls.direction { match controls.direction {
Direction::Left => { Direction::Left => {
if self.player.pos.x > 0 { if self.player.pos.x > 0 {
self.player.pos.x -= 1; self.player.pos.x -= 1;
true self.player.sprite.animate(&self.assets, dt);
} else {
false
} }
} }
Direction::Right => { Direction::Right => {
if self.player.pos.x < 224 - 16 { if self.player.pos.x < 224 - 16 {
self.player.pos.x += 1; self.player.pos.x += 1;
true self.player.sprite.animate(&self.assets, dt);
} else {
false
} }
} }
_ => 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<Vec<Option<Invader>>> {
(0..COLS) (0..COLS)
.map(|x| { .map(|x| {
Some(Invader { Some(Invader {
sprite: SpriteRef::new(assets, Blipjoy1), sprite: SpriteRef::new(assets, Blipjoy1, Duration::default()),
pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID, pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID,
score: 10, score: 10,
}) })
@ -342,7 +330,7 @@ fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
(0..COLS) (0..COLS)
.map(|x| { .map(|x| {
Some(Invader { Some(Invader {
sprite: SpriteRef::new(assets, Ferris1), sprite: SpriteRef::new(assets, Ferris1, Duration::default()),
pos: START + FERRIS_OFFSET + Point::new(x, y) * GRID, pos: START + FERRIS_OFFSET + Point::new(x, y) * GRID,
score: 10, score: 10,
}) })
@ -353,7 +341,7 @@ fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
(0..COLS) (0..COLS)
.map(|x| { .map(|x| {
Some(Invader { Some(Invader {
sprite: SpriteRef::new(assets, Cthulhu1), sprite: SpriteRef::new(assets, Cthulhu1, Duration::default()),
pos: START + CTHULHU_OFFSET + Point::new(x, y) * GRID, pos: START + CTHULHU_OFFSET + Point::new(x, y) * GRID,
score: 10, score: 10,
}) })

View file

@ -1,4 +1,5 @@
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration;
use crate::loader::Assets; use crate::loader::Assets;
use crate::{Point, SCREEN_WIDTH}; use crate::{Point, SCREEN_WIDTH};
@ -26,13 +27,15 @@ pub(crate) enum Frame {
// Laser2, // 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)] #[derive(Debug)]
pub(crate) struct Sprite { pub(crate) struct Sprite {
width: usize, width: usize,
height: usize, height: usize,
pixels: Vec<u8>, pixels: Vec<u8>,
frame: Frame,
} }
/// SpriteRefs can be drawn and animated. /// SpriteRefs can be drawn and animated.
@ -44,6 +47,8 @@ pub(crate) struct SpriteRef {
height: usize, height: usize,
pixels: Rc<Vec<u8>>, pixels: Rc<Vec<u8>>,
frame: Frame, frame: Frame,
duration: Duration,
dt: Duration,
} }
/// Drawables can be blitted to the pixel buffer and animated. /// Drawables can be blitted to the pixel buffer and animated.
@ -51,15 +56,43 @@ pub(crate) trait Drawable {
fn width(&self) -> usize; fn width(&self) -> usize;
fn height(&self) -> usize; fn height(&self) -> usize;
fn pixels(&self) -> &[u8]; fn pixels(&self) -> &[u8];
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>); }
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::*; use Frame::*;
let assets = assets.sprites(); let assets = assets.sprites();
let (pixels, frame) = match self.frame() { let (pixels, frame) = match self.frame {
Blipjoy1 => (assets.get(&Blipjoy2).unwrap().2.clone(), Blipjoy2), Blipjoy1 => (assets.get(&Blipjoy2).unwrap().2.clone(), Blipjoy2),
Blipjoy2 => (assets.get(&Blipjoy1).unwrap().2.clone(), Blipjoy1), Blipjoy2 => (assets.get(&Blipjoy1).unwrap().2.clone(), Blipjoy1),
@ -78,34 +111,8 @@ pub(crate) trait Drawable {
// Laser2 => (assets.get(&Laser1).unwrap().2.clone(), Laser1), // Laser2 => (assets.get(&Laser1).unwrap().2.clone(), Laser1),
}; };
self.update_pixels(pixels); self.pixels = pixels;
self.update_frame(frame); self.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,
}
} }
} }
@ -121,18 +128,6 @@ impl Drawable for Sprite {
fn pixels(&self) -> &[u8] { fn pixels(&self) -> &[u8] {
&self.pixels &self.pixels
} }
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>) {
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 { impl Drawable for SpriteRef {
@ -147,17 +142,20 @@ impl Drawable for SpriteRef {
fn pixels(&self) -> &[u8] { fn pixels(&self) -> &[u8] {
&self.pixels &self.pixels
} }
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>) {
self.pixels = pixels;
} }
fn frame(&self) -> &Frame { impl Animation for SpriteRef {
&self.frame fn animate(&mut self, assets: &Assets, dt: Duration) {
} if self.duration.subsec_nanos() == 0 {
self.step_frame(assets);
} else {
self.dt += dt;
fn update_frame(&mut self, frame: Frame) { while self.dt >= self.duration {
self.frame = frame; self.dt -= self.duration;
self.step_frame(assets);
}
}
} }
} }