Skip to content

Commit

Permalink
Add a basic implementation of the Topplegrass creature
Browse files Browse the repository at this point in the history
Refer to issue amethyst#61
Topplegrass is explained here: https://community.amethyst.rs/t/evoli-creature-designs/814/10

Any entity with the Movement component intelligently avoids obstacles. However, not all entities that can move can steer; for example, Topplegrass is moved only by the wind. Therefore, add a new component tag AvoidObstaclesTag to all entities that are supposed to steer.

Add new resource Wind. Implement debug keyboard inputs to change wind direction and speed. Load initial wind vector from a config file. In order to load from file, pass the assets directory path to the loading state.

Add gravity system that affects entities with the FallingTag.

Add system that deletes Topplegrass entities when they leave the world bounds.
  • Loading branch information
Jazarro authored and Arjan Boschman committed Sep 4, 2019
1 parent 4425158 commit 90f876e
Show file tree
Hide file tree
Showing 23 changed files with 504 additions and 6 deletions.
3 changes: 3 additions & 0 deletions resources/assets/topplegrass.bin
Git LFS file not shown
3 changes: 3 additions & 0 deletions resources/assets/topplegrass.gltf
Git LFS file not shown
3 changes: 2 additions & 1 deletion resources/input.ron
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(
axes: {

"ChangeWindDirection": Emulated(pos: Key(J), neg: Key(L)),
"ChangeWindSpeed": Emulated(pos: Key(I), neg: Key(K)),
},
actions: {
"ToggleDebug": [
Expand Down
1 change: 1 addition & 0 deletions resources/prefabs/creatures/carnivore.ron
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Prefab (
),
),
intelligence_tag: (),
avoid_obstacles_tag: (),
perception: (
range: 3.0,
),
Expand Down
1 change: 1 addition & 0 deletions resources/prefabs/creatures/herbivore.ron
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Prefab (
),
),
intelligence_tag: (),
avoid_obstacles_tag: (),
perception: (
range: 2.5,
),
Expand Down
19 changes: 19 additions & 0 deletions resources/prefabs/creatures/topplegrass.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![enable(implicit_some)]
Prefab (
entities: [
(
data: (
name: (
name: "Topplegrass"
),
gltf: File("assets/Topplegrass.gltf", ()),
movement: (
velocity: [0.0, 0.0, 0.0],
max_movement_speed: 10.0,
),
despawn_when_out_of_bounds_tag: (),
topplegrass_tag: (),
),
),
],
)
6 changes: 6 additions & 0 deletions resources/wind.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(
// Initial wind speed.
// Speed can be altered during game using the debug controls.
// Values between about 1.5 and 5 seem to result in a nice, semi-realistic looking effect.
wind: [2.0, 0.0],
)
43 changes: 43 additions & 0 deletions src/components/creatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,45 @@ impl Component for RicochetTag {
type Storage = NullStorage<Self>;
}

/// Entities tagged with this Component (and of course a Transform and Movement) will actively
/// avoid obstacles by steering away from them.
/// The world bounds currently (v0.2.0) are the only obstacles.
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PrefabData)]
#[prefab(Component)]
pub struct AvoidObstaclesTag;

impl Component for AvoidObstaclesTag {
type Storage = NullStorage<Self>;
}


/// Required on Topplegrass, this is what gives it its toppling animation.
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PrefabData)]
#[prefab(Component)]
pub struct TopplegrassTag;

impl Component for TopplegrassTag {
type Storage = NullStorage<Self>;
}

/// Gives this tag to any entity that is falling and should be affected by gravity.
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PrefabData)]
#[prefab(Component)]
pub struct FallingTag;

impl Component for FallingTag {
type Storage = NullStorage<Self>;
}

/// Entities tagged with this Component will despawn as soon as their position is outside the world bounds.
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PrefabData)]
#[prefab(Component)]
pub struct DespawnWhenOutOfBoundsTag;

impl Component for DespawnWhenOutOfBoundsTag {
type Storage = NullStorage<Self>;
}

#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PrefabData)]
#[prefab(Component)]
pub struct IntelligenceTag;
Expand Down Expand Up @@ -96,4 +135,8 @@ pub struct CreaturePrefabData {
intelligence_tag: Option<IntelligenceTag>,
perception: Option<Perception>,
ricochet_tag: Option<RicochetTag>,
avoid_obstacles_tag: Option<AvoidObstaclesTag>,
despawn_when_out_of_bounds_tag: Option<DespawnWhenOutOfBoundsTag>,
topplegrass_tag: Option<TopplegrassTag>,
falling_tag: Option<FallingTag>,
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fn main() -> amethyst::Result<()> {

// Set up the core application.
let mut game: Application<GameData> =
CoreApplication::build(resources, LoadingState::default())?
CoreApplication::build(resources.clone(), LoadingState::new(resources))?
.with_frame_limit(FrameRateLimitStrategy::Sleep, 60)
.build(game_data)?;
game.run();
Expand Down
1 change: 1 addition & 0 deletions src/resources/experimental/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod spatial_grid;
pub mod wind;
25 changes: 25 additions & 0 deletions src/resources/experimental/wind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use amethyst::core::math::Vector2;
use serde::{Deserialize, Serialize};

/// Keeps track of the wind conditions in the world.
/// Currently, wind is represented by a 2D vector.
#[derive(Deserialize, Serialize)]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct Wind {
pub wind: Vector2<f32>,
}

impl Wind {
pub fn new(x: f32, y: f32) -> Wind {
Wind {
wind: Vector2::new(x, y),
}
}
}

impl Default for Wind {
fn default() -> Self {
Wind::new(0.0, 0.0)
}
}
11 changes: 11 additions & 0 deletions src/states/controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ impl ControlsState {
),
)
})
.chain(input_handler.bindings.axes().map(|axis| {
(
axis.clone(),
format!(
"{:?}",
input_handler
.bindings
.axis(axis)
),
)
}))
.collect::<Vec<(String, String)>>();
bindings.sort();
bindings
Expand Down
15 changes: 15 additions & 0 deletions src/states/loading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
resources::{
audio::initialise_audio,
prefabs::{initialize_prefabs, update_prefabs},
wind::*,
world_bounds::WorldBounds,
},
states::{main_game::MainGameState, menu::MenuState},
Expand All @@ -18,12 +19,23 @@ use amethyst::{
const SKIP_MENU_ARG: &str = "no_menu";

pub struct LoadingState {
config_path: String,
prefab_loading_progress: Option<ProgressCounter>,
}

impl Default for LoadingState {
fn default() -> Self {
LoadingState {
config_path: "".to_string(),
prefab_loading_progress: None,
}
}
}

impl LoadingState {
pub fn new(config_path: String) -> Self {
LoadingState {
config_path: config_path,
prefab_loading_progress: None,
}
}
Expand All @@ -40,6 +52,9 @@ impl SimpleState for LoadingState {
data.world.add_resource(DebugLines::new());
data.world
.add_resource(WorldBounds::new(-10.0, 10.0, -10.0, 10.0));
let wind_config_path = self.config_path.clone() + "/wind.ron";
let wind_config = Wind::load(wind_config_path);
data.world.add_resource(wind_config);
}

fn update(&mut self, data: &mut StateData<GameData>) -> SimpleTrans {
Expand Down
26 changes: 26 additions & 0 deletions src/states/main_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,30 @@ impl MainGameState {
"swarm_spawn",
&[],
)
.with(
topplegrass::TopplegrassSpawnSystem::default(),
"topplegrass_spawn_system",
&[],
)
.with(
topplegrass::TopplingSystem::default(),
"toppling_system",
&[],
).with(
gravity::GravitySystem::default(),
"gravity_system",
&[],
)
.with(
out_of_bounds::OutOfBoundsDespawnSystem::default(),
"out_of_bounds_despawn_system",
&[],
)
.with(
wind_control::DebugWindControlSystem::default(),
"wind_control_system",
&[],
)
.with(
swarm_behavior::SwarmBehaviorSystem::default(),
"swarm_behavior",
Expand Down Expand Up @@ -336,6 +360,8 @@ impl SimpleState for MainGameState {
.world
.write_resource::<EventChannel<spawner::CreatureSpawnEvent>>();
// TODO unfortunate naming here; plants are not creatures...OrganismSpawnEvent or just SpawnEvent?
// I would go for something more generic than OrganismSpawnEvent; for example,
// Topplegrass isn't really one organism, but more of a set of organisms, both dead and alive.
spawn_events.single_write(spawner::CreatureSpawnEvent {
creature_type: "Plant".to_string(),
entity: plant_entity,
Expand Down
9 changes: 6 additions & 3 deletions src/systems/behaviors/obstacle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use amethyst::{

use std::cmp::Ordering;

use crate::components::creatures::Movement;
use crate::components::creatures::{AvoidObstaclesTag, Movement};
use crate::resources::world_bounds::WorldBounds;
use crate::systems::behaviors::decision::Closest;

Expand Down Expand Up @@ -47,19 +47,22 @@ impl<'s> System<'s> for ClosestObstacleSystem {
ReadStorage<'s, Transform>,
ReadStorage<'s, Movement>,
ReadExpect<'s, WorldBounds>,
ReadStorage<'s, AvoidObstaclesTag>,
WriteStorage<'s, Closest<Obstacle>>,
);

fn run(
&mut self,
(entities, transforms, movements, world_bounds, mut closest_obstacle): Self::SystemData,
(entities, transforms, movements, world_bounds, avoid_obstacles, mut closest_obstacle): Self::SystemData,
) {
// Right now the only obstacles are the world bound walls, so it's
// safe to clear this out.
closest_obstacle.clear();

let threshold = 3.0f32.powi(2);
for (entity, transform, _) in (&entities, &transforms, &movements).join() {
for (entity, transform, _, _) in
(&entities, &transforms, &avoid_obstacles, &movements).join()
{
// Find the closest wall to this entity
let wall_dir = closest_wall(&transform.translation(), &world_bounds);
if wall_dir.magnitude_squared() < threshold {
Expand Down
3 changes: 3 additions & 0 deletions src/systems/behaviors/wander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ impl<'s> System<'s> for WanderSystem {
let desired_velocity = target - position;

movement.velocity += desired_velocity * delta_time;
// Quick and dirty fix to keep entities from wandering into the ground if they target
// an entity not on the same z-level as themselves.
movement.velocity.z = 0.0;

let change = 10.0;
if rng.gen::<bool>() {
Expand Down
26 changes: 26 additions & 0 deletions src/systems/experimental/gravity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use amethyst::{core::timing::Time, ecs::*};

use crate::{components::creatures::FallingTag, components::creatures::Movement};

/// Acceleration due to gravity.
const GRAVITY: f32 = 4.0;

/// Applies the force of gravity on all entities with the FallingTag.
/// Will remove the tag if an entity has reached the ground again.
#[derive(Default)]
pub struct GravitySystem;

impl<'s> System<'s> for GravitySystem {
type SystemData = (
WriteStorage<'s, Movement>,
ReadStorage<'s, FallingTag>,
Read<'s, Time>,
);

fn run(&mut self, (mut movements, falling_tags, time): Self::SystemData) {
for (movement, _) in (&mut movements, &falling_tags).join() {
//TODO: Add terminal velocity cap on falling speed.
movement.velocity.z = movement.velocity.z - GRAVITY * time.delta_seconds();
}
}
}
4 changes: 4 additions & 0 deletions src/systems/experimental/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pub mod out_of_bounds;
pub mod perception;
pub mod topplegrass;
pub mod wind_control;
pub mod gravity;
31 changes: 31 additions & 0 deletions src/systems/experimental/out_of_bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::resources::world_bounds::WorldBounds;
use amethyst::{core::transform::components::Transform, ecs::*};

use crate::components::creatures::DespawnWhenOutOfBoundsTag;

/// Deletes any entity tagged with DespawnWhenOutOfBoundsTag if they are detected to be outside
/// the world bounds.
#[derive(Default)]
pub struct OutOfBoundsDespawnSystem;

impl<'s> System<'s> for OutOfBoundsDespawnSystem {
type SystemData = (
Entities<'s>,
ReadStorage<'s, Transform>,
ReadStorage<'s, DespawnWhenOutOfBoundsTag>,
ReadExpect<'s, WorldBounds>,
);

fn run(&mut self, (entities, locals, tags, bounds): Self::SystemData) {
for (entity, local, _) in (&*entities, &locals, &tags).join() {
let pos = local.translation();
if pos.x > bounds.right
|| pos.x < bounds.left
|| pos.y > bounds.top
|| pos.y < bounds.bottom
{
let _ = entities.delete(entity);
}
}
}
}
Loading

0 comments on commit 90f876e

Please sign in to comment.