Refactor simple-invaders
This commit is contained in:
parent
1e5621c820
commit
d815bb464b
|
@ -14,6 +14,7 @@ fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
let window = winit::window::WindowBuilder::new()
|
let window = winit::window::WindowBuilder::new()
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(width, height))
|
.with_inner_size(winit::dpi::LogicalSize::new(width, height))
|
||||||
|
.with_title("pixel invaders")
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let surface = wgpu::Surface::create(&window);
|
let surface = wgpu::Surface::create(&window);
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
use std::collections::HashMap;
|
mod loader;
|
||||||
use std::io::Cursor;
|
mod sprites;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
type CachedSprite = (usize, usize, Rc<Vec<u8>>);
|
use loader::{load_assets, Assets};
|
||||||
|
use sprites::{blit, Sprite, SpriteRef, Sprites};
|
||||||
|
|
||||||
|
/// The screen width is constant (units are in pixels)
|
||||||
|
pub const SCREEN_WIDTH: usize = 224;
|
||||||
|
/// The screen height is constant (units are in pixels)
|
||||||
|
pub const SCREEN_HEIGHT: usize = 256;
|
||||||
|
|
||||||
// Invader positioning
|
// Invader positioning
|
||||||
const START: Point = Point::new(24, 60);
|
const START: Point = Point::new(24, 60);
|
||||||
|
@ -10,10 +15,6 @@ const GRID: Point = Point::new(16, 16);
|
||||||
const ROWS: usize = 5;
|
const ROWS: usize = 5;
|
||||||
const COLS: usize = 11;
|
const COLS: usize = 11;
|
||||||
|
|
||||||
// Screen handling
|
|
||||||
pub const SCREEN_WIDTH: usize = 224;
|
|
||||||
pub const SCREEN_HEIGHT: usize = 256;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
invaders: Invaders,
|
invaders: Invaders,
|
||||||
|
@ -26,13 +27,6 @@ pub struct World {
|
||||||
screen: Vec<u8>,
|
screen: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A list of assets loaded into memory.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Assets {
|
|
||||||
// sounds: TODO
|
|
||||||
sprites: HashMap<String, CachedSprite>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A tiny position vector
|
/// A tiny position vector
|
||||||
#[derive(Debug, Default, Eq, PartialEq)]
|
#[derive(Debug, Default, Eq, PartialEq)]
|
||||||
struct Point {
|
struct Point {
|
||||||
|
@ -40,7 +34,7 @@ struct Point {
|
||||||
y: usize,
|
y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of invaders.
|
/// A formation of invaders.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Invaders {
|
struct Invaders {
|
||||||
grid: Vec<Vec<Option<Invader>>>,
|
grid: Vec<Vec<Option<Invader>>>,
|
||||||
|
@ -103,34 +97,8 @@ struct Cannon {
|
||||||
pos: Point,
|
pos: Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sprites can be drawn and animated.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Sprite {
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
frame: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SpriteRefs can be drawn and animated.
|
|
||||||
///
|
|
||||||
/// They reference their pixel data (instead of owning it).
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SpriteRef {
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
pixels: Rc<Vec<u8>>,
|
|
||||||
frame: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Sprites {
|
|
||||||
fn width(&self) -> usize;
|
|
||||||
fn height(&self) -> usize;
|
|
||||||
fn pixels(&self) -> &[u8];
|
|
||||||
fn frame(&self) -> &str;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
/// Create a new simple-invaders `World`.
|
||||||
pub fn new() -> World {
|
pub fn new() -> World {
|
||||||
// Load assets first
|
// Load assets first
|
||||||
let assets = load_assets();
|
let assets = load_assets();
|
||||||
|
@ -167,6 +135,7 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the internal state.
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
// Find the next invader
|
// Find the next invader
|
||||||
let mut invader = None;
|
let mut invader = None;
|
||||||
|
@ -177,30 +146,20 @@ impl World {
|
||||||
let invader = invader.unwrap();
|
let invader = invader.unwrap();
|
||||||
|
|
||||||
// Animate the invader
|
// Animate the invader
|
||||||
let (pixels, frame) = match invader.sprite.frame.as_ref() {
|
let assets = self.assets.sprites();
|
||||||
"blipjoy1" => (
|
let (pixels, frame) = match invader.sprite.frame().as_ref() {
|
||||||
self.assets.sprites.get("blipjoy2").unwrap().2.clone(),
|
"blipjoy1" => (assets.get("blipjoy2").unwrap().2.clone(), "blipjoy2".into()),
|
||||||
"blipjoy2".into(),
|
"blipjoy2" => (assets.get("blipjoy1").unwrap().2.clone(), "blipjoy1".into()),
|
||||||
),
|
"ferris1" => (assets.get("ferris2").unwrap().2.clone(), "ferris2".into()),
|
||||||
"blipjoy2" => (
|
"ferris2" => (assets.get("ferris1").unwrap().2.clone(), "ferris1".into()),
|
||||||
self.assets.sprites.get("blipjoy1").unwrap().2.clone(),
|
|
||||||
"blipjoy1".into(),
|
|
||||||
),
|
|
||||||
"ferris1" => (
|
|
||||||
self.assets.sprites.get("ferris2").unwrap().2.clone(),
|
|
||||||
"ferris2".into(),
|
|
||||||
),
|
|
||||||
"ferris2" => (
|
|
||||||
self.assets.sprites.get("ferris1").unwrap().2.clone(),
|
|
||||||
"ferris1".into(),
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
invader.sprite.pixels = pixels;
|
invader.sprite.update_pixels(pixels);
|
||||||
invader.sprite.frame = frame;
|
invader.sprite.update_frame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw the internal state to the screen.
|
||||||
pub fn draw(&mut self) -> &[u8] {
|
pub fn draw(&mut self) -> &[u8] {
|
||||||
// Clear the screen
|
// Clear the screen
|
||||||
self.clear();
|
self.clear();
|
||||||
|
@ -217,6 +176,7 @@ impl World {
|
||||||
&self.screen
|
&self.screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear the screen
|
||||||
fn clear(&mut self) {
|
fn clear(&mut self) {
|
||||||
for (i, byte) in self.screen.iter_mut().enumerate() {
|
for (i, byte) in self.screen.iter_mut().enumerate() {
|
||||||
*byte = if i % 4 == 3 { 255 } else { 0 };
|
*byte = if i % 4 == 3 { 255 } else { 0 };
|
||||||
|
@ -287,162 +247,6 @@ impl Default for Bounds {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 Sprites for Sprite {
|
|
||||||
fn width(&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> usize {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pixels(&self) -> &[u8] {
|
|
||||||
&self.pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame(&self) -> &str {
|
|
||||||
&self.frame
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl 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 Sprites for SpriteRef {
|
|
||||||
fn width(&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> usize {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pixels(&self) -> &[u8] {
|
|
||||||
&self.pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame(&self) -> &str {
|
|
||||||
&self.frame
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load all static assets into an `Assets` structure
|
|
||||||
fn load_assets() -> Assets {
|
|
||||||
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")));
|
|
||||||
|
|
||||||
Assets { sprites }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert PCX data to raw pixels
|
|
||||||
fn load_pcx(pcx: &[u8]) -> CachedSprite {
|
|
||||||
let mut reader = pcx::Reader::new(Cursor::new(pcx)).unwrap();
|
|
||||||
let width = reader.width() as usize;
|
|
||||||
let height = reader.height() as usize;
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
if reader.is_paletted() {
|
|
||||||
// Read the raw pixel data
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
buffer.resize_with(width * height, Default::default);
|
|
||||||
for y in 0..height {
|
|
||||||
let a = y as usize * width;
|
|
||||||
let b = a + width;
|
|
||||||
reader.next_row_paletted(&mut buffer[a..b]).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the pallete
|
|
||||||
let mut palette = Vec::new();
|
|
||||||
let palette_length = reader.palette_length().unwrap() as usize;
|
|
||||||
palette.resize_with(palette_length * 3, Default::default);
|
|
||||||
reader.read_palette(&mut palette).unwrap();
|
|
||||||
|
|
||||||
// Copy to result with an alpha component
|
|
||||||
let pixels = buffer
|
|
||||||
.into_iter()
|
|
||||||
.map(|pal| {
|
|
||||||
let i = pal as usize * 3;
|
|
||||||
&palette[i..i + 3]
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<u8>>();
|
|
||||||
result.extend_from_slice(&pixels);
|
|
||||||
} else {
|
|
||||||
for _ in 0..height {
|
|
||||||
// Read the raw pixel data
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
buffer.resize_with(width * 3, Default::default);
|
|
||||||
reader.next_row_rgb(&mut buffer[..]).unwrap();
|
|
||||||
|
|
||||||
// Copy to result with an alpha component
|
|
||||||
let pixels = buffer
|
|
||||||
.chunks(3)
|
|
||||||
.map(|rgb| {
|
|
||||||
let mut rgb = rgb.to_vec();
|
|
||||||
rgb.push(255);
|
|
||||||
rgb
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<u8>>();
|
|
||||||
result.extend_from_slice(&pixels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(width, height, Rc::new(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a grid of invaders.
|
/// Create a grid of invaders.
|
||||||
fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
||||||
const BLIPJOY_OFFSET: Point = Point::new(3, 4);
|
const BLIPJOY_OFFSET: Point = Point::new(3, 4);
|
||||||
|
@ -485,49 +289,3 @@ fn make_invader_grid(assets: &Assets) -> Vec<Vec<Option<Invader>>> {
|
||||||
}))
|
}))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blit<S>(screen: &mut [u8], dest: &Point, sprite: &S)
|
|
||||||
where
|
|
||||||
S: Sprites,
|
|
||||||
{
|
|
||||||
let pixels = sprite.pixels();
|
|
||||||
let width = sprite.width() * 4;
|
|
||||||
|
|
||||||
let mut s = 0;
|
|
||||||
for y in 0..sprite.height() {
|
|
||||||
let i = dest.x * 4 + dest.y * SCREEN_WIDTH * 4 + y * SCREEN_WIDTH * 4;
|
|
||||||
screen[i..i + width].copy_from_slice(&pixels[s..s + width]);
|
|
||||||
s += width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pcx() {
|
|
||||||
let pixels = load_pcx(include_bytes!("assets/blipjoy1.pcx"));
|
|
||||||
let expected = vec![
|
|
||||||
0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
|
|
||||||
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0,
|
|
||||||
0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
|
||||||
255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
|
|
||||||
0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
|
||||||
0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
|
||||||
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255,
|
|
||||||
255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255,
|
|
||||||
255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255,
|
|
||||||
255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
|
||||||
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0,
|
|
||||||
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(pixels.0, 10, "Width differs");
|
|
||||||
assert_eq!(pixels.1, 8, "Height differs");
|
|
||||||
assert_eq!(Rc::try_unwrap(pixels.2).unwrap(), expected, "Pixels differ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
145
simple-invaders/src/loader.rs
Normal file
145
simple-invaders/src/loader.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::sprites::CachedSprite;
|
||||||
|
|
||||||
|
/// A list of assets loaded into memory.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Assets {
|
||||||
|
// sounds: TODO
|
||||||
|
sprites: HashMap<String, CachedSprite>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Assets {
|
||||||
|
pub(crate) fn sprites(&self) -> &HashMap<String, CachedSprite> {
|
||||||
|
&self.sprites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load all static assets into an `Assets` structure
|
||||||
|
pub(crate) fn load_assets() -> Assets {
|
||||||
|
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")));
|
||||||
|
|
||||||
|
Assets { sprites }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert PCX data to raw pixels
|
||||||
|
fn load_pcx(pcx: &[u8]) -> CachedSprite {
|
||||||
|
let mut reader = pcx::Reader::new(Cursor::new(pcx)).unwrap();
|
||||||
|
let width = reader.width() as usize;
|
||||||
|
let height = reader.height() as usize;
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
if reader.is_paletted() {
|
||||||
|
// Read the raw pixel data
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
buffer.resize_with(width * height, Default::default);
|
||||||
|
for y in 0..height {
|
||||||
|
let a = y as usize * width;
|
||||||
|
let b = a + width;
|
||||||
|
reader.next_row_paletted(&mut buffer[a..b]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the pallete
|
||||||
|
let mut palette = Vec::new();
|
||||||
|
let palette_length = reader.palette_length().unwrap() as usize;
|
||||||
|
palette.resize_with(palette_length * 3, Default::default);
|
||||||
|
reader.read_palette(&mut palette).unwrap();
|
||||||
|
|
||||||
|
// Copy to result with an alpha component
|
||||||
|
let pixels = buffer
|
||||||
|
.into_iter()
|
||||||
|
.map(|pal| {
|
||||||
|
let i = pal as usize * 3;
|
||||||
|
&palette[i..i + 3]
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
result.extend_from_slice(&pixels);
|
||||||
|
} else {
|
||||||
|
for _ in 0..height {
|
||||||
|
// Read the raw pixel data
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
buffer.resize_with(width * 3, Default::default);
|
||||||
|
reader.next_row_rgb(&mut buffer[..]).unwrap();
|
||||||
|
|
||||||
|
// Copy to result with an alpha component
|
||||||
|
let pixels = buffer
|
||||||
|
.chunks(3)
|
||||||
|
.map(|rgb| {
|
||||||
|
let mut rgb = rgb.to_vec();
|
||||||
|
rgb.push(255);
|
||||||
|
rgb
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
result.extend_from_slice(&pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(width, height, Rc::new(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pcx() {
|
||||||
|
let pixels = load_pcx(include_bytes!("assets/blipjoy1.pcx"));
|
||||||
|
let expected = vec![
|
||||||
|
0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
|
||||||
|
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0,
|
||||||
|
0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
||||||
|
255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
|
||||||
|
0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
||||||
|
0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
||||||
|
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255,
|
||||||
|
255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255,
|
||||||
|
255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255,
|
||||||
|
255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255,
|
||||||
|
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0,
|
||||||
|
255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255,
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(pixels.0, 10, "Width differs");
|
||||||
|
assert_eq!(pixels.1, 8, "Height differs");
|
||||||
|
assert_eq!(Rc::try_unwrap(pixels.2).unwrap(), expected, "Pixels differ");
|
||||||
|
}
|
||||||
|
}
|
124
simple-invaders/src/sprites.rs
Normal file
124
simple-invaders/src/sprites.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::loader::Assets;
|
||||||
|
use crate::{Point, SCREEN_WIDTH};
|
||||||
|
|
||||||
|
// This is the type stored in the `Assets` hash map
|
||||||
|
pub(crate) type CachedSprite = (usize, usize, Rc<Vec<u8>>);
|
||||||
|
|
||||||
|
/// Sprites can be drawn and animated.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Sprite {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
pixels: Vec<u8>,
|
||||||
|
frame: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SpriteRefs can be drawn and animated.
|
||||||
|
///
|
||||||
|
/// They reference their pixel data (instead of owning it).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SpriteRef {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
pixels: Rc<Vec<u8>>,
|
||||||
|
frame: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Sprites {
|
||||||
|
fn new(assets: &Assets, name: &str) -> Self;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixels(&self) -> &[u8] {
|
||||||
|
&self.pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>) {
|
||||||
|
self.pixels.copy_from_slice(&pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(&self) -> &str {
|
||||||
|
&self.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(&mut self, frame: &str) {
|
||||||
|
self.frame = frame.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixels(&self) -> &[u8] {
|
||||||
|
&self.pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_pixels(&mut self, pixels: Rc<Vec<u8>>) {
|
||||||
|
self.pixels = pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(&self) -> &str {
|
||||||
|
&self.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_frame(&mut self, frame: &str) {
|
||||||
|
self.frame = frame.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn blit<S>(screen: &mut [u8], dest: &Point, sprite: &S)
|
||||||
|
where
|
||||||
|
S: Sprites,
|
||||||
|
{
|
||||||
|
let pixels = sprite.pixels();
|
||||||
|
let width = sprite.width() * 4;
|
||||||
|
|
||||||
|
let mut s = 0;
|
||||||
|
for y in 0..sprite.height() {
|
||||||
|
let i = dest.x * 4 + dest.y * SCREEN_WIDTH * 4 + y * SCREEN_WIDTH * 4;
|
||||||
|
screen[i..i + width].copy_from_slice(&pixels[s..s + width]);
|
||||||
|
s += width;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue