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)]
#[storage(VecStorage)]
pub struct Position {
@ -40,7 +45,32 @@ impl Position {
#[derive(Component)]
#[storage(VecStorage)]
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)]

View File

@ -2,34 +2,54 @@ use crate::components::*;
use specs::{Builder, EntityBuilder, World, WorldExt};
/// Cut down on the common syntax boilerplate
fn common_entity<'w>(
fn static_entity<'w>(
world: &'w mut World,
position: Position,
z: u8,
sprite_path: &str,
) -> EntityBuilder<'w> {
return world
let sprite_path = sprite_path.to_string();
world
.create_entity()
.with(position.with_z(z))
.with(Renderable {
path: sprite_path.to_string(),
});
.with(Renderable::new_static(sprite_path))
}
/// 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) {
common_entity(world, position, 10, "/images/wall.png")
static_entity(world, position, 10, "/images/wall.png")
.with(Wall {})
.with(Immovable)
.build();
}
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) {
let path = format!("/images/box_{}.png", color);
common_entity(world, position, 10, &path)
let mut sprites: Vec<String> = vec![];
for x in 1..=2 {
sprites.push(format!("/images/box_{}_{}.png", color, x));
}
animated_entity(world, position, 10, sprites)
.with(Box { color })
.with(Movable)
.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) {
let path = format!("/images/box_spot_{}.png", color);
common_entity(world, position, 9, &path)
static_entity(world, position, 9, &path)
.with(BoxSpot { color })
.build();
}
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(Movable)
.build();

View File

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

View File

@ -2,6 +2,7 @@ use ggez::event::KeyCode;
use specs::World;
use std::fmt;
use std::fmt::Display;
use std::time::Duration;
#[derive(Default)]
pub struct InputQueue {
@ -14,6 +15,11 @@ pub struct Gameplay {
pub moves_count: usize,
}
#[derive(Default)]
pub struct Time {
pub delta: Duration,
}
pub enum GameplayState {
Playing,
Won,
@ -39,4 +45,5 @@ impl Display for GameplayState {
pub fn register_resources(world: &mut World) {
world.insert(InputQueue::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 specs::{Join, Read, ReadStorage, System};
use crate::components::{Position, Renderable};
use std::time::Duration;
use crate::components::*;
use crate::constants::TILE_WIDTH;
use crate::resources::Gameplay;
use crate::resources::*;
pub struct RenderingSystem<'a> {
pub context: &'a mut Context,
@ -28,17 +30,39 @@ impl RenderingSystem<'_> {
)
.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> {
type SystemData = (
Read<'a, Gameplay>,
Read<'a, Time>,
ReadStorage<'a, Position>,
ReadStorage<'a, Renderable>,
);
fn run(&mut self, data: Self::SystemData) {
let (gameplay, positions, renderables) = data;
let (gameplay, time, positions, renderables) = data;
// Clear the screen/set the background
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.
for (position, renderable) in rendering_data.iter() {
// 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 y = position.y as f32 * TILE_WIDTH;