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)]
|
#[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)]
|
||||||
|
@ -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();
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user