From d7fc1eca3cdcbdf47d423b2d694a8ae4bc3722d0 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Mon, 7 Oct 2019 20:22:34 -0700 Subject: [PATCH] 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. --- simple-invaders/src/assets/cthulhu1.pcx | Bin 0 -> 470 bytes simple-invaders/src/assets/cthulhu2.pcx | Bin 0 -> 470 bytes simple-invaders/src/lib.rs | 49 +++++----- simple-invaders/src/loader.rs | 54 +++++------ simple-invaders/src/sprites.rs | 118 +++++++++++++++++------- 5 files changed, 131 insertions(+), 90 deletions(-) create mode 100644 simple-invaders/src/assets/cthulhu1.pcx create mode 100644 simple-invaders/src/assets/cthulhu2.pcx diff --git a/simple-invaders/src/assets/cthulhu1.pcx b/simple-invaders/src/assets/cthulhu1.pcx new file mode 100644 index 0000000000000000000000000000000000000000..1dda719d25cb7b2e40fd80e3a93f00c19973ea3f GIT binary patch literal 470 zcmb772?~HP49gHfMSPdvUh5EFRXcac#A_`jq)pPHH664@^q94)tC?V`O{c+u#Q`NS zR$-t7!X^YS`ujwdpnlyavd@ zv#Ez1gk1%mAZiK7bt;n=Zyq0+uvpZh5zdGIn?xVQX~f6MWIoRM^^dQKfHFA%Trs#j literal 0 HcmV?d00001 diff --git a/simple-invaders/src/lib.rs b/simple-invaders/src/lib.rs index c4499dd..cfb0bfd 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, Sprite, SpriteRef, Sprites}; +use sprites::{blit, Drawable, Frame, Sprite, SpriteRef}; mod controls; mod loader; @@ -31,7 +31,7 @@ pub struct World { lasers: Vec, shields: Vec, player: Player, - cannons: Vec, + bullets: Vec, score: u32, assets: Assets, screen: Vec, @@ -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>> { + 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>> { (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>> { (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>> { (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, }) diff --git a/simple-invaders/src/loader.rs b/simple-invaders/src/loader.rs index 83dcd80..88b39fa 100644 --- a/simple-invaders/src/loader.rs +++ b/simple-invaders/src/loader.rs @@ -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, + sprites: HashMap, } impl Assets { - pub(crate) fn sprites(&self) -> &HashMap { + pub(crate) fn sprites(&self) -> &HashMap { &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 } } diff --git a/simple-invaders/src/sprites.rs b/simple-invaders/src/sprites.rs index 22e4b88..f91428b 100644 --- a/simple-invaders/src/sprites.rs +++ b/simple-invaders/src/sprites.rs @@ -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>); +/// 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, - frame: String, + frame: Frame, } /// SpriteRefs can be drawn and animated. @@ -23,30 +43,73 @@ pub(crate) struct SpriteRef { width: usize, height: usize, pixels: Rc>, - 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>); - 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(); +impl Sprite { + pub(crate) fn new(assets: &Assets, frame: Frame) -> Sprite { + let (width, height, pixels) = assets.sprites().get(&frame).unwrap(); + Sprite { - width: cached_sprite.0, - height: cached_sprite.1, - pixels: cached_sprite.2.to_vec(), - frame: name.into(), + 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>) { - 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(screen: &mut [u8], dest: &Point, sprite: &S) where - S: Sprites, + S: Drawable, { let pixels = sprite.pixels(); let width = sprite.width() * 4;