Skip to content

Commit

Permalink
Close #30 - Merge branch 'feature/30/make-the-knockback-an-action'
Browse files Browse the repository at this point in the history
  • Loading branch information
idanarye committed Oct 8, 2024
2 parents 3562f35 + 3a60ace commit 79c7dc8
Show file tree
Hide file tree
Showing 14 changed files with 398 additions and 440 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ NOTE: Subcrates have their own changelogs: [bevy-tnua-physics-integration-layer]

## [Unreleased]
### Added
- Pushover - the ability of characters to get knocked back by external forces.
- Controlled by the `pushover_...` settings of `TnuaBuiltinWalk`.
- A `TnuaBuiltinKnockback` action for applying knockback that will not be
nullified even with very high walk acceleration settings (see
https://github.com/idanarye/bevy-tnua/issues/30)

## 0.19.0 - 2024-07-05
### Changed
Expand Down
1 change: 1 addition & 0 deletions demos/src/bin/platformer_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ fn setup_player(mut commands: Commands) {
dash: Default::default(),
one_way_platforms_min_proximity: 1.0,
falling_through: FallingThroughControlScheme::SingleFall,
knockback: Default::default(),
});

// An entity's Tnua behavior can be toggled individually with this component, if inserted.
Expand Down
1 change: 1 addition & 0 deletions demos/src/bin/platformer_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ fn setup_player(mut commands: Commands, asset_server: Res<AssetServer>) {
dash: Default::default(),
one_way_platforms_min_proximity: 1.0,
falling_through: FallingThroughControlScheme::SingleFall,
knockback: Default::default(),
});

// An entity's Tnua behavior can be toggled individually with this component, if inserted.
Expand Down
1 change: 1 addition & 0 deletions demos/src/bin/shooter_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ fn setup_player(mut commands: Commands, asset_server: Res<AssetServer>) {
dash: Default::default(),
one_way_platforms_min_proximity: 1.0,
falling_through: FallingThroughControlScheme::SingleFall,
knockback: Default::default(),
});

cmd.insert(ForwardFromCamera::default());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bevy::ecs::query::QueryData;
use bevy::prelude::*;
use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinDash, TnuaBuiltinJumpState};
use bevy_tnua::builtins::{
TnuaBuiltinCrouch, TnuaBuiltinDash, TnuaBuiltinJumpState, TnuaBuiltinKnockback,
};
use bevy_tnua::math::Float;
use bevy_tnua::prelude::*;
use bevy_tnua::{TnuaAnimatingState, TnuaAnimatingStateDirective};
Expand All @@ -16,54 +17,7 @@ pub enum AnimationState {
Crouching,
Crawling(Float),
Dashing,
KnockedBack(Dir3),
}

impl AnimationState {
fn force_forward(&self) -> Option<Dir3> {
match self {
AnimationState::Standing => None,
AnimationState::Running(_) => None,
AnimationState::Jumping => None,
AnimationState::Falling => None,
AnimationState::Crouching => None,
AnimationState::Crawling(_) => None,
AnimationState::Dashing => None,
AnimationState::KnockedBack(direction) => Some(-*direction),
}
}
}

#[derive(QueryData)]
#[query_data(mutable)]
pub struct ForwardForcing {
pub transform: &'static mut Transform,
#[cfg(feature = "rapier3d")]
rapier3d_locked_axes: &'static mut bevy_rapier3d::prelude::LockedAxes,
#[cfg(feature = "avian3d")]
avian3d_locked_axes: &'static mut avian3d::prelude::LockedAxes,
}

impl ForwardForcingItem<'_> {
fn lock_rotation(&mut self) {
#[cfg(feature = "rapier3d")]
self.rapier3d_locked_axes
.insert(bevy_rapier3d::prelude::LockedAxes::ROTATION_LOCKED_Y);
#[cfg(feature = "avian3d")]
{
*self.avian3d_locked_axes = self.avian3d_locked_axes.lock_rotation_y();
}
}

fn unlock_rotation(&mut self) {
#[cfg(feature = "rapier3d")]
self.rapier3d_locked_axes
.remove(bevy_rapier3d::prelude::LockedAxes::ROTATION_LOCKED_Y);
#[cfg(feature = "avian3d")]
{
*self.avian3d_locked_axes = self.avian3d_locked_axes.unlock_rotation_y();
}
}
KnockedBack,
}

#[allow(clippy::unnecessary_cast)]
Expand All @@ -80,13 +34,10 @@ pub fn animate_platformer_character(
// for deciding which animation to play.
&TnuaController,
&AnimationsHandler,
ForwardForcing,
)>,
mut animation_players_query: Query<&mut AnimationPlayer>,
) {
for (mut animating_state, controller, handler, mut forward_forcing) in
animations_handlers_query.iter_mut()
{
for (mut animating_state, controller, handler) in animations_handlers_query.iter_mut() {
let Ok(mut player) = animation_players_query.get_mut(handler.player_entity) else {
continue;
};
Expand Down Expand Up @@ -138,6 +89,7 @@ pub fn animate_platformer_character(
// For the dash, we don't need the internal state of the dash action to determine
// the action - so there is no need to downcast.
Some(TnuaBuiltinDash::NAME) => AnimationState::Dashing,
Some(TnuaBuiltinKnockback::NAME) => AnimationState::KnockedBack,
Some(other) => panic!("Unknown action {other}"),
None => {
// If there is no action going on, we'll base the animation on the state of the
Expand All @@ -146,9 +98,7 @@ pub fn animate_platformer_character(
else {
continue;
};
if let Some(pushover_direction) = basis_state.pushover() {
AnimationState::KnockedBack(pushover_direction)
} else if basis_state.standing_on_entity().is_none() {
if basis_state.standing_on_entity().is_none() {
AnimationState::Falling
} else {
let speed = basis_state.running_velocity.length();
Expand Down Expand Up @@ -226,24 +176,13 @@ pub fn animate_platformer_character(
AnimationState::Dashing => {
player.start(handler.animations["Dashing"]).set_speed(10.0);
}
AnimationState::KnockedBack(_) => {
AnimationState::KnockedBack => {
player
.start(handler.animations["KnockedBack"])
.set_speed(1.0);
}
}
}
}

// The knockback animation needs the character to be in a specific direciton. We want the
// character to face that way immediately (a gradual turn would look weird here) and we
// also want to lock the rotation so that the physics engine won't be able to slightly
// change it between frames (which Tnua will try to do)
if let Some(forward) = animating_state.get().and_then(|s| s.force_forward()) {
forward_forcing.lock_rotation();
forward_forcing.transform.look_to(forward, Dir3::Y);
} else {
forward_forcing.unlock_rotation();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use bevy::prelude::*;
#[cfg(feature = "egui")]
use bevy_egui::{egui, EguiContexts};
use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinCrouchState, TnuaBuiltinDash};
use bevy_tnua::builtins::{
TnuaBuiltinCrouch, TnuaBuiltinCrouchState, TnuaBuiltinDash, TnuaBuiltinKnockback,
};
use bevy_tnua::control_helpers::{
TnuaCrouchEnforcer, TnuaSimpleAirActionsCounter, TnuaSimpleFallThroughPlatformsHelper,
};
Expand Down Expand Up @@ -381,6 +383,7 @@ pub struct CharacterMotionConfigForPlatformerDemo {
pub dash: TnuaBuiltinDash,
pub one_way_platforms_min_proximity: Float,
pub falling_through: FallingThroughControlScheme,
pub knockback: TnuaBuiltinKnockback,
}

impl UiTunable for CharacterMotionConfigForPlatformerDemo {
Expand Down Expand Up @@ -408,6 +411,9 @@ impl UiTunable for CharacterMotionConfigForPlatformerDemo {
);
self.falling_through.tune(ui);
});
ui.collapsing("Knockback:", |ui| {
self.knockback.tune(ui);
});
}
}

Expand Down
65 changes: 20 additions & 45 deletions demos/src/level_mechanics/push_effect.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use bevy::{ecs::query::QueryData, prelude::*};
use bevy_tnua::math::Vector3;
use bevy::prelude::*;
use bevy_tnua::{
builtins::TnuaBuiltinKnockback,
math::{AsF32, Vector3},
prelude::TnuaController,
};

use crate::character_control_systems::platformer_control_systems::CharacterMotionConfigForPlatformerDemo;

pub struct PushEffectPlugin;

Expand All @@ -14,54 +20,23 @@ pub enum PushEffect {
Impulse(Vector3),
}

#[derive(QueryData)]
#[query_data(mutable)]
struct VelocityQuery {
#[cfg(feature = "rapier2d")]
rapier2d_velocity: Option<&'static mut bevy_rapier2d::prelude::Velocity>,

#[cfg(feature = "rapier3d")]
rapier3d_velocity: Option<&'static mut bevy_rapier3d::prelude::Velocity>,

#[cfg(feature = "avian2d")]
avian2d_linear_velocity: Option<&'static mut avian2d::prelude::LinearVelocity>,

#[cfg(feature = "avian3d")]
avian3d_linear_velocity: Option<&'static mut avian3d::prelude::LinearVelocity>,
}

impl VelocityQueryItem<'_> {
fn apply_impulse(&mut self, impulse: Vector3) {
#[cfg(feature = "rapier2d")]
if let Some(velocity) = self.rapier2d_velocity.as_mut() {
velocity.linvel += impulse.truncate();
}

#[cfg(feature = "rapier3d")]
if let Some(velocity) = self.rapier3d_velocity.as_mut() {
velocity.linvel += impulse;
}

#[cfg(feature = "avian2d")]
if let Some(velocity) = self.avian2d_linear_velocity.as_mut() {
velocity.0 += impulse.truncate();
}

#[cfg(feature = "avian3d")]
if let Some(velocity) = self.avian3d_linear_velocity.as_mut() {
velocity.0 += impulse;
}
}
}

fn apply_push_effect(
mut query: Query<(Entity, &PushEffect, VelocityQuery)>,
mut query: Query<(
Entity,
&PushEffect,
&mut TnuaController,
&CharacterMotionConfigForPlatformerDemo,
)>,
mut commands: Commands,
) {
for (entity, push_effect, mut velocity) in query.iter_mut() {
for (entity, push_effect, mut controller, config) in query.iter_mut() {
match push_effect {
PushEffect::Impulse(impulse) => {
velocity.apply_impulse(*impulse);
controller.action(TnuaBuiltinKnockback {
shove: *impulse,
force_forward: Dir3::new(-impulse.reject_from(Vector3::Y).f32()).ok(),
..config.knockback
});
commands.entity(entity).remove::<PushEffect>();
}
}
Expand Down
57 changes: 25 additions & 32 deletions demos/src/ui/tuning.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(feature = "egui")]
use std::ops::RangeInclusive;

use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinDash};
use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinDash, TnuaBuiltinKnockback};
#[allow(unused_imports)]
use bevy_tnua::math::{float_consts, Float};
use bevy_tnua::prelude::*;
Expand Down Expand Up @@ -113,37 +113,6 @@ impl UiTunable for TnuaBuiltinWalk {
0.0..=200.0,
);

slider_or_infinity(
ui,
"Pushover: Threshold",
&mut self.pushover_threshold,
0.0..=4.0,
);
ui.add(
egui::Slider::new(&mut self.pushover_no_push_timeout, 0.0..=2.0)
.text("Pushover: No Push Timeout"),
);
ui.add(
egui::Slider::new(
&mut self.pushover_barrier_strength_diminishing,
0.01..=100.0,
)
.logarithmic(true)
.text("Pushover: Barrier Strengh Diminishing"),
);
slider_or_infinity(
ui,
"Pushover: Acceleration Limit",
&mut self.pushover_acceleration_limit,
0.0..=20.0,
);
slider_or_infinity(
ui,
"Pushover: Air Acceleration Limit",
&mut self.pushover_air_acceleration_limit,
0.0..=20.0,
);

ui.add(egui::Slider::new(&mut self.coyote_time, 0.0..=1.0).text("Coyote Time"));

ui.add(
Expand Down Expand Up @@ -266,3 +235,27 @@ impl UiTunable for TnuaBuiltinDash {
);
}
}

impl UiTunable for TnuaBuiltinKnockback {
#[cfg(feature = "egui")]
fn tune(&mut self, ui: &mut egui::Ui) {
ui.add(egui::Slider::new(&mut self.no_push_timeout, 0.0..=2.0).text("No Push Timeout"));
ui.add(
egui::Slider::new(&mut self.barrier_strength_diminishing, 0.01..=100.0)
.logarithmic(true)
.text("Barrier Strengh Diminishing"),
);
slider_or_infinity(
ui,
"Acceleration Limit",
&mut self.acceleration_limit,
0.0..=20.0,
);
slider_or_infinity(
ui,
"Air Acceleration Limit",
&mut self.air_acceleration_limit,
0.0..=20.0,
);
}
}
6 changes: 5 additions & 1 deletion src/basis_action_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use bevy::prelude::*;
use bevy::time::Stopwatch;
use bevy_tnua_physics_integration_layer::math::{Float, Vector3};

use std::any::Any;
use std::{any::Any, time::Duration};

use crate::{TnuaMotor, TnuaProximitySensor, TnuaRigidBodyTracker};

Expand Down Expand Up @@ -230,6 +230,10 @@ impl<'a> TnuaActionContext<'a> {
}
}

pub fn frame_duration_as_duration(&self) -> Duration {
Duration::from_secs_f64(self.frame_duration.into())
}

/// The direction considered as "up".
pub fn up_direction(&self) -> Dir3 {
Dir3::Y
Expand Down
Loading

0 comments on commit 79c7dc8

Please sign in to comment.