#bevy_tnua

1 messages ยท Page 2 of 1

atomic vigil
#

kinda like shifting a line vertically than horizontally

celest summit
#

Yea. You want something perpendicular to the normal.

atomic vigil
#

Something like this?

celest summit
#

Not sure what's going on in this picture, but why do you need two reject_froms?

atomic vigil
#

Ah i get it now

surreal kettle
atomic vigil
surreal kettle
#

โŒ๐Ÿฑ?

atomic vigil
#

Dont you sass me

#

hahahah

atomic vigil
#

let walk_direction_on_slope = normal_direction.reject_from(linear_velocity_xz); So something like this?

atomic vigil
#

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

celest summit
atomic vigil
surreal kettle
atomic vigil
#

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

celest summit
#

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);
atomic vigil
#

Ah my boost breaks things

#

thanks for you aid aeon

#

Sorry for the bothering

celest summit
# atomic vigil Ah my boost breaks things

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)

surreal kettle
#

does tnua not apply forces to things a controller is standing on?

celest summit
daring nebula
#

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.

celest summit
wispy marten
#

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.

celest summit
#

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?

wispy marten
#

Let me just make my repo public real quick

#

Also, what's a good simple video tool for linux I can use real quick?

celest summit
#

Which I now see is deprecated.

wispy marten
#

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.

celest summit
#

Are you leaving enough floating height between the collider and the floor?

wispy marten
#

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

wispy marten
# wispy marten

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

wispy marten
# wispy marten

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.

celest summit
#

Weird that the physics engine does not prevent this...

raven fossil
# wispy marten What's confusing is that the air actions counter still reads 0 when this happens...

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()
        });
    }


raven fossil
#

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?

surreal kettle
#

mesh origin is at its feet, capsule origin is in its center

#

probably just put the mesh on a child

wispy marten
#

Just trying to isolate where the issue might be

raven fossil
#

yep

celest summit
wispy marten
celest summit
celest summit
wispy marten
#

Yeah, it's from when I updated it to the latest bevy before the original author got around to it.

celest summit
#

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)

wheat valley
#

@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

wheat valley
wheat valley
#

Update2 OOOOH I see the issue. I'm running this on an observer, not every frame!

#

Yep, resetting it every frame works ๐Ÿ˜„

celest summit
#

Does the observer mean that when you don't touch the controls there are no events and the system doesn't run?

wheat valley
#

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

wheat valley
#

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?

celest summit
#

Try setting the player's friction to zero?

wheat valley
wheat valley
#

I'm curious, why does this work?

celest summit
#

So it really was friction?

#

It was a long shot.

wheat valley
celest summit
#

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)

celest summit
#

Go ahead

wheat valley
#

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!

celest summit
#

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.

wheat valley
celest summit
#

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.

wheat valley
#

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?

celest summit
#

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

GitHub

With #85 I've removed one cause of #14 (character's collider penetrating another collider and the shapecast starts from there), but removing my original fix (ignoring cast hits with collide...

wheat valley
wispy marten
wispy marten
#

Alright, it should work now

#

If it still gives you issues, let me know and I can tweak some things.

wheat valley
celest summit
#

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?

wispy marten
raven fossil
#

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

lone dawn
#

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?

celest summit
# lone dawn I'm using the rapier backend. Is there a way to let the character be pushed arou...

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.

GitHub

A floating character controller for Bevy. Contribute to idanarye/bevy-tnua development by creating an account on GitHub.

wispy marten
#

Sometimes I wish I could just use gdb with bevy

lone dawn
#

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

celest summit
#

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)

wispy marten
#

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.

raven fossil
raven fossil
#

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

GitHub

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...

celest summit
#

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?

raven fossil
#

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());.

celest summit
#

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?

raven fossil
#

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

celest summit
#

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)

raven fossil
#

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

celest summit
#

On the frame where it's "true 1 1" the prepare_animations system should have everything it needs to pass its three if-lets...

raven fossil
#

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!

wheat valley
#

@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?

celest summit
#

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?

wheat valley
#

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

celest summit
#

The sensor shape is a circle with the same radius as the collider?

wheat valley
#

radius of the collider = 0.8
radius of the sensor shape = 0.79

celest summit
#

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?

wheat valley
#

way better

#

sec

celest summit
#

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.

wheat valley
#

I could just disable collisions between props and characters, but that would feel really weird

#

Any idea if I could hack in something else?

celest summit
#

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.

wheat valley
#

Let me know if I can help in any way!

celest summit
#

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)

GitHub

A floating character controller for Bevy. Contribute to idanarye/bevy-tnua development by creating an account on GitHub.

wheat valley
celest summit
#

Aren't they one and the same?

#

But... the collider, I think

wheat valley
#

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 ๐Ÿ˜„

celest summit
#

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...)

wheat valley
#

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)

celest summit
#

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?

wheat valley
#

(not sure though)

#

actually, let me check real quick

celest summit
#

Either way, I'm out for the night.

wheat valley
#

and thanks again ๐Ÿ™‚

wheat valley
surreal kettle
celest summit
#

So for now let's go with TnuaNotPlatform marker component solution.

wheat valley
celest summit
#

@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?

wheat valley
raven fossil
#

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

celest summit
#

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?

raven fossil
#

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

celest summit
#
  1. What are the other (relevant) AnimationState variants, and how do you set them?
  2. How/when are you starting the actual animations?
  3. Are you using TnuaAnimatingState?
raven fossil
#

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

celest summit
#

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)

raven fossil
#

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๐Ÿซก

celest summit
#

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.

raven fossil
#

oooh, that makes sense. thanks!

wheat valley
celest summit
celest summit
#

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.

wheat valley
#

(not that I'm much of an expert on this)

celest summit
#

Not really a priority right now.

#

But thanks for the offer.

wheat valley
#

@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?

celest summit
wheat valley
#

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

celest summit
#

Is you sensor shape, perchance, wider than your collider?

wheat valley
celest summit
#

Try making the chairs static rigid bodies, just to see if the problem is with dynamic objects

wheat valley
wheat valley
#

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

#

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

wheat valley
celest summit
#

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.

celest summit
wheat valley
celest summit
#

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.

wheat valley
celest summit
wheat valley
#

that gif looks really cool ๐Ÿ˜„

#

good work!

#

This would enable coding something like wallrunning on your own, right?

celest summit
#

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...

raven fossil
#

I just saw the new release on reddit.
this is so cool!
nice job, I think many people were asking for a wall jumps

noble oyster
#

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

celest summit
#

Also I saw TnuaSimpleAirActionsCounter and TnuaAirActionsTracker but 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)

noble oyster
celest summit
#

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?

noble oyster
#

ok. I will post it in general

celest summit
#

Should probably also add a video.

warm smelt
#

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.

celest summit
#

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?

warm smelt
#

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

warm smelt
#

Perhaps gravity could be disabled when the spring is active.

celest summit
atomic vigil
# celest summit

@celest summit My floating character is making my fps fall by 50 frames

#

Lovely right

#

hahahahaha

celest summit
# atomic vigil <@358755443834880000> My floating character is making my fps fall by 50 frames

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.

atomic vigil
#

i see

wheat valley
#

@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?

surreal kettle
#

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

celest summit
# wheat valley <@358755443834880000> does Tnua do input accumulation for me? So e.g. if I happe...

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.

celest summit
# surreal kettle when tnua's systems run, it applies forces to make the simulation more similar t...

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.

wheat valley
#

@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 ๐Ÿ™‚

wheat valley
wheat valley
wheat valley
celest summit
#

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.

wheat valley
#

I won't worry about input accumulation for jumps then

celest summit
#

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?

wheat valley
surreal kettle
#

i just make all of my actions "held button"

iron vine
#

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 TnuaGhostSensor is picking up the ghost platform correctly, at the right proximity
  • the TnuaSimpleFallThroughPlatformsHelper is 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
surreal kettle
#

does removing the chain fix it

iron vine
#

no, seems like it's unrelated

celest summit
#

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.

iron vine
#

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 upsidedowncry

#

really don't like the schedule/systemset distinction

wheat valley
iron vine
#

i would very much enjoy that

celest summit
iron vine
#

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

celest summit
#

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.

iron vine
#

while reducing the opportunities for incorrect configuration

wheat valley
atomic vigil
#

@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

gritty drift
#

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

celest summit
celest summit
gritty drift
#

@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

celest summit
#

Why is RapierPhysicsPlugin commented out?

gritty drift
#

@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.

gritty drift
#

@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

celest summit
#

I didn't really do anything, but... you're welcome?

atomic vigil
#

You can focu on the crate psycho_player specifically the shared part logic

shrewd edge
#

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?

atomic vigil
#

you can grab the spatial raycast and check if collides with above plate

celest summit
shrewd edge
#

hmm ok, sounds like i can't escape applying the force manually if i want to move the plate itself as a result

surreal kettle
#

imo tnua should apply forces to things you stand on

shrewd edge
#

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

wheat valley
#

Makes it obvious that you just triggered something ๐Ÿ™‚

atomic vigil
#

@celest summit Oh my god you actually reviewed it thanks for the input

raven fossil
#

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

wheat valley
wheat valley
#

Like this ๐Ÿ™‚

raven fossil
#

hmm, will it affect standing on slopes?

wheat valley
#

Shouldn't, I believe? Not sure

raven fossil
#

ok, I'll check it out

wheat valley
#

If you test, please let me know ๐Ÿ™‚

raven fossil
celest summit
#

Do you want the player to noclip into the wall? I don't think the physics engine would allow this.

raven fossil
#

hmm, not really, I want the collider to do what it's suppose to do but not affect the animation

wheat valley
raven fossil
#

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!

raven fossil
#

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


celest summit
#

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.

raven fossil
raven fossil
#

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

surreal kettle
#

it's a floating character controller, the character's collider has to float

raven fossil
#

ok, I figured it out by adjusting mesh relative to parent entity, but man, this simple fact was hard to grasp

raven fossil
# wheat valley If you test, please let me know ๐Ÿ™‚

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

wheat valley
raven fossil
#

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

wheat valley
#

nice background music btw

wheat valley
#

Or I guess that would make the collider stick too much in the air, hmm

raven fossil
kind cargo
#

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

surreal kettle
kind cargo
#

when it wobbles in the middle I am not even touching the controls

#

but also surely this is already over the max slope angle

surreal kettle
#

iirc the default max slope is really high

kind cargo
#

it looked very spring-like but I guess there are actual spring properties

surreal kettle
#

that part probably depends on your scale

kind cargo
#

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

celest summit
kind cargo
#

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

wheat valley
wheat valley
#

I believe LWIM does a bunch of this for you? Not sure, I switched to bevy_enhanced_input

wheat valley
# kind cargo 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 ๐Ÿ™‚

kind cargo
#

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

wheat valley
#

Ha, understandable

wheat valley
#

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?

wheat valley
# kind cargo how do they know how I spawned my camera

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

kind cargo
#

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

raven fossil
astral smelt
#

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.

players spawn here

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.

wheat valley
#

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),
);
astral smelt
astral smelt
#

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?

#

i think im having similar issue with jump height x.x

wheat valley
#

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()
    });
}
astral smelt
wheat valley
astral smelt
#

rn i just tried doing a specific speed depending on the sign

#

of the input

#

xd

#

was checking this

astral smelt
wheat valley
astral smelt
surreal kettle
astral smelt
astral smelt
muted kettle
#

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. )

surreal kettle
#

with a sensor shape?

muted kettle
#

No this is with a ray

tropic prism
#

is there a way to prevent the character from sort of sticking to walls and also losing horizontal speed when bumping its head?

celest summit
#

Set the friction to zero

tropic prism
#

thanks

tawny cedar
#

fwd: #1415540694545596416 message

wheat valley
#

@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

celest summit
#

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.

wheat valley
#

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 ๐Ÿ™‚

celest summit
#

I find these things are easier to fix after the official release, when the release notes of the relevant crates are finalized.

wispy marten
#

Is there a good example on using TnuaBuiltinClimb? Thanks.

celest summit
# wispy marten Is there a good example on using TnuaBuiltinClimb? Thanks.
GitHub

A floating character controller for Bevy. Contribute to idanarye/bevy-tnua development by creating an account on GitHub.

wispy marten
#

Thanks, I just needed something to look at.

tropic prism
#

@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.```
wheat valley
tropic prism
#

yep

#

well it's probably on the same tick

#

but in code it doesn't work when either before or after

wheat valley
#

hrmm

#

that's weird

#

did Avian change something?

tropic prism
#

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

wheat valley
#

I can check tomorrow

tropic prism
#

sure

wheat valley
#

But do ask in #1124043933886976171 if something changed

celest summit
#

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?

raven fossil
#

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" }

tropic prism
#

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

candid light
#

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

GitHub

Contribute to xenon615/tnua_test development by creating an account on GitHub.

celest summit
#

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.

celest summit
#

Annnnd... it's upgraded

wheat valley
#

thanks a bunch!

candid light
#

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

celest summit
#

@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;
         }
 
     }
candid light
#

@celest summit Thanks for the hint, maybe I really should turn down the speed, I was confused that it worked in 0.16

celest summit
#

Maybe Avian changed it?

candid light
celest summit
#

I can also address this scenario in Tnua itself.

candid light
#

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.

wheat valley
#

Pretty sure a static body should ignore any velocity

#

as that concept does not apply to it

surreal kettle
#

the issue was that tnua wasn't reading the rigidbody type when reading velocity afaict

celest summit
#

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.

wheat valley
#

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 hmm

buoyant badge
#

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

buoyant badge
#

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 ๐Ÿ˜…

wheat valley
#

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

celest summit
wheat valley
#

@buoyant badge any known workaround? hmm

celest summit
#

Don't use LockedAxes and let Tnua fix the tilt

wheat valley
#

Alright, that's good for me then. Thanks!

celest summit
#

If it gets too wobbly without LockedAxes, set tilt_offset_angvel and tilt_offset_angacl to very high values.

candid light
celest summit
celest summit
frosty depot
wheat valley
wispy marten
#

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?

celest summit
#

Make sure TnuaControllerPlugin and TnuaAvian3dPlugin both use the same schedule, and that it matches the one used by Avian (sans the Post)

wispy marten
#

Will check

buoyant badge
#

It'll be in a patch release coming within a couple days

wispy marten
#

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.

celest summit
#

Nothing changed on Tnua's side - it's working fine in the demo.

#

Is your code online somewhere I can see it?

wispy marten
#

Gimme a minute and i'll push what i'm working with

wispy marten
#

Relevent sections are:
src/player.rs
src/controller/player_controller.rs
src/furniture/ladder.rs

wispy marten
#

Let me know if there's anything else you need.

celest summit
#

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.
GitHub

Contribute to Zrocket/bevyrpg development by creating an account on GitHub.

wispy marten
#

Weird. It working under bevy 0.16 like that must have been a glitch or something.

wispy marten
#

I tried mimicking the demo, but I'm still having a hard time getting TnuaBuiltInClimb working. Most likely, I'm not understanding it properly.

celest summit
#

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:

  1. The action itself does not interact with the climbable entity's geometry. The climbable_entity field is for you - and for TnuaBlipReuseAvoidance - 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 at anchor and desired_vec_to_anchor and "takes your word" that these describe a climbable surface.
  2. 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 DOWN arrow 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.
surreal kettle
#

(that second part was pretty funny for me, since i have "in"/"out" controls in addition to up/down/left/right)

wispy marten
celest summit
#

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.

wispy marten
#

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

celest summit
knotty peak
#

examples doesn't works ?

celest summit
#

@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...

quaint shore
#

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.

celest summit
quaint shore
celest summit
raven fossil
#

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?

celest summit
#

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.

quaint shore
celest summit
quaint shore
celest summit
#

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...)

quaint shore
celest summit
#

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?

quaint shore
#

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 ๐Ÿ™‚

celest summit
#

So something similar to what happens when you set a max_slope and stand on a slope too steep?

quaint shore
# celest summit So something similar to what happens when you set a `max_slope` and stand on a s...

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

celest summit
#

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.

quaint shore
#

So what changes exactly does TnuaNotPlatform do? Something with how it handles slopes on colliders?

celest summit
#

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...

quaint shore
#

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

celest summit
#

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.

quaint shore
#

Thanks ๐Ÿ™‚ I will experiment with this and see if I can gently push them out of the way

celest summit
#

Don't be too gentle. Knock them away.

raven fossil
#

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

celest summit
# raven fossil I'm trying new control scheme approach and stumbled on something I do not unders...

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.

raven fossil
wispy marten
#

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.

wispy marten
#
celest summit
# wispy marten Can someone help me understand these traits?: https://docs.rs/bevy-tnua/latest/b...

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

GitHub

A floating character controller for Bevy. Contribute to idanarye/bevy-tnua development by creating an account on GitHub.

wispy marten
#

Thanks, I didn't see the demo had them.

celest summit
#

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.

wispy marten
#

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"?

celest summit
#

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.

tropic prism
#

with the builtin walk is it possible to allow a faster pivot?

#

also, how do you disable the hitbox bouncing?

celest summit
#

What do you mean by "faster pivot"? And what do you mean by "hitbox bouncing?"

tropic prism
#

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

celest summit
tropic prism
#

i'll fix that first then see if tnua is still having issues

tropic prism
#

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

tropic prism
#

@celest summit do you know how i might be able to fix that?

celest summit
#

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)

tropic prism
#

what happens is that the float isn't applied

celest summit
#

Is the float height high enough?

tropic prism
#

yeah, even turning it up really high doesn't change anything

celest summit
#

I might have messed something up...

#

Do you have a minimal example? How are you creating the character entity?

wispy marten
#

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.

wispy marten
#

Never mind; it was a navmesh issue.

wispy marten
#

Anyone made a Swim basis yet?

#

I'm currently trying to make one, and thought I would ask.

celest summit
#

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?

tropic prism
#

is there an option to customise pivot speed?

celest summit
tropic prism
#

how does the new control system work? should it go alongside bei or replace it

celest summit
#

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)

tropic prism
#

if i set it to 0, or a huge number, or infinity, it acts the same

#

even NaN