Skip to content

Commit

Permalink
init ECS specs
Browse files Browse the repository at this point in the history
  • Loading branch information
o2sh committed Dec 20, 2020
1 parent 0df7d7d commit c52c193
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
newline_style = "Unix"
# The "Default" setting has a heuristic which splits lines too aggresively.
use_small_heuristics = "Max"
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ repository = "https://github.com/o2sh/teki"


[dependencies]
clap = "2.33.3"
specs = "0.16.1"
specs-derive = "0.4"

[dependencies.sdl2]
version = "0.34.3"
default-features = false
features = ["image"]
Binary file added assets/neko.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions src/components.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use sdl2::rect::{Point, Rect};
use specs::prelude::*;
use specs_derive::Component;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Up,
Down,
Left,
Right,
}

#[derive(Component, Debug, Default)]
#[storage(NullStorage)]
pub struct KeyboardControlled;

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Position(pub Point);

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Velocity {
pub speed: i32,
pub direction: Direction,
}

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Sprite {
pub spritesheet: usize,
pub region: Rect,
}
34 changes: 34 additions & 0 deletions src/keyboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use specs::prelude::*;

use crate::components::*;

use super::MovementCommand;

const PLAYER_MOVEMENT_SPEED: i32 = 20;

pub struct Keyboard;

impl<'a> System<'a> for Keyboard {
type SystemData = (
ReadExpect<'a, Option<MovementCommand>>,
ReadStorage<'a, KeyboardControlled>,
WriteStorage<'a, Velocity>,
);

fn run(&mut self, mut data: Self::SystemData) {
let movement_command = match &*data.0 {
Some(movement_command) => movement_command,
None => return, // no change
};

for (_, vel) in (&data.1, &mut data.2).join() {
match movement_command {
&MovementCommand::Move(direction) => {
vel.speed = PLAYER_MOVEMENT_SPEED;
vel.direction = direction;
}
MovementCommand::Stop => vel.speed = 0,
}
}
}
}
124 changes: 122 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,123 @@
fn main() {
println!("Hello, world!");
mod components;
mod keyboard;
mod physics;
mod renderer;

use clap::{crate_description, crate_name, crate_version, App};

use sdl2::event::Event;
use sdl2::image::{self, InitFlag, LoadTexture};
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect};
use specs::prelude::*;
use std::time::Duration;

use crate::components::*;

pub enum MovementCommand {
Stop,
Move(Direction),
}

fn main() -> Result<(), String> {
App::new(crate_name!()).version(crate_version!()).about(crate_description!());

let sdl_context = sdl2::init()?;
let video_subsystem = sdl_context.video()?;
let _image_context = image::init(InitFlag::PNG)?;
let window = video_subsystem
.window("game tutorial", 800, 600)
.position_centered()
.build()
.expect("could not initialize video subsystem");

let mut canvas = window.into_canvas().build().expect("could not make a canvas");

let texture_creator = canvas.texture_creator();

let mut dispatcher = DispatcherBuilder::new()
.with(keyboard::Keyboard, "Keyboard", &[])
.with(physics::Physics, "Physics", &["Keyboard"])
.build();

let mut world = World::new();
dispatcher.setup(&mut world);
renderer::SystemData::setup(&mut world);

let movement_command: Option<MovementCommand> = None;
world.insert(movement_command);

let textures = [texture_creator.load_texture("assets/neko.png")?];

let spritesheet = 0;
let player_top_left_frame = Rect::new(0, 0, 45, 45);
let (frame_width, frame_height) = player_top_left_frame.size();

let sprite = Sprite {
spritesheet,
region: Rect::new(
player_top_left_frame.x(),
player_top_left_frame.y(),
frame_width,
frame_height,
),
};

world
.create_entity()
.with(KeyboardControlled)
.with(Position(Point::new(0, 0)))
.with(Velocity { speed: 0, direction: Direction::Right })
.with(sprite)
.build();

let mut event_pump = sdl_context.event_pump()?;
let mut i = 0;
'running: loop {
let mut movement_command = None;

// Handle events
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
break 'running;
}
Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
movement_command = Some(MovementCommand::Move(Direction::Left));
}
Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
movement_command = Some(MovementCommand::Move(Direction::Right));
}
Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
movement_command = Some(MovementCommand::Move(Direction::Up));
}
Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
movement_command = Some(MovementCommand::Move(Direction::Down));
}
Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. }
| Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. }
| Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. }
| Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
movement_command = Some(MovementCommand::Stop);
}
_ => {}
}
}

*world.write_resource() = movement_command;

// Update
i = (i + 1) % 255;
dispatcher.dispatch(&mut world);
world.maintain();

// Render
renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;

// Time management!
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
}

Ok(())
}
29 changes: 29 additions & 0 deletions src/physics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use specs::prelude::*;

use crate::components::*;

pub struct Physics;

impl<'a> System<'a> for Physics {
type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>);

fn run(&mut self, mut data: Self::SystemData) {
use self::Direction::*;
for (pos, vel) in (&mut data.0, &data.1).join() {
match vel.direction {
Left => {
pos.0 = pos.0.offset(-vel.speed, 0);
}
Right => {
pos.0 = pos.0.offset(vel.speed, 0);
}
Up => {
pos.0 = pos.0.offset(0, -vel.speed);
}
Down => {
pos.0 = pos.0.offset(0, vel.speed);
}
}
}
}
}
35 changes: 35 additions & 0 deletions src/renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect};
use sdl2::render::{Texture, WindowCanvas};
use specs::prelude::*;

use crate::components::*;

// Type alias for the data needed by the renderer
pub type SystemData<'a> = (ReadStorage<'a, Position>, ReadStorage<'a, Sprite>);

pub fn render(
canvas: &mut WindowCanvas,
background: Color,
textures: &[Texture],
data: SystemData,
) -> Result<(), String> {
canvas.set_draw_color(background);
canvas.clear();

let (width, height) = canvas.output_size()?;

for (pos, sprite) in (&data.0, &data.1).join() {
let current_frame = sprite.region;

// Treat the center of the screen as the (0, 0) coordinate
let screen_position = pos.0 + Point::new(width as i32 / 2, height as i32 / 2);
let screen_rect =
Rect::from_center(screen_position, current_frame.width(), current_frame.height());
canvas.copy(&textures[sprite.spritesheet], current_frame, screen_rect)?;
}

canvas.present();

Ok(())
}

0 comments on commit c52c193

Please sign in to comment.