Skip to content

Crouch and crawl with hitbox change

const STANDING_COLLIDER = Collider(width = 16, height = 32)
const CROUCH_COLLIDER   = Collider(width = 16, height = 16)
const CRAWL_SPEED        = 70.0   // replaces MAX_SPEED while crouched

state is_crouching = false
state collider      = STANDING_COLLIDER

function can_stand_up():
    // Shapecast the STANDING collider's extra height upward from the current
    // (crouched) top, to see if there's room to expand into.
    extra_height = STANDING_COLLIDER.height - CROUCH_COLLIDER.height
    return cast_shape_up(shape = STANDING_COLLIDER, distance = extra_height) == null

function set_collider(new_collider):
    // Anchor at the FEET, not the center — resizing must not move the ground
    // contact point, or the character will visibly sink or pop upward.
    feet_y = position.y + collider.height / 2.0
    collider = new_collider
    position.y = feet_y - collider.height / 2.0

function handle_crouch_input():
    wants_crouch = input.is_down("crouch")

    if wants_crouch and not is_crouching:
        is_crouching = true
        set_collider(CROUCH_COLLIDER)

    elif not wants_crouch and is_crouching:
        if can_stand_up():
            is_crouching = false
            set_collider(STANDING_COLLIDER)
        // else: stay crouched, ceiling is blocking — re-check every frame
        // until the player releases crouch AND there's room.

function apply_crawl_speed_modifier():
    if is_crouching:
        MAX_SPEED_EFFECTIVE = CRAWL_SPEED
    else:
        MAX_SPEED_EFFECTIVE = MAX_SPEED

function update(dt):
    handle_crouch_input()
    apply_crawl_speed_modifier()
    apply_horizontal_acceleration(dt)   // using MAX_SPEED_EFFECTIVE as target
    if not is_crouching:
        handle_jump_input()             // typically disallowed while crouched
    apply_gravity(dt)
    integrate_position(dt)
    integrate_vertical(dt)

How it works

This is fundamentally a State Machine (standing / crouching) that swaps out the physics collider on transition, plus a speed modifier applied while crouched — "crawling" is just "moving while the short collider is active," not a separate movement system.

Two details make the resize behave correctly instead of buggily:

  • Anchor at the feet. A collider's position typically refers to its center. If you swap STANDING_COLLIDER for CROUCH_COLLIDER without correcting position, the center stays put and the character either sinks half the height difference into the floor (collider shrank, center didn't move down) or hovers (grew without moving up). set_collider computes the world-space feet position first, applies the resize, then re-derives position.y from the new collider so the feet stay planted.
  • Can't stand into a ceiling. Releasing crouch under a low overhang must not let the standing collider clip into geometry above it. can_stand_up() shapecasts the would-be standing collider's extra height before committing to the swap; if blocked, the character simply stays crouched and the check re-runs every frame until the ceiling clears — this is why the check lives in the "player wants to stop crouching" branch rather than being a one-time trigger.

Jumping is disabled while crouched in the pseudocode above, which is a common but not universal choice — some games instead implement a "crouch-jump" that stands the character up as part of the jump's takeoff (running can_stand_up() and set_collider(STANDING) at the same moment start_jump() fires).