forked from tutorials/rust-sokoban
Finished 3.2 Animations
This commit is contained in:
parent
5b0c26d3de
commit
feac27c6b6
@ -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)]
|
||||
|
@ -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();
|
||||
|
10
src/main.rs
10
src/main.rs
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user