Finished 3.2 Animations

This commit is contained in:
Timothy Warren 2020-07-24 18:56:48 -04:00
parent 5b0c26d3de
commit feac27c6b6
5 changed files with 110 additions and 18 deletions

View File

@ -19,6 +19,11 @@ impl Display for BoxColor {
} }
} }
pub enum RenderableKind {
Static,
Animated,
}
#[derive(Debug, Component, Clone, Copy)] #[derive(Debug, Component, Clone, Copy)]
#[storage(VecStorage)] #[storage(VecStorage)]
pub struct Position { pub struct Position {
@ -40,7 +45,32 @@ impl Position {
#[derive(Component)] #[derive(Component)]
#[storage(VecStorage)] #[storage(VecStorage)]
pub struct Renderable { pub struct Renderable {
pub path: String, paths: Vec<String>,
}
impl Renderable {
pub fn new_static(path: String) -> Self {
Self { paths: vec![path] }
}
pub fn new_animated(paths: Vec<String>) -> Self {
Self { paths }
}
pub fn kind(&self) -> RenderableKind {
match self.paths.len() {
0 => panic!("invalid renderable"),
1 => RenderableKind::Static,
_ => RenderableKind::Animated,
}
}
pub fn path(&self, path_index: usize) -> String {
// If we get asked for a path that is larger than the
// number of paths we actually have, we simply mod the index
// with the length to get an index that is in range
self.paths[path_index % self.paths.len()].clone()
}
} }
#[derive(Component)] #[derive(Component)]

View File

@ -2,34 +2,54 @@ use crate::components::*;
use specs::{Builder, EntityBuilder, World, WorldExt}; use specs::{Builder, EntityBuilder, World, WorldExt};
/// Cut down on the common syntax boilerplate /// Cut down on the common syntax boilerplate
fn common_entity<'w>( fn static_entity<'w>(
world: &'w mut World, world: &'w mut World,
position: Position, position: Position,
z: u8, z: u8,
sprite_path: &str, sprite_path: &str,
) -> EntityBuilder<'w> { ) -> EntityBuilder<'w> {
return world let sprite_path = sprite_path.to_string();
world
.create_entity() .create_entity()
.with(position.with_z(z)) .with(position.with_z(z))
.with(Renderable { .with(Renderable::new_static(sprite_path))
path: sprite_path.to_string(), }
});
/// Cut down on animated entity boilerplate,
/// and accept vectors of `&str` and `String`
fn animated_entity<S>(
world: &mut World,
position: Position,
z: u8,
sprites: Vec<S>,
) -> EntityBuilder
where
S: Into<String>,
{
let sprites: Vec<String> = sprites.into_iter().map(|s| s.into()).collect();
world
.create_entity()
.with(position.with_z(z))
.with(Renderable::new_animated(sprites))
} }
pub fn create_wall(world: &mut World, position: Position) { pub fn create_wall(world: &mut World, position: Position) {
common_entity(world, position, 10, "/images/wall.png") static_entity(world, position, 10, "/images/wall.png")
.with(Wall {}) .with(Wall {})
.with(Immovable) .with(Immovable)
.build(); .build();
} }
pub fn create_floor(world: &mut World, position: Position) { pub fn create_floor(world: &mut World, position: Position) {
common_entity(world, position, 5, "/images/floor.png").build(); static_entity(world, position, 5, "/images/floor.png").build();
} }
pub fn create_box(world: &mut World, position: Position, color: BoxColor) { pub fn create_box(world: &mut World, position: Position, color: BoxColor) {
let path = format!("/images/box_{}.png", color); let mut sprites: Vec<String> = vec![];
common_entity(world, position, 10, &path) for x in 1..=2 {
sprites.push(format!("/images/box_{}_{}.png", color, x));
}
animated_entity(world, position, 10, sprites)
.with(Box { color }) .with(Box { color })
.with(Movable) .with(Movable)
.build(); .build();
@ -37,13 +57,18 @@ pub fn create_box(world: &mut World, position: Position, color: BoxColor) {
pub fn create_box_spot(world: &mut World, position: Position, color: BoxColor) { pub fn create_box_spot(world: &mut World, position: Position, color: BoxColor) {
let path = format!("/images/box_spot_{}.png", color); let path = format!("/images/box_spot_{}.png", color);
common_entity(world, position, 9, &path) static_entity(world, position, 9, &path)
.with(BoxSpot { color }) .with(BoxSpot { color })
.build(); .build();
} }
pub fn create_player(world: &mut World, position: Position) { pub fn create_player(world: &mut World, position: Position) {
common_entity(world, position, 10, "/images/player.png") let sprites = vec![
"/images/player_1.png",
"/images/player_2.png",
"/images/player_3.png",
];
animated_entity(world, position, 10, sprites)
.with(Player {}) .with(Player {})
.with(Movable) .with(Movable)
.build(); .build();

View File

@ -1,7 +1,7 @@
use ggez; use ggez;
use ggez::event::KeyCode; use ggez::event::KeyCode;
use ggez::event::KeyMods; use ggez::event::KeyMods;
use ggez::{conf, event, Context, GameResult}; use ggez::{conf, event, timer, Context, GameResult};
use specs::{RunNow, World, WorldExt}; use specs::{RunNow, World, WorldExt};
use std::path; use std::path;
@ -23,7 +23,7 @@ struct Game {
} }
impl event::EventHandler for Game { impl event::EventHandler for Game {
fn update(&mut self, _context: &mut Context) -> GameResult { fn update(&mut self, context: &mut Context) -> GameResult {
// Run input system // Run input system
{ {
let mut is = InputSystem {}; let mut is = InputSystem {};
@ -36,6 +36,12 @@ impl event::EventHandler for Game {
gss.run_now(&self.world); gss.run_now(&self.world);
} }
// Get an update time resource
{
let mut time = self.world.write_resource::<Time>();
time.delta += timer::delta(context);
}
Ok(()) Ok(())
} }

View File

@ -2,6 +2,7 @@ use ggez::event::KeyCode;
use specs::World; use specs::World;
use std::fmt; use std::fmt;
use std::fmt::Display; use std::fmt::Display;
use std::time::Duration;
#[derive(Default)] #[derive(Default)]
pub struct InputQueue { pub struct InputQueue {
@ -14,6 +15,11 @@ pub struct Gameplay {
pub moves_count: usize, pub moves_count: usize,
} }
#[derive(Default)]
pub struct Time {
pub delta: Duration,
}
pub enum GameplayState { pub enum GameplayState {
Playing, Playing,
Won, Won,
@ -39,4 +45,5 @@ impl Display for GameplayState {
pub fn register_resources(world: &mut World) { pub fn register_resources(world: &mut World) {
world.insert(InputQueue::default()); world.insert(InputQueue::default());
world.insert(Gameplay::default()); world.insert(Gameplay::default());
world.insert(Time::default());
} }

View File

@ -4,9 +4,11 @@ use ggez::nalgebra as na;
use ggez::{graphics, Context}; use ggez::{graphics, Context};
use specs::{Join, Read, ReadStorage, System}; use specs::{Join, Read, ReadStorage, System};
use crate::components::{Position, Renderable}; use std::time::Duration;
use crate::components::*;
use crate::constants::TILE_WIDTH; use crate::constants::TILE_WIDTH;
use crate::resources::Gameplay; use crate::resources::*;
pub struct RenderingSystem<'a> { pub struct RenderingSystem<'a> {
pub context: &'a mut Context, pub context: &'a mut Context,
@ -28,17 +30,39 @@ impl RenderingSystem<'_> {
) )
.expect("expected drawing queued text"); .expect("expected drawing queued text");
} }
pub fn get_image(&mut self, renderable: &Renderable, delta: Duration) -> Image {
let path_index = match renderable.kind() {
RenderableKind::Static => {
// We only have one image, so we just return that
0
}
RenderableKind::Animated => {
// If we have multiple, we want to select the right one based on the delta time.
// First we get the delta in milliseconds, we % by 1000 to get the seconds only
// and finally we divide by 250 to get a number between 0 and 4. If it's 4
// we technically are on the next iteration of the loop (or on 0), but we will let
// the renderable handle this logic of wrapping frames.
((delta.as_millis() % 1000) / 250) as usize
}
};
let image_path = renderable.path(path_index);
Image::new(self.context, image_path).expect("expected image")
}
} }
impl<'a> System<'a> for RenderingSystem<'a> { impl<'a> System<'a> for RenderingSystem<'a> {
type SystemData = ( type SystemData = (
Read<'a, Gameplay>, Read<'a, Gameplay>,
Read<'a, Time>,
ReadStorage<'a, Position>, ReadStorage<'a, Position>,
ReadStorage<'a, Renderable>, ReadStorage<'a, Renderable>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let (gameplay, positions, renderables) = data; let (gameplay, time, positions, renderables) = data;
// Clear the screen/set the background // Clear the screen/set the background
graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0)); graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0));
@ -52,7 +76,7 @@ impl<'a> System<'a> for RenderingSystem<'a> {
// and draw it at the specified position. // and draw it at the specified position.
for (position, renderable) in rendering_data.iter() { for (position, renderable) in rendering_data.iter() {
// Load the image // Load the image
let image = Image::new(self.context, renderable.path.clone()).expect("expected image"); let image = self.get_image(renderable, time.delta);
let x = position.x as f32 * TILE_WIDTH; let x = position.x as f32 * TILE_WIDTH;
let y = position.y as f32 * TILE_WIDTH; let y = position.y as f32 * TILE_WIDTH;