A powerful 3D/2.5D game controller plugin for Bevy Engine.
The Character Controller System is a comprehensive 3D/2.5D movement system designed for Bevy Engine. It provides realistic character movement with features like walking, running, sprinting, jumping, crouching, sliding, wall running, and advanced physics interactions.
The main component that defines character behavior and capabilities:
#[derive(Component, Debug, Reflect)]
pub struct CharacterController {
// Movement speeds
pub walk_speed: f32, // Default: 4.0
pub run_speed: f32, // Default: 7.0
pub sprint_speed: f32, // Default: 10.0
pub crouch_speed: f32, // Default: 2.5
// Rotation settings
pub turn_speed: f32, // Default: 10.0
pub stationary_turn_speed: f32, // Default: 180.0
pub moving_turn_speed: f32, // Default: 200.0
pub use_tank_controls: bool, // Default: false
// Jump settings
pub jump_power: f32, // Default: 6.0
pub jump_hold_bonus: f32, // Default: 2.0
pub max_jump_hold_time: f32, // Default: 0.25
// State flags
pub can_move: bool, // Default: true
pub is_dead: bool, // Default: false
pub is_strafing: bool, // Default: false
// Movement smoothing
pub acceleration: f32, // Default: 10.0
pub deceleration: f32, // Default: 15.0
// Falling damage
pub fall_damage_enabled: bool, // Default: true
pub min_velocity_for_damage: f32, // Default: 12.0
pub falling_damage_multiplier: f32, // Default: 5.0
// Crouch sliding
pub crouch_sliding_enabled: bool, // Default: true
pub crouch_sliding_speed: f32, // Default: 12.0
pub crouch_sliding_duration: f32, // Default: 1.0
// Obstacle detection
pub obstacle_detection_distance: f32, // Default: 0.5
// Advanced features
pub fixed_axis: Option<Vec3>, // For 2.5D games
pub use_root_motion: bool, // Default: false
pub zero_gravity_mode: bool, // Default: false
pub free_floating_mode: bool, // Default: false
}
Tracks the current movement state and internal timers:
#[derive(Component, Debug, Default, Reflect)]
pub struct CharacterMovementState {
// Movement data
pub raw_move_dir: Vec3, // Raw input direction
pub lerped_move_dir: Vec3, // Smoothed movement direction
pub is_running: bool, // Currently running
pub is_sprinting: bool, // Currently sprinting
pub is_crouching: bool, // Currently crouching
pub wants_to_jump: bool, // Jump requested
pub jump_held: bool, // Jump button held
pub current_speed: f32, // Current movement speed
pub current_normal: Vec3, // Current ground normal
// Internal state
pub last_vertical_velocity: f32, // For fall damage calculation
pub air_time: f32, // Time spent in air
pub jump_hold_timer: f32, // Jump hold duration
pub crouch_sliding_active: bool, // Crouch slide active
pub crouch_sliding_timer: f32, // Crouch slide duration
pub obstacle_found: bool, // Obstacle detected
pub quick_turn_active: bool, // Quick turn in progress
pub quick_turn_timer: f32, // Quick turn duration
// Wall running
pub wall_running_active: bool, // Wall running active
pub wall_side: Option<Vec3>, // Wall normal for wall running
// Root motion
pub root_motion_translation: Vec3, // Root motion translation
pub root_motion_rotation: Quat, // Root motion rotation
// Vehicle state
pub is_in_vehicle: bool, // In vehicle flag
pub vehicle_entity: Option<Entity>, // Vehicle entity
}
Manages animation transitions and blending:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Reflect)]
pub enum CharacterAnimationMode {
#[default]
Idle,
Walk,
Run,
Sprint,
CrouchIdle,
CrouchWalk,
JumpStart,
JumpAir,
Fall,
Land,
}
#[derive(Component, Debug, Default, Reflect)]
pub struct CharacterAnimationState {
pub mode: CharacterAnimationMode, // Current animation mode
pub forward: f32, // Forward movement blend
pub turn: f32, // Turn movement blend
}
The character controller uses a multi-system approach with proper execution order:
handle_character_input - Processes input and updates movement stateupdate_character_movement - Calculates current movement speedupdate_character_rotation - Handles character orientationupdate_character_animation - Updates animation stateapply_character_physics - Applies physics and movementcheck_ground_state - Updates ground detectionupdate_friction_material - Adjusts friction based on movementhandle_falling_damage - Calculates fall damagehandle_crouch_sliding - Manages crouch slidinghandle_obstacle_detection - Detects obstacleshandle_wall_running_detection - Detects wall running opportunitiesThe system uses a dual-direction approach:
raw_move_dir: Direct input from playerlerped_move_dir: Smoothed direction for natural movementMovement speed is determined by:
Jump Features:
Jump Process:
Crouch Features:
Slide Process:
Uses dual raycasts (left and right) to detect obstacles:
Wall Running Features:
Alternative control scheme where:
Allows animation-driven movement:
Locks character to specific axes for 2.5D games:
fixed_axis defines the constraint planeDisables gravity and enables 3D movement:
Allows characters to climb small obstacles:
Prevents characters from getting stuck on ledges:
Dynamically adjusts friction based on movement:
Calculates damage based on:
The animation system automatically determines the appropriate animation mode based on:
Grounded States:
Airborne States:
use bevy::prelude::*;
use bevy_allinone::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(GameControllerPlugin)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
// Spawn a character
let character = spawn_character(&mut commands, Vec3::new(0.0, 1.0, 0.0));
// You can customize the controller
commands.entity(character).insert(CharacterController {
walk_speed: 5.0,
sprint_speed: 12.0,
jump_power: 7.0,
..default()
});
}
fn setup_advanced(mut commands: Commands) {
// Create custom character
let character = commands.spawn((
Name::new("Custom Player"),
CharacterController {
walk_speed: 3.5,
run_speed: 6.0,
sprint_speed: 9.0,
crouch_speed: 2.0,
jump_power: 5.5,
jump_hold_bonus: 1.5,
max_jump_hold_time: 0.2,
acceleration: 8.0,
deceleration: 12.0,
crouch_sliding_enabled: true,
crouch_sliding_speed: 10.0,
crouch_sliding_duration: 0.8,
..default()
},
CharacterMovementState::default(),
CharacterAnimationState::default(),
Transform::from_translation(Vec3::new(0.0, 2.0, 0.0)),
GlobalTransform::default(),
))
.insert((
RigidBody::Dynamic,
Collider::capsule(0.4, 1.0),
LockedAxes::ROTATION_LOCKED,
GravityScale(1.0),
Friction::new(0.0),
Restitution::new(0.0),
LinearVelocity::default(),
AngularVelocity::default(),
))
.id();
}
// Add custom fields to CharacterController
#[derive(Component, Debug, Reflect)]
pub struct CharacterController {
// ... existing fields ...
pub custom_movement_enabled: bool,
pub custom_movement_speed: f32,
}
// Extend CharacterMovementState
#[derive(Component, Debug, Default, Reflect)]
pub struct CharacterMovementState {
// ... existing fields ...
pub is_custom_moving: bool,
}
// Add custom movement logic
fn update_custom_movement(
mut query: Query<(&CharacterController, &mut CharacterMovementState)>
) {
for (controller, mut state) in query.iter_mut() {
if controller.custom_movement_enabled {
state.is_custom_moving = true;
state.current_speed = controller.custom_movement_speed;
}
}
}
// Add custom animation modes
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Reflect)]
pub enum CharacterAnimationMode {
// ... existing modes ...
CustomMode,
SpecialAbility,
}
// Update animation system
fn update_character_animation(
mut query: Query<(&CharacterMovementState, &mut CharacterAnimationState)>
) {
for (movement, mut anim) in query.iter_mut() {
// ... existing logic ...
if movement.is_custom_moving {
anim.mode = CharacterAnimationMode::CustomMode;
}
}
}
Character doesn’t move:
can_move flagCharacter falls through ground:
Animation issues:
Potential areas for expansion: