add player movement with basic physics ecs system

Former-commit-id: f2e151971a42b25bfd1971311f5a06535a577007
This commit is contained in:
Imberflur 2019-03-01 22:48:30 -05:00
parent 993ee68037
commit 6f5f80f749
12 changed files with 226 additions and 27 deletions

View File

@ -7,10 +7,7 @@ use vek::*;
use threadpool; use threadpool;
// Project // Project
use common::{ use common::{comp::phys::Vel, state::State, terrain::TerrainChunk};
state::State,
terrain::TerrainChunk,
};
use world::World; use world::World;
#[derive(Debug)] #[derive(Debug)]
@ -21,6 +18,7 @@ pub enum Error {
pub struct Input { pub struct Input {
// TODO: Use this type to manage client input // TODO: Use this type to manage client input
pub move_vec: Vec2<f32>,
} }
pub struct Client { pub struct Client {
@ -61,6 +59,7 @@ impl Client {
// TODO: Get rid of this // TODO: Get rid of this
pub fn with_test_state(mut self) -> Self { pub fn with_test_state(mut self) -> Self {
self.chunk = Some(self.world.generate_chunk(Vec3::zero())); self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
self.player = Some(self.state.new_test_player());
self self
} }
@ -76,6 +75,11 @@ impl Client {
/// Get a mutable reference to the client's game state. /// Get a mutable reference to the client's game state.
pub fn state_mut(&mut self) -> &mut State { &mut self.state } pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get the player entity
pub fn player(&self) -> Option<EcsEntity> {
self.player
}
/// Get the current tick number. /// Get the current tick number.
pub fn get_tick(&self) -> u64 { pub fn get_tick(&self) -> u64 {
self.tick self.tick
@ -95,6 +99,27 @@ impl Client {
// 4) Go through the terrain update queue and apply all changes to the terrain // 4) Go through the terrain update queue and apply all changes to the terrain
// 5) Finish the tick, passing control of the main thread back to the frontend // 5) Finish the tick, passing control of the main thread back to the frontend
// (step 1)
if let Some(p) = self.player {
let vel = input.move_vec;
const MIN_LOOKING: f32 = 0.5;
const LEANING_FAC: f32 = 0.05;
let dir = Vec3::from([
// Rotation
match vel.magnitude() > MIN_LOOKING {
true => vel[0].atan2(vel[1]),
_ => 0.0,
},
// Lean
Vec2::new(vel[0], vel[1]).magnitude() * LEANING_FAC,
]);
// TODO: Set acceleration instead and adjust dir calculations accordingly
self.state.write_component(p, Vel(Vec3::from(vel)));
}
// Tick the client's LocalState (step 3) // Tick the client's LocalState (step 3)
self.state.tick(dt); self.state.tick(dt);

View File

@ -4,6 +4,7 @@ pub mod clock;
pub mod comp; pub mod comp;
pub mod figure; pub mod figure;
pub mod state; pub mod state;
pub mod sys;
pub mod terrain; pub mod terrain;
pub mod util; pub mod util;
pub mod volumes; pub mod volumes;

View File

@ -2,15 +2,12 @@
use std::time::Duration; use std::time::Duration;
// Library // Library
use specs::World as EcsWorld;
use shred::{Fetch, FetchMut}; use shred::{Fetch, FetchMut};
use specs::{Builder, Component, DispatcherBuilder, Entity as EcsEntity, World as EcsWorld};
use vek::*; use vek::*;
// Crate // Crate
use crate::{ use crate::{comp, sys, terrain::TerrainMap};
comp,
terrain::TerrainMap,
};
/// How much faster should an in-game day be compared to a real day? /// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this // TODO: Don't hard-code this
@ -22,6 +19,10 @@ struct TimeOfDay(f64);
/// A resource to store the tick (i.e: physics) time /// A resource to store the tick (i.e: physics) time
struct Time(f64); struct Time(f64);
/// A resource used to store the time since the last tick
#[derive(Default)]
pub struct DeltaTime(pub f64);
pub struct Changes { pub struct Changes {
pub new_chunks: Vec<Vec3<i32>>, pub new_chunks: Vec<Vec3<i32>>,
pub changed_chunks: Vec<Vec3<i32>>, pub changed_chunks: Vec<Vec3<i32>>,
@ -59,6 +60,7 @@ impl State {
// Register resources used by the ECS // Register resources used by the ECS
ecs_world.add_resource(TimeOfDay(0.0)); ecs_world.add_resource(TimeOfDay(0.0));
ecs_world.add_resource(Time(0.0)); ecs_world.add_resource(Time(0.0));
ecs_world.add_resource(DeltaTime(0.0));
ecs_world.add_resource(TerrainMap::new()); ecs_world.add_resource(TerrainMap::new());
// Register common components with the state // Register common components with the state
@ -70,15 +72,36 @@ impl State {
} }
} }
// TODO: Get rid of this
pub fn new_test_player(&mut self) -> EcsEntity {
self.ecs_world
.create_entity()
.with(comp::phys::Pos(Vec3::default()))
.with(comp::phys::Vel(Vec3::default()))
.with(comp::phys::Dir(Vec3::default()))
.build()
}
/// Write a component
pub fn write_component<C: Component>(&mut self, e: EcsEntity, c: C) {
let _ = self.ecs_world.write_storage().insert(e, c);
}
/// Get a reference to the internal ECS world /// Get a reference to the internal ECS world
pub fn ecs_world(&self) -> &EcsWorld { &self.ecs_world } pub fn ecs_world(&self) -> &EcsWorld {
&self.ecs_world
}
/// Get a reference to the `Changes` structure of the state. This contains /// Get a reference to the `Changes` structure of the state. This contains
/// information about state that has changed since the last game tick. /// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes { &self.changes } pub fn changes(&self) -> &Changes {
&self.changes
}
// TODO: Get rid of this since it shouldn't be needed // TODO: Get rid of this since it shouldn't be needed
pub fn changes_mut(&mut self) -> &mut Changes { &mut self.changes } pub fn changes_mut(&mut self) -> &mut Changes {
&mut self.changes
}
/// Get the current in-game time of day. /// Get the current in-game time of day.
/// ///
@ -95,12 +118,12 @@ impl State {
} }
/// Get a reference to this state's terrain. /// Get a reference to this state's terrain.
pub fn terrain<'a>(&'a self) -> Fetch<'a, TerrainMap> { pub fn terrain(&self) -> Fetch<TerrainMap> {
self.ecs_world.read_resource::<TerrainMap>() self.ecs_world.read_resource::<TerrainMap>()
} }
// TODO: Get rid of this since it shouldn't be needed // TODO: Get rid of this since it shouldn't be needed
pub fn terrain_mut<'a>(&'a mut self) -> FetchMut<'a, TerrainMap> { pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> {
self.ecs_world.write_resource::<TerrainMap>() self.ecs_world.write_resource::<TerrainMap>()
} }
@ -109,6 +132,17 @@ impl State {
// Change the time accordingly // Change the time accordingly
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR; self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR;
self.ecs_world.write_resource::<Time>().0 += dt.as_float_secs(); self.ecs_world.write_resource::<Time>().0 += dt.as_float_secs();
// Run systems to update the world
self.ecs_world.write_resource::<DeltaTime>().0 = dt.as_float_secs();
// Create and run dispatcher for ecs systems
let mut dispatch_builder = DispatcherBuilder::new();
sys::add_local_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel
dispatch_builder.build().dispatch(&self.ecs_world.res);
self.ecs_world.maintain();
} }
/// Clean up the state after a tick /// Clean up the state after a tick

11
common/src/sys/mod.rs Normal file
View File

@ -0,0 +1,11 @@
pub mod phys;
// External
use specs::DispatcherBuilder;
// System names
const MOVEMENT_SYS: &str = "movement_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(phys::MovementSys, MOVEMENT_SYS, &[]);
}

25
common/src/sys/phys.rs Normal file
View File

@ -0,0 +1,25 @@
// Library
use specs::{Join, Read, ReadStorage, System, WriteStorage};
// Crate
use crate::{
comp::phys::{Pos, Vel},
state::DeltaTime,
};
// Basic ECS physics system
pub struct MovementSys;
impl<'a> System<'a> for MovementSys {
type SystemData = (
WriteStorage<'a, Pos>,
ReadStorage<'a, Vel>,
Read<'a, DeltaTime>,
);
fn run(&mut self, (mut positions, velocities, dt): Self::SystemData) {
(&mut positions, &velocities)
.join() // this can be parallelized with par_join()
.for_each(|(pos, vel)| pos.0 += vel.0 * dt.0 as f32 * 100.0);
}
}

32
voxygen/src/key_state.rs Normal file
View File

@ -0,0 +1,32 @@
use vek::Vec2;
pub struct KeyState {
pub right: bool,
pub left: bool,
pub up: bool,
pub down: bool,
pub jump: bool,
}
impl KeyState {
pub fn new() -> KeyState {
KeyState {
right: false,
left: false,
up: false,
down: false,
jump: false,
}
}
pub fn dir_vec(&self) -> Vec2<f32> {
Vec2::<f32>::new(
if self.right { 1.0 } else { 0.0 } + if self.left { -1.0 } else { 0.0 },
if self.up { 1.0 } else { 0.0 } + if self.down { -1.0 } else { 0.0 },
)
}
pub fn jump(&self) -> bool {
self.jump
}
}

View File

@ -2,6 +2,7 @@
pub mod anim; pub mod anim;
pub mod error; pub mod error;
pub mod key_state;
pub mod menu; pub mod menu;
pub mod mesh; pub mod mesh;
pub mod render; pub mod render;

View File

@ -67,11 +67,14 @@ impl Vertex {
} }
impl Locals { impl Locals {
pub fn default() -> Self { pub fn new(model_mat: Mat4<f32>) -> Self {
Self { Self {
model_mat: arr_to_mat(Mat4::identity().into_col_array()), model_mat: arr_to_mat(model_mat.into_col_array()),
} }
} }
pub fn default() -> Self {
Self::new(Mat4::identity())
}
} }
impl BoneData { impl BoneData {
@ -82,9 +85,7 @@ impl BoneData {
} }
pub fn default() -> Self { pub fn default() -> Self {
Self { Self::new(Mat4::identity())
bone_mat: arr_to_mat(Mat4::identity().into_col_array()),
}
} }
} }

View File

@ -54,6 +54,8 @@ impl Camera {
/// Rotate the camera about its focus by the given delta, limiting the input accordingly. /// Rotate the camera about its focus by the given delta, limiting the input accordingly.
pub fn rotate_by(&mut self, delta: Vec3<f32>) { pub fn rotate_by(&mut self, delta: Vec3<f32>) {
self.ori += delta; self.ori += delta;
// Wrap camera roll
self.ori.x = self.ori.x % (2.0 * PI);
// Clamp camera pitch to the vertical limits // Clamp camera pitch to the vertical limits
self.ori.y = self.ori.y self.ori.y = self.ori.y
.min(PI / 2.0) .min(PI / 2.0)
@ -75,4 +77,7 @@ impl Camera {
pub fn get_aspect_ratio(&self) -> f32 { self.aspect } pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
/// Set the aspect ratio of the camera. /// Set the aspect ratio of the camera.
pub fn set_aspect_ratio(&mut self, aspect: f32) { self.aspect = aspect; } pub fn set_aspect_ratio(&mut self, aspect: f32) { self.aspect = aspect; }
/// Get the orientation of the camera
pub fn get_orientation(&self) -> Vec3<f32> { self.ori }
} }

View File

@ -7,8 +7,8 @@ use vek::*;
use dot_vox; use dot_vox;
// Project // Project
use common::figure::Segment;
use client::Client; use client::Client;
use common::{comp::phys::Pos as PosComp, figure::Segment};
// Crate // Crate
use crate::{ use crate::{
@ -138,6 +138,22 @@ impl Scene {
/// Maintain data such as GPU constant buffers, models, etc. To be called once per tick. /// Maintain data such as GPU constant buffers, models, etc. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
// Get player position
let player_pos = match client.player() {
Some(entity) => {
client
.state()
.ecs_world()
.read_storage::<PosComp>()
.get(entity)
.expect("There was no position component on the player entity!")
.0
}
None => Vec3::default(),
};
// Alter camera position to match player
self.camera.set_focus_pos(player_pos);
// Compute camera matrices // Compute camera matrices
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
@ -161,7 +177,15 @@ impl Scene {
&mut self.test_figure.skeleton, &mut self.test_figure.skeleton,
client.state().get_time(), client.state().get_time(),
); );
self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap();
// Calculate entity model matrix
let model_mat = Mat4::<f32>::translation_3d(player_pos);
//* Mat4::rotation_z(PI - entity.look_dir().x)
//* Mat4::rotation_x(entity.look_dir().y);
self.test_figure
.update_locals(renderer, FigureLocals::new(model_mat))
.unwrap();
self.test_figure.update_skeleton(renderer).unwrap(); self.test_figure.update_skeleton(renderer).unwrap();
} }

View File

@ -17,6 +17,7 @@ use crate::{
PlayState, PlayState,
PlayStateResult, PlayStateResult,
GlobalState, GlobalState,
key_state::KeyState,
window::{Event, Key}, window::{Event, Key},
render::Renderer, render::Renderer,
scene::Scene, scene::Scene,
@ -27,6 +28,7 @@ const FPS: u64 = 60;
pub struct SessionState { pub struct SessionState {
scene: Scene, scene: Scene,
client: Client, client: Client,
key_state: KeyState,
} }
/// Represents an active game session (i.e: one that is being played) /// Represents an active game session (i.e: one that is being played)
@ -38,6 +40,7 @@ impl SessionState {
// Create a scene for this session. The scene handles visible elements of the game world // Create a scene for this session. The scene handles visible elements of the game world
scene: Scene::new(renderer, &client), scene: Scene::new(renderer, &client),
client, client,
key_state: KeyState::new(),
} }
} }
} }
@ -48,7 +51,16 @@ const BG_COLOR: Rgba<f32> = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 };
impl SessionState { impl SessionState {
/// Tick the session (and the client attached to it) /// Tick the session (and the client attached to it)
pub fn tick(&mut self, dt: Duration) -> Result<(), Error> { pub fn tick(&mut self, dt: Duration) -> Result<(), Error> {
self.client.tick(client::Input {}, dt)?; // Calculate the movement input vector of the player from the current key presses and the camera direction
let ori = self.scene.camera().get_orientation();
let unit_vecs = (
Vec2::new(ori[0].cos(), -ori[0].sin()),
Vec2::new(ori[0].sin(), ori[0].cos()),
);
let dir_vec = self.key_state.dir_vec();
let move_vec = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
self.client.tick(client::Input { move_vec }, dt)?;
Ok(()) Ok(())
} }
@ -101,6 +113,16 @@ impl PlayState for SessionState {
Event::KeyDown(Key::ToggleCursor) => { Event::KeyDown(Key::ToggleCursor) => {
global_state.window.grab_cursor(!global_state.window.is_cursor_grabbed()); global_state.window.grab_cursor(!global_state.window.is_cursor_grabbed());
}, },
// Movement Key Pressed
Event::KeyDown(Key::MoveForward) => self.key_state.up = true,
Event::KeyDown(Key::MoveBack) => self.key_state.down = true,
Event::KeyDown(Key::MoveLeft) => self.key_state.left = true,
Event::KeyDown(Key::MoveRight) => self.key_state.right = true,
// Movement Key Released
Event::KeyUp(Key::MoveForward) => self.key_state.up = false,
Event::KeyUp(Key::MoveBack) => self.key_state.down = false,
Event::KeyUp(Key::MoveLeft) => self.key_state.left = false,
Event::KeyUp(Key::MoveRight) => self.key_state.right = false,
// Pass all other events to the scene // Pass all other events to the scene
event => { self.scene.handle_input_event(event); }, event => { self.scene.handle_input_event(event); },
}; };

View File

@ -2,6 +2,7 @@
use glutin; use glutin;
use gfx_window_glutin; use gfx_window_glutin;
use vek::*; use vek::*;
use std::collections::HashMap;
// Crate // Crate
use crate::{ use crate::{
@ -18,6 +19,7 @@ pub struct Window {
renderer: Renderer, renderer: Renderer,
window: glutin::GlWindow, window: glutin::GlWindow,
cursor_grabbed: bool, cursor_grabbed: bool,
key_map: HashMap<glutin::VirtualKeyCode, Key>,
} }
@ -46,6 +48,13 @@ impl Window {
&events_loop, &events_loop,
).map_err(|err| Error::BackendError(Box::new(err)))?; ).map_err(|err| Error::BackendError(Box::new(err)))?;
let mut key_map = HashMap::new();
key_map.insert(glutin::VirtualKeyCode::Escape, Key::ToggleCursor);
key_map.insert(glutin::VirtualKeyCode::W, Key::MoveForward);
key_map.insert(glutin::VirtualKeyCode::A, Key::MoveLeft);
key_map.insert(glutin::VirtualKeyCode::S, Key::MoveBack);
key_map.insert(glutin::VirtualKeyCode::D, Key::MoveRight);
let tmp = Ok(Self { let tmp = Ok(Self {
events_loop, events_loop,
renderer: Renderer::new( renderer: Renderer::new(
@ -56,6 +65,7 @@ impl Window {
)?, )?,
window, window,
cursor_grabbed: false, cursor_grabbed: false,
key_map,
}); });
tmp tmp
} }
@ -69,6 +79,7 @@ impl Window {
let cursor_grabbed = self.cursor_grabbed; let cursor_grabbed = self.cursor_grabbed;
let renderer = &mut self.renderer; let renderer = &mut self.renderer;
let window = &mut self.window; let window = &mut self.window;
let key_map = &self.key_map;
let mut events = vec![]; let mut events = vec![];
self.events_loop.poll_events(|event| match event { self.events_loop.poll_events(|event| match event {
@ -85,11 +96,13 @@ impl Window {
}, },
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode { glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode {
Some(glutin::VirtualKeyCode::Escape) => events.push(if input.state == glutin::ElementState::Pressed { Some(keycode) => match key_map.get(&keycode) {
Event::KeyDown(Key::ToggleCursor) Some(&key) => events.push(match input.state {
} else { glutin::ElementState::Pressed => Event::KeyDown(key),
Event::KeyUp(Key::ToggleCursor) _ => Event::KeyUp(key),
}), }),
_ => {},
},
_ => {}, _ => {},
}, },
_ => {}, _ => {},
@ -126,8 +139,13 @@ impl Window {
} }
/// Represents a key that the game recognises after keyboard mapping /// Represents a key that the game recognises after keyboard mapping
#[derive(Clone, Copy)]
pub enum Key { pub enum Key {
ToggleCursor, ToggleCursor,
MoveForward,
MoveBack,
MoveLeft,
MoveRight,
} }
/// Represents an incoming event from the window /// Represents an incoming event from the window