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:
parent
d0a16f9a71
commit
31bc8c7614
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue