Added basic agent AI

Former-commit-id: bb17edc8f2027c0c63c6a3ef0fc80c7a68c9aa05
This commit is contained in:
Joshua Barretto 2019-04-16 22:06:33 +01:00
parent 3fa8861e82
commit 2c650f9cff
11 changed files with 140 additions and 11 deletions

1
Cargo.lock generated
View File

@ -1902,6 +1902,7 @@ dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
"mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"shred 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -154,10 +154,11 @@ impl Client {
self.state.read_component_cloned::<comp::phys::Vel>(self.player),
self.state.read_component_cloned::<comp::phys::Dir>(self.player),
) {
self.state.write_component(self.player, comp::phys::Vel(vel.0 + input.move_dir * 2.0 - vel.0.map(|e| e * e.abs() + e) * 0.03));
self.state.write_component(self.player, comp::Control {
move_dir: input.move_dir,
});
if input.move_dir.magnitude() > 0.01 {
self.state.write_component(self.player, comp::phys::Dir(vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0)));
self.state.write_component(self.player, comp::Animation::Run);
} else {
self.state.write_component(self.player, comp::Animation::Idle);

View File

@ -18,3 +18,4 @@ serde = "1.0"
serde_derive = "1.0"
bincode = "1.0"
log = "0.4"
rand = "0.5"

28
common/src/comp/agent.rs Normal file
View File

@ -0,0 +1,28 @@
use specs::{Component, VecStorage};
use vek::*;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum Agent {
Wanderer(Vec2<f32>),
}
impl Component for Agent {
type Storage = VecStorage<Self>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Control {
pub move_dir: Vec2<f32>,
}
impl Default for Control {
fn default() -> Self {
Self {
move_dir: Vec2::zero(),
}
}
}
impl Component for Control {
type Storage = VecStorage<Self>;
}

View File

@ -1,8 +1,10 @@
pub mod agent;
pub mod character;
pub mod player;
pub mod phys;
// Reexports
pub use agent::{Agent, Control};
pub use character::Character;
pub use player::Player;
pub use character::Animation;

View File

@ -101,6 +101,8 @@ impl State {
ecs.internal_mut().register::<comp::phys::Vel>();
ecs.internal_mut().register::<comp::phys::Dir>();
ecs.internal_mut().register::<comp::Animation>();
ecs.internal_mut().register::<comp::Agent>();
ecs.internal_mut().register::<comp::Control>();
// Register resources used by the ECS
ecs.internal_mut().add_resource(TimeOfDay(0.0));

34
common/src/sys/agent.rs Normal file
View File

@ -0,0 +1,34 @@
// Library
use specs::{Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::comp::{Agent, Control, phys::Pos};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
WriteStorage<'a, Agent>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Control>,
);
fn run(&mut self, (mut agents, pos, mut controls): Self::SystemData) {
for (mut agent, pos, mut control) in (&mut agents, &pos, &mut controls).join() {
match agent {
Agent::Wanderer(bearing) => {
*bearing += Vec2::new(
rand::random::<f32>().fract() - 0.5,
rand::random::<f32>().fract() - 0.5,
) - *bearing * 0.05 - pos.0 * 0.001;
if bearing.magnitude_squared() != 0.0 {
control.move_dir = bearing.normalized();
}
},
}
}
}
}

28
common/src/sys/control.rs Normal file
View File

@ -0,0 +1,28 @@
// Library
use specs::{Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::comp::{Control, phys::{Pos, Vel, Dir}};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
WriteStorage<'a, Vel>,
WriteStorage<'a, Dir>,
ReadStorage<'a, Control>,
);
fn run(&mut self, (mut vels, mut dirs, controls): Self::SystemData) {
for (mut vel, mut dir, control) in (&mut vels, &mut dirs, &controls).join() {
// TODO: Don't hard-code this
vel.0 = vel.0 + control.move_dir * 2.0 - vel.0.map(|e| e * e.abs() + e) * 0.03;
if control.move_dir.magnitude() > 0.01 {
dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0) ;
}
}
}
}

View File

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

View File

@ -8,9 +8,9 @@ use crate::{
};
// Basic ECS physics system
pub struct MovementSys;
pub struct Sys;
impl<'a> System<'a> for MovementSys {
impl<'a> System<'a> for Sys {
type SystemData = (
WriteStorage<'a, Pos>,
ReadStorage<'a, Vel>,

View File

@ -13,7 +13,7 @@ use common::{
comp,
msg::{ClientMsg, ServerMsg},
net::PostOffice,
state::State,
state::{State, Uid},
terrain::TerrainChunk,
};
use specs::{
@ -52,8 +52,11 @@ impl Server {
pub fn new() -> Result<Self, Error> {
let (chunk_tx, chunk_rx) = mpsc::channel();
Ok(Self {
state: State::new(),
let mut state = State::new();
state.ecs_mut().internal_mut().register::<comp::phys::ForceUpdate>();
let mut this = Self {
state,
world: World::new(),
postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?,
@ -65,7 +68,17 @@ impl Server {
chunk_tx,
chunk_rx,
pending_chunks: HashSet::new(),
})
};
for i in 0..4 {
this.create_character(comp::Character::test())
.with(comp::Agent::Wanderer(Vec2::zero()))
.with(comp::Control::default())
.with(comp::Animation::Run)
.build();
}
Ok(this)
}
/// Get a reference to the server's game state.
@ -90,6 +103,19 @@ impl Server {
&mut self.world
}
/// Build a non-player character
#[allow(dead_code)]
pub fn create_character(&mut self, character: comp::Character) -> EcsEntityBuilder {
self.state
.ecs_mut()
.create_entity_synced()
.with(comp::phys::Pos(Vec3::zero()))
.with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::zero()))
.with(character)
.with(comp::Animation::Idle)
}
/// Execute a single server tick, handle input and update the game state by the given duration
#[allow(dead_code)]
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
@ -219,7 +245,7 @@ impl Server {
state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
if let Some(character) = character {
state.write_component(entity, character);
}
client.state = ClientState::Connected;
@ -339,7 +365,7 @@ impl Server {
};
match force_update {
Some(_) => self.clients.notify_connected(msg),
None => self.clients.notify_connected_except(entity, msg),