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};
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<u8>,
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<Vec<Option<Invader>>> {
(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<Vec<Option<Invader>>> {
(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<Vec<Option<Invader>>> {
(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,
})

View file

@ -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<u8>,
frame: Frame,
}
/// SpriteRefs can be drawn and animated.
@ -44,6 +47,8 @@ pub(crate) struct SpriteRef {
height: usize,
pixels: Rc<Vec<u8>>,
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<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::*;
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<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 {
@ -147,17 +142,20 @@ impl Drawable for SpriteRef {
fn pixels(&self) -> &[u8] {
&self.pixels
}
}
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>) {
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;
while self.dt >= self.duration {
self.dt -= self.duration;
self.step_frame(assets);
}
fn frame(&self) -> &Frame {
&self.frame
}
fn update_frame(&mut self, frame: Frame) {
self.frame = frame;
}
}