2020-01-06
Sokoban game
use ggez::event::{self, EventHandler};
use ggez::graphics::{self, Color};
use ggez::{conf, Context, ContextBuilder, GameError, GameResult};
use std::path;
// Game hold all the game state
struct Game {}
impl Game {
pub fn new(_ctx: &mut Context) -> Game {
Game {}
}
}
impl EventHandler<GameError> for Game {
fn update(&mut self, _ctx: &mut Context) -> GameResult {
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
graphics::clear(ctx, Color::WHITE);
graphics::present(ctx)
}
}
fn main() {
// create a game context and event loop
let context_builder = ContextBuilder::new("babidiii_sokoban", "sokoban")
.window_setup(conf::WindowSetup::default().title("Sokoban!"))
.window_mode(conf::WindowMode::default().dimensions(1000.0, 600.0))
.add_resource_path(path::PathBuf::from("./resources"));
let (mut ctx, event_loop) = context_builder.build().expect("Could not create ggez game");
// Create game state
let game = Game::new(&mut ctx);
event::run(ctx, event_loop, game)
}
These are the three core concepts. Each entity is associated with some components. Those entities and components are processed by systems.
This way, you have your data (components) completely seperated from the behaviour (systems).
An entity just logically group components; so a velocity component can be applied to the Position component of the same entity.
ECS Crate: specs
[dependencies]
specs = { version = "0.16.1", features = ["specs-derive"] }
+-----------+ 0..n 1 +--------+
| Component | ------------ | Entity |
+-----------+ +--------+
// Struct of an entity
struct Entity(u32, Generation);
// u32 is for the id
// Generation is used to check if the entity has been deleted
Data driven proramming
+-------+
| Force |
+-------+
| +--------------------+ +----------+
| --> | integration system | -----> | Velocity |
| +--------------------+ +----------+
+------+
| Mass |
+------+
Hello world
use specs::{Component, VecStorage};
#[derive(Debug)]
struct Position {
x: f32,
y: f32,
}
impl Component for Position {
type Storage = VecStorage<Self>;
}
Could be replace by using the specs-derive
crate with custom #[derive]
[dependencies]
specs = { version = "0.16.1", features = ["specs-derive"] }
use specs::{Component, VecStorage};
// First way of doing
#[derive(Debug)]
struct Position {
x: f32,
y: f32,
}
impl Component for Position {
type Storage = VecStorage<Self>;
}
// With drive specs-derive
#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Velocity {
x: f32,
y: f32,
}
use specs::{Builder, Component, VecStorage, World, WorldExt};
// World is a struct coming from shred, an important dependency of Specs.
// Whenever you call functions specific to Specs, you will need to import the WorldExt trait.
//...
fn main() {
let mut world = World::new();
world.register::<Position>();
world.register::<Velocity>();
let ball = world
.create_entity()
.with(Position { x: 4.0, y: 7.0 })
.build();
}
struct HelloWorld;
impl<'a> System<'a> for HelloWorld {
type SystemData = ReadStorage<'a, Position>;
fn run(&mut self, position: Self::SystemData) {
use specs::Join;
for position in position.join() {
println!("hello, {:?}", &position);
}
}
}
fn main() {
// world creation and entities registering/creation
let mut hello_world = HelloWorld;
hello_world.run_now(&world);
world.maintain();
// If entities are created or deleted while a system is running, calling maintain will record the changes in its internal data structure.
}
// create dispatcher with no dependancies
let mut dispatcher = DispatcherBuilder::new()
.with(HelloWorld, "hello_world", &[])
.build();
If we create a new System like the following one which will update the position for entities that have a Position and Velocity Components:
struct UpdatePos;
impl<'a> System<'a> for UpdatePos {
type SystemData = (ReadStorage<'a, Velocity>, WriteStorage<'a, Position>);
fn run(&mut self, (vel, mut pos): Self::SystemData) {
use specs::Join;
for (vel, pos) in (&vel, &mut pos).join() {
pos.x += vel.x * 0.05;
pos.y += vel.y * 0.05;
}
}
}
Now if we add the new system to the dispatcher
fn main() {
let mut world = World::new();
world.register::<Position>();
world.register::<Velocity>();
// first entity with only Position component
world
.create_entity()
.with(Position { x: 4.0, y: 7.0 })
.build();
// second entity with Position and Velocity
world
.create_entity()
.with(Position { x: 4.0, y: 7.0 })
.with(Velocity { x: 0.1, y: 0.2 })
.build();
let mut dispatcher = DispatcherBuilder::new()
.with(HelloWorld, "hello_world", &[])
.with(UpdatePos, "update_pos", &["hello_world"]) // update_pos depends on hello_world
.with(HelloWorld, "hello_updated", &["update_pos"]) // hello_updated depends on update_pos
.build();
// hello_world.run_now(&world);
dispatcher.dispatch(&mut world);
// Dispatch all the systems with given resources and context and then run thread local systems.
world.maintain();
}
Running `target/debug/specs-game`
hello, Position { x: 4.0, y: 7.0 } // hello_world on entity 1
hello, Position { x: 4.0, y: 7.0 } // hello_updated on entity 1
hello, Position { x: 4.0, y: 7.0 } // hello_world on entity 2
hello, Position { x: 4.005, y: 7.001} // hello_updated on entity 2
Let's create the resource
#[derive(Default)]
struct DeltaTime(f32);
We then need to insert it in our container
fn main() {
let mut world = World::new();
world.register::<Position>();
world.register::<Velocity>();
// Inserts a resource into this container
world.insert(DeltaTime(0.05));
// update the deltatime
{
let mut delta = world.write_resource::<DeltaTime>(); // Fetches a resource for writing.
*delta = DeltaTime(0.04);
}
// ...
Then in our system we can access it using Read
impl<'a> System<'a> for UpdatePos {
type SystemData = (
Read<'a, DeltaTime>,
ReadStorage<'a, Velocity>,
WriteStorage<'a, Position>,
);
fn run(&mut self, data: Self::SystemData) {
let (delta, vel, mut pos) = data;
let delta = delta.0; // Deref -> coerce to &Deltatime
for (vel, pos) in (&vel, &mut pos).join() {
pos.x += vel.x * delta;
pos.y += vel.y * delta;
}
}
}
use specs::prelude::*;
use specs::*;
#[derive(Default)]
struct Gravity;
#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Velocity;
struct SimulationSystem;
impl<'a> System<'a> for SimulationSystem {
type SystemData = (Read<'a, Gravity>, WriteStorage<'a, Velocity>);
fn run(&mut self, (gra, mut vel): Self::SystemData) {}
}
fn main() {
let mut world = World::new();
// world.insert(Gravity);
// world.register::<Velocity>();
let mut dispatcher = DispatcherBuilder::new()
.with(SimulationSystem, "simulation_sys", &[])
.build();
dispatcher.setup(&mut world); // will replace insert and register
for _ in 0..5 {
world.create_entity().with(Velocity).build();
}
dispatcher.dispatch(&mut world);
world.maintain();
}
Basic
for (pos, vel) in (&mut pos_storage, &vel_storage).join() {
*pos += *vel;
}
Basic with enitity
for (ent, pos, vel) in (&*entities, &mut pos_storage, &vel_storage).join() {
println!("Processing entity: {:?}", ent);
*pos += *vel;
}
Optional components
for (pos, vel, mass) in
(&mut pos_storage, &vel_storage, (&mut mass_storage).maybe()).join() {
println!("Processing entity: {:?}", ent);
*pos += *vel;
if let Some(mass) = mass {
let x = *vel / 300_000_000.0;
let y = 1 - x * x;
let y = y.sqrt();
mass.current = mass.constant / y;
}
}
Do not have a join of only MaybeJoins
Manual fetching with Storage::get()
or Storage::get_mut()
for (target, damage) in (&target_storage, &damage_storage).join() {
let target_health: Option<&mut Health> = health_storage.get_mut(target.ent);
if let Some(target_health) = target_health {
target_health.current -= damage.value;
}
}
Excluding components
for (ent, pos, vel, ()) in (
&*entities,
&mut pos_storage,
&vel_storage,
!&frozen_storage,
).join() {
println!("Processing entity: {:?}", ent);
*pos += *vel;
}
Parallel Join
fn run(&mut self, (vel, mut pos): Self::SystemData) {
use specs::Join;
// This loop runs sequentially on a single thread.
for (vel, pos) in (&vel, &mut pos).join() {
pos.x += vel.x * 0.05;
pos.y += vel.y * 0.05;
}
}
fn run(&mut self, (vel, mut pos): Self::SystemData) {
use rayon::prelude::*;
use specs::ParJoin;
(&vel, &mut pos)
.par_join()
.for_each(|(vel, pos)| {
pos.x += vel.x * 0.05;
pos.y += vel.y * 0.05;
});
}