Improve organization of controls

This commit is contained in:
timokoesters 2019-06-09 21:33:20 +02:00
parent 69cb2ed84f
commit b947d78dac
No known key found for this signature in database
GPG Key ID: CD80BE9AAEE78097
11 changed files with 251 additions and 180 deletions

View File

@ -4,12 +4,18 @@ use vek::*;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Respawning;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MoveDir(pub Vec2<f32>);
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Attacking {
pub time: f32,
pub applied: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct OnGround;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Jumping;
@ -29,10 +35,18 @@ impl Attacking {
}
}
impl Component for MoveDir {
type Storage = VecStorage<Self>;
}
impl Component for Attacking {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
impl Component for OnGround {
type Storage = NullStorage<Self>;
}
impl Component for Jumping {
type Storage = NullStorage<Self>;
}

View File

@ -20,6 +20,8 @@ pub use controller::Controller;
pub use inputs::Attacking;
pub use inputs::Gliding;
pub use inputs::Jumping;
pub use inputs::MoveDir;
pub use inputs::OnGround;
pub use inputs::Respawning;
pub use player::Player;
pub use stats::Dying;

View File

@ -109,6 +109,8 @@ impl State {
ecs.register::<comp::phys::Pos>();
ecs.register::<comp::phys::Vel>();
ecs.register::<comp::phys::Ori>();
ecs.register::<comp::MoveDir>();
ecs.register::<comp::OnGround>();
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Controller>();

View File

@ -1,29 +0,0 @@
use crate::{comp::Attacking, state::DeltaTime};
use specs::{Entities, Join, Read, System, WriteStorage};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
WriteStorage<'a, Attacking>,
);
fn run(&mut self, (entities, dt, mut attacks): Self::SystemData) {
for attack in (&mut attacks).join() {
attack.time += dt.0;
}
let finished_attacks = (&entities, &mut attacks)
.join()
.filter(|(_, a)| a.time > 0.50) // TODO: constant
.map(|(e, _)| e)
.collect::<Vec<_>>();
for entity in finished_attacks {
attacks.remove(entity);
}
}
}

View File

@ -4,9 +4,8 @@ use rand::{seq::SliceRandom, thread_rng};
use specs::{Entities, Join, ReadStorage, System, WriteStorage};
use vek::*;
// Basic ECS AI agent system
/// This system will allow NPCs to modify their controller
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,

View File

@ -1,15 +1,68 @@
use crate::{comp::AnimationInfo, state::DeltaTime};
use specs::{Join, Read, System, WriteStorage};
use crate::{
comp::{phys, Animation, AnimationInfo, Attacking, Gliding, Jumping, OnGround},
state::DeltaTime,
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
// Basic ECS AI agent system
/// This system will apply the animation that fits best to the users actions
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (Read<'a, DeltaTime>, WriteStorage<'a, AnimationInfo>);
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
ReadStorage<'a, phys::Vel>,
ReadStorage<'a, OnGround>,
ReadStorage<'a, Jumping>,
ReadStorage<'a, Gliding>,
ReadStorage<'a, Attacking>,
WriteStorage<'a, AnimationInfo>,
);
fn run(&mut self, (dt, mut animation_infos): Self::SystemData) {
for mut animation_info in (&mut animation_infos).join() {
fn run(
&mut self,
(entities, dt, velocities, on_grounds, jumpings, glidings, attackings, mut animation_infos): Self::SystemData,
) {
for (entity, vel, on_ground, jumping, gliding, attacking, mut animation_info) in (
&entities,
&velocities,
on_grounds.maybe(),
jumpings.maybe(),
glidings.maybe(),
attackings.maybe(),
&mut animation_infos,
)
.join()
{
animation_info.time += dt.0 as f64;
let moving = vel.0.magnitude() > 3.0;
fn impossible_animation() -> Animation {
warn!("Impossible animation");
Animation::Idle
}
let animation = match (
on_ground.is_some(),
moving,
attacking.is_some(),
gliding.is_some(),
) {
(true, false, false, false) => Animation::Idle,
(true, true, false, false) => Animation::Run,
(false, _, false, false) => Animation::Jump,
(_, _, false, true) => Animation::Gliding,
(_, _, true, false) => Animation::Attack,
(_, _, true, true) => impossible_animation(),
};
let last = animation_info.clone();
let changed = last.animation != animation;
*animation_info = AnimationInfo {
animation,
time: if changed { 0.0 } else { last.time },
changed,
};
}
}
}

View File

@ -1,7 +1,8 @@
use crate::{
comp::{
phys::{ForceUpdate, Ori, Pos, Vel},
Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, Stats,
Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, MoveDir,
OnGround, Respawning, Stats,
},
state::{DeltaTime, Uid},
terrain::TerrainMap,
@ -11,16 +12,7 @@ use log::warn;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
const HUMANOID_ACCEL: f32 = 100.0;
const HUMANOID_SPEED: f32 = 500.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
const GLIDE_ACCEL: f32 = 15.0;
const GLIDE_SPEED: f32 = 45.0;
// Gravity is 9.81 * 4, so this makes gravity equal to .15
const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95;
/// This system is responsible for validating controller inputs
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
@ -32,8 +24,11 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
WriteStorage<'a, MoveDir>,
WriteStorage<'a, OnGround>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, Respawning>,
WriteStorage<'a, Gliding>,
WriteStorage<'a, ForceUpdate>,
);
@ -49,65 +44,64 @@ impl<'a> System<'a> for Sys {
positions,
mut velocities,
mut orientations,
mut jumps,
mut attacks,
mut glides,
mut move_dirs,
mut on_grounds,
mut jumpings,
mut attackings,
mut respawns,
mut glidings,
mut force_updates,
): Self::SystemData,
) {
for (entity, controller, stats, pos, mut vel, mut ori) in (
for (entity, controller, stats, pos, mut vel, mut ori, on_ground) in (
&entities,
&controllers,
&stats,
&positions,
&mut velocities,
&mut orientations,
on_grounds.maybe(),
)
.join()
{
if stats.is_dead {
// Respawn
if controller.respawn {
respawns.insert(entity, Respawning);
}
continue;
}
let on_ground = terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
.unwrap_or(false)
&& vel.0.z <= 0.0;
let gliding = controller.glide && vel.0.z < 0.0;
let move_dir = if controller.move_dir.magnitude() > 1.0 {
controller.move_dir.normalized()
// Glide
if controller.glide && on_ground.is_none() && attackings.get(entity).is_none() {
glidings.insert(entity, Gliding);
} else {
controller.move_dir
};
if on_ground {
// Move player according to move_dir
if vel.0.magnitude() < HUMANOID_SPEED {
vel.0 += Vec2::broadcast(dt.0) * move_dir * HUMANOID_ACCEL;
}
// Jump
if controller.jump && vel.0.z <= 0.0 {
vel.0.z = HUMANOID_JUMP_ACCEL;
}
} else if gliding && vel.0.magnitude() < GLIDE_SPEED {
let anti_grav = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2;
vel.0.z += dt.0 * anti_grav * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0);
vel.0 += Vec2::broadcast(dt.0) * move_dir * GLIDE_ACCEL;
} else if vel.0.magnitude() < HUMANOID_AIR_SPEED {
vel.0 += Vec2::broadcast(dt.0) * move_dir * HUMANOID_AIR_ACCEL;
glidings.remove(entity);
}
// Set direction based on velocity
if vel.0.magnitude_squared() != 0.0 {
ori.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0);
}
// Move dir
move_dirs.insert(
entity,
MoveDir(if controller.move_dir.magnitude() > 1.0 {
controller.move_dir.normalized()
} else {
controller.move_dir
}),
);
// Attack
if controller.attack && attacks.get(entity).is_none() {
attacks.insert(entity, Attacking::start());
if controller.attack
&& attackings.get(entity).is_none()
&& glidings.get(entity).is_none()
{
attackings.insert(entity, Attacking::start());
}
// Jump
if on_grounds.get(entity).is_some() && controller.jump && vel.0.z <= 0.0 {
jumpings.insert(entity, Jumping);
} else {
jumpings.remove(entity);
}
}
}

View File

@ -1,7 +1,8 @@
use crate::{
comp::{
phys::{ForceUpdate, Ori, Pos, Vel},
Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, Stats,
Animation, AnimationInfo, Attacking, Gliding, HealthSource, Jumping, MoveDir, OnGround,
Respawning, Stats,
},
state::{DeltaTime, Uid},
terrain::TerrainMap,
@ -11,7 +12,17 @@ use log::warn;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
// Basic ECS AI agent system
const HUMANOID_ACCEL: f32 = 100.0;
const HUMANOID_SPEED: f32 = 500.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
const GLIDE_ACCEL: f32 = 15.0;
const GLIDE_SPEED: f32 = 45.0;
// Gravity is 9.81 * 4, so this makes gravity equal to .15
const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95;
/// This system is responsible for handling accepted inputs like moving or attacking
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
@ -20,11 +31,13 @@ impl<'a> System<'a> for Sys {
Read<'a, DeltaTime>,
ReadExpect<'a, TerrainMap>,
ReadStorage<'a, Pos>,
ReadStorage<'a, OnGround>,
ReadStorage<'a, MoveDir>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
WriteStorage<'a, AnimationInfo>,
WriteStorage<'a, Stats>,
ReadStorage<'a, Controller>,
WriteStorage<'a, Respawning>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Gliding>,
WriteStorage<'a, Attacking>,
@ -39,88 +52,99 @@ impl<'a> System<'a> for Sys {
dt,
terrain,
positions,
on_grounds,
move_dirs,
mut velocities,
mut orientations,
mut animation_infos,
mut stats,
controllers,
mut jumps,
glides,
mut attacks,
mut respawnings,
mut jumpings,
glidings,
mut attackings,
mut force_updates,
): Self::SystemData,
) {
for (entity, pos, controller, stats, mut ori, mut vel) in (
// Attacks
(&entities, &uids, &positions, &orientations, &mut attackings)
.join()
.filter_map(|(entity, uid, pos, ori, mut attacking)| {
if !attacking.applied {
// Go through all other entities
for (b, pos_b, stat_b, mut vel_b) in
(&entities, &positions, &mut stats, &mut velocities).join()
{
// Check if it is a hit
if entity != b
&& !stat_b.is_dead
&& pos.0.distance_squared(pos_b.0) < 50.0
&& ori.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
// Deal damage
stat_b.hp.change_by(-10, HealthSource::Attack { by: *uid }); // TODO: variable damage and weapon
vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0;
vel_b.0.z = 15.0;
if let Err(err) = force_updates.insert(b, ForceUpdate) {
warn!("Inserting ForceUpdate for an entity failed: {:?}", err);
}
}
}
attacking.applied = true;
}
if attacking.time > 0.5 {
Some(entity)
} else {
attacking.time += dt.0;
None
}
})
.collect::<Vec<_>>()
.into_iter()
.for_each(|e| {
attackings.remove(e);
});
// Apply movement inputs
for (entity, mut vel, mut ori, on_ground, move_dir, jumping, gliding) in (
&entities,
&positions,
&controllers,
&stats,
&mut orientations,
&mut velocities,
&mut orientations,
on_grounds.maybe(),
move_dirs.maybe(),
jumpings.maybe(),
glidings.maybe(),
)
.join()
{
let on_ground = terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
.unwrap_or(false)
&& vel.0.z <= 0.0;
let animation = if on_ground {
if controller.move_dir.magnitude() > 0.01 {
Animation::Run
} else if attacks.get(entity).is_some() {
Animation::Attack
} else {
Animation::Idle
}
} else if controller.glide {
Animation::Gliding
} else {
Animation::Jump
};
let last = animation_infos
.get_mut(entity)
.cloned()
.unwrap_or(AnimationInfo::default());
let changed = last.animation != animation;
if let Err(err) = animation_infos.insert(
entity,
AnimationInfo {
animation,
time: if changed { 0.0 } else { last.time },
changed,
},
) {
warn!("Inserting AnimationInfo for an entity failed: {:?}", err);
}
}
for (entity, &uid, pos, ori, attacking) in
(&entities, &uids, &positions, &orientations, &mut attacks).join()
{
if !attacking.applied {
for (b, pos_b, stat_b, mut vel_b) in
(&entities, &positions, &mut stats, &mut velocities).join()
{
// Check if it is a hit
if entity != b
&& !stat_b.is_dead
&& pos.0.distance_squared(pos_b.0) < 50.0
&& ori.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
// Deal damage
stat_b.hp.change_by(-10, HealthSource::Attack { by: uid }); // TODO: variable damage and weapon
vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0;
vel_b.0.z = 15.0;
if let Err(err) = force_updates.insert(b, ForceUpdate) {
warn!("Inserting ForceUpdate for an entity failed: {:?}", err);
// Move player according to move_dir
if let Some(move_dir) = move_dir {
vel.0 += Vec2::broadcast(dt.0)
* move_dir.0
* match (on_ground.is_some(), gliding.is_some()) {
(true, false) if vel.0.magnitude() < HUMANOID_SPEED => HUMANOID_ACCEL,
(false, true) if vel.0.magnitude() < GLIDE_SPEED => GLIDE_ACCEL,
(false, false) if vel.0.magnitude() < HUMANOID_AIR_SPEED => {
HUMANOID_AIR_ACCEL
}
}
}
attacking.applied = true;
_ => 0.0,
};
}
// Jump
if jumping.is_some() {
vel.0.z = HUMANOID_JUMP_ACCEL;
}
// Glide
if gliding.is_some() && vel.0.magnitude() < GLIDE_SPEED && vel.0.z < 0.0 {
let anti_grav = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2;
vel.0.z += dt.0 * anti_grav * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0);
}
// Set direction based on velocity
if vel.0.magnitude_squared() != 0.0 {
ori.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0);
}
}
}

View File

@ -1,4 +1,3 @@
pub mod actions;
pub mod agent;
pub mod animation;
pub mod controller;
@ -12,17 +11,15 @@ use specs::DispatcherBuilder;
// System names
const AGENT_SYS: &str = "agent_sys";
const CONTROLLER_SYS: &str = "controller_sys";
const INPUTS_SYS: &str = "inputs_sys";
const ACTIONS_SYS: &str = "actions_sys";
const PHYS_SYS: &str = "phys_sys";
const INPUTS_SYS: &str = "inputs_sys";
const ANIMATION_SYS: &str = "animation_sys";
const STATS_SYS: &str = "stats_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[]);
dispatch_builder.add(actions::Sys, ACTIONS_SYS, &[]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[]);
dispatch_builder.add(inputs::Sys, INPUTS_SYS, &[]);
dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[INPUTS_SYS]);

View File

@ -1,18 +1,15 @@
use crate::{
comp::{
phys::{Pos, Vel},
Stats,
OnGround, Stats,
},
state::DeltaTime,
terrain::TerrainMap,
vol::{ReadVol, Vox},
};
use specs::{Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
// Basic ECS physics system
pub struct Sys;
const GRAVITY: f32 = 9.81 * 4.0;
const FRIC_GROUND: f32 = 0.15;
const FRIC_AIR: f32 = 0.015;
@ -38,34 +35,53 @@ fn integrate_forces(dt: f32, mut lv: Vec3<f32>, damp: f32) -> Vec3<f32> {
lv
}
/// This system applies forces and calculates new positions and velocities
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>,
ReadStorage<'a, Stats>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, OnGround>,
);
fn run(&mut self, (terrain, dt, stats, mut positions, mut velocities): Self::SystemData) {
for (stats, pos, vel) in (&stats, &mut positions, &mut velocities).join() {
fn run(
&mut self,
(entities, terrain, dt, stats, mut positions, mut velocities, mut on_grounds): Self::SystemData,
) {
for (entity, stats, pos, vel) in (&entities, &stats, &mut positions, &mut velocities).join()
{
// Disable while dead TODO: Replace with client states
if stats.is_dead {
continue;
}
let on_ground = terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
.unwrap_or(false)
&& vel.0.z <= 0.0;
// Movement
pos.0 += vel.0 * dt.0;
// Update OnGround component
if terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
.unwrap_or(false)
&& vel.0.z <= 0.0
{
on_grounds.insert(entity, OnGround);
} else {
on_grounds.remove(entity);
}
// Integrate forces
// Friction is assumed to be a constant dependent on location
let friction = 50.0 * if on_ground { FRIC_GROUND } else { FRIC_AIR };
let friction = 50.0
* if on_grounds.get(entity).is_some() {
FRIC_GROUND
} else {
FRIC_AIR
};
vel.0 = integrate_forces(dt.0, vel.0, friction);
// Basic collision with terrain

View File

@ -5,9 +5,8 @@ use crate::{
use log::warn;
use specs::{Entities, Join, Read, System, WriteStorage};
// Basic ECS AI agent system
/// This system kills players
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,