Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Character Controllers

Character controllers provide smooth player movement with collision handling, slopes, and stairs.

First-Person Player

The easiest way to get started:

#![allow(unused)]
fn main() {
use nightshade::ecs::physics::commands::spawn_first_person_player;
use nightshade::ecs::physics::character_controller::character_controller_input_system;

fn initialize(&mut self, world: &mut World) {
    let (player_entity, camera_entity) = spawn_first_person_player(
        world,
        Vec3::new(0.0, 2.0, 0.0),
    );

    self.player = Some(player_entity);

    if let Some(controller) = world.core.get_character_controller_mut(player_entity) {
        controller.max_speed = 5.0;
        controller.is_sprinting = false;
        controller.jump_impulse = 6.0;
    }
}
}

Custom Character Controller

For third-person or specialized characters:

#![allow(unused)]
fn main() {
fn spawn_character(world: &mut World, position: Vec3) -> Entity {
    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | CHARACTER_CONTROLLER,
        1,
    )[0];

    world.core.set_name(entity, Name("Player".to_string()));
    world.core.set_local_transform(entity, LocalTransform {
        translation: position,
        ..Default::default()
    });

    if let Some(controller) = world.core.get_character_controller_mut(entity) {
        *controller = CharacterControllerComponent::new_capsule(0.5, 0.3);
        controller.max_speed = 3.0;
        controller.acceleration = 15.0;
        controller.jump_impulse = 4.0;
        controller.is_sprinting = false;
        controller.is_crouching = false;
    }

    entity
}
}

Controller Properties

PropertyDescriptionDefault
max_speedWalking speed5.0
is_sprintingSprint activefalse
accelerationSpeed up rate20.0
jump_impulseJump strength5.0
can_jumpAllow jumpingtrue
is_crouchingCrouch activefalse

Movement Input

The built-in character_controller_input_system(world) handles WASD movement, jumping, sprinting, and crouching automatically. Call it each frame:

#![allow(unused)]
fn main() {
fn run_systems(&mut self, world: &mut World) {
    character_controller_input_system(world);
}
}

Ground Detection

Check if the character is grounded:

#![allow(unused)]
fn main() {
if let Some(controller) = world.core.get_character_controller(player) {
    if controller.grounded {
        // On ground - can jump
    } else {
        // In air
    }
}
}

Slope Handling

Controllers automatically handle slopes:

#![allow(unused)]
fn main() {
if let Some(controller) = world.core.get_character_controller_mut(player) {
    controller.config.max_slope_climb_angle = 0.8;  // ~45 degrees in radians
    controller.config.min_slope_slide_angle = 0.5;   // ~30 degrees
}
}

Camera Integration

First-Person Camera

The camera is a child of the player:

#![allow(unused)]
fn main() {
let camera = world.spawn_entities(
    LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | CAMERA | PARENT,
    1
)[0];

world.core.set_parent(camera, Parent(Some(player)));
world.core.set_local_transform(camera, LocalTransform {
    translation: Vec3::new(0.0, 0.8, 0.0),  // Eye height
    ..Default::default()
});

world.resources.active_camera = Some(camera);
}

Third-Person Camera

Follow the character with an offset:

#![allow(unused)]
fn main() {
fn third_person_camera_system(world: &mut World, player: Entity, camera: Entity) {
    let Some(player_pos) = world.core.get_local_transform(player).map(|t| t.translation) else {
        return;
    };

    // Camera behind and above player
    let offset = Vec3::new(0.0, 3.0, 8.0);
    let target_pos = player_pos + offset;

    if let Some(pan_orbit) = world.core.get_pan_orbit_camera_mut(camera) {
        pan_orbit.target_focus = player_pos + Vec3::new(0.0, 1.0, 0.0);
    }
}
}

Step Climbing

Controllers can automatically climb small steps:

#![allow(unused)]
fn main() {
if let Some(controller) = world.core.get_character_controller_mut(player) {
    controller.config.autostep_max_height = Some(0.3);  // Can climb 30cm steps
    controller.config.autostep_min_width = Some(0.2);   // Minimum step width
}
}

Interaction Cooldowns

Prevent rapid repeated actions:

#![allow(unused)]
fn main() {
struct PlayerState {
    interaction_cooldown: f32,
}

fn update_cooldown(state: &mut PlayerState, dt: f32) {
    state.interaction_cooldown = (state.interaction_cooldown - dt).max(0.0);
}

fn can_interact(state: &PlayerState) -> bool {
    state.interaction_cooldown <= 0.0
}

fn set_cooldown(state: &mut PlayerState, duration: f32) {
    state.interaction_cooldown = duration;
}
}