#bevy_tnua
1 messages ยท Page 2 of 1
Yea. You want something perpendicular to the normal.
Something like this?
Not sure what's going on in this picture, but why do you need two reject_froms?
Ah i get it now
Exactly
โ๐ฑ?
let walk_direction_on_slope = normal_direction.reject_from(linear_velocity_xz); So something like this?
perhaps we could just make a future ray cast in the player to be position
And them just grab the distinct y distanc between the two
my my thati s truly way simpler than the whole normal thing
Is that a typo, or are you rejecting the normal from the normal?
yeah i am stuck tbh
you want to reject the velocity from the normal, not the reverse
Okay if would you kindly walk me through this I would appreciate it.
I have the XY normal direction, with
let normal_direction = ray_data
.normal1
.reject_from_normalized(Vec3::Y)
.normalize_or_zero();```
This gets me the direction orthogonal to the normal
You don't really need it (unless you are trying to build a ClimbVectors like I did). What you want to do is take your walk_direction and project it on the slope's plane:
let walk_direction_on_slope = walk_direction.reject_from_normalized(ray_data.normal1);
Yeah it seens to be the correct value indeed the problem is there still a little bit of shifting maybe because of excessive ride height?
Ah my boost breaks things
thanks for you aid aeon
Sorry for the bothering
Protip: applying acceleration (force) is usually better that applying impulses. Only apply an impulse if you want to reach a certain velocity on the next frame (in which case it'd be more accurate)
does tnua not apply forces to things a controller is standing on?
It doesn't. Never bothered to implement it...
Is there a way to get the current position of an entity through the tnua controller? I have some code where I'm trying to supply some start and end coordinates to navigate to.
Tnua does not keep that information in the controller component. When the controller runs, it gets that information (which the basis and actions need) from a tracker component - so you can get it from there.
Alternatively - you can just get it directly from the GlobalTransform or from the physics engine.
Newtonian state of the rigid body.
I'm trying to diagnose a weird bug regarding the built-in jump. Something prevents me from jumping immediately after landing, as if there's a ceiling over my head that I'm bumping into. Let me know what info you want me to provide. Thanks.
What's confusing is that the air actions counter still reads 0 when this happens, so it does not think i'm in the air.
I'm thinking it has something to do with the player collider.
I don't think I've ever encountered this issue, so I'll need more info. How are you setting up the character and environment? How are you applying the jump? Can you post a video?
Let me just make my repo public real quick
Also, what's a good simple video tool for linux I can use real quick?
Personally I use this: https://github.com/phw/peek
Which I now see is deprecated.
It works for this real quick
hold on, got to fix peek first
Here you go
My phone needs charging, so github won't let me make the repo public until I can use it to authenticate.
Are you leaving enough floating height between the collider and the floor?
let me see what I have it set to
1.5
let's see what happens when I increase that a lot
still occurs at 5.5
let me take a look at my player entity setup
Might be the float height
nope, that didn't help
I know that just shows builtinwalk, but the height setting is the same
better view
TnuaAvian3dSensorShape doesn't seem to do it either.
I'll probably make a 3rd person debug view to try and see what's happening
Here's my current theory
When landing, you can see there's a moment where the player eases back up.
I think what's happening is that the bottom of the player is slightly below the ground, and it's being pushed back on top.
So when trying to jump during this time causes an issue, as the bottom of the player is still technically below the ground for some reason.
Weird that the physics engine does not prevent this...
I think I'm experiencing same thing as @wispy marten
setup:
let mesh = SceneRoot(gltf.scenes[0].clone());
let pos = Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)).with_rotation(player_rot);
let collider = Collider::capsule(cfg.player.hitbox.radius, cfg.player.hitbox.height);
let collider_mesh = Mesh::from(Capsule3d::new(
cfg.player.hitbox.radius,
cfg.player.hitbox.height,
));
let debug_collider_mesh = Mesh3d(meshes.add(collider_mesh.clone()));
let debug_collider_color: MeshMaterial3d<StandardMaterial> =
MeshMaterial3d(materials.add(Color::srgba(0.9, 0.9, 0.9, 0.2)));
commands.spawn((
mesh,
pos,
Player::default(),
ThirdPersonCameraTarget,
// tnua stuff
TnuaController::default(),
// Tnua can fix the rotation, but the character will still get rotated before it can do so.
// By locking the rotation we can prevent this.
LockedAxes::ROTATION_LOCKED.unlock_rotation_y(),
TnuaAnimatingState::<AnimationState>::default(),
TnuaSimpleAirActionsCounter::default(),
// physics
// A sensor shape is not strictly necessary, but without it we'll get weird results.
TnuaAvian3dSensorShape(collider.clone()),
RigidBody::Dynamic,
collider,
debug_collider_mesh,
debug_collider_color,
));
...
// in system
controller.basis(TnuaBuiltinWalk {
desired_velocity: direction.normalize_or_zero() * speed,
desired_forward: Dir3::new(player_rot).ok(),
float_height: 0.0001,
..Default::default()
});
let mut air_counter = air_counter.single_mut();
air_counter.update(controller.as_mut());
if state.just_pressed(&Action::Jump) {
controller.action(TnuaBuiltinJump {
height: 4.0,
allow_in_air: true,
..Default::default()
});
}
I'm also a bit confused by the fact that gltf model I import seem to be standing on collider?..
for example in video I use radius 0.5 and height 1.5
So I'm spawning collider on mesh. but mesh seem to be elevated by half of heaight I give collider capsule. Any ideas?
mesh origin is at its feet, capsule origin is in its center
probably just put the mesh on a child
Quick question. Are you also using Avian3d?
Just trying to isolate where the issue might be
yep
Is it charged yet?
The floating capsule model usually works better when the character actually floats, but in your case float_height: 0.0001 seems a bit low float height. Also, the shapecast is not flat (which is why a float height that's lower than the collider's half height (including caps) even works. But... it leaves very little buffer) which is something I don't usually test with.
Here you go:
https://github.com/Zrocket/bevyrpg
avian_interpolation3d = { path = "../avian_interpolation/crates/avian_interpolation3d" } #awaiting pull
I need this dependency to build.
I'll assume it's your main branch in https://github.com/Zrocket/avian_interpolation
Yeah, it's from when I updated it to the latest bevy before the original author got around to it.
I changed it to use that branch, but I still can't build due to other reasons. For example:
error[E0277]: `CameraMovement` does not implement `FromReflect` so cannot be created through reflection
--> src/controller/mod.rs:23:10
|
23 | pub enum CameraMovement {
| ^^^^^^^^^^^^^^ the trait `bevy_reflect::from_reflect::FromReflect` is not implemented for `CameraMovement`
or
error[E0277]: `leafwing_input_manager::action_state::ActionState<player_controller::Action>` is not a `Component`
--> src/controller/console_controller.rs:9:10
|
9 | key: Query<&ActionState<Action>, With<Player>>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid `Component`
I think all the the Git dependencies got outdated in the week since you last touched it (though it's weird - I'd figure the Cargo.lock would prevent such issues)
@celest summit could you help me with a little sliding problem? I'm running this controller with this walk on a smooth surface that consists of avian convex hulls. For some reason, when I stop pressing and the basis stops being updated, the character still eeeever so slightly continues sliding in its last direction
I have already checked whether that problem persists when just applying impulses instead of using Tnua, and that works fine
As the original author, I would advise using bevy_transform_interpolation over it nowadays. It's directly incorporated into Avian ๐
Update: Problem also persists when using this collider:
let mesh = world
.resource_mut::<Assets<Mesh>>()
.add(Plane3d::default().mesh().size(128.0, 128.0));
let material = world
.resource_mut::<Assets<StandardMaterial>>()
.add(Color::WHITE);
world.spawn((
Mesh3d(mesh),
MeshMaterial3d(material),
RigidBody::Static,
Collider::half_space(Vec3::Y),
));
Update2 OOOOH I see the issue. I'm running this on an observer, not every frame!
Yep, resetting it every frame works ๐
Does the observer mean that when you don't touch the controls there are no events and the system doesn't run?
Exactly
That's the idea behind bevy_enhanced_input
I just happened to replace my LWIM code with that crate and didn't think about the consequences of not running my input handlers every frame ๐
Now I just reset the basis in PreUpdate
Hrmm, unfortunately I ran into another problem. Turns out I can climp the 90 degree corner between two walls
By holding space, I can float all the way up
(jump height is just a meter, the walls are like 5 meters tall)
again, those are convex hulls
got any tips?
Try setting the player's friction to zero?
Behaves the same, unfortunately
nvm, this fixed it! I also had to set the combine_rule to CoefficientCombine::Multiply, otherwise it still takes the average of the contacting bodies
I'm curious, why does this work?
Yep. Specifically, this fixes it:
Friction {
dynamic_coefficient: 0.0,
static_coefficient: 0.0,
combine_rule: CoefficientCombine::Multiply,
},
The weird thing is that the ray/shape cast is not supposed to hit anything in the air, so I'm not sure myself why the friction extends the jump.
(friction is just one of my usual suspects)
Should I open a bug report?
Go ahead
I don't have an ultra minimal example, since I load the map using bevy_trenchbroom, but I think it should be debuggable using my code
There you go. I'm just happy I have a workaround for now. Thanks!
This has nothing to do with jumping - I can climb the wall without even jumping, simply by looking up.
I think this may have something to do with the character entity pitching with the camera.
Huh, interesting
Are you sure? The character has LockedAxes::ROTATION_LOCKED and the apply_movement function is only doing a yaw around the Y axis
Yes. I've added this to rotate_player:
info!("{:?}", transform.rotation * Vec3::Y);
And I see, as I move around, that the UP vector changes.
Well, that explains stuff
Ah, I see, when I mean to rotate the camera I must be accidentally rotating the player!
Tested it locally. Yeah, when I fix my systems to only rotate a camera and not the player itself, everything works ๐
Thanks for the help! Should I close the issue as a user error?
This is still something I should probably fix eventually. As long as the up direction is correct, there should be no reason the character would be able to do that. And it certainly shouldn't be affected by friction!
I think this may be the same root issue as https://github.com/idanarye/bevy-tnua/issues/87
I see, makes sense. Thanks for the tip with disabling friction though, turns out that just flat out feels better to control IMO, so I'll leave it disabled ๐
Thanks. I'm aware. I just wanted to get my jump issue fixed before messing with that again.
Let me just push my local version, as it should fix that.
Alright, it should work now
If it still gives you issues, let me know and I can tweak some things.
Ah, that makes sense ๐
Found the problem. @wispy marten - you are using action_state.just_pressed(&Action::Jump). @raven fossil - you do it too. Tnua assumes the jump action (any action, actually) is fed as long as the button is pressed, so you need to use .pressed() instead of .just_pressed().
The reason this prevents a jump immediately after landing is that you press the jump when the character is below its floating point. On the next frame, when you've stopped feeding the action, Tnua assumes the jump is over and goes into the FallSection. In this state, it waits until the character falls below the floating height - which you are already at - and then stops the jump, which means that regular spring force applies and keep you at the floating height.
When you start a jump not immediately after a landing, you are at the floating height - maybe not exactly, but close enough that the next frame will put you above the floating height. At this point you have the upward velocity from the impulse that starts the jump, and since you are above the float height - you don't enter the FallSection yet and the jump continues.
I do see a problem though that when I change it to .pressed() and keep holding the button the character keeps a slow upward rising until a certain height and only then falls. This is very weird - Tnua should not be doing this. Are you touching the transform and/velocity/forces somewhere else?
That makes sense. I switched to just_pressed() to solve the flying issue, so I'll have to see why that's happening. The only place transform/velocity/forces are touched are in the player controller and where the player is initially defied in devroom.
yep, changing to pressed fixed my issue. I'm astonished how comprehensive bevy_tnua is. Still figuring out how to actually run animations tho
Thank you for a crate dude
I'm using the rapier backend. Is there a way to let the character be pushed around or face resistance when colliding with dynamic bodies?
I tried to implement something like this in the past, but it's not as easy as it sounds (I used this ticket as a devlog: https://github.com/idanarye/bevy-tnua/issues/30)
Eventually I settled for an action - TnuaBuiltinKnockback - which you need to apply manually when colliding with these dynamic bodies.
Are there any good debug tools/plugins I can use to try and find out what's causing this?
Sometimes I wish I could just use gdb with bevy
Thanks, this is what I needed
A bit of a shame that there isn't a fully built in character controller and physics engine for bevy, would make this way more efficient to integrate
Good old debug prints
I can at least confirm that this is not Tnua's doing. I've added this:
app.add_systems(Update, (|query: Query<&bevy_tnua::TnuaMotor, With<Player>>| {
if let Ok(motor) = query.get_single() {
info!("{:?}", motor.lin);
}
}).after(TnuaPipelineStages::Logic)
.before(TnuaPipelineStages::Motors)
);
And during the slow-rising section of the jump it prints:
2025-04-09T15:30:00.020598Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.037307Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.053868Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.071021Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.087454Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.106048Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.120794Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.136619Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.153513Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
2025-04-09T15:30:00.169673Z INFO bevyrpg::level: TnuaVelChange { acceleration: Vec3(0.0, 0.0, 0.0), boost: Vec3(0.0, 0.0, 0.0) }
Which means that Tnua is not applying any forces on the entity during that time.
Wait...
Why do you have this in the player's construction?
GravityScale(0.0),
Disabling gravity for the player would explain this weird behavior.
(doesn't explain why the jump eventually stops though. Tnua should continue the jump until the peak, which is detected by vertical velocity, which should not be changing since there are neither forces from Tnua nor gravity to change it)
What!? I don't remember adding that at all. Huh.
Yup, that definitely solves it.
Thanks
I probably took it by accident from an example or something.
I used gdb with bevy to find recursive ..default call bug. Not sure it's as useful in all issues tho
I'm having a hard time trying to get animation to work.
I am basically trying something straight from this example. I see animation names imported in gltf(printed keys from gltf.named_animations), but on the prepare_animations system the AnimationPlayer is not accessible(basically get_mut query fails). I am not sure why.
Almost all animation examples(like this) involve doing something like
let (graph, node) = AnimationGraph::from_clip(asset_server.load(
GltfAssetLabel::Animation(2).from_asset("my_file.gltf")
);
and I guess this magically creates AnimationPlayers? but none seem to be available after I just load gltf as an asset
I'm a bit at a loss here, no idea at what moment AnimationPlayer is created
How can Bevy's documentation be improved? bevy_animation::AnimationPlayer doesn't explain: how you should create an animation player if you even should create an animation player manually h...
The animation loading in Tnua's examples and demos is kind of an hack. It's not part of Tnua itself, because Tnua does not do animation, but it does provide information for animations and in order to test this I had to implement animation in the examples&demos.
I believe the AnimationPlayer should get inserted automatically when you load a GTLF scene with the SceneRoot component. Are you loading the scene via different means?
Also - did you disable some of the default plugins?
at this point I realized that its a general animation question so I'd better ask it in animation channel.
in short - I load gltf, store handle and then spawn sceneroot using this: SceneRoot(gltf.scenes[0].clone());.
One thing I can think of - do you keep the gltf resource handle alive after adding the SceneRoot component?
The prepare_animations I use assumes that both the Gltf resource and and the AnimationPlayer component are up and running at the same time.
My hypothesis is that maybe, if you don't keep the gltf component alive, Bevy releases the Gltf resource before the AnimationPlayer gets inserted?
well I'm saving it into separate struct which I then insert as a resource separately, and I am able to list animations on it, so I doubt it''s the case
Try running this system:
fn log_stuff(
handle: Option<Res<PlayerGltfHandle>>,
gltf_assets: Res<Assets<Gltf>>,
animation_player_query: Query<&AnimationPlayer>,
) {
info!(
"{} {} {}",
handle.is_some(),
gltf_assets.len(),
animation_player_query.iter().len()
);
}
What does it print? (at the beginning, before it stabilizes)
true 1 0 when entering the screen but after the system that spawning the scene root, but true 1 1 right on the next update tick. I guess I'll just play with the order of systems
On the frame where it's "true 1 1" the prepare_animations system should have everything it needs to pass its three if-lets...
Ok, I figured how to get it to work: I changed prepare_animation to be an observer on built in bevy SceneInstanceReady event and attached it specifically to a SceneRoot spawned as a child.
I'm happy with what I have atm, thank you for help!
@celest summit I'm faced with the following situation:
- I throw a light object, say a cup, at a character controller
- I expect that the cup bounces off the character
- Instead, Tnua tries to climb the cup as it hits the character, resulting in a sudden jump
Is there a setting to tweak this behavior?
Related video:
This is weird. When the object goes under - that I understand. Tnua detects it as the ground. Not really sure what I can do about that. But when it hits the fox?
Maybe something similar to this happens, and the colliders push into each other?
Does the behavior replicate when you use raycast instead of shapecast?
Oh sorry, I forgot to mention that the collider is higher than one would think
It does go slightly under it
sec
See, when I hit the fox above the vertical center, it behaves normally
it's just weird when it's near the center of mass or below it
The sensor shape is a circle with the same radius as the collider?
Yeah, a tiny bit smaller
radius of the collider = 0.8
radius of the sensor shape = 0.79
How do I set it to raycast?
Just remove the sensor shape component.
I'm not really sure how this should be solved. I don't want to just disable the detection of dynamic rigid bodies...
Maybe add a marker component?
Raycast has other issues though, so it's not a permanent solution.
It'd still be a problem if the book goes directly below the center of the fox.
Though, not as big a problem, probably.
Can confirm, the poor guy cannot walk up stairs anymore
I could just disable collisions between props and characters, but that would feel really weird
Any idea if I could hack in something else?
I'll need to add a feature for Tnua to ignore these entities when ray/shape casting. Just need to decide if it should be opt-in or opt-out.
I would really appreciate it if you implemented that, thank you very much ๐
Let me know if I can help in any way!
Please try with this branch: https://github.com/idanarye/bevy-tnua/tree/feature/opt-out-of-being-ground
Add the TnuaNotPlatform component to the book (and any other dynamic object you don't want the fox to be able to climb on)
I only implemented it for Avian3D for now, and haven't tested it on my side (I don't have an appropriate test level)
Do I add it to the rigid body or the collider?
Nope, the colliders are children of the rigidbody
They can be on the same entity
but don't have to
seems to behave the same
I added it to the rigid body and the colliders
Oh wait my bad, I'm not actually adding it to the colliders
sec
@celest summit it works!!
great ๐
Okay.
I'm turning in for the night. I'll try to make a proper release tomorrow.
(though I'm also considering merging it with the bigger release I'm planning to do soon...)
Thank you so much for fixing this so quickly ๐
Just one suggestion: the API would be a bit more idiomatic and nicer to use from Avian's point of view if we checked the entity with the rigidbody for the opt out, not the collider
You can get the rigid body by querying the ColliderParent of the collider
Every collider is guaranteed to have one
(on avian main it's called ColliderOf instead)
This may be a good idea for another reason. I currently use this:
other_object_query: Query<(
Option<(&Position, &LinearVelocity, &AngularVelocity)>,
Option<&CollisionLayers>,
Option<&ColliderParent>,
Has<TnuaGhostPlatform>,
Has<Sensor>,
Has<TnuaNotPlatform>,
)>,
I assume the collider will not have position and velocities?
The collider certainly has a Position, but I don't think it has Velocitys
(not sure though)
actually, let me check real quick
Either way, I'm out for the night.
This is correct; in a hierarchy like this:
- rigid body
- collider
the collider has aPosition, but noVelocitys. It does however hold theCollisionLayers.
- collider
all you have to do is apply forces to things you stand on, newton's third law
that way you can't walk on a thrown rock
Eventually... yes. This is probably a good idea. But it has much more kinks to work out because it'll have to be done on the action level (some forces take it into consideration, other don't)
So for now let's go with TnuaNotPlatform marker component solution.
Little update on this: the approach is working really well for me, thanks! I just added a little helper observer to copy a RigidBody's TnuaNotPlatform to the colliders until that part is changed in Tnua ๐
Would you mind pinging me when that feature is on main so I can update my Cargo.toml?
@wheat valley I've just pushed a commit to that branch which checks the parent for TnuaNotPlatform if the collider does not have one of its own.
(it also checks for all the other components of the parent, like velocities, collision layers, etc.)
Can you check it on your end?
Sure, will do later when Iโm home
Works perfectly, thanks!
I'm having a hard time timing a TnuaBuiltinJump, both control and animation.
I tried using timer like anything one would use timer for, but i works poorly, I think there should be an easier way for timing jump animation and the jump itself.
If allow_in_air is disabled it does work as expected
//timer component
#[derive(Component)]
pub struct StepTimer(pub Timer);
#[derive(Component)]
pub struct JumpTimer(pub Timer);
// spawned together with player
JumpTimer(Timer::from_seconds(0.5, TimerMode::Repeating)),
//control system
...
mut jump_timer: Query<&mut JumpTimer>,
...
if let Ok(mut timer) = jump_timer.get_single_mut() {
// if state.pressed(&Action::Jump) && timer.0.tick(time.delta()).just_finished() { // uncomment this and jump stops working completely, timings are weird
if state.pressed(&Action::Jump) {
controller.action(TnuaBuiltinJump {
// The height is the only mandatory field of the jump button.
height: 1.0,
allow_in_air: true,
..Default::default()
});
}
}
...
// handle_animation
...
Some(TnuaBuiltinJump::NAME) => {
// this has no effect, jump animation triggers over and over while jump action pressed
if let Ok(mut timer) = jump_timer.get_single_mut() {
if timer.0.tick(time.delta()).just_finished() {
return;
}
}
// In case of jump, we want to cast it so that we can get the concrete jump state.
let (_, jump_state) = controller
.concrete_action::<TnuaBuiltinJump>()
.expect("action name mismatch");
// Depending on the state of the jump, we need to decide if we want to play the jump
// animation or the fall animation.
match jump_state {
TnuaBuiltinJumpState::NoJump => return,
TnuaBuiltinJumpState::StartingJump { .. } => AnimationState::JumpStart,
..
}
}
Something weird is definitely going on and it seems to be triggered by allow_in_air
I don't think the TnuaBuiltinJumpState variants should be tied as closely to the animation. I should probably document them better, but basically the purpose of StartingJump is to provide the character with the initial vertical velocity required to reach the jump height. Ideally this happens during the first frame of the jump (the action may go though multiple states during the same frame, so the StartingJump step may apply its impulse and then immediately switch to MaintainingJump. Technically it goes through SlowDownTooFastSlopeJump, but that's only relevant for slopes and happen in the same frame)
This means you'll only be getting JumpStart for one frame - if the character was a little bit lower than the floating height - or not at all if the character was a little bit higher. Assuming you are using AnimationState::JumpStart to start the animation - this can be an issue.
What are you trying to achieve?
let me show you what I mean. So here when I press and release space once animation gets cut, but if I hold the space animation gets one full cycle and then just gets start spammed until I release space. I'd like to avoid that, only starting animation when timer is finished
- What are the other (relevant)
AnimationStatevariants, and how do you set them? - How/when are you starting the actual animations?
- Are you using
TnuaAnimatingState?
I think I did not go far from this example, the biggest change is I added timer tick and .is_finished check inside controller.action_name match in Some(TnuaBuiltinJump::NAME) branch
The existence of AnimationState::JumpStart suggests that you did go far - in a meaningful way. This variant does not exist in the example - there is only one jump-related variant, AnimationState::Jumping. The distinction between starting the jump and still being in the jump is done via TnuaAnimatingStateDirective::Alter vs TnuaAnimatingStateDirective::Maintain - and for that to work, the AnimationState must not change during the jump (more precisely - the variant must not change. It's okay if the variant's payload changes)
oh, that explains it a bit.
I just added JumpStart, JumpLoop and JumpLand variants.
so far I think it makes sense and feels nice to couple animation with tnua, I just struggle to grasp the approach for now, but I'll get gud๐ซก
The important thing to remember with TnuaAnimatingState is that when you move between variants of the enum you get TnuaAnimatingStateDirective::Alter, which usually means you'll start a new animation from frame 0. Even if that animation happens to be the same clip (e.g. - if you put the jump animation on both AnimationState::JumpStart and AnimationState::MidJump) it'll probably restart the clip. Unless, of course, you specifically detect the clip position of the old animation and start from the middle. Or maybe use Bevy's animation blending features. The point is that you need to do something special.
So, if you want to use TnuaAnimatingState, you need to keep the same variant of AnimationState for the entire jump so that it'd be easy to not switch animation in the middle.
oooh, that makes sense. thanks!
If you want an example of how to blend between animations while using TnuaAnimatingState, this code here should help ๐
The all-in-one Bevy 3D game template for desktop. Contribute to janhohenheim/foxtrot development by creating an account on GitHub.
Maybe I should change my examples to use AnimationTransitions as well. I was not familiar with it and used the AnimationPlayer directly...
Want a PR? ๐
I rather do it myself so I can learn. I should probably use it in my games (whenever I get to actually make some) so I need to have some experience with it.
Sure, let me know if you need help!
(not that I'm much of an expert on this)
@celest summit I've fine tuned my constants so that my player can nicely glide up the stairs, but now I find that they can also easily step onto chairs, which I'd like to prevent. Note that using TnuaNotPlatform would be suboptimal in this case, as then the player can no longer jump while being on the chair.
Is there a specific constant I should tweak to improve this?
Floating character controllers don't really have the concept of a "step size" that other controllers have, right?
Also, I guess it wouldn't make sense to have something that behaves exactly like TnuaNotPlatform but still allows jumping from it, right?
It does - the float height. Have your character float high enough to be able to climb the stairs but low enough to not be able to go over the chairs.
That makes sense, but it's not behaving well
As you can see, I still jump up the chair, but crawl up the stairs
I tried playing around with the spring_strength and max_slope to combat this, but so far no luck
Is you sensor shape, perchance, wider than your collider?
lemme check
nope, it's PLAYER_RADIUS - 0.01
Dunno if it helps, but here's the same video with colliders showing
Try making the chairs static rigid bodies, just to see if the problem is with dynamic objects
Ah, also in case it's relevant: the camera is about 30 cm above the origin of the collider. Not that that contributes to the behavior, just making sure you know where the collider is located
sec
That fixes it completely
Maybe it has something to do with the fact that the chair is leaning forwards a bit when walking into it, creating a slope?
Oh, yep. I thought TAU / 4 would be enough of a max slope, but apparently not
The behavior is much better when using TAU / 8
That at least fixes the "jumping up chairs" issue ๐
Now I'll try to tweak the height so that stairs work again
alright, that was a bit tough, as such a well behaved value for float_height didn't exist. I instead tweaked the spring strength to a way higher value (2000) and now it's fine
@celest summit does this issue here mean that currently I cannot e.g. change the velocity based on the slope so that walking up a slope is slow?
You can always do it manually (get the normal from the sensor and adjust accordingly) but at some point I'd like to have Tnua do more of the work here.
I'm working on designing a big refactor, and would like everyone's opinion on it: https://github.com/idanarye/bevy-tnua/discussions/97
The main purpose is to replace the dyn usage in Tnua with an enum and a #[derive] macro.
Motivation Tnua's basis&action architecture works nicely, but it relies too much on Any. The performance penalty is probably negligible (and swallowed by all the physics queries and calcula...
Am I reading correctly that this would allow configuring things like the float height directly as an asset on disk by (de)serializing the scheme config?
Yes. But I would also like, if I can, to retain the option to override certain fields when using the action.
So, for example, when you detect that the character is walking on ice you can decrease the acceleration.
yeah that makes sense
without looking to deeply into the design discussion, I can say that this feature would be amazing for me while prototyping because of hot-reloading
In case anyone misses it - big release: https://discord.com/channels/691052431525675048/1364365732900507818
Oh HECK YEAH
that gif looks really cool ๐
good work!
This would enable coding something like wallrunning on your own, right?
Right. Eventually I'd like to have the popular actions builtin, but for now I rushed it a bit to have release before Bevy 0.16 is out.
I've been working on this for months now, and prefer not to have something this big in a side-branch while updating the main branch for a new Bevy version because then I'm have a huge Git conflict to deal with...
Oh yeah definitely
I just saw the new release on reddit.
this is so cool!
nice job, I think many people were asking for a wall jumps
I dont understand how to do 3-parts compound jump animation (start, in_air, end) with TnuaController
I tried this:
TnuaAnimatingStateDirective::Alter {
old_state,
state,
} => match state {
PlayerAnimationState::Airborne => {
transitions
.play(
&mut anim_player,
animations.jump,
Duration::from_millis(100),
)
.repeat();
}
PlayerAnimationState::JumpStart => {
transitions
.play(
&mut anim_player,
animations.jump_start,
Duration::from_millis(300),
)
.repeat();
}
PlayerAnimationState::Standing => {
if old_state == Some(PlayerAnimationState::Airborne) {
transitions.play(
&mut anim_player,
animations.jump_end,
Duration::from_millis(200),
);
}
transitions
.play(
&mut anim_player,
animations.idle,
Duration::from_millis(600),
)
.repeat();
}
}
}
But I am not sure it is the simplest and correct way
Animation transitions between standing and jump_end is not good enough
Also I saw TnuaSimpleAirActionsCounter and TnuaAirActionsTracker but did not realize how to use them
Also I saw
TnuaSimpleAirActionsCounterandTnuaAirActionsTrackerbut did not realize how to use them
Getting these out of the way first - they are only useful if you want air jumps (or air dashes). They are not relevant for the animation part (unless, of course, you want different animations for air jumps)
got it, I dont use it
I thought to add a component with Timer...
Animation transitions between standing and jump_end is not good enough
Why is Tnua relevant here? Isn't it, from this point on, purely Bevy's animation?
its because I thought TnuaSimpleAirActionsCounter and TnuaAirActionsTracker are relevant to my issue
ok. I will post it in general
Should probably also add a video.
is there a good way to get the character controller to come to perfect rest?
LinearVelocity(Vec3(0.0, -5.020329e-7, 0.0))
I get something on the order of this when stopped and it's unnecessary networking (my game has client authoritative player position)
another thing i notice is that when walking into walls there is a lot of jitter (very obvious first person, less so 3rd), though that might be more inherent to the floating character controller design.
Not sure about perfect rest - the character is floating which means it will be affected by gravity.
Maybe I can "cheat" by ofsetting gravity when the vertical velocity is tiny and the character is close to the floating height?
I'm thinking I might be able to cheat just by rounding velocities near zero
it would just have to be before networking
or after physics
The floating works via a spring force right?
Perhaps gravity could be disabled when the spring is active.
@celest summit My floating character is making my fps fall by 50 frames
Lovely right
hahahahaha
From my experience, the heaviest thing in a character controller is querying the physics engine. For example - when I first tried to implement the obstacles radar (for environment actions) I tried doing it with a sphere that that had its collision forces turned off but still detects the collision geometry (Avian sensors do it, but with Rapier you need to use SolverGroups). Turns out that doing it with a dynamic rigid body's sub-collider really hurts performance - I'm talking about single-digit FPS or worse - and thus I had to find a different way.
If you are having performance issues, first thing I'd suggest you do is disable all the logic of your character controller, leaving only the queries against the physics engine. See that the FPS is still low. Then, try disabling these queries (which shouldn't be an issue - because you've already disabled the logic that relies on them) until you find the query (or queries) that kills your performance.
i see
@celest summit does Tnua do input accumulation for me? So e.g. if I happen to have 3 variable updates and one fixed update, where I press the following inputs:
- var 1: nothing
- var 2: W
- var 3: nothing
- fixed: nothing
And set my basis every frame:
- var1: move nowhere
- var2: move forward
- var3: move nowhere
- fixed: move nowhere
will Tnua know that it still has a queue up movement basis that wasnโt applied yet because fixed updates didnโt run yet?
Or should I do my own input accumulation and set the basis according to the last pending input?
when tnua's systems run, it applies forces to make the simulation more similar to the configuration of the controller, and when you set the controller's state, it overrides whatever was previously there
events like jumping modify the basis, but setting the basis discards the previous, from what i can tell
When apply_controller_system runs (in your case - in the fixed step) it looks at the current basis. If between these fixed updates the basis changes multiple times - Tnua will only see the last value (technically if the basis' type changes it'll force the controller to drop the old basis' state - but that's seldom the case). This means that Tnua will not see the change in var 2 and will just treat respond to your "move nowhere", not knowing about the instruction to move forward.
One correction - the jumping is not part of the basis, it's an action.
Actions too are not accumulated, but unlike basis they are cleared by Tnua rather than by the user control system (although the user control system can override them with more actions in the same step, and there are actually two input slots here (current_action and contender_action) - but I diverge)
This means that for a similar scenario but with jumping instead of moving forward the jump action would be set at var 2 - and var 3 will not clear it because actions are only cleared (after begin processed) by Tnua's internal systems and user control systems can, at most, stop feeding them. Of course, unless variable jump height is disabled (shorten_extra_gravity is set to 0.0) when the jump is not fed during the next step (because the button is no longer pressed) the jump will be extremely short, maybe even unnoticeable.
@celest summit got it, thanks.
Do you think Tnua could do input accumulation for the movement basis by default? Not asking you to implement it or anything, just wondering if it's worth opening an issue ๐
Also opened a very related issue on BEI: https://github.com/projectharmonia/bevy_enhanced_input/issues/106
Update: Opened an issue on Tnua as well https://github.com/idanarye/bevy-tnua/issues/100
I actually need a little clear-up. For jumping, is there a difference between these two scenarios?
- var1: jump
- var2: nothing
- fixed: process inputs
and - var1: jump
- var2: jump
- fixed: process inputs
If everything works correctly, I would expect the first scenario corresponding to the extremely short jump you mentioned. Did I understand you correctly?
As far as Tnua is concerned, these two scenarios are identical. The reason for the short jump is the natural continuation of these scenarios:
- var1: jump
- var2: nothing
- fixed1: process inputs
- var3: nothing
- fixed2: process inputs
versus
- var1: jump
- var2: jump
- fixed1: process inputs
- var3: jump
- fixed2: process inputs
fixed1 will be identical, but in fixed2 the first scenario will stop the jump while the second scenario will continue it.
Note that a scenario where var2: nothing, var3: jump makes little sense when you think of where the input comes from - the player. If they left the jump button at var2, they probably won't be quick enough to press it again in var3.
Thanks, that makes sense ๐
I won't worry about input accumulation for jumps then
BTW - why is it even a problem? Shouldn't the time between fixed updates be shorter than the time it takes the player to press and release a button?
Depends on your fixed schedule Hz, which is configurable, and frame duration variability. You can very much press and release a button in 30 ms.
i just make all of my actions "held button"
hey tnua-ers, i could use some help figuring out the last kink in my one-way platforms implementation
i'm using Avian2D and tnua, and though the one-way platforms wiki page seems specific to rapier, i was able to get it to "sort of work" by using Avian2D collision hooks - which works in my favor because i have other physics objects i need to throw through these platforms
so basically everything does work, but my TnuaSimpleFallThroughPlatformsHelper is stopping just short of actually keeping my character floating above the ghost platform
as far as i can tell:
- the
TnuaGhostSensoris picking up the ghost platform correctly, at the right proximity - the
TnuaSimpleFallThroughPlatformsHelperis updating the proximity sensor to pick up the ghost platform correctly - yet, something else in the system is not keeping my character at the right float height or in the grounded state
does removing the chain fix it
no, seems like it's unrelated
I think this may be a floating height issue. Is your floating height too low? It is calculated from the Position, not from the bottom of the collider, and it needs to be high enough to leave some room between the collider and the ground.
What I suspect happening is that you don't leave enough room and the collider actually touches the ground. With regular ground Avian itself prevents it from falling through, but with fall-through platforms only the floating does that - so you are getting different behavior.
i thought it was that, so i was tuning the floating height and min proximity a lot trying to fix it
turns out, it was as simple as my system being in Update instead of FixedUpdate, despite being in the TnuaUserControlsSystemSet set it wasn't actually ordered against anything
i put it in Update because it was TnuaControllerPlugin::default() and i didn't think i had overridden it 
really don't like the schedule/systemset distinction
Youโre not alone! There is some talk about dropping schedules in favor of only system sets
i would very much enjoy that
How exactly? Schedules are the things that get invoked - system sets are just a mechanism for organizing constraints. That'd be like forgoing binders in favor of dividers.
i don't know anything about what's been suggested, but semantically, both schedules and systemsets feel like containers of systems - just one is responsible for actually sorting and running them and the other confers constraints and run conditions to its members
seems like the data should exist to collect and run all systems in a given set according to an executor
it's very strange that both of them act like sets of systems (as in literally sets, the data structure) but systems in a systemset "should" be strictly a subset of systems in the schedule a systemset has been configured for, yet there's absolutely nothing to enforce that and it just fails silently for the disjoint portion
The "fails silently" part (which is less of a "fails silently" and more of a "produces wrong results") can easily be solved, without changing the entire scheme, by panicking when there are systems inside system-sets that have zero configuration in that schedule, and the error message can print the names of these other schedules.
For that matter, a system set should be considered as configured in a schedule even if that configuration is empty:
app.configure_sets(Update, MySystemSet);
The reason is that a plugin may want to provide a system set for it's systems but let users configure it if they need to.
true, and panicking in that situation would be a good move I think, but the problem can also be solved without a runtime panic by changing the scheme - and you'd also get ergonomics benefits and reduced boilerplate in 95% of cases, e.g. .add_systems(Update, system.in_set(MyUpdateSet)) becomes .add_systems(MyUpdateSet, system) and have the system added to the set's schedule automatically
while reducing the opportunities for incorrect configuration
That is entirely true, but at the same time, they can all be viewed as just buckets you put systems into.
Note that removing schedules is not planned exactly, just an idea that was floating around a while by the ECS people. Very possible that it got rejected, I'm not up to date ๐
@celest summit Hey I made a tnua based controller, that is networking friendly. And specialized for my game
Would you kindly give me some feedback on it? If you can
I fixed a little bit of the perfomance issue but I still have a little bit of trouble when it comes to animation
Hi
Are there any examples for creating tnua 2d + TnuaRapier2dPlugin? I make player spawn but my function fn move_player(mat query: Query<&mut TnuaController, With<Player>>, keyboard: Res<ButtonInput<KeyCode>>) does not move the character
Tnua-based, or Tnua-inspired? Either way - just post the link and tell me what to focus on. I can't promise I'll be able to do it this week though (I have something going on that doesn't leave me much free time...)
The demos have a Rapier option -though they are a bit hard to read. Just look for the things marked with #[cfg(feature = "rapier2d")] in https://github.com/idanarye/bevy-tnua/blob/main/demos/src/bin/platformer_2d.rs. Other than that it should be similar to the Avian example, and 3D itself is quite similar to 2D.
You can also post your code and me (or someone else) can take a look.
tnua inspired i guess
@celest summit If it's not too much trouble to help. My simplified code looks like this. I am using bevy_asset_loader to load resources. And then I launch the PlayerBundle
Why is RapierPhysicsPlugin commented out?
@celest summit In fact, it is not commented out in my game project. But in the current snippet of the code, I did not span the platform and comment this plugin. It seems to me that I connected the controller to the player component incorrectly. Because I get into the move player function but nothing happens.
@celest summit A thank you for your help ๐ I decided to simplify the code and not make a separate bundle for the player, and everything worked
I didn't really do anything, but... you're welcome?
@celest summit Invited you to the private repo
You can focu on the crate psycho_player specifically the shared part logic
i have pressure plates in my game. setting the float height appropriately causes the player to not exert a downward force on the pressure plate when standing upon it. that makes sense to me, but before i hack around it, is there an idiomatic solution to this in tnua?
you can grab the spatial raycast and check if collides with above plate
Not sure what you mean by "grab the spatial raycast", but you can check for the entity field of the sensor's output to see if the player is standing on a pressure plate (and which)
Information from TnuaProximitySensor that have detected another collider.
Distance from another collider in a certain direction, and information on that collider.
hmm ok, sounds like i can't escape applying the force manually if i want to move the plate itself as a result
imo tnua should apply forces to things you stand on
yeah i worked around it, i was worried about losing the physicality of the player-pressing compared to pressing with an object
but looks ok with a shapecast and impulses
I like the little lag the pressure plate has when jumping on it
Makes it obvious that you just triggered something ๐
@celest summit Oh my god you actually reviewed it thanks for the input
Is there a setting to disregard meshes for ThuaBuiltInWalk? For example if I run into a wall - animation slows down significantly.
I want character be able to go full speed ahead into a wall if player wants
Disable friction on the character
// Movement feels nicer without friction.
Friction {
dynamic_coefficient: 0.0,
static_coefficient: 0.0,
combine_rule: CoefficientCombine::Multiply,
},
Like this ๐
hmm, will it affect standing on slopes?
Shouldn't, I believe? Not sure
ok, I'll check it out
If you test, please let me know ๐
hmm, I tried adding friction ZERO to the entity with tnua and it did not work
Do you want the player to noclip into the wall? I don't think the physics engine would allow this.
hmm, not really, I want the collider to do what it's suppose to do but not affect the animation
Then you probably have some animation code that is making the animation speed scale with the movement speed. Simply remove that ๐
ooooh, right, I multiply basis running_velocity to player speed, I just need to get rid of basis there.
Thank you for shoving me in the right direction!
Is it normal to feed Crouch every frame? because right now I see that using this, TnuaBuiltinCrouchState::Maintaining effectively never comes
2025-06-24T19:53:56.412327Z INFO game::player::animation: CROUCH: speed: 8, state:Sinking
2025-06-24T19:53:56.416890Z INFO game::player::animation: CROUCH: speed: 8, state:Sinking
2025-06-24T19:53:56.423357Z INFO game::player::animation: CROUCH: speed: 8, state:Sinking
2025-06-24T19:53:56.430506Z INFO game::player::animation: CROUCH: speed: 8, state:Sinking
Yes - every action in Tnua needs to be fed every frame, and Tnua interprets a frame where the action was not fed as "the user no longer wants this action". For some actions - like dash or knockback - this doesn't really matter because the action, by design, continues despite the user's wish (or in knockback's case - the game's wish) to stop it. But for actions like crouch which can be stopped immediately - not feeding them cause them to stop immediately (ish - when you stop crouch the action doesn't really stop until the character rises back to its original position, which based on the parameters can be very slow or very fast)
Also note that in crouch's state transitions from Sinking to Maintaining when the character reaches the float height of the crouch. If you've set it too low - low enough that the collider needs to dig into the ground in order to reach it - it'll never reach that state.
ooh, that makes sense, I could fuck up with this one. My char floating at 0.01 and crouch floating at -0.6
I'm completely confused with floating height now
2025-06-25T19:29:24.513844Z INFO game::player::animation: state:Sinking, standing_offset: 0.98355794
2025-06-25T19:29:26.018633Z INFO game::player::animation: state:Sinking, standing_offset: 0.49999166
2025-06-25T19:35:06.383169Z INFO game::player::animation: state:Sinking, standing_offset: -0.01
These number fluctuations do not make any sense to me.
So I have walk floating distance at 0.01 and crouch at -1.0
But sometimes after crouch ended it is left at -0.01, sometimes at 0.98
same if I just remove my float offsets for both crouch and walk and leave default 0.0 for both: sometimes standing offset is -0, sometimes it's 0.98, but hey, at least I see Maintaining being fired
While crouching I always see the number ~0.48, however I do not understand where it comes from, is it half height of a tnua entity collider?
the only way it works, if I set a big floating height, like 1.0 and crouch like -0.3, but then character is literally flying above ground, and I want it to at least resemble running on it
it's a floating character controller, the character's collider has to float
ok, I figured it out by adjusting mesh relative to parent entity, but man, this simple fact was hard to grasp
I tested it and I found i funny that the character was started sliding of even a slightly angled surfaces, but eventually I dropped the whole idea of being able to sprint into a wall. Maybe some day when I learn about tnua more I'll return to it, but right now its just too much time and effort
@celest summit that shouldnโt happen, right? Since Tnua AFAIK doesnโt rely on friction for slopes because itโs, well, floating
yeah, but if the crouch lowers collider it causes all kinds of strange behaviors, one of which is this:
I'm holding crouch and mostly not moving
Ooooh right, the crouch would make you no longer float ๐
nice background music btw
I haven't used crouch yet, but can't you make the regular float distance something like 0.5 m and the crouch distance 0.1 m?
Or I guess that would make the collider stick too much in the air, hmm
so there is a walk float, crouch float and a condition when to crouch that one would want to tune.
I managed to work it out but walk float is still higher I would've wanted
kind of a noob question but how important is it to put the things in FixedUpdate?
I think I don't put nearly enough in there
Is it supposed to be like that
What does the apply controls thing being in the TnuaUserControlsSystemSet mean? can I still apply my own ordering? I have a system that edits LWIM's dual axis input from my arrow keys in-place to apply the rotation from the camera
but of course it needs to run before the movement
there's a "max slope angle" option
when it wobbles in the middle I am not even touching the controls
but also surely this is already over the max slope angle
iirc the default max slope is really high
it looked very spring-like but I guess there are actual spring properties
that part probably depends on your scale
max_slope: float_consts::FRAC_PI_2
that's 90 degrees ๐ค
you weren't kidding
I changed it to pi/4 and it's a bit better but still lots of jank when spamming jump and such
oh well
Tnua interprets not feeding an action during a step as ending the action. If Tnua runs ion FixedUpdate and your user control system runs in Update, it'd be possible for two fixed updates to run without a non-fixed update running between them which will end your actions (e.g. - end a jump) even if you don't intend to.
I think Bevy in general has sort of idiomatically shifted to 'pretty much everything should be in fixedupdate'
Examples of systems that should run once per render frame include (but are not limited to):
UI
Input handling
Audio control
hmm
Wonder if I can make my own layer between the two, to make Tnua not do that, though I haven't noticed problems
Put your controller code in FixedUpdate and sort it inside TnuaUserControlSystemSet
We currently cannot have totally correct input handling in fixedupdate because winit has no timestamps on inputs (long story)
So the most correct thing you can currently do is read the inputs before the fixed update, accumulate them in case the fixed update doesnโt happen this frame, and then on the next fixed update drain them
I believe LWIM does a bunch of this for you? Not sure, I switched to bevy_enhanced_input
What made you switch?
It's the spiritual successor. Alice has shifted her attention from LWIM to BEI and has stated that it is more likely to become upstreamed than LWIM in the future
From a user perspective, it works with observers, which makes it automatically use the correct schedule (PreUpdate after the input was forwarded from winit), so you don't need to care about that
See this repo I setup for how to use BEI + Tnua ๐
Specifically:
I feel like I'm going to miss some LWIM features
In Bevyโs 3D space, the -Z axis points forward and the +Z axis points toward the camera.
I don't get statements like this
What is "the camera"
how do they know how I spawned my camera
Ha, understandable
It makes more sense when you consider these local coordinates
The camera bit comes from this
My thumb is X, my index is Y, and my middle finger is Z
Notice how my middle finger points directly at the viewer / "the camera"
๐
Does that make sense?
It also makes more sense if you think about a 3D model you imported. Let's say a rat. The rat is looking in some direction, but which?
Well, if you spawn it with Transform::default(), its nose will look at -Z and its right hand will be at +X
Yeah I guess, still seems like imperfect phrasing to talk about a camera
it's more like some default front or whatever
btw how is DeadZone the modifier that normalises movement
I guess I should go to that channel
You have no idea how much time I spent staring at mine
Hi! I've been trying to get bevy_tnua to work in my game but I seem to be having issues : my characters do not to move at all :
I've defined the on_move observer here : the info log here prints the player entity id and trigger.value from bevy_enhanced_input just fine now but upon updating the controller basis : the characters seem to not move : i've tried multiplying the values incase they are too small but no help.
and I'm plugging in the observer + other Physics plugins right here
I saw the example code used apply_controls.in_set(TnuaUserControlsSystemSet), but I'm using an observer(from BEI) here - So I tried passing in the Input Values as an event To a system(exactly like tnua example) from the observer - but that didn't move my characters still. Although I tried a minimal example and just updating the tnua controller basis from the observer worked there - it just doesn't in my whole game setup : would love if anyone can point me in the right direction.
Pretty sure youโre having scheduling issues. Take a look at https://github.com/janhohenheim/how-i-make-3d-games/tree/main/src/demo
especially input.rs and movement.rs
Itโs from the previous BEI version, but same concept ๐
Important bits:
#[derive(InputContext)]
#[input_context(schedule = FixedPreUpdate)]
pub struct PlayerInput;
And
app.add_systems(
FixedUpdate,
apply_movement.in_set(TnuaUserControlsSystemSet),
);
ohh! I haven't put in the schedule for the InputContext. I'll check out your example Thanks!!!!
Hi again! I'm further ahead but still something feels off.
I tried keeping an acceleration like this - or changed it down to 90.0 but my characters move too slow : is there some scaling I maybe messing up?
this is with the insane number
this is with just 90 acceleration
i think im having similar issue with jump height x.x
Usually you scale the velocity input you give tnua instead
For example, here's my apply_movement function in a 3D project:
fn apply_movement(
player: Single<(&mut TnuaController, &Actions<PlayerInput>), With<Player>>,
camera: Single<&Transform, With<PlayerCamera>>,
) {
let (mut controller, actions) = player.into_inner();
let move_input = actions.get::<Move>().unwrap();
let yaw = camera.rotation.to_euler(EulerRot::YXZ).0;
let yaw_quat = Quat::from_axis_angle(Vec3::Y, yaw);
controller.basis(TnuaBuiltinWalk {
desired_velocity: yaw_quat * move_input.value * 10.0,
float_height: PLAYER_HEIGHT + PLAYER_FLOAT_OFFSET,
max_slope: TAU / 8.0,
..default()
});
}
yeah that's what I was scaling : kept acceleration as default and it was still slow : so i started playing with other parameters
Oh I misready your message, sorry
rn i just tried doing a specific speed depending on the sign
of the input
xd
was checking this
oh and btw I ended up watching your how you make 3d games talk! i haven't done much 3d yet but was very interesting!
Oh wow thanks! Happy to hear you liked it 
yeah! i def wanna try TrenchBroom now coz of it xd
if that's not solved check if your colliders are dragging on the floor
Oh that maybe the case, I'll check it out!
Yeah that fixed it!! Thanks!
Im having a slight problem w avian and tnua. I dont see a tnua help channel here. But basically my character isnt always being โpushed upโ properly after walking up a shallow incline . I feel like it has something to do w the โspring โ sensor under them
My issue is walking up a slight slope , my character just stays the same height so i clip into the hill
When i jump, i properlly land on top of the hill
So wtf
Btw thats w the default settings (3d. )
with a sensor shape?
No this is with a ray
is there a way to prevent the character from sort of sticking to walls and also losing horizontal speed when bumping its head?
Set the friction to zero
thanks
fwd: #1415540694545596416 message
@celest summit would you appreciate me dropping a PR to update Tnua to 0.17.0-rc? ๐
I'm going through my dependencies and checking which I can migrate myself, and I think I should be able to handle tnua
You can create one if you want, and I'll keep it open so that early upgraders can use your branch (like they did with 0.16), but I probably won't merge it and won't make a crates.io release.
Works for me ๐
here it is in action
Since the PR won't be merged anyways, I only put in the minimum effort to have it working in 0.17
Heads-up: the Tnua demo scene is broken. Something breaks interpolation (or we never had interpolation? idk) and the kinematic floor doesn't move anymore
But I haven't investigated that
Note how the camera jitters around and how the square blue platform doesn't move and causes you to slide
No one needs to fix this right now, as I suspect this is not a Tnua issue per-se, but an issue with how the playground is setup
Just mentioning it so you're aware of it when the time for the proper 0.17 release comes ๐
I find these things are easier to fix after the official release, when the release notes of the relevant crates are finalized.
Yeah I thought so ๐
Is there a good example on using TnuaBuiltinClimb? Thanks.
I wouldn't call it a "good" example, but you can take a look at how I do it in the demo:
https://github.com/idanarye/bevy-tnua/blob/8f1b3efd74aae3993f9e70ef69e3868db8bcf413/demos/src/character_control_systems/platformer_control_systems.rs#L373
Thanks, I just needed something to look at.
@wheat valley i'm using your branch for 0.17, but i get this panic:
Encountered an error in run condition `<bevy_tnua_avian2d::TnuaAvian2dPlugin as bevy_app::plugin::Plugin>::build::{{closure}}`: Parameter `Res<'_, Time<Physics>>` failed validation: Resource does not exist
If this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `If<T>` to skip the system when it happens.```
did you make sure to add it after the PhysicsPlugins?
yep
well it's probably on the same tick
but in code it doesn't work when either before or after
possibly
i even tried manually inserting the resource
so i think it's probably tnua but can't be sure
@wheat valley could there be some temporary fix i could do? probably doing it on the next tick would work? just need to figure out how to do taht
sorry, I'm knee deep in audio right now haha
I can check tomorrow
sure
But do ask in #1124043933886976171 if something changed
My money is on slight variations in the commit hashes (since there are not officially released versions) that cause Cargo to see them as separate crates and generate separate symbols for the same paths.
What does cargo tree --invert avian2d print?
yeah, avian is newer. this should fix it(checked only for 3d):
[patch.crates-io]
avian3d = { git = "https://github.com/Jondolf/avian", branch = "main" }
bevy-tnua = { git = "https://github.com/janhohenheim/bevy-tnua", branch = "bevy-0.17.0-dev" }
bevy-tnua-avian3d = { git = "https://github.com/janhohenheim/bevy-tnua", branch = "bevy-0.17.0-dev" }
error: There are multiple `avian2d` packages in your project, and the specification `avian2d` is ambiguous. Please re-run this command with one of the following specifications: git+https://github.com/Jondolf/avian?branch=main#[email protected] git+https://github.com/Jondolf/avian#[email protected]
aha
nice thanks guys
Hi everyone, I discovered this strange thing.
In 0.17 the platform to which the force is applied, lifts the capsule (which have tnua controller), then stops, the capsule, for unknown reasons, either hangs at a much higher height than the float_height, or starts jumping.
[dependencies]
avian3d = { git = "https://github.com/Jondolf/avian", branch = "main" }
bevy-tnua = { git = "https://github.com/janhohenheim/bevy-tnua", branch = "bevy-0.17.0-dev" }
bevy-tnua-avian3d = { git = "https://github.com/janhohenheim/bevy-tnua", branch = "bevy-0.17.0-dev" }
[dependencies.bevy]
version = "0.17.2"
and in case of 0.16 - work as expected
[dependencies]
bevy-tnua = "0.25"
bevy-tnua-avian3d = "0.6.0"
avian3d= "0.3.1"
[dependencies.bevy]
version = "0.16.1"
for demonstration
https://github.com/xenon615/tnua_test
I'll do an official upgrade and fix all the kinks once Avian (or Rapier) make an official Bevy 0.17 release. Until then - Tnua does not support Bevy 0.17.
Annnnd... it's upgraded
Let me return to my first message
#1173981291801223239 message
avian3d = "0.4"
bevy-tnua = "0.26"
bevy-tnua-avian3d = "0.7.0"
the rest remains unchanged
the strange behavior has not gone away
@candid light I played a bit with your code, and I think the problem is that you are stopping the platform by changing its rigid body type to static, but that static rigid body retains its velocity. My hypothesis is that Avian simply ignores that component for static bodies, but Tnua does look at it, concluded that the platform is still moving at that velocity, and calculates accordingly.
When I reset the velocity it worked:
diff --git a/src/env.rs b/src/env.rs
index 34bc4de..56a9f76 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -45,11 +45,11 @@ fn startup(
fn apply_key(
mut cmd: Commands,
keys: Res<ButtonInput<KeyCode>>,
- platform_q: Single<(Entity, Option<&Moving>), With<Platform>>
+ platform_q: Single<(Entity, Option<&Moving>, &mut LinearVelocity), With<Platform>>
) {
if keys.just_pressed (KeyCode::Space) {
- let (e, om) = platform_q.into_inner();
+ let (e, om, mut linear_velocity) = platform_q.into_inner();
if om.is_none() {
cmd.entity(e).insert((
RigidBody::Dynamic,
@@ -64,8 +64,9 @@ fn apply_key(
Moving
)>();
cmd.entity(e).insert(RigidBody::Static);
+ linear_velocity.0 = Vec3::ZERO;
}
}
@celest summit Thanks for the hint, maybe I really should turn down the speed, I was confused that it worked in 0.16
Maybe Avian changed it?
it is possible
I can also address this scenario in Tnua itself.
In any case, if the workaround you suggested works, then it suits me perfectly.
Thanks again for the idea, if anything goes wrong, I'll let you know.
Pretty sure a static body should ignore any velocity
as that concept does not apply to it
the issue was that tnua wasn't reading the rigidbody type when reading velocity afaict
Yup - I was assuming that static bodies won't have any velocities, rather than having velocities which they ignore.
At any rate, I've just published a bugfix release for the Avian integration crates that fixes this. Which means, @candid light, that the workaround is no longer needed.
Though... I'm not sure if "workaround" is the correct term. Even with my fix to Tnua, no zeroing the linear velocity means that when the platform become a dynamic rigid body again it'd retain the velocity from last time instead of starting to accelerate from zero.
@buoyant badge something to consider for when we split the RigidBody variants into their own markers
Would be neat if kinematic bodies that were converted to statics got things like their velocities removed automatically
But also, in this case it very much looks like the correct fix would be to leave it as kinematic and zero the velocity 
Mm right, before the solver body stuff there was this in the velocity integration system
But now it doesn't do velocity integration at all for static bodies (which is good), because they don't have SolverBody, and the velocity is never cleared
I guess for now we could/should just reset it automatically when the rigid body type is changed to static
yeah this would be nice
the RigidBody component split and my idea of combining linear and angular velocity under Velocity (similar to Rapier) are both things that I'd like to do, but they're also kinda scary since they're such fundamental APIs ๐
@celest summit since the Tnua update the character orientation is no longer updated 
It's some fairly normal code that sets this:
let velocity = desired_velocity.velocity();
let forward = Dir3::try_from(velocity).ok();
info!("forward: {:?}", forward);
controller.basis(TnuaBuiltinWalk {
desired_velocity: velocity,
desired_forward: forward,
float_height: NPC_FLOAT_HEIGHT,
spring_strength: 1500.0,
max_slope: NPC_MAX_SLOPE,
..default()
});
looking at the logs, the desired_forward is getting regular values
Are you using LockedAxes? Because there is a bug in Avian:
https://github.com/Jondolf/avian/issues/865
I've noticed two issues with LockedAxes in Avian 4.0. I've created a minimal example which demonstrates them: Repository: https://github.com/idanarye/demonstrate-avian-4.0-locked-axis-bug W...
I am!
@buoyant badge any known workaround? 
Don't use LockedAxes and let Tnua fix the tilt
oh, neat!
Alright, that's good for me then. Thanks!
Yep, works as expected ๐
If it gets too wobbly without LockedAxes, set tilt_offset_angvel and tilt_offset_angacl to very high values.
.. that the workaround is no longer needed ...
I confirm that since bevy-tnua-avian3d 0.7.1 it is no longer needed.
Thank you.
Since everyone is here - I'm not sure about the syntax for the big Schemes refactor I'm planning, so I've opened a poll:
https://github.com/idanarye/bevy-tnua/discussions/110
Please vote
Idea regarding the first syntax: https://github.com/idanarye/bevy-tnua/discussions/110#discussioncomment-14690884
Hey do you have this project up publicly? Would be interested to see how you're putting it all together.
Yeah it's https://github.com/janhohenheim/foxtrot ๐
Thanks!
I'm too tired to get a recording right now (later), but after finally getting my project working in bevy 0.17, I seem to be having Tnua problems. More specifically, my player capsule keeps bouncing on the ground. Stuff I've tried to no effect:
- Extending the float height
- lowering gravity
- shrinking the collidler/TnuaAvian3dSensorShape (they're the same)
- changing collider type
Any ideas?
Make sure TnuaControllerPlugin and TnuaAvian3dPlugin both use the same schedule, and that it matches the one used by Avian (sans the Post)
Will check
Fixed this finally
https://github.com/avianphysics/avian/pull/881
It'll be in a patch release coming within a couple days
Did anything change with TnuaBuiltInClimb for the bevy 0.17 release? My player attaches to the climbable object but won't move up or down anymore. I'm poking around at my controller and player setup systems to see if i'm doing something wrong, but I'm not seeing anything.
Nothing changed on Tnua's side - it's working fine in the demo.
Is your code online somewhere I can see it?
Gimme a minute and i'll push what i'm working with
Here you go:
https://github.com/Zrocket/bevyrpg/tree/bevy-0.17
Relevent sections are:
src/player.rs
src/controller/player_controller.rs
src/furniture/ladder.rs
Let me know if there's anything else you need.
I'm not sure how it is supposed to work? The only place where I see you use TnuaBuiltinClimb is here:
https://github.com/Zrocket/bevyrpg/blob/f48a33fc31e47b6156432e59f76bb65c1cd842d2/src/controller/player_controller.rs#L234-L237
And the only field you are setting is climbable_entity - which is only there for the user to keep track on which object the character is climbing. Tnua itself does absolutely nothing with it.
The crucial fields for TnuaBuiltinClimb are:
anchor- which determines the location of the climbable object (really a point on its surface, but that's enough)desired_vec_to_anchor- which determines how far the center of the character is from the anchor, and also used to deduce the wall normal.desired_climb_velocity- which determines if you want to go up or down and how fast.
(other important fields really are configuration fields and have sensible defaults)
I don't see you setting any of these fields.
Weird. It working under bevy 0.16 like that must have been a glitch or something.
I tried mimicking the demo, but I'm still having a hard time getting TnuaBuiltInClimb working. Most likely, I'm not understanding it properly.
Maybe I need to improve the documentation, and hopefully the refactor will allow me to better structure it, but I'd say there are probably two intuitive things about TnuaBuiltinClimb:
- The action itself does not interact with the climbable entity's geometry. The
climbable_entityfield is for you - and forTnuaBlipReuseAvoidance- to avoid, for example, jumping from a ladder only to immediately cling back to it. It does not matter for the physics calculations. The action itself looks atanchoranddesired_vec_to_anchorand "takes your word" that these describe a climbable surface. - Tnua does not care about how the camera and how it affects the direction from the user input. It expects to receive vectors in the world's coordinates. This is true in general, but becomes a bit tricky when climbing is involved because what was usually in/out now also dubs as up/down. Tnua does nothing to resolve this ambiguity, leaving it up to you. Does the
DOWNarrow go down the ladder, or does it move the character back, making it detach from the ladder? And what if it's platformer controls and the character happens to be facing toward the camera? Tnua's philosophy is that the action should not deal with these questions. I may add some helpers for this in the future (although I'll have to figure the semantics for that helper first) but for now its your responsibility to handle these decisions.
(that second part was pretty funny for me, since i have "in"/"out" controls in addition to up/down/left/right)
If I can make a recommendation, adding documentation to the blip lens system would be very helpful. I've been able to figure it out on my own, but I feel proper documentation would be immensely helpful.
Recommendation accepted. I think the methods themselves - even the undocumented ones - are self-explanatory, but now that I look at the docs it is certainly not clear how to obtains a TnuaRadarBlipLens.
Yeah, it was essentially that. Plus, I guess I'm just the kind of person who likes understanding why something is done even if I can get it working.
Anyways, I think I see my error.
Side note, but would it make more sense to name climable_entity to climbed_entity, or something to that effect? Climbable implies possible future action, not a currently occurring one. Oh well, just my two cents
I plan a big refactor where this field will be moved out of the action, which will mean its name will be different (since it'll no longer refer to just climbing)
See https://github.com/idanarye/bevy-tnua/discussions/97#discussioncomment-14875977
examples doesn't works ?
Pulled that from the main branch and it doesn't seem to work
I've just released version 0.27 - the big refactor I was talking about: https://discord.com/channels/691052431525675048/1457126877486649444
@knotty peak - I've merged the branch of that big refactor a few days ago, so the examples were already ported to it even though the crate itself was not updated. It should work if you update bevy-tnua to 0.27 (and bevy-tnua-avian3d to 0.9, to match)
I've also noticed that bevy_rapier was upgraded to Bevy 0.17 a month ago. I've somehow missed that. I'll need to upgrade the integration, but that can wait for tomorrow...
My actors are very easily sliding on top of eachother.. has anyone experienced this before? What can I do to stop it? If an enemy actor walks up to me often times they will slide directly on top of my head.
They also get stuck on top of each other and myself if they land on top.
If you put TnuaNotPlatform on an entity, (other) Tnua controlled characters won't be autoclimbing on top of it.
Marker component for colliders which Tnua should not treat as platform.
If an enemy lands on my head it seems to get stuck there, I am using capsule collider with avian3d integration - any tips to prevent this?
WDYM "lands"? The enemy jumps or falls and ends up on top of the player character?
(I'm assuming here that TnuaNotPlatform worked and the enemy no longer climbs on top of the player character merely by walking into it)
Did anyone differentiate animation players for each spawned scene? I'm looking at this example and cannot help but think that the animation_player_entity here will be the first random one from that might be from the wrong scene if you spawn several of them simultaniously.
Am I wrong? Am I missing something ECS related?
animation_player_entity in that example will not necessarily be the first random one - it's also possible that the first two are spawned in the same frame and thus single() will always return Err and no entity at all will receive any animations.
If you have multiple entities that needs this, you can make the PlayerGltfHandle and the AnimationNodes components instead of resources - and then you can just put them on the relevant entities.
Or you can even make AnimationNodes an asset, so that you won't have to build the clips anew every time.
Yeah it seems to have worked. If an enemy falls and ends up on top of the player character or vice versa though, they get stuck.
What is the enemy AI telling it to do? Always go toward the player?
Well it happens with my player character too, if I jump on top of an enemy, I sort of get stuck up there. I have some other stuff setup too though, I am guessing that is the issue if this isn't anything you've ever encountered before.
Yea... but in the few games I did with multiple Tnua-controller entities, I usually had something happen when they collider with each other - which could have been what prevented this.
This this also happen before 0.27? (not that I think did anything that changes the physics in that version, but who knows...)
Okay, I tried that and it did help before. I had a system setup that gently pushed them away from each other.. I am trying to do swarms of enemies and I have a generalized Actor bundle which has the TnuaController on it. This happened with both versions, I actually just migrated to the new version to see if this fixed it but it did not.
What are you expecting to happen when one character entity jumps/falls over another?
Oh - and if gently pushing them away for each other is not enough, maybe you can try applying a knockback?
Thanks. I was expecting the player to sort of slide off and for gravity to pull the character down / inside the crowd & between the enemies. I think my capsule collider is too wide as well which sort of gives the appearance that my character is just sort of floating. I think this is really just an issue with how I have things setup in my own game - not anything with Tnua. Thank you for the help ๐
So something similar to what happens when you set a max_slope and stand on a slope too steep?
Perhaps, I have not encountered this yet or even configured max_slope. My character state shows as being airborne and I can sort of just get 'stuck' between three enemy actors colliders if I am on top of them. Where the rounded edges of their colliders slope down if they are pooled together it makes a sort of pit that I can get stuck in. I think it can be solved by reintroducing the system that gently pushes them away or some change in my AI behavior to make them do something differently if my player is above them though
One of Tnua's limitations is that the controller knows what's below it (and technically - what's above it) but not what's around it (the obstacles radar is not reflected to the controller - not directly)
Though, now that a basis is allowed to define sensors, maybe I can introduce a sensor in the direction of movement, so that it'd know when it's walking into something?
I'll probably want to make it cast the entity's collider instead of just a ray though...
But maybe if the range is really short it won't be such an issue.
So what changes exactly does TnuaNotPlatform do? Something with how it handles slopes on colliders?
No. And actually, maybe it's not the right solution for you after all.
TnuaNotPlatform simply makes the downward ray/shape cast (and the other sensors, but they are not important RN) not register hits on that entity.
It was originally made so that the game can have small objects on the ground and the player - which floats above them - won't treat them as a fully fledged platform.
This was very problematic because Tnua does not (currently) respect Newton's third law of motion - when it registers these small objects, the objects push the character up but the character does not push them down.
And even worse - if the object was moving (e.g. - because it'd collider with the player collider) Tnua will consider them a moving platform.
Imagine stepping on a chestnut (one hard enough to not break). The chestnut moves forward a bit because of your leg's motion - let's say at 40kph, which seems reasonable (I think) because it's so much lighter than you.
You now stand on a chestnut moving at 40kph, and because physics is broken you also move at 40kph with it.
But physics is broken so you don't apply a force on the chestnut in the other direction.
So it keeps moving forward.
And since you keep touching it - and that part of physics is not broken - you apply even more force on the chestnut.
So now it went to 80kph - and you with it.
Fun times.
So the solution was to add TnuaNotPlatform to the chestnut, which makes the sensor ignore it as a potential floor and just continue casting the ray until it reaches the actual floor.
Maybe it's not such a good idea with enemy characters, which I assume I roughly the same size as the player...
I probably need to deal better with dynamic characters...
It is roughly the same size in my case. I am not great at physics / not sure what the expected behavior should be, but from my own perspective I assumed that if I had a big group of people together, depending on how tightly packed they are, if something is above them it would tend to squeeze between them and push them out of the way to reach the ground - just from gravity I guess and depending on the amount of friction? If that makes sense.
If you replaced 'people' with some other capsule shaped object I think it makes more sense
It makes sense - if Newton's third law is working properly.
And there is also the issue that the accelerations Tnua applies on the entities can be too strong and overpower the physics engine's forces too quickly.
I struggled with that a lot (https://github.com/idanarye/bevy-tnua/issues/30) and eventually settled on creating TnuaBuiltinKnockback as a poor man's replacement for such things.
Thanks ๐ I will experiment with this and see if I can gently push them out of the way
Don't be too gentle. Knock them away.
I'm trying new control scheme approach and stumbled on something I do not understand.
Let's say I have this simple Jump observer from BEI input that is supposed to regulate jump: the longer you press it the higher the jump
fn handle_jump(
on: On<Fire<Jump>>,
mut player_query: Query<
(
&mut TnuaController<ControlScheme>,
&mut TnuaSimpleAirActionsCounter<ControlScheme>,
&mut JumpTimer,
),
With<Player>,
>,
) -> Result {
let (mut controller, mut air_counter, mut _jump_timer) = player_query.get_mut(on.context)?;
air_counter.update(controller.as_mut()); // Update air counter
controller.action(ControlScheme::Jump(TnuaBuiltinJump {
allow_in_air: true,
..Default::default()
}));
Ok(())
}
with new approach it panics if I do not feed basis every frame IIUC?
thread 'main' panicked at /home/pickle/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy-tnua-0.28.0/src/controller.rs:295:9:
Feeding action without invoking `initiate_action_feeding()`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `(...big system signature bundle...)`!
Encountered a panic in system `bevy_ecs::apply_deferred`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
Any tips how to do such actions in observer that will be fired while button is pressed?
I thought I could just swith to Start to do it once, but tnua panics all the same...
Or if I can alter something if input accumulates each time Fire event is...well fired
Switch to Start, but also use Tnua's action_start instead of just action. And you also need to place an observer on Complete and/or on Cancel (I'm not familiar enough with BEI to know what's the difference between them, but I think it's related to how you've configured the action) and call action_end there.
Alternatively, if you can somehow arrange for initiate_action_feeding to be called each frame before all the BEI observers are triggered, you could keep using Fire and action.
The main component used for interaction with the controls and animation code (needs a TnuaConfig)
oh. I somehow missed action_start. it'll work for now. but I will explore feeding manually
thank you!
I'm trying to update from 0.16, and I can't seem to get anything working.
But so far my player just falls to the ground and can't move.
Nevermind. I needed to update the other Tnua libraries.
Can someone help me understand these traits?:
https://docs.rs/bevy-tnua/latest/bevy_tnua/control_helpers/trait.TnuaHasTargetEntity.html
https://docs.rs/bevy-tnua/latest/bevy_tnua/control_helpers/trait.TnuaAirActionDefinition.html
Must be implemented by control schemes that want to use TnuaBlipReuseAvoidance or
Must be implemented by control schemes that want to use TnuaAirActionsTracker or TnuaSimpleAirActionsCounter.
As the doc says - you need to implement them on the scheme if you want to use control helpers like TnuaSimpleAirActionsCounter or TnuaBlipReuseAvoidance. See the example in the demo: https://github.com/idanarye/bevy-tnua/blob/0bd139b38c06d8de7c92a2dfafce6dd6f34c6557/demos/src/character_control_systems/platformer_control_scheme.rs#L43-L69
Thanks, I didn't see the demo had them.
I should probably add some examples to the docs...
Note that in (hopefully near) future versions I may deprecate TnuaAirActionDefinition when I make a more sophisticated air actions tracker (need to design it first...) - but TnuaHasTargetEntity is here to stay because that information about entities is no longer part of the action itself. Then again - I may integrate it into the macro.
I'm still confused as to implementing TnuaHasTargetEntity. What exactly am I providing for target_entity?
Wait
Is it "return the entity you're interacting with if said action involves an interaction"?
Correct. I've added WallSlide in the example, but TBH currently it's mostly for Climb. Consider a game where the player automatically climbs a rope as soon as it comes into contact with it. When the player jumps or drops form the rope - the frame after they stop the climb action they are still in contact with the rope, so if you apply the controls logic blindly they are supposed to climb the rope again.
(even if you take the input direction into account, it's possible to climb from a rope while the player is on the opposite side of where the input direction goes)
The solution is to use TnuaHasTargetEntity and return the entity of the rope (or whatever else the player climbs on). Then TnuaBlipReuseAvoidance will tell you, when queries (and assuming you update it properly) that this entity was already "in use" so you can know not to climb on it again. Once the player is no longer in contact with the rope it gets removed from the TnuaBlipReuseAvoidance which means they can climb on it again on the next contact.
with the builtin walk is it possible to allow a faster pivot?
also, how do you disable the hitbox bouncing?
What do you mean by "faster pivot"? And what do you mean by "hitbox bouncing?"
after a jump the hitbox appears to bounce
by pivot, i mean if the character is walking right, and suddenly turns left
or vice versa
right now it has to decelerate before going in the other direction, but i'm wondering if it's possible to skip the deceleration and start accelerating from zero velocity. to make the controls feel more tight
You can use acceleration, but it affects both braking and accelerating.
API documentation for the Rust TnuaBuiltinWalkConfig struct in crate bevy_tnua.
As for the bounce - that's because of the floating nature of the character controller. Try increasing spring_strengh and spring_dampening
API documentation for the Rust TnuaBuiltinWalkConfig struct in crate bevy_tnua.
i think some of my issues have probably been caused by me not being able to adjust the position of the hitbox for some reason, i have no idea why
i'll fix that first then see if tnua is still having issues
so for some reason whenever i add the collider as a component, the transform isn't applied, but when i add it as a child it clips straight into the ground, ignoring the tnua float that's supposed to be there
@celest summit do you know how i might be able to fix that?
If the collider itself clips through the ground - that's on the physics engine, because Tnua couldn't have caused it even if it tried.
(unless the ground is a ghost platform, of course)
what happens is that the float isn't applied
Is the float height high enough?
yeah, even turning it up really high doesn't change anything
I might have messed something up...
Do you have a minimal example? How are you creating the character entity?
I'm trying to move a simple NPC entity, and I don't know what I'm doing wrong. Here's my simple movement system that trys to move them along a navmesh:
fn apply_walking(
mut character_query: Query<(&mut TnuaController<PlayerControlScheme>, &AgentDesiredVelocity3d), Without<Player>>,
) {
trace!("SYSTEM: apply_walking");
for (mut controller, desired_velocity) in &mut character_query {
let Ok(direction) = Dir3::new(desired_velocity.velocity()) else { return };
println!("{:?}", direction);
controller.initiate_action_feeding();
controller.basis = TnuaBuiltinWalk { desired_motion: direction.normalize_or_zero(), desired_forward: Some(direction) };
}
}
I don't have any issues when moving the player character, only this.
Never mind; it was a navmesh issue.
Anyone made a Swim basis yet?
I'm currently trying to make one, and thought I would ask.
Not really sure how to go about it. Is water support from the physics engine required in order to develop it?
I also don't really know if it should be a basis or an action. While it's true that one may want to use it as a basis and then add underwater actions, many games support jumping out of water - which means converting changing the basis while an action is active. I guess Tnua will need to add support for carrying an active action from one basis to another?
is there an option to customise pivot speed?
Sorry for the delay in replying. You'd want TnuaBuiltinWalkConfig::turning_angvel.
API documentation for the Rust TnuaBuiltinWalkConfig struct in crate bevy_tnua.
how does the new control system work? should it go alongside bei or replace it
Alongside BEI - or any other user input management system. Tnua does not handle user input directly, and that did not change with the new control system ("Schemes")
What did change was the addition of methods like action_start that better suit for BEI's observer-based push-style input management (the original action method - which is still available, and is still my preferred way - is for pull-style input management - as in, check the input every frame)
The main component used for interaction with the controls and animation code (needs a TnuaConfig)
i'm trying to use this but it doesn't seem to change much
if i set it to 0, or a huge number, or infinity, it acts the same
even NaN