Refactor SpriteCache
and animation (#3)
* Refactor SpriteCache and animations - Animation frames are now enumerated - Renamed the `Sprites` trait into the more apt `Drawable` - Moved animation to a default impl on `Drawable` - Added the Cthulhu invader - Clarify that `World::step_invaders()` is only needed for handling the invader grid - Renamed `Cannon` to `Bullet` * Improve readability of some `SpriteCache` uses.
This commit is contained in:
parent
e4249ed766
commit
d7fc1eca3c
BIN
simple-invaders/src/assets/cthulhu1.pcx
Normal file
BIN
simple-invaders/src/assets/cthulhu1.pcx
Normal file
Binary file not shown.
BIN
simple-invaders/src/assets/cthulhu2.pcx
Normal file
BIN
simple-invaders/src/assets/cthulhu2.pcx
Normal file
Binary file not shown.
|
@ -8,7 +8,7 @@ use std::time::Duration;
|
|||
|
||||
pub use controls::{Controls, Direction};
|
||||
use loader::{load_assets, Assets};
|
||||
use sprites::{blit, Sprite, SpriteRef, Sprites};
|
||||
use sprites::{blit, Drawable, Frame, Sprite, SpriteRef};
|
||||
|
||||
mod controls;
|
||||
mod loader;
|
||||
|
@ -31,7 +31,7 @@ pub struct World {
|
|||
lasers: Vec<Laser>,
|
||||
shields: Vec<Shield>,
|
||||
player: Player,
|
||||
cannons: Vec<Cannon>,
|
||||
bullets: Vec<Bullet>,
|
||||
score: u32,
|
||||
assets: Assets,
|
||||
screen: Vec<u8>,
|
||||
|
@ -103,7 +103,7 @@ struct Laser {
|
|||
|
||||
/// The cannon entity.
|
||||
#[derive(Debug)]
|
||||
struct Cannon {
|
||||
struct Bullet {
|
||||
sprite: SpriteRef,
|
||||
pos: Point,
|
||||
}
|
||||
|
@ -111,6 +111,8 @@ struct Cannon {
|
|||
impl World {
|
||||
/// Create a new simple-invaders `World`.
|
||||
pub fn new() -> World {
|
||||
use Frame::*;
|
||||
|
||||
// Load assets first
|
||||
let assets = load_assets();
|
||||
|
||||
|
@ -120,12 +122,12 @@ impl World {
|
|||
bounds: Bounds::default(),
|
||||
};
|
||||
let player = Player {
|
||||
sprite: SpriteRef::new(&assets, "player1"),
|
||||
sprite: SpriteRef::new(&assets, Player1),
|
||||
pos: Point::new(80, 216),
|
||||
};
|
||||
let shields = (0..4)
|
||||
.map(|i| Shield {
|
||||
sprite: Sprite::new(&assets, "shield"),
|
||||
sprite: Sprite::new(&assets, Shield1),
|
||||
pos: Point::new(i * 45 + 32, 192),
|
||||
})
|
||||
.collect();
|
||||
|
@ -139,7 +141,7 @@ impl World {
|
|||
lasers: Vec::new(),
|
||||
shields,
|
||||
player,
|
||||
cannons: Vec::new(),
|
||||
bullets: Vec::new(),
|
||||
score: 0,
|
||||
assets,
|
||||
screen,
|
||||
|
@ -153,17 +155,21 @@ impl World {
|
|||
///
|
||||
/// * `dt`: The time delta since last update.
|
||||
/// * `controls`: The player inputs.
|
||||
pub fn update(&mut self, dt: Duration, controls: Controls) {
|
||||
pub fn update(&mut self, dt: Duration, _controls: Controls) {
|
||||
let one_frame = Duration::new(0, 16_666_667);
|
||||
|
||||
// Advance the timer by the delta time
|
||||
self.timing += dt;
|
||||
|
||||
// Step the game logic by one frame at a time
|
||||
// Step the invaders one by one
|
||||
while self.timing >= one_frame {
|
||||
self.timing -= one_frame;
|
||||
self.step(&controls);
|
||||
self.step_invaders();
|
||||
}
|
||||
|
||||
// TODO: Handle controls to move the player
|
||||
// TODO: Handle lasers and bullets
|
||||
// Movements can be multiplied by the delta-time frame count, instead of looping
|
||||
}
|
||||
|
||||
/// Draw the internal state to the screen.
|
||||
|
@ -183,7 +189,7 @@ impl World {
|
|||
&self.screen
|
||||
}
|
||||
|
||||
fn step(&mut self, _controls: &Controls) {
|
||||
fn step_invaders(&mut self) {
|
||||
// Find the next invader
|
||||
let mut invader = None;
|
||||
while let None = invader {
|
||||
|
@ -192,18 +198,10 @@ impl World {
|
|||
}
|
||||
let invader = invader.unwrap();
|
||||
|
||||
// Animate the invader
|
||||
let assets = self.assets.sprites();
|
||||
let (pixels, frame) = match invader.sprite.frame().as_ref() {
|
||||
"blipjoy1" => (assets.get("blipjoy2").unwrap().2.clone(), "blipjoy2"),
|
||||
"blipjoy2" => (assets.get("blipjoy1").unwrap().2.clone(), "blipjoy1"),
|
||||
"ferris1" => (assets.get("ferris2").unwrap().2.clone(), "ferris2"),
|
||||
"ferris2" => (assets.get("ferris1").unwrap().2.clone(), "ferris1"),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// TODO: Move the invader
|
||||
|
||||
invader.sprite.update_pixels(pixels);
|
||||
invader.sprite.update_frame(frame);
|
||||
// Animate the invader
|
||||
invader.sprite.animate(&self.assets);
|
||||
}
|
||||
|
||||
/// Clear the screen
|
||||
|
@ -279,6 +277,8 @@ impl Default for Bounds {
|
|||
|
||||
/// Create a grid of invaders.
|
||||
fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
||||
use Frame::*;
|
||||
|
||||
const BLIPJOY_OFFSET: Point = Point::new(3, 4);
|
||||
const FERRIS_OFFSET: Point = Point::new(3, 5);
|
||||
|
||||
|
@ -287,7 +287,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),
|
||||
pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID,
|
||||
score: 10,
|
||||
})
|
||||
|
@ -298,7 +298,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),
|
||||
pos: START + FERRIS_OFFSET + Point::new(x, y) * GRID,
|
||||
score: 10,
|
||||
})
|
||||
|
@ -309,8 +309,7 @@ fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
|||
(0..COLS)
|
||||
.map(|x| {
|
||||
Some(Invader {
|
||||
// TODO: Need a third invader
|
||||
sprite: SpriteRef::new(assets, "blipjoy1"),
|
||||
sprite: SpriteRef::new(assets, Cthulhu1),
|
||||
pos: START + BLIPJOY_OFFSET + Point::new(x, y) * GRID,
|
||||
score: 10,
|
||||
})
|
||||
|
|
|
@ -2,55 +2,43 @@ use std::collections::HashMap;
|
|||
use std::io::Cursor;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::sprites::CachedSprite;
|
||||
use crate::sprites::{CachedSprite, Frame};
|
||||
|
||||
/// A list of assets loaded into memory.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Assets {
|
||||
// sounds: TODO
|
||||
sprites: HashMap<String, CachedSprite>,
|
||||
sprites: HashMap<Frame, CachedSprite>,
|
||||
}
|
||||
|
||||
impl Assets {
|
||||
pub(crate) fn sprites(&self) -> &HashMap<String, CachedSprite> {
|
||||
pub(crate) fn sprites(&self) -> &HashMap<Frame, CachedSprite> {
|
||||
&self.sprites
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all static assets into an `Assets` structure
|
||||
pub(crate) fn load_assets() -> Assets {
|
||||
use Frame::*;
|
||||
|
||||
let mut sprites = HashMap::new();
|
||||
|
||||
sprites.insert(
|
||||
"blipjoy1".into(),
|
||||
load_pcx(include_bytes!("assets/blipjoy1.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"blipjoy2".into(),
|
||||
load_pcx(include_bytes!("assets/blipjoy2.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"ferris1".into(),
|
||||
load_pcx(include_bytes!("assets/ferris1.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"ferris2".into(),
|
||||
load_pcx(include_bytes!("assets/ferris2.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"player1".into(),
|
||||
load_pcx(include_bytes!("assets/player1.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"player2".into(),
|
||||
load_pcx(include_bytes!("assets/player2.pcx")),
|
||||
);
|
||||
sprites.insert(
|
||||
"shield".into(),
|
||||
load_pcx(include_bytes!("assets/shield.pcx")),
|
||||
);
|
||||
// sprites.insert("laser1".into(), load_pcx(include_bytes!("assets/laser1.pcx")));
|
||||
// sprites.insert("laser2".into(), load_pcx(include_bytes!("assets/laser2.pcx")));
|
||||
sprites.insert(Blipjoy1, load_pcx(include_bytes!("assets/blipjoy1.pcx")));
|
||||
sprites.insert(Blipjoy2, load_pcx(include_bytes!("assets/blipjoy2.pcx")));
|
||||
|
||||
sprites.insert(Ferris1, load_pcx(include_bytes!("assets/ferris1.pcx")));
|
||||
sprites.insert(Ferris2, load_pcx(include_bytes!("assets/ferris2.pcx")));
|
||||
|
||||
sprites.insert(Cthulhu1, load_pcx(include_bytes!("assets/cthulhu1.pcx")));
|
||||
sprites.insert(Cthulhu2, load_pcx(include_bytes!("assets/cthulhu2.pcx")));
|
||||
|
||||
sprites.insert(Player1, load_pcx(include_bytes!("assets/player1.pcx")));
|
||||
sprites.insert(Player2, load_pcx(include_bytes!("assets/player2.pcx")));
|
||||
|
||||
sprites.insert(Shield1, load_pcx(include_bytes!("assets/shield.pcx")));
|
||||
|
||||
// sprites.insert(Laser1, load_pcx(include_bytes!("assets/laser1.pcx")));
|
||||
// sprites.insert(Laser2, load_pcx(include_bytes!("assets/laser2.pcx")));
|
||||
|
||||
Assets { sprites }
|
||||
}
|
||||
|
|
|
@ -6,13 +6,33 @@ use crate::{Point, SCREEN_WIDTH};
|
|||
// This is the type stored in the `Assets` hash map
|
||||
pub(crate) type CachedSprite = (usize, usize, Rc<Vec<u8>>);
|
||||
|
||||
/// Frame identifier for managing animations.
|
||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) enum Frame {
|
||||
Blipjoy1,
|
||||
Blipjoy2,
|
||||
|
||||
Ferris1,
|
||||
Ferris2,
|
||||
|
||||
Cthulhu1,
|
||||
Cthulhu2,
|
||||
|
||||
Player1,
|
||||
Player2,
|
||||
|
||||
Shield1,
|
||||
// Laser1,
|
||||
// Laser2,
|
||||
}
|
||||
|
||||
/// Sprites can be drawn and animated.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Sprite {
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixels: Vec<u8>,
|
||||
frame: String,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
/// SpriteRefs can be drawn and animated.
|
||||
|
@ -23,30 +43,73 @@ pub(crate) struct SpriteRef {
|
|||
width: usize,
|
||||
height: usize,
|
||||
pixels: Rc<Vec<u8>>,
|
||||
frame: String,
|
||||
frame: Frame,
|
||||
}
|
||||
|
||||
pub(crate) trait Sprites {
|
||||
fn new(assets: &Assets, name: &str) -> Self;
|
||||
/// Drawables can be blitted to the pixel buffer and animated.
|
||||
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) -> &str;
|
||||
fn update_frame(&mut self, frame: &str);
|
||||
fn frame(&self) -> &Frame;
|
||||
fn update_frame(&mut self, frame: Frame);
|
||||
|
||||
fn animate(&mut self, assets: &Assets) {
|
||||
use Frame::*;
|
||||
|
||||
let assets = assets.sprites();
|
||||
let (pixels, frame) = match self.frame() {
|
||||
Blipjoy1 => (assets.get(&Blipjoy2).unwrap().2.clone(), Blipjoy2),
|
||||
Blipjoy2 => (assets.get(&Blipjoy1).unwrap().2.clone(), Blipjoy1),
|
||||
|
||||
Ferris1 => (assets.get(&Ferris2).unwrap().2.clone(), Ferris2),
|
||||
Ferris2 => (assets.get(&Ferris1).unwrap().2.clone(), Ferris1),
|
||||
|
||||
Cthulhu1 => (assets.get(&Cthulhu2).unwrap().2.clone(), Cthulhu2),
|
||||
Cthulhu2 => (assets.get(&Cthulhu1).unwrap().2.clone(), Cthulhu1),
|
||||
|
||||
Player1 => (assets.get(&Player2).unwrap().2.clone(), Player2),
|
||||
Player2 => (assets.get(&Player1).unwrap().2.clone(), Player1),
|
||||
|
||||
// This should not happen, but here we are!
|
||||
Shield1 => (assets.get(&Shield1).unwrap().2.clone(), Shield1),
|
||||
// Laser1 => (assets.get(&Laser2).unwrap().2.clone(), Laser2),
|
||||
// Laser2 => (assets.get(&Laser1).unwrap().2.clone(), Laser1),
|
||||
};
|
||||
|
||||
self.update_pixels(pixels);
|
||||
self.update_frame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
impl Sprites for Sprite {
|
||||
fn new(assets: &Assets, name: &str) -> Sprite {
|
||||
let cached_sprite = assets.sprites().get(name).unwrap();
|
||||
Sprite {
|
||||
width: cached_sprite.0,
|
||||
height: cached_sprite.1,
|
||||
pixels: cached_sprite.2.to_vec(),
|
||||
frame: name.into(),
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drawable for Sprite {
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
@ -60,29 +123,19 @@ impl Sprites for Sprite {
|
|||
}
|
||||
|
||||
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>) {
|
||||
self.pixels.copy_from_slice(&pixels);
|
||||
self.pixels = pixels.to_vec();
|
||||
}
|
||||
|
||||
fn frame(&self) -> &str {
|
||||
fn frame(&self) -> &Frame {
|
||||
&self.frame
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, frame: &str) {
|
||||
self.frame = frame.into();
|
||||
fn update_frame(&mut self, frame: Frame) {
|
||||
self.frame = frame;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sprites for SpriteRef {
|
||||
fn new(assets: &Assets, name: &str) -> SpriteRef {
|
||||
let cached_sprite = assets.sprites().get(name).unwrap();
|
||||
SpriteRef {
|
||||
width: cached_sprite.0,
|
||||
height: cached_sprite.1,
|
||||
pixels: cached_sprite.2.clone(),
|
||||
frame: name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drawable for SpriteRef {
|
||||
fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
@ -99,18 +152,19 @@ impl Sprites for SpriteRef {
|
|||
self.pixels = pixels;
|
||||
}
|
||||
|
||||
fn frame(&self) -> &str {
|
||||
fn frame(&self) -> &Frame {
|
||||
&self.frame
|
||||
}
|
||||
|
||||
fn update_frame(&mut self, frame: &str) {
|
||||
self.frame = frame.into();
|
||||
fn update_frame(&mut self, frame: Frame) {
|
||||
self.frame = frame;
|
||||
}
|
||||
}
|
||||
|
||||
/// Blit a drawable to the pixel buffer.
|
||||
pub(crate) fn blit<S>(screen: &mut [u8], dest: &Point, sprite: &S)
|
||||
where
|
||||
S: Sprites,
|
||||
S: Drawable,
|
||||
{
|
||||
let pixels = sprite.pixels();
|
||||
let width = sprite.width() * 4;
|
||||
|
|
Loading…
Reference in a new issue