diff --git a/resources/assets/topplegrass.bin b/resources/assets/topplegrass.bin new file mode 100644 index 0000000..b83d70f --- /dev/null +++ b/resources/assets/topplegrass.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4b092a6d1c826d11a38627f82b9d8823bcfdfb9da39b7a5be8aef40a8d576df +size 227976 diff --git a/resources/assets/topplegrass.gltf b/resources/assets/topplegrass.gltf new file mode 100644 index 0000000..3521040 --- /dev/null +++ b/resources/assets/topplegrass.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc85a2e0ed4de51f9ffc9111afde1ede24154e101fef4579f39e2e608e72cd48 +size 5040 diff --git a/resources/input.ron b/resources/input.ron index 0876c3d..a6b39b1 100644 --- a/resources/input.ron +++ b/resources/input.ron @@ -1,6 +1,7 @@ ( axes: { - + "ChangeWindDirection": Emulated(pos: Key(J), neg: Key(L)), + "ChangeWindSpeed": Emulated(pos: Key(I), neg: Key(K)), }, actions: { "ToggleDebug": [ diff --git a/resources/prefabs/creatures/carnivore.ron b/resources/prefabs/creatures/carnivore.ron index 155ec13..f8c5fe4 100644 --- a/resources/prefabs/creatures/carnivore.ron +++ b/resources/prefabs/creatures/carnivore.ron @@ -47,6 +47,7 @@ Prefab ( ), ), intelligence_tag: (), + avoid_obstacles_tag: (), perception: ( range: 3.0, ), diff --git a/resources/prefabs/creatures/herbivore.ron b/resources/prefabs/creatures/herbivore.ron index 55ffa05..48b6999 100644 --- a/resources/prefabs/creatures/herbivore.ron +++ b/resources/prefabs/creatures/herbivore.ron @@ -47,6 +47,7 @@ Prefab ( ), ), intelligence_tag: (), + avoid_obstacles_tag: (), perception: ( range: 2.5, ), diff --git a/resources/prefabs/creatures/topplegrass.ron b/resources/prefabs/creatures/topplegrass.ron new file mode 100644 index 0000000..3939c85 --- /dev/null +++ b/resources/prefabs/creatures/topplegrass.ron @@ -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: (), + ), + ), + ], +) diff --git a/resources/wind.ron b/resources/wind.ron new file mode 100644 index 0000000..8e104b6 --- /dev/null +++ b/resources/wind.ron @@ -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], +) diff --git a/src/components/creatures.rs b/src/components/creatures.rs index 568444c..72b46c4 100644 --- a/src/components/creatures.rs +++ b/src/components/creatures.rs @@ -33,6 +33,45 @@ impl Component for RicochetTag { type Storage = NullStorage; } +/// 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; +} + + +/// 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; +} + +/// 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; +} + +/// 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; +} + #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PrefabData)] #[prefab(Component)] pub struct IntelligenceTag; @@ -96,4 +135,8 @@ pub struct CreaturePrefabData { intelligence_tag: Option, perception: Option, ricochet_tag: Option, + avoid_obstacles_tag: Option, + despawn_when_out_of_bounds_tag: Option, + topplegrass_tag: Option, + falling_tag: Option, } diff --git a/src/main.rs b/src/main.rs index 264cfb6..c4375d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,7 +97,7 @@ fn main() -> amethyst::Result<()> { // Set up the core application. let mut game: Application = - CoreApplication::build(resources, LoadingState::default())? + CoreApplication::build(resources.clone(), LoadingState::new(resources))? .with_frame_limit(FrameRateLimitStrategy::Sleep, 60) .build(game_data)?; game.run(); diff --git a/src/resources/experimental/mod.rs b/src/resources/experimental/mod.rs index f79489c..db27017 100644 --- a/src/resources/experimental/mod.rs +++ b/src/resources/experimental/mod.rs @@ -1 +1,2 @@ pub mod spatial_grid; +pub mod wind; diff --git a/src/resources/experimental/wind.rs b/src/resources/experimental/wind.rs new file mode 100644 index 0000000..a38b8f5 --- /dev/null +++ b/src/resources/experimental/wind.rs @@ -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, +} + +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) + } +} diff --git a/src/states/controls.rs b/src/states/controls.rs index 130adf9..2c84bc3 100644 --- a/src/states/controls.rs +++ b/src/states/controls.rs @@ -36,6 +36,17 @@ impl ControlsState { ), ) }) + .chain(input_handler.bindings.axes().map(|axis| { + ( + axis.clone(), + format!( + "{:?}", + input_handler + .bindings + .axis(axis) + ), + ) + })) .collect::>(); bindings.sort(); bindings diff --git a/src/states/loading.rs b/src/states/loading.rs index 96805bb..5719887 100644 --- a/src/states/loading.rs +++ b/src/states/loading.rs @@ -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}, @@ -18,12 +19,23 @@ use amethyst::{ const SKIP_MENU_ARG: &str = "no_menu"; pub struct LoadingState { + config_path: String, prefab_loading_progress: Option, } 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, } } @@ -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) -> SimpleTrans { diff --git a/src/states/main_game.rs b/src/states/main_game.rs index b21fd57..4f21222 100644 --- a/src/states/main_game.rs +++ b/src/states/main_game.rs @@ -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", @@ -336,6 +360,8 @@ impl SimpleState for MainGameState { .world .write_resource::>(); // 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, diff --git a/src/systems/behaviors/obstacle.rs b/src/systems/behaviors/obstacle.rs index 16399e9..d6cf77f 100644 --- a/src/systems/behaviors/obstacle.rs +++ b/src/systems/behaviors/obstacle.rs @@ -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; @@ -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>, ); 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 { diff --git a/src/systems/behaviors/wander.rs b/src/systems/behaviors/wander.rs index 9606c99..ae3680c 100644 --- a/src/systems/behaviors/wander.rs +++ b/src/systems/behaviors/wander.rs @@ -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::() { diff --git a/src/systems/experimental/gravity.rs b/src/systems/experimental/gravity.rs new file mode 100644 index 0000000..ecdd0e2 --- /dev/null +++ b/src/systems/experimental/gravity.rs @@ -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(); + } + } +} diff --git a/src/systems/experimental/mod.rs b/src/systems/experimental/mod.rs index d9db8c2..893a874 100644 --- a/src/systems/experimental/mod.rs +++ b/src/systems/experimental/mod.rs @@ -1 +1,5 @@ +pub mod out_of_bounds; pub mod perception; +pub mod topplegrass; +pub mod wind_control; +pub mod gravity; diff --git a/src/systems/experimental/out_of_bounds.rs b/src/systems/experimental/out_of_bounds.rs new file mode 100644 index 0000000..a5d78ef --- /dev/null +++ b/src/systems/experimental/out_of_bounds.rs @@ -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); + } + } + } +} diff --git a/src/systems/experimental/topplegrass.rs b/src/systems/experimental/topplegrass.rs new file mode 100644 index 0000000..1e8003e --- /dev/null +++ b/src/systems/experimental/topplegrass.rs @@ -0,0 +1,200 @@ +use crate::resources::world_bounds::WorldBounds; +use amethyst::{ + core::{ + math::{Vector2, Vector3}, + timing::Time, + transform::components::Transform, + }, + ecs::*, + shrev::EventChannel, +}; + +use rand::{thread_rng, Rng}; +use std::f32; + +use crate::{ + components::creatures::FallingTag, components::creatures::Movement, + components::creatures::TopplegrassTag, resources::wind::Wind, + systems::spawner::CreatureSpawnEvent, +}; + +/// A new topplegrass entity is spawned periodically, SPAWN_INTERVAL is the period in seconds. +/// Spawn interval is currently set quite fast, for testing purposes. In the final game, +/// a spawn internal of at least a few minutes might be better. +const SPAWN_INTERVAL: f32 = 10.0; +/// The standard scaling to apply to the entity. +const TOPPLEGRASS_BASE_SCALE: f32 = 0.002; +/// At which height the topplegrass entity should spawn. +const HEIGHT: f32 = 0.5; +/// If we knew the radius of the toppleweed, we could calculate the perfect angular velocity, +/// but instead we'll use this magic value we got through trial and error. +/// It should be close enough to the actual value that the entity doesn't appear to slip. +const ANGULAR_V_MAGIC: f32 = 2.0; +/// The minimum velocity that a topplegrass entity must have in order to start jumping up into the air. +/// This is to prevent topplegrass from jumping in a weird way when there is (almost) no wind. +const JUMP_THRESHOLD: f32 = 1.0; +/// The chance per elapsed second since last frame that any given non-falling +/// topplegrass will jump up into the air slightly. +/// Not a great way of doing it, but probably good enough until we get a physics system? +const JUMP_PROBABILITY: f32 = 4.0; + +/// Periodically schedules a Topplegrass entity to be spawned in through a CreatureSpawnEvent. +#[derive(Default)] +pub struct TopplegrassSpawnSystem { + secs_to_next_spawn: f32, +} + +impl<'s> System<'s> for TopplegrassSpawnSystem { + type SystemData = ( + Entities<'s>, + Read<'s, LazyUpdate>, + Write<'s, EventChannel>, + Read<'s, Time>, + Read<'s, WorldBounds>, + Read<'s, Wind>, + ); + + fn run( + &mut self, + (entities, lazy_update, mut spawn_events, time, world_bounds, wind): Self::SystemData, + ) { + if self.ready_to_spawn(time.delta_seconds()) { + let mut transform = Transform::default(); + transform.set_scale(Vector3::new( + TOPPLEGRASS_BASE_SCALE, + TOPPLEGRASS_BASE_SCALE, + TOPPLEGRASS_BASE_SCALE, + )); + transform.append_translation(Self::gen_spawn_location(&wind, &world_bounds)); + let entity = lazy_update.create_entity(&entities).with(transform).build(); + spawn_events.single_write(CreatureSpawnEvent { + creature_type: "Topplegrass".to_string(), + entity, + }); + } + } +} + +impl TopplegrassSpawnSystem { + /// Checks the time elapsed since the last spawn. If the system is ready to spawn another + /// entity, the timer will be reset and this function will return true. + fn ready_to_spawn(&mut self, delta_seconds: f32) -> bool { + self.secs_to_next_spawn -= delta_seconds; + if self.secs_to_next_spawn.is_sign_negative() { + self.secs_to_next_spawn = SPAWN_INTERVAL; + true + } else { + false + } + } + + /// Returns a Vector3 representing the position in which to spawn the next entity. + /// Entities will be spawned at a random point on one of the four world borders; specifically, + /// the one that the wind direction is facing away from. In other words: upwind from the + /// center of the world. + fn gen_spawn_location(wind: &Wind, bounds: &WorldBounds) -> Vector3 { + let mut rng = thread_rng(); + if Self::wind_towards_direction(wind.wind, Vector2::new(1.0, 0.0)) { + Vector3::new( + bounds.left, + rng.gen_range(bounds.bottom, bounds.top), + HEIGHT, + ) + } else if Self::wind_towards_direction(wind.wind, Vector2::new(0.0, 1.0)) { + Vector3::new( + rng.gen_range(bounds.left, bounds.right), + bounds.bottom, + HEIGHT, + ) + } else if Self::wind_towards_direction(wind.wind, Vector2::new(-1.0, 0.0)) { + Vector3::new( + bounds.right, + rng.gen_range(bounds.bottom, bounds.top), + HEIGHT, + ) + } else { + Vector3::new(rng.gen_range(bounds.left, bounds.right), bounds.top, HEIGHT) + } + } + + /// Returns true if and only if the given wind vector is roughly in line with the given + /// cardinal_direction vector, within a margin of a 1/4 PI RAD. + fn wind_towards_direction(wind: Vector2, cardinal_direction: Vector2) -> bool { + wind.angle(&cardinal_direction).abs() < f32::consts::FRAC_PI_4 + } +} + +/// Controls the rolling animation of the Topplegrass. +/// Also makes the entity skip up into the air every so often, to simulate it bumping into small +/// rocks or the wind catching it or something. +#[derive(Default)] +pub struct TopplingSystem; + +impl<'s> System<'s> for TopplingSystem { + type SystemData = ( + Entities<'s>, + WriteStorage<'s, Movement>, + WriteStorage<'s, Transform>, + ReadStorage<'s, TopplegrassTag>, + WriteStorage<'s, FallingTag>, + Read<'s, Wind>, + Read<'s, Time>, + ); + + fn run( + &mut self, + (entities, mut movements, mut transforms, topple_tags, mut falling_tags, wind, time): Self::SystemData, + ) { + let mut rng = thread_rng(); + // Set topplegrass velocity to equal wind velocity. + // Rotate topplegrass. + for (movement, transform, _) in (&mut movements, &mut transforms, &topple_tags).join() { + transform.prepend_rotation_x_axis( + -ANGULAR_V_MAGIC * movement.velocity.y * time.delta_seconds(), + ); + transform.prepend_rotation_y_axis( + ANGULAR_V_MAGIC * movement.velocity.x * time.delta_seconds(), + ); + movement.velocity.x = wind.wind.x; + movement.velocity.y = wind.wind.y; + } + // Select some of the topplegrass that are on ground to jump up into the air slightly. + let airborne = (&entities, &mut movements, &topple_tags, !&falling_tags) + .join() + .filter_map(|(entity, movement, _, _)| { + if movement.velocity.magnitude() > JUMP_THRESHOLD + && rng.gen::() < JUMP_PROBABILITY * time.delta_seconds() + { + movement.velocity.z = rng.gen_range(0.4, 0.7); + Some(entity) + } else { + None + } + }) + .collect::>(); + // Attach the falling tag to the selected topplegrass entities, which lets the GravitySystem + // know to start affecting it. + for entity in airborne { + falling_tags + .insert(entity, FallingTag) + .expect("Unable to add falling tag to entity"); + } + // Check which entities are no longer falling (because they reached the ground); remove + // their falling tag, set their vertical speed to zero (we don't bounce) and correct their position. + let no_longer_falling = (&entities, &mut transforms, &mut movements, &falling_tags, &topple_tags) + .join() + .filter_map(|(entity, transform, movement, _, _)| { + if transform.translation().z <= HEIGHT && movement.velocity.z.is_sign_negative() { + transform.translation_mut().z = HEIGHT; + movement.velocity.z = 0.0; + Some(entity) + } else { + None + } + }) + .collect::>(); + for entity in no_longer_falling { + falling_tags.remove(entity); + } + } +} diff --git a/src/systems/experimental/wind_control.rs b/src/systems/experimental/wind_control.rs new file mode 100644 index 0000000..fa88890 --- /dev/null +++ b/src/systems/experimental/wind_control.rs @@ -0,0 +1,73 @@ +use amethyst::{ + core::{math::Vector2, timing::Time}, + ecs::*, + input::{InputHandler, StringBindings}, +}; + +use crate::resources::wind::Wind; +use std::f32; + +/// Wind speed cannot decrease below this number. +const MIN_WIND_SPEED: f32 = 0.0; +/// Wind speed cannot increase above this number. +const MAX_WIND_SPEED: f32 = 5.0; +/// Speed with which to rotate wind speed in radians per second. +const WIND_TURN_SPEED: f32 = f32::consts::FRAC_PI_4; +/// Speed with which to increase or decrease wind speed in meters?? per second per second. +const WIND_ACCELERATION: f32 = 2.0; + +/// DebugWindControlSystem allows players to change the wind speed and direction at runtime. +/// Use the ChangeWindDirection input axis to change the wind direction at WIND_TURN_SPEED radians per second. +/// Use the ChangeWindSpeed input axis to change the wind speed between MIN_WIND_SPEED and MAX_WIND_SPEED. +#[derive(Default)] +pub struct DebugWindControlSystem; + +impl<'s> System<'s> for DebugWindControlSystem { + type SystemData = ( + Read<'s, InputHandler>, + Write<'s, Wind>, + Read<'s, Time>, + ); + + fn run(&mut self, (input, mut wind, time): Self::SystemData) { + let change_direction = input + .axis_value("ChangeWindDirection") + .filter(|signum| signum.abs() > std::f32::EPSILON); + let change_speed = input + .axis_value("ChangeWindSpeed") + .filter(|signum| signum.abs() > std::f32::EPSILON); + if !change_direction.is_some() && !change_speed.is_some() { + return; + } + let new_angle = calc_wind_angle(change_direction, &wind, &time); + let new_speed = calc_wind_speed(change_speed, &wind, &time); + wind.wind = Vector2::new( + new_speed * new_angle.cos(), + new_speed * new_angle.sin(), + ); + println!( + "Changed wind vector to: ({:?},{:?}) angle={:?} speed={:?}", + wind.wind.x, wind.wind.y, new_angle, new_speed + ); + } +} + +fn calc_wind_angle(input_signum: Option, wind: &Wind, time: &Time) -> f32 { + let old_wind_angle = wind.wind.y.atan2(wind.wind.x); + if let Some(signum) = input_signum { + old_wind_angle + signum * WIND_TURN_SPEED * time.delta_seconds() + } else { + old_wind_angle + } +} + +fn calc_wind_speed(input_signum: Option, wind: &Wind, time: &Time) -> f32 { + let magnitude = wind.wind.magnitude(); + if let Some(signum) = input_signum { + (magnitude + signum * WIND_ACCELERATION * time.delta_seconds()) + .max(MIN_WIND_SPEED) + .min(MAX_WIND_SPEED) + } else { + magnitude + } +} diff --git a/src/systems/movement.rs b/src/systems/movement.rs index 0617fd7..78329ba 100644 --- a/src/systems/movement.rs +++ b/src/systems/movement.rs @@ -20,6 +20,7 @@ impl<'s> System<'s> for MovementSystem { } transform.prepend_translation_x(movement.velocity.x * delta_time); transform.prepend_translation_y(movement.velocity.y * delta_time); + transform.prepend_translation_z(movement.velocity.z * delta_time); } for (movement, transform, _) in (&mut movements, &mut transforms, &creature_tags).join() { let angle = movement.velocity.y.atan2(movement.velocity.x); diff --git a/src/systems/swarm_behavior.rs b/src/systems/swarm_behavior.rs index f8da897..7579b23 100644 --- a/src/systems/swarm_behavior.rs +++ b/src/systems/swarm_behavior.rs @@ -13,7 +13,7 @@ use std::f32; use crate::{ components::{ - creatures::{Movement, Wander}, + creatures::{AvoidObstaclesTag, Movement, Wander}, swarm::{SwarmBehavior, SwarmCenter}, }, systems::spawner::CreatureSpawnEvent, @@ -54,6 +54,8 @@ impl<'s> System<'s> for SwarmSpawnSystem { angle: 0.0, }; swarm_entity_builder = swarm_entity_builder.with(wander); + let avoid_obstacles_tag: AvoidObstaclesTag = AvoidObstaclesTag; + swarm_entity_builder = swarm_entity_builder.with(avoid_obstacles_tag); let swarm_entity = swarm_entity_builder.build(); let mut swarm_center = SwarmCenter::default(); let nb_swarm_individuals = rng.gen_range(3, 10);