### Plugin Setup with Avian3D Physics Source: https://context7.com/idanarye/bevy-tnua/llms.txt Demonstrates setting up the `TnuaControllerPlugin` and `TnuaAvian3dPlugin` for a custom `ControlScheme`. Ensure the plugins use the same schedule as the physics engine (e.g., `FixedUpdate`). The `setup_player` function shows how to initialize a player entity with Tnua components. ```rust use bevy::prelude::*; use avian3d::prelude::*; use bevy_tnua::prelude::*; use bevy_tnua_avian3d::prelude::*; #[derive(TnuaScheme)] #[scheme(basis = TnuaBuiltinWalk)] enum ControlScheme { Jump(TnuaBuiltinJump), } fn main() { App::new() .add_plugins(( DefaultPlugins, // Avian physics PhysicsPlugins::default(), // Tnua core — must match the physics schedule TnuaControllerPlugin::::new(FixedUpdate), // Tnua ↔ Avian bridge TnuaAvian3dPlugin::new(FixedUpdate), )) .add_systems(Startup, setup_player) .add_systems(Update, apply_controls.in_set(TnuaUserControlsSystems)) .run(); } fn setup_player( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut configs: ResMut>, ) { commands.spawn(( Mesh3d(meshes.add(Capsule3d { radius: 0.5, half_length: 0.5 })), MeshMaterial3d(materials.add(Color::WHITE)), Transform::from_xyz(0.0, 2.0, 0.0), RigidBody::Dynamic, Collider::capsule(0.5, 1.0), TnuaController::::default(), TnuaConfig::(configs.add(ControlSchemeConfig { basis: TnuaBuiltinWalkConfig { float_height: 1.5, // must exceed collider half-height ..Default::default() }, jump: TnuaBuiltinJumpConfig { height: 4.0, // only required field ..Default::default() }, })), TnuaAvian3dSensorShape(Collider::cylinder(0.49, 0.0)), // recommended LockedAxes::ROTATION_LOCKED, )); } ``` -------------------------------- ### Run 3D Platformer Demo with Avian Source: https://github.com/idanarye/bevy-tnua/blob/main/README.md This is a specific example of running the 3D platformer demo using the Avian physics backend. It demonstrates how to combine demo names and feature flags for execution. ```sh $ cargo run --bin platformer_3d --features avian3d ``` -------------------------------- ### TnuaBuiltinKnockback / TnuaBuiltinKnockbackConfig Source: https://context7.com/idanarye/bevy-tnua/llms.txt Shoves the character in a direction, for example, from taking damage, in a way that the walk basis cannot immediately cancel. This action is applied with `action_interrupt` to override any ongoing action. ```APIDOC ## `TnuaBuiltinKnockback` / `TnuaBuiltinKnockbackConfig` — knockback action Shoves the character in a direction (e.g. from taking damage) in a way that the walk basis cannot immediately cancel. Applied with `action_interrupt` so it overrides any ongoing action. ### Configuration ```rust let knockback_cfg = TnuaBuiltinKnockbackConfig { no_push_timeout: 0.1, // abandon boundary after this long without push barrier_strength_diminishing: 2.0, // keep > 1.0 for stable behavior ..Default::default() }; ``` ### Usage Example ```rust // Triggered by a game event (outside TnuaUserControlsSystems): fn handle_hit_events( mut events: EventReader, mut query: Query<&mut TnuaController>, ) { for event in events.read() { if let Ok(mut controller) = query.get_mut(event.target) { controller.action_interrupt(ControlScheme::Knockback(TnuaBuiltinKnockback { shove: event.direction * 12.0, force_forward: Dir3::new(-event.direction).ok(), })); } } } ``` ``` -------------------------------- ### Control Entity with TnuaController Source: https://context7.com/idanarye/bevy-tnua/llms.txt Use TnuaController to manage entity actions and physics. Initiate action feeding each frame and set the controller's basis. Actions can be triggered, started, or interrupted. ```rust use bevy::prelude::*; use bevy_tnua::prelude::*; fn apply_controls( keyboard: Res>, mut query: Query<&mut TnuaController>, ) { let Ok(mut controller) = query.single_mut() else { return }; // Must be called first each frame if using controller.action(...) controller.initiate_action_feeding(); // --- Basis: set every frame (even when standing still) --- let mut dir = Vec3::ZERO; if keyboard.pressed(KeyCode::ArrowUp) { dir -= Vec3::Z; } if keyboard.pressed(KeyCode::ArrowDown) { dir += Vec3::Z; } if keyboard.pressed(KeyCode::ArrowLeft) { dir -= Vec3::X; } if keyboard.pressed(KeyCode::ArrowRight) { dir += Vec3::X; } controller.basis = TnuaBuiltinWalk { desired_motion: dir.normalize_or_zero(), // turn the model in the movement direction: desired_forward: Dir3::new(dir).ok(), }; // --- Actions: feed as long as button is held --- if keyboard.pressed(KeyCode::Space) { controller.action(ControlScheme::Jump(Default::default())); } // Push-fashion one-shot trigger (e.g. from an event system) // controller.action_trigger(ControlScheme::Dash(TnuaBuiltinDash { ... })); // Start/hold pattern (release explicitly) // controller.action_start(ControlScheme::Crouch(TnuaBuiltinCrouch)); // controller.action_end(ControlSchemeActionDiscriminant::Crouch); // Interrupt: overrides any contender, cannot itself be overridden // controller.action_interrupt(ControlScheme::Knockback( ... )); // Read back state for animation / logic let _is_airborne: bool = controller.is_airborne().unwrap_or(false); let _flow = controller.action_flow_status(); // TnuaActionFlowStatus } ``` -------------------------------- ### Retrieve Running Velocity from Tnua Controller Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md Access the TnuaController to get the state of the TnuaBuiltinWalk basis and retrieve the running velocity. This replaces the older animating_output.running_velocity. ```rust let Some((basis_input, basis_state)) = controller.concrete_basis::() else { // in case some other basis is used - usually happens before the first basis is fed. continue; }; // basis_input is the `TnuaBuiltinWalk` command, which is usually not very interesting. // This replaces `let speed = animating_output.running_velocity.length();`: let speed = basis_state.running_velocity.length(); ``` -------------------------------- ### Monitor Action Lifecycle with TnuaActionFlowStatus Source: https://context7.com/idanarye/bevy-tnua/llms.txt Use `controller.action_flow_status()` to get the current state of an action (Started, Ongoing, Ended, Cancelled). This is useful for triggering events like sound effects or animations based on the action's lifecycle. ```rust use bevy_tnua::controller::TnuaActionFlowStatus; use bevy_tnua::prelude::*; fn react_to_jumps(query: Query<&TnuaController>) { let Ok(controller) = query.single() else { return }; match controller.action_flow_status() { TnuaActionFlowStatus::NoAction => { /* idle */ } TnuaActionFlowStatus::ActionStarted(d) => { println!("Action just started: {:?}", d); // Play sound, spawn VFX, etc. } TnuaActionFlowStatus::ActionOngoing(d) => { println!("Action ongoing: {:?}", d); } TnuaActionFlowStatus::ActionEnded(d) => { println!("Action ended: {:?}", d); } TnuaActionFlowStatus::Cancelled { old, new } => { println!("Action {:?} cancelled into {:?}", old, new); } } } ``` -------------------------------- ### Match Action Name in Tnua Controller Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md Use TnuaController::action_name to get the name of the current action and match against the NAME constant for efficient handling of different actions. ```rust match controller.action_name() { Some(TnuaBuiltinJump::NAME) => { /* ... */ } Some(TnuaBuiltinCrouch::NAME) => { /* ... */ } Some(other) => panic!("Unknown action {other}"), None => { /* ... */ } } ``` -------------------------------- ### Run Bevy Tnua Demos Locally Source: https://github.com/idanarye/bevy-tnua/blob/main/README.md Use this command to run any demo locally. Replace `` with the specific demo and `` with the desired physics engine (e.g., `rapier2d`, `rapier3d`, `avian2d`, `avian3d`). Ensure the backend's dimensionality matches the demo. ```sh $ cargo run --bin --features ``` -------------------------------- ### Configure TnuaBuiltinWalk Source: https://context7.com/idanarye/bevy-tnua/llms.txt Configure TnuaBuiltinWalk with parameters like speed, hover height, acceleration, and coyote time. Default values can be used for most settings. ```rust use bevy_tnua::builtins::{TnuaBuiltinWalk, TnuaBuiltinWalkConfig, TnuaBuiltinWalkHeadroom}; // Configuration (asset field named `basis` in the config struct): let walk_cfg = TnuaBuiltinWalkConfig { speed: 10.0, // max horizontal speed (units/sec) float_height: 1.5, // center-of-mass hover height above ground cling_distance: 1.0, // spring still active this far above float_height spring_strength: 400.0, // spring force coefficient spring_dampening: 1.2, // vertical velocity dampening (keep < 2.0) acceleration: 60.0, // horizontal acceleration on ground air_acceleration: 20.0, // horizontal acceleration in air (0 = no air control) coyote_time: 0.15, // seconds to still jump after walking off a ledge free_fall_extra_gravity: 60.0, // extra downward force when in free fall max_slope: std::f32::consts::FRAC_PI_4, // max walkable slope (π/2 disables) headroom: Some(TnuaBuiltinWalkHeadroom { distance_to_collider_top: 1.0, // half-height of character collider sensor_extra_distance: 0.1, }), ..Default::default() }; // Each frame input (set on TnuaController::basis): let walk_input = TnuaBuiltinWalk { desired_motion: Vec3::new(1.0, 0.0, 0.0), // normalised horizontal vector * speed multiplier desired_forward: Some(Dir3::NEG_Z), // model faces -Z; None = don't rotate }; ``` -------------------------------- ### Configure and Input TnuaBuiltinCrouch Source: https://context7.com/idanarye/bevy-tnua/llms.txt Configure crouch parameters for smooth height adjustment. Must be fed every frame while the crouch button is held. Requires `TnuaBuiltinWalkConfig::headroom` to be configured to prevent standing under ceilings. ```rust use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinCrouchConfig}; let crouch_cfg = TnuaBuiltinCrouchConfig { float_offset: -0.9, // lower the float height by this much height_change_impulse_for_duration: 0.04, // transition duration (seconds) height_change_impulse_limit: 40.0, // max transition impulse }; // In the control system — hold to crouch, release to stand: fn apply_controls( keyboard: Res>, mut query: Query<&mut TnuaController>, ) { let Ok(mut controller) = query.single_mut() else { return }; controller.initiate_action_feeding(); // ... set basis ... if keyboard.pressed(KeyCode::ControlLeft) { controller.action(ControlScheme::Crouch(TnuaBuiltinCrouch)); } } ``` -------------------------------- ### Configure Ghost Platform Components Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Set up a platform entity to be a 'ghost' platform by configuring its solver groups and adding the `TnuaGhostPlatform` component. Ensure the solver groups exclude the character that should pass through. ```rust cmd.insert(SolverGroups { // Or pick some other configuration - as long as it excludes the character // that needs to jump or fall through this platform. memberships: Group::empty(), filters: Group::empty(), }); cmd.insert(TnuaGhostPlatform); ``` -------------------------------- ### Defining a Control Scheme with `#[derive(TnuaScheme)]` Source: https://context7.com/idanarye/bevy-tnua/llms.txt Illustrates how to define a complex control scheme for a 3D platformer using the `#[derive(TnuaScheme)]` macro. This includes defining actions like Jump, Crouch, Dash, WallJump, and WallSlide, with options for sharing triggers and carrying extra data. ```rust use bevy_tnua::prelude::*; use bevy_tnua::builtins::*; // A 3-D platformer with walk, jump, crouch, dash, and wall-slide #[derive(TnuaScheme)] #[scheme(basis = TnuaBuiltinWalk)] enum PlatformerScheme { Jump(TnuaBuiltinJump), Crouch(TnuaBuiltinCrouch), Dash(TnuaBuiltinDash), // WallJump shares its trigger state with Jump (same button press) #[scheme(same_trigger(Jump))] WallJump(TnuaBuiltinJump), // Carry extra data alongside the action (e.g. the wall entity) WallSlide(TnuaBuiltinWallSlide, Entity), } // The macro generates `PlatformerSchemeConfig`, which is a Bevy `Asset`: // PlatformerSchemeConfig { // basis: TnuaBuiltinWalkConfig { ... }, // jump: TnuaBuiltinJumpConfig { ... }, // crouch: TnuaBuiltinCrouchConfig { ... }, // dash: TnuaBuiltinDashConfig { ... }, // wall_jump: TnuaBuiltinJumpConfig { ... }, // wall_slide: TnuaBuiltinWallSlideConfig { ... }, // } ``` -------------------------------- ### Instantiate TnuaController with Config Asset Handle Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md When using `TnuaController`, instantiate it with a config asset handle. The configuration struct is generated by the `TnuaScheme` macro. You can load this configuration from a file or inject it directly into the `Assets` resource. ```rust cmd.insert(TnuaController::::new( control_scheme_configs.add(ControlSchemeConfig { basis: TnuaBuiltinWalkConfig { ..Default::default() }, jump: TnuaBuiltinJumpConfig { ..Default::default() }, crouch: TnuaBuiltinCrouchConfig { ..Default::default() }, different_kind_of_jump: TnuaBuiltinJumpConfig { ..Default::default() }, }) )); ``` -------------------------------- ### Configure and Trigger TnuaBuiltinDash Source: https://context7.com/idanarye/bevy-tnua/llms.txt Configure dash parameters like speed and distance. Use `action_trigger` for this one-shot action, as it self-terminates upon completion of motion, not when the button is released. ```rust use bevy_tnua::builtins::{TnuaBuiltinDash, TnuaBuiltinDashConfig}; let dash_cfg = TnuaBuiltinDashConfig { speed: 80.0, horizontal_distance: 10.0, // multiplier on TnuaBuiltinDash::displacement.xz vertical_distance: 0.0, // multiplier on TnuaBuiltinDash::displacement.y brake_to_speed: 20.0, // brake after dash until below this speed acceleration: 400.0, brake_acceleration: 200.0, input_buffer_time: 0.2, }; // Trigger on button press event (not held): if keyboard.just_pressed(KeyCode::ShiftLeft) { controller.action_trigger(ControlScheme::Dash(TnuaBuiltinDash { displacement: direction.normalize_or_zero(), desired_forward: Dir3::new(direction).ok(), allow_in_air: true, })); } ``` -------------------------------- ### TnuaBuiltinJump / TnuaBuiltinJumpConfig Source: https://context7.com/idanarye/bevy-tnua/llms.txt Implements a snappy, variable-height jump with configurable extra gravity phases. Must be fed every frame while the jump button is held to allow short-hops; stopping the feed triggers shorten gravity. ```APIDOC ## `TnuaBuiltinJump` / `TnuaBuiltinJumpConfig` — variable-height jump action Implements a snappy, variable-height jump with configurable extra gravity phases. Must be fed every frame while the jump button is held to allow short-hops; stopping the feed triggers shorten gravity. ```rust use bevy_tnua::builtins::{TnuaBuiltinJump, TnuaBuiltinJumpConfig}; // Configuration (asset field named after the enum variant, e.g. `jump`): let jump_cfg = TnuaBuiltinJumpConfig { height: 4.0, // full jump height from float_height takeoff_extra_gravity: 30.0, // makes takeoff feel snappy takeoff_above_velocity: 2.0, // takeoff gravity range fall_extra_gravity: 20.0, // extra gravity after reaching peak shorten_extra_gravity: 40.0, // extra gravity when button released early peak_prevention_at_upward_velocity: 1.0, peak_prevention_extra_gravity: 20.0, reschedule_cooldown: Some(0.2), // jump buffer: seconds to reschedule after landing input_buffer_time: 0.15, // seconds to pre-buffer jump before landing ..Default::default() }; // Input (fed to controller.action() while Space is held): let jump_input = TnuaBuiltinJump { // Optional: displace the jump arc horizontally (e.g. wall jump) horizontal_displacement: Some(Vec3::X * 3.0), // Allow jumping while airborne (e.g. for double-jump, wall-jump) allow_in_air: false, force_forward: None, }; ``` ``` -------------------------------- ### Configure and Input TnuaBuiltinJump Source: https://context7.com/idanarye/bevy-tnua/llms.txt Configure jump parameters like height and gravity. Feed input every frame while the jump button is held for variable height jumps; stopping the feed triggers shortened gravity. ```rust use bevy_tnua::builtins::{TnuaBuiltinJump, TnuaBuiltinJumpConfig}; // Configuration (asset field named after the enum variant, e.g. `jump`): let jump_cfg = TnuaBuiltinJumpConfig { height: 4.0, // full jump height from float_height takeoff_extra_gravity: 30.0, // makes takeoff feel snappy takeoff_above_velocity: 2.0, // takeoff gravity range fall_extra_gravity: 20.0, // extra gravity after reaching peak shorten_extra_gravity: 40.0, // extra gravity when button released early peak_prevention_at_upward_velocity: 1.0, peak_prevention_extra_gravity: 20.0, reschedule_cooldown: Some(0.2), // jump buffer: seconds to reschedule after landing input_buffer_time: 0.15, // seconds to pre-buffer jump before landing ..Default::default() }; // Input (fed to controller.action() while Space is held): let jump_input = TnuaBuiltinJump { // Optional: displace the jump arc horizontally (e.g. wall jump) horizontal_displacement: Some(Vec3::X * 3.0), // Allow jumping while airborne (e.g. for double-jump, wall-jump) allow_in_air: false, force_forward: None, }; ``` -------------------------------- ### Implement Wall Slide Action in Bevy Tnua Source: https://context7.com/idanarye/bevy-tnua/llms.txt Configure and apply the TnuaBuiltinWallSlide action to clamp fall and sideways speeds when a character is against a wall. Requires TnuaObstacleRadar for wall detection. ```rust use bevy_tnua::builtins::{TnuaBuiltinWallSlide, TnuaBuiltinWallSlideConfig}; use bevy_tnua::radar_lens::{TnuaBlipSpatialRelation, TnuaRadarLens}; use bevy_tnua::TnuaObstacleRadar; let wall_slide_cfg = TnuaBuiltinWallSlideConfig { max_fall_speed: 0.5, max_sideways_speed: 0.0, max_sideways_acceleration: 60.0, maintain_distance: None, }; // Spawn entity with obstacle radar to detect walls: commands.spawn(( /* ... mesh, physics ... */ TnuaController::::default(), TnuaConfig::( /* ... */ ), TnuaObstacleRadar::new(0.6 /* radius */, 1.0 /* height */), )); // In the control system — feed WallSlide when pressing against a wall: fn apply_controls( keyboard: Res>, mut query: Query<(&mut TnuaController, &TnuaObstacleRadar)>, spatial_ext: TnuaSpatialExtAvian3d, ) { let Ok((mut controller, radar)) = query.single_mut() else { return }; controller.initiate_action_feeding(); let dir = Vec3::NEG_Z; // player input direction controller.basis = TnuaBuiltinWalk { desired_motion: dir, ..Default::default() }; let lens = TnuaRadarLens::new(radar, &spatial_ext); for blip in lens.iter_blips() { if let TnuaBlipSpatialRelation::Aeside(blip_dir) = blip.spatial_relation(0.5) { if 0.5 < blip_dir.dot(dir) { if let Ok(normal) = Dir3::new(blip.normal_from_closest_point()) { controller.action(ControlScheme::WallSlide( TnuaBuiltinWallSlide { contact_point_with_wall: blip.closest_point().get(), normal, force_forward: None, }, blip.entity(), )); } } } } if keyboard.pressed(KeyCode::Space) { if let Some(ControlSchemeActionState::WallSlide(state, _)) = &controller.current_action { let wall_normal = *state.input.normal; controller.action(ControlScheme::WallJump(TnuaBuiltinJump { horizontal_displacement: Some(wall_normal), allow_in_air: true, force_forward: None, })); } else { controller.action(ControlScheme::Jump(Default::default())); } } } ``` -------------------------------- ### Update TnuaController Configuration in Bevy Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md Migrate from inserting `TnuaController::new` to inserting both `TnuaController::default` and `TnuaConfig`. `TnuaConfig` now holds the configuration asset handle. ```rust // Old way cmd.insert( TnuaController::::new(asset_server.load("configuration.ron")), ); // New way cmd.insert(( TnuaController::::default(), TnuaConfig::(asset_server.load("configuration.ron")), )); ``` -------------------------------- ### TnuaBuiltinWalk / TnuaBuiltinWalkConfig Source: https://context7.com/idanarye/bevy-tnua/llms.txt The standard locomotion basis implementing floating, running, slope handling, coyote time, and tilt correction. `desired_motion` controls horizontal velocity, and `desired_forward` controls model rotation. ```APIDOC ## `TnuaBuiltinWalk` / `TnuaBuiltinWalkConfig` — the walk basis The standard locomotion basis that implements floating, running, slope handling, coyote time, and tilt correction. `desired_motion` drives horizontal velocity; `desired_forward` turns the character model. ### Configuration (`TnuaBuiltinWalkConfig`) - `speed` (f32): max horizontal speed (units/sec). - `float_height` (f32): center-of-mass hover height above ground. - `cling_distance` (f32): spring still active this far above float_height. - `spring_strength` (f32): spring force coefficient. - `spring_dampening` (f32): vertical velocity dampening (keep < 2.0). - `acceleration` (f32): horizontal acceleration on ground. - `air_acceleration` (f32): horizontal acceleration in air (0 = no air control). - `coyote_time` (f32): seconds to still jump after walking off a ledge. - `free_fall_extra_gravity` (f32): extra downward force when in free fall. - `max_slope` (f32): max walkable slope (π/2 disables). - `headroom` (Option<`TnuaBuiltinWalkHeadroom`>): Configuration for headroom detection. ### Input (`TnuaBuiltinWalk`) - `desired_motion` (Vec3): Normalized horizontal vector multiplied by speed multiplier, defining the desired movement direction and magnitude. - `desired_forward` (Option): The direction the model should face. `None` means no rotation. ``` -------------------------------- ### Use TnuaSimpleFallThroughPlatformsHelper Handle Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Pass proximity and ghost sensors to the `TnuaSimpleFallThroughPlatformsHelper`'s `with` method to obtain a handle for controlling fall-through behavior. ```rust let mut handle = fall_through_helper.with(proximity_sensor.as_mut(), ghost_sensor, MIN_PROXIMITY); ``` -------------------------------- ### TnuaBuiltinCrouch / TnuaBuiltinCrouchConfig Source: https://context7.com/idanarye/bevy-tnua/llms.txt Lowers the character's floating height smoothly. Must be fed every frame while the crouch button is held. Requires `TnuaBuiltinWalkConfig::headroom` to be configured so that the character cannot stand up under a ceiling. ```APIDOC ## `TnuaBuiltinCrouch` / `TnuaBuiltinCrouchConfig` — crouch action Lowers the character's floating height smoothly. Must be fed every frame while the crouch button is held. Requires `TnuaBuiltinWalkConfig::headroom` to be configured so that the character cannot stand up under a ceiling. ```rust use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinCrouchConfig}; let crouch_cfg = TnuaBuiltinCrouchConfig { float_offset: -0.9, // lower the float height by this much height_change_impulse_for_duration: 0.04, // transition duration (seconds) height_change_impulse_limit: 40.0, // max transition impulse }; // In the control system — hold to crouch, release to stand: fn apply_controls( keyboard: Res>, mut query: Query<&mut TnuaController>, ) { let Ok(mut controller) = query.single_mut() else { return }; controller.initiate_action_feeding(); // ... set basis ... if keyboard.pressed(KeyCode::ControlLeft) { controller.action(ControlScheme::Crouch(TnuaBuiltinCrouch)); } } ``` ``` -------------------------------- ### Create TnuaRadarLens with Spatial Extension Source: https://github.com/idanarye/bevy-tnua/wiki/Using-the-obstacle-radar Combine the TnuaObstacleRadar with a spatial extension from the physics backend to create a TnuaRadarLens. This is typically done within a system that queries for the controller and radar. ```rust fn user_control_system( mut query: Query<(&mut TnuaController, &TnuaObstacleRadar)>, spatial_ext: TnuaSpatialExtAvian3d, // this depends on the physics backend you use ) { for (controller, obstacle_radar) in query.iter_mut() { let radar_lens = TnuaRadarLens::new(obstacle_radar, &spatial_ext); // ... } } ``` -------------------------------- ### Clone Configuration in Controls System Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md In your controls system, clone the configuration from the ECS component and apply it to the controller. This is necessary because `TnuaBuiltinWalk::desired_velocity` requires a vector, and only the magnitude (speed) is stored in the config. ```rust controller.basis(TnuaBuiltinWalk { desired_velocity: direction * config.speed, ..config.walk.clone() }); if crouch { controller.action(config.crouch.clone()); } if jump { controller.action(config.jump.clone()); } ``` -------------------------------- ### Implement Fall-Through Platforms with TnuaGhostPlatform Source: https://context7.com/idanarye/bevy-tnua/llms.txt Mark platform colliders as 'ghost' using TnuaGhostPlatform to allow selective ignoring. The TnuaSimpleFallThroughPlatformsHelper on the character manages which platforms to stand on versus fall through. Requires `bevy_tnua`. ```rust use bevy_tnua::{TnuaGhostPlatform, TnuaGhostSensor}; use bevy_tnua::control_helpers::TnuaSimpleFallThroughPlatformsHelper; use bevy_tnua::ghost_overrides::TnuaGhostOverwrites; // Spawn a one-way platform: commands.spawn(( /* mesh, collider, rigid body */ TnuaGhostPlatform, // Also set solver groups to exclude the player collider )); // Spawn the player with ghost-sensing enabled: commands.spawn(( /* ... */ TnuaController::::default(), TnuaGhostSensor::default(), // lets proximity sensor detect ghost platforms TnuaGhostOverwrites::::default(), TnuaSimpleFallThroughPlatformsHelper::default(), )); // In the control system, each frame: fn apply_controls( keyboard: Res>, mut query: Query<( &mut TnuaController, &mut TnuaSimpleFallThroughPlatformsHelper, &mut TnuaGhostOverwrites, &TnuaGhostSensor, )>, ) { let Ok((mut controller, mut helper, mut overwrites, ghost_sensor)) = query.single_mut() else { return }; controller.initiate_action_feeding(); let (ghost_overwrite, _) = overwrites.ground_mut(); // basis-dependent accessor let mut handle = helper.with(ghost_overwrite, ghost_sensor, 1.0 /* min_proximity */); if keyboard.pressed(KeyCode::ArrowDown) { handle.fall(true /* just_pressed or continuously */); } else { handle.dont_fall(); } } ``` -------------------------------- ### TnuaController Source: https://context7.com/idanarye/bevy-tnua/llms.txt The main interface for interacting with Tnua. It allows setting the character's basis, initiating action feeding, and feeding actions each frame. ```APIDOC ## `TnuaController` — the per-entity control interface The main component for interacting with Tnua. Every frame the game code sets `controller.basis`, calls `controller.initiate_action_feeding()`, then feeds zero or more actions. Tnua reads these values and applies forces in the physics backend. ### Methods - `initiate_action_feeding()`: Must be called first each frame if using `controller.action(...)`. - `basis = ...`: Set the character's movement basis (e.g., `TnuaBuiltinWalk`). This should be set every frame. - `action(action)`: Feeds a specific action for the current frame. - `action_trigger(action)`: Triggers a one-shot action. - `action_start(action)`: Starts or holds an action. - `action_end(action_discriminant)`: Explicitly ends an action. - `action_interrupt(action)`: Overrides any current action and cannot be overridden. - `is_airborne()`: Returns a boolean indicating if the character is airborne. - `action_flow_status()`: Returns the current status of actions. ``` -------------------------------- ### Implement Knockback Action in Bevy Tnua Source: https://context7.com/idanarye/bevy-tnua/llms.txt Apply the TnuaBuiltinKnockback action to shove a character in a specific direction, overriding other actions. Useful for damage feedback. ```rust use bevy_tnua::builtins::{TnuaBuiltinKnockback, TnuaBuiltinKnockbackConfig}; use bevy::prelude::Vec3; let knockback_cfg = TnuaBuiltinKnockbackConfig { no_push_timeout: 0.1, barrier_strength_diminishing: 2.0, ..Default::default() }; // Triggered by a game event (outside TnuaUserControlsSystems): fn handle_hit_events( mut events: EventReader, mut query: Query<&mut TnuaController>, ) { for event in events.read() { if let Ok(mut controller) = query.get_mut(event.target) { controller.action_interrupt(ControlScheme::Knockback(TnuaBuiltinKnockback { shove: event.direction * 12.0, force_forward: Dir3::new(-event.direction).ok(), })); } } } ``` -------------------------------- ### TnuaBuiltinDash / TnuaBuiltinDashConfig Source: https://context7.com/idanarye/bevy-tnua/llms.txt One-shot dash in any direction. Self-terminating — use `action_trigger` rather than `action` because the dash ends when the motion is complete, not when the button is released. ```APIDOC ## `TnuaBuiltinDash` / `TnuaBuiltinDashConfig` — dash action One-shot dash in any direction. Self-terminating — use `action_trigger` rather than `action` because the dash ends when the motion is complete, not when the button is released. ```rust use bevy_tnua::builtins::{TnuaBuiltinDash, TnuaBuiltinDashConfig}; let dash_cfg = TnuaBuiltinDashConfig { speed: 80.0, horizontal_distance: 10.0, // multiplier on TnuaBuiltinDash::displacement.xz vertical_distance: 0.0, // multiplier on TnuaBuiltinDash::displacement.y brake_to_speed: 20.0, // brake after dash until below this speed acceleration: 400.0, brake_acceleration: 200.0, input_buffer_time: 0.2, }; // Trigger on button press event (not held): if keyboard.just_pressed(KeyCode::ShiftLeft) { controller.action_trigger(ControlScheme::Dash(TnuaBuiltinDash { displacement: direction.normalize_or_zero(), desired_forward: Dir3::new(direction).ok(), allow_in_air: true, })); } ``` ``` -------------------------------- ### Serialize and Load Tnua Configuration with RON Source: https://context7.com/idanarye/bevy-tnua/llms.txt The `TnuaSchemeConfig` can be serialized to RON format using `write_if_not_exist` for design-time tweaking. Load this configuration at runtime using `AssetServer` with `TnuaConfig`. ```rust use bevy_tnua::TnuaSchemeConfig; // Write a default config file on first run (idempotent): ControlSchemeConfig { basis: TnuaBuiltinWalkConfig { float_height: 1.5, speed: 12.0, ..Default::default() }, jump: TnuaBuiltinJumpConfig { height: 4.0, ..Default::default() }, } .write_if_not_exist("assets/player_config.ron") .unwrap(); ``` ```rust // Load at runtime via AssetServer (requires a custom AssetLoader for your scheme): commands.spawn(( TnuaController::::default(), TnuaConfig::(asset_server.load("player_config.ron")), )); ``` -------------------------------- ### Set Base Movement Velocity in Tnua 0.10 Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md Use TnuaBuiltinWalk basis to set desired velocity and float height. The desired_velocity field now controls both speed and direction. ```rust controller.basis(TnuaBuiltinWalk { desired_velocity: the_desired_velocity, float_height: 2.0, // must be passed as well in this new scheme ..Default::default() }); ``` -------------------------------- ### Implement Jump-Through Logic Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Manually override the character's proximity sensor output with the closest detected ghost platform. This ensures the character stands on the platform when jumping through. ```rust fn apply_tnua_fall_through_controls( mut query: Query< &mut TnuaProximitySensor, &TnuaGhostSensor, >, ) { for (mut proximity_sensor, ghost_sensor) in query.iter_mut() { for ghost_platform in ghost_sensor.iter() { if MIN_PROXIMITY <= ghost_platform.proximity { proximity_sensor.output = Some(ghost_platform.clone()); break; } } } } ``` -------------------------------- ### TnuaBuiltinWallSlide / TnuaBuiltinWallSlideConfig Source: https://context7.com/idanarye/bevy-tnua/llms.txt Clamps fall speed and sideways speed when the character is pressing against a wall. This action is typically used with TnuaObstacleRadar to detect walls and TnuaBuiltinJump for wall jumping. ```APIDOC ## `TnuaBuiltinWallSlide` / `TnuaBuiltinWallSlideConfig` — wall-slide action Clamps fall speed and sideways speed when the character is pressing against a wall. Typically used together with `TnuaObstacleRadar` to detect the wall and with `TnuaBuiltinJump { allow_in_air: true }` for wall jumping. ### Configuration ```rust let wall_slide_cfg = TnuaBuiltinWallSlideConfig { max_fall_speed: 0.5, max_sideways_speed: 0.0, max_sideways_acceleration: 60.0, maintain_distance: None, }; ``` ### Usage Example ```rust // Spawn entity with obstacle radar to detect walls: commands.spawn(( /* ... mesh, physics ... */ TnuaController::::default(), TnuaConfig::( /* ... */ ), TnuaObstacleRadar::new(0.6 /* radius */, 1.0 /* height */), )); // In the control system — feed WallSlide when pressing against a wall: fn apply_controls( keyboard: Res>, mut query: Query<(&mut TnuaController, &TnuaObstacleRadar)>, spatial_ext: TnuaSpatialExtAvian3d, ) { let Ok((mut controller, radar)) = query.single_mut() else { return }; controller.initiate_action_feeding(); let dir = Vec3::NEG_Z; // player input direction controller.basis = TnuaBuiltinWalk { desired_motion: dir, ..Default::default() }; let lens = TnuaRadarLens::new(radar, &spatial_ext); for blip in lens.iter_blips() { if let TnuaBlipSpatialRelation::Aeside(blip_dir) = blip.spatial_relation(0.5) { if 0.5 < blip_dir.dot(dir) { if let Ok(normal) = Dir3::new(blip.normal_from_closest_point()) { controller.action(ControlScheme::WallSlide( TnuaBuiltinWallSlide { contact_point_with_wall: blip.closest_point().get(), normal, force_forward: None, }, blip.entity(), )); } } } } if keyboard.pressed(KeyCode::Space) { if let Some(ControlSchemeActionState::WallSlide(state, _)) = &controller.current_action { let wall_normal = *state.input.normal; controller.action(ControlScheme::WallJump(TnuaBuiltinJump { horizontal_displacement: Some(wall_normal), allow_in_air: true, force_forward: None, })); } else { controller.action(ControlScheme::Jump(Default::default())); } } } ``` ``` -------------------------------- ### Manage Animation State with TnuaAnimatingState Source: https://context7.com/idanarye/bevy-tnua/llms.txt Use TnuaAnimatingState to track animation states and drive AnimationPlayer. It provides directives to either maintain the current animation or alter it based on the character's state. Requires `bevy_tnua` and `bevy::prelude`. ```rust use bevy::prelude::* use bevy_tnua::{TnuaAnimatingState, TnuaAnimatingStateDirective, prelude::*}; use bevy_tnua.builtins.TnuaBuiltinJumpMemory; pub enum AnimationState { Standing, Running(f32), Jumping, Falling } fn handle_animating( mut query: Query<( &TnuaController, &mut TnuaAnimatingState, )>, mut anim_query: Query<&mut AnimationPlayer>, nodes: Option>, ) { let (Some(nodes), Ok((controller, mut anim_state)), Ok(mut anim_player)) = ( nodes, query.single_mut(), anim_query.single_mut(), ) else { return }; let new_state = match controller.current_action.as_ref() { Some(ControlSchemeActionState::Jump(state)) => match state.memory { TnuaBuiltinJumpMemory::FallSection => AnimationState::Falling, _ => AnimationState::Jumping, }, None => { if controller.basis_memory.standing_on_entity().is_none() { AnimationState::Falling } else { let speed = controller.basis_memory.running_velocity.length(); if speed > 0.01 { AnimationState::Running(speed * 0.1) } else { AnimationState::Standing } } } }; match anim_state.update_by_discriminant(new_state) { TnuaAnimatingStateDirective::Maintain { state } => { if let AnimationState::Running(spd) = state { anim_player.animation_mut(nodes.running).map(|a| a.set_speed(*spd)); } } TnuaAnimatingStateDirective::Alter { state, .. } => { anim_player.stop_all(); match state { AnimationState::Standing => { anim_player.start(nodes.standing).set_speed(1.0).repeat(); } AnimationState::Running(s) => { anim_player.start(nodes.running).set_speed(*s).repeat(); } AnimationState::Jumping => { anim_player.start(nodes.jumping).set_speed(2.0); } AnimationState::Falling => { anim_player.start(nodes.falling).set_speed(1.0); } } } } } ``` -------------------------------- ### Add Ghost Sensor to Character Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Equip the character entity with `TnuaGhostSensor` in addition to `TnuaProximitySensor`. This sensor will track detected ghost platforms. ```rust app.add_system(apply_tnua_fall_through_controls.in_set(TnuaUserControlsSystemSet)); ``` -------------------------------- ### Add TnuaSimpleFallThroughPlatformsHelper Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Incorporate the `TnuaSimpleFallThroughPlatformsHelper` component into the character entity for more robust fall-through behavior, especially when input is released early. ```rust cmd.insert(TnuaPlatformerBundle { config: TnuaPlatformerConfig { // ... }, ..Default::default() }); cmd.insert(TnuaGhostSensor::default()); // we already needed this one for the previous methods cmd.insert(TnuaSimpleFallThroughPlatformsHelper::default()); ``` -------------------------------- ### Insert Motion Configuration Component into Entity Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md Add the custom motion configuration component to a character entity using a command. Ensure all fields are correctly initialized. ```rust cmd.insert(CharacterMotionConfigForPlatformerDemo { speed: 40.0, walk: TnuaBuiltinWalk { float_height: 2.0, ..Default::default() }, jump: TnuaBuiltinJump { height: 4.0, ..Default::default() }, crouch: TnuaBuiltinCrouch { float_offset: -0.9, ..Default::default() }, }); ``` -------------------------------- ### Implement Simplistic Fall-Through Logic Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Allow falling through platforms by conditionally overriding the proximity sensor output only when a specific 'fall through' input is invoked. If the input is released, the fall-through is cancelled. ```rust if fall_through_input_invoked() { for ghost_platform in ghost_sensor.iter() { if MIN_PROXIMITY <= ghost_platform.proximity { proximity_sensor.output = Some(ghost_platform.clone()); break; } } } ``` -------------------------------- ### Trigger Jump Action in Tnua 0.10 Source: https://github.com/idanarye/bevy-tnua/blob/main/MIGRATION-GUIDES.md Use the TnuaBuiltinJump action to initiate a jump. The height is controlled by the action's height field, and the jump stops when the action is no longer provided. ```rust if should_jump { controller.action(TnuaBuiltinJump { height: 4.0, ..Default::default() }); } ``` -------------------------------- ### Fall Through One Layer of Platforms Source: https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms Use this when the player should fall through a single layer of ghost platforms. The `just_pressed` argument should be true only on the initial input press to detect platforms. Subsequent frames should pass `false` until the fall is complete. ```rust if fall_through_input_invoked() { handle.try_falling(that_input_was_just_pressed()) } else { handle.dont_fall(); } ``` -------------------------------- ### Insert TnuaObstacleRadar Component Source: https://github.com/idanarye/bevy-tnua/wiki/Using-the-obstacle-radar Add the TnuaObstacleRadar component to an entity using a command. The first parameter is the radius and the second is the height of the radar cylinder. ```rust cmd.insert(TnuaObstacleRadar::new(1.0, 3.0)); ```