Bevy All-in-One Documentation

A powerful 3D/2.5D game controller plugin for Bevy Engine.


Project maintained by yaskhan Hosted on GitHub Pages — Theme by mattgraham

Climb & Parkour System

The Climb System provides a complete solution for vertical traversal, ledge hanging, wall running, and parkour mechanics. It allows players to detect climbable surfaces via raycasting, transition seamlessly between movement states, and interact with the environment in a fluid, “Assassin’s Creed”-like manner.

Documentation Contents

Part 1: Core Architecture

Part 2: Ledge Detection & Physics

Part 3: Animation & Advanced Topics


Overview

The Climb System separates “movement” from “climbing”. When a climbable edge is detected, the standard character controller logic (gravity, friction, walking speed) is suspended, and the ClimbState takes over.

Key Capabilities


The State Machine

The heart of the system is the ClimbState enum. This finite state machine determines allowed inputs and movement vectors.

#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
pub enum ClimbState {
    None,           // Standard character movement
    Approaching,    // Moving towards a detected ledge (auto-adjust)
    Hanging,        // Idle on the wall
    ClimbingUp,     // Ascending
    ClimbingDown,   // Descending
    ClimbingLeft,   // Shimmying left
    ClimbingRight,  // Shimmying right
    Vaulting,       // Quick hop over low obstacles
    Falling,        // Special fall state (detached from wall)
}

State Flow

  1. Detection: System scans for a ledge while ClimbState::None.
  2. Transition: If detected + Input (e.g., Jump/Forward), switch to Approaching.
  3. Hanging: Once aligned, switch to Hanging. Gravity is disabled.
  4. Movement: Player input (WASD) switches state to ClimbingUp/Down/Left/Right.
  5. Exit:
    • Jump: Launches player away/up (ForceMode::Impulse).
    • Drop: Pressing Crouch/Drop key switches to Falling or None.
    • Top Out: Moving up past the edge switches to standard “Grounded” state.

Component Reference

1. ClimbLedgeSystem

This is the “Brain” of the climb system. It contains typically static configuration settings for how climbing feels. It is attached to the Player entity.

Main Settings

| Field | Type | Description | | :— | :— | :— | | climb_ledge_active | bool | Master switch for the system. | | climb_ledge_ray_forward_distance | f32 | How far forward to check for walls (default: 1.0). | | climb_ledge_ray_down_distance | f32 | How far down to check for the lip of the ledge (default: 1.0). | | layer_mask_to_check | u32 | Physics layer mask for climbable geometry. |

Movement & Positioning

| Field | Type | Description | | :— | :— | :— | | adjust_to_hold_on_ledge_position_speed | f32 | Lerp speed when snapping to the wall (default: 3.0). | | adjust_to_hold_on_ledge_rotation_speed | f32 | Slerp speed for aligning rotation to wall normal (default: 10.0). | | hand_offset | f32 | Vertical offset to align visual hands with the ledge edge (default: 0.2). | | hold_on_ledge_offset | Vec3 | Fine-tuning offset for the hang position. | | climb_ledge_speed_first_person | f32 | climbing speed when in FP mode. |

Input & Logic

| Field | Type | Description | | :— | :— | :— | | only_grab_ledge_if_moving_forward | bool | Prevents accidental grabs when backing up. | | can_jump_when_hold_ledge | bool | Allows wall jumping. | | jump_force_when_hold_ledge | f32 | Ejection force for wall jumps. | | auto_climb_in_third_person | bool | If true, automatically mounts ledges without jump button. |

Debug State (Read-Only)

These fields are useful for debugging in the inspector but shouldn’t be set manually:

2. ClimbStateTracker

Tracks dynamic runtime data.

#[derive(Component, Debug, Reflect)]
pub struct ClimbStateTracker {
    pub current_state: ClimbState,
    pub previous_state: ClimbState,
    pub state_timer: f32,       // Time in current state (useful for anims)
    pub stamina: f32,           // Current energy (0-100)
    pub max_stamina: f32,
    pub stamina_drain_rate: f32,
    pub is_stamina_depleted: bool,
}

3. LedgeZone

A marker component for Trigger volumes or specific collider entities that overrides climb behavior.

#[derive(Component, Debug, Reflect)]
pub struct LedgeZone {
    pub ledge_zone_active: bool,
    pub ledge_zone_can_be_climbed: bool, // Set false to make "unclimbable" paint
    pub custom_climb_speed: Option<f32>, // (Future Feature)
}

4. LedgeDetection

A transient component updated every frame by the raycasting system.

#[derive(Component, Debug, Reflect)]
pub struct LedgeDetection {
    pub ledge_found: bool,
    pub ledge_position: Vec3, // World space point of the edge
    pub ledge_normal: Vec3,   // Normal of the wall face
    pub surface_type: SurfaceType, // Material (Wood, Stone, Metal)
}

Configuration Guide

Setting up a Player

To enable climbing on a character:

  1. Add ClimbLedgeSystem::default().
  2. Add ClimbStateTracker::default().
  3. Add LedgeDetection::default() (required for the system to write data).
  4. Ensure CharacterController is present (the systems read character.is_dead etc).

Tuning Raycasts

If your character isn’t grabbing ledges:

  1. Check Forward Ray: Increase climb_ledge_ray_forward_distance. The character’s collider radius might keep them too far from the wall.
  2. Check Down Ray: climb_ledge_ray_down_distance must be long enough to reach from “Forward Ray Hit” down to the actual ledge lip.
  3. Offsets: Use hand_offset to fix “floating hands” or “hands inside wall” visual issues.

First Person vs Third Person

The system supports both modes via flags in ClimbLedgeSystem:


Ledge Detection Logic

The detection system runs in the FixedUpdate loop to ensure physics consistency. It primarily uses the detect_ledge system.

The Dual-Ray Algorithm

To robustly detect a climbable edge (a “cliff” or “wall top”) without relying on manual triggers, the system uses two raycasts:

  1. Forward Wall Check:
    • Origin: Player’s chest/head height.
    • Direction: PlayerTransform.forward.
    • Distance: climb_ledge_ray_forward_distance.
    • Purpose: Finds the face of the wall. If nothing is hit, there is no wall to climb.
  2. Downward Ledge Check:
    • Origin: A point slightly forward of the wall hit (offset by a small epsilon) and above the player.
    • Direction: Vec3::DOWN.
    • Distance: climb_ledge_ray_down_distance.
    • Purpose: Finds the top of the wall.

Success Condition:

If successful, LedgeDetection.ledge_position is set to the hit point of Ray 2, which represents the exact edge coordinates for the hands to grab.

Auto-Hang (detect_ledge_below)

This secondary system (detect_ledge_below) allows players to walk off a roof and automatically turn around to grab the ledge, or “drop down” onto a ledge below them.


Wall Movement & Physics

When in ClimbState::ClimbingUp/Left/Right, standard physics forces are modified.

Shimmying (Horizontal)

Vaulting

If the player climbs up and there is no wall above (it’s a floor), the state transitions to Vaulting.

Jumping Off

When jumping from a ledge:


Surface Types

Not all walls are equal. The SurfaceType enum allows different gameplay properties based on the material detected by the raycast (e.g., via Collider material or tag).

#[derive(Debug, Clone, Copy, PartialEq, Reflect, Default)]
pub enum SurfaceType {
    #[default] Default,
    Stone,   // Default speed
    Wood,    // 0.9x speed (rough)
    Metal,   // 0.8x speed (slippery)
    Ice,     // 1.2x speed (fast slide) / High stamina drain
    Rope,    // 0.7x speed
    Custom(f32),
}

Impact on Gameplay

  1. Climb Speed: calculate_climb_speed applies the multiplier to movement.
  2. Stamina Cost: calculate_stamina_cost scales effort (e.g., Ice drains stamina faster).
  3. Jump Force: Some surfaces might provide less push-off force.
  4. Sound: Triggers different foley sounds (Wood creak vs Stone scrape).

Animation Integration

The system includes helper components for syncing movement with animations, particularly useful for Target Matching (aligning the hand bone exactly with the ledge).

ClimbAnimation Component

Used to provide data to your Animation Graph (e.g., bevy_animation_graph or standard Bevy animator).

Implementation Pattern

  1. State Change: When ClimbState switches to Vaulting, trigger the “Vault” animation.
  2. Target Set: Set LedgeDetection.ledge_position as the Target Match point.
  3. Update Loop: During the animation frame, interpolate the Character’s root transform so that at match_end_value, the hand/foot is exactly at the ledge_position.

This prevents “clipping” where the character’s hands go inside the mesh or hover above it.


Stamina System

Climbing is physically demanding. The ClimbStateTracker manages a stamina pool to prevent infinite climbing (unless configured otherwise).

pub struct ClimbStateTracker {
    pub stamina: f32,           // Current: 0.0 - 100.0
    pub max_stamina: f32,       // Cap: 100.0
    pub stamina_drain_rate: f32,// Per second cost
    pub stamina_regen_rate: f32,// Per second recovery (Grounded only)
}

Mechanics


Troubleshooting

1. “Player walks into wall instead of climbing”

2. “Player grabs ledge but hangs inside the geometry”

3. “Player grabs the air above the wall”

4. “Cannot climb specific walls”

5. “Character jitters while hanging”

Future Roadmap