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;
// Project
use common::{
state::State,
terrain::TerrainChunk,
};
use common::{comp::phys::Vel, state::State, terrain::TerrainChunk};
use world::World;
#[derive(Debug)]
@ -21,6 +18,7 @@ pub enum Error {
pub struct Input {
// TODO: Use this type to manage client input
pub move_vec: Vec2<f32>,
}
pub struct Client {
@ -61,6 +59,7 @@ impl Client {
// TODO: Get rid of this
pub fn with_test_state(mut self) -> Self {
self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
self.player = Some(self.state.new_test_player());
self
}
@ -76,6 +75,11 @@ impl Client {
/// Get a mutable reference to the client's game 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.
pub fn get_tick(&self) -> u64 {
self.tick
@ -95,6 +99,27 @@ impl Client {
// 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
// (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)
self.state.tick(dt);

View File

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

View File

@ -2,15 +2,12 @@
use std::time::Duration;
// Library
use specs::World as EcsWorld;
use shred::{Fetch, FetchMut};
use specs::{Builder, Component, DispatcherBuilder, Entity as EcsEntity, World as EcsWorld};
use vek::*;
// Crate
use crate::{
comp,
terrain::TerrainMap,
};
use crate::{comp, sys, terrain::TerrainMap};
/// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this
@ -22,6 +19,10 @@ struct TimeOfDay(f64);
/// A resource to store the tick (i.e: physics) time
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 new_chunks: Vec<Vec3<i32>>,
pub changed_chunks: Vec<Vec3<i32>>,
@ -59,6 +60,7 @@ impl State {
// Register resources used by the ECS
ecs_world.add_resource(TimeOfDay(0.0));
ecs_world.add_resource(Time(0.0));
ecs_world.add_resource(DeltaTime(0.0));
ecs_world.add_resource(TerrainMap::new());
// 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
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
/// 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
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.
///
@ -95,12 +118,12 @@ impl State {
}
/// 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>()
}
// 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>()
}
@ -109,6 +132,17 @@ impl State {
// Change the time accordingly
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();
// 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

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 error;
pub mod key_state;
pub mod menu;
pub mod mesh;
pub mod render;

View File

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

View File

@ -54,6 +54,8 @@ impl Camera {
/// Rotate the camera about its focus by the given delta, limiting the input accordingly.
pub fn rotate_by(&mut self, delta: Vec3<f32>) {
self.ori += delta;
// Wrap camera roll
self.ori.x = self.ori.x % (2.0 * PI);
// Clamp camera pitch to the vertical limits
self.ori.y = self.ori.y
.min(PI / 2.0)
@ -75,4 +77,7 @@ impl Camera {
pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
/// Set the aspect ratio of the camera.
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;
// Project
use common::figure::Segment;
use client::Client;
use common::{comp::phys::Pos as PosComp, figure::Segment};
// Crate
use crate::{
@ -138,6 +138,22 @@ impl Scene {
/// 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) {
// 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
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
@ -161,7 +177,15 @@ impl Scene {
&mut self.test_figure.skeleton,
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();
}

View File

@ -17,6 +17,7 @@ use crate::{
PlayState,
PlayStateResult,
GlobalState,
key_state::KeyState,
window::{Event, Key},
render::Renderer,
scene::Scene,
@ -27,6 +28,7 @@ const FPS: u64 = 60;
pub struct SessionState {
scene: Scene,
client: Client,
key_state: KeyState,
}
/// 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
scene: Scene::new(renderer, &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 {
/// Tick the session (and the client attached to it)
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(())
}
@ -101,6 +113,16 @@ impl PlayState for SessionState {
Event::KeyDown(Key::ToggleCursor) => {
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
event => { self.scene.handle_input_event(event); },
};

View File

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