Skip to content

Movement

This page documents how entity movement and physics are implemented.

WARNING

This page is a work-in-progress and may not be fully accurate.

Tick Logic

java
// converts strafe/forward input into a velocity impulse along the entity's facing direction
void applyInput(float strafe, float forward, float acceleration) {
    float length = sqrt((strafe * strafe) + (forward * forward));

    if (length < 0.01) {
        return;
    }

    if (length < 1.0) {
        length = 1.0;
    }

    strafe /= length;
    forward /= length;

    velocity.x += (strafe * cos(yaw) - forward * sin(yaw)) * acceleration;
    velocity.z += (forward * cos(yaw) + strafe * sin(yaw)) * acceleration;
}

// moves the entity by the given vector, handling cobwebs, sneaking, block collisions, and fall state
void move(Vector3f movement) {
    ySize *= 0.4;

    if (inCobweb) {
        inCobweb = false;
        movement.x *= COBWEB_HORIZONTAL_DRAG;
        movement.y *= COBWEB_VERTICAL_DRAG;
        movement.z *= COBWEB_HORIZONTAL_DRAG;
        velocity = Vector3f.ZERO;
    }

    Vector3f original = movement.copy();

    if (onGround && sneaking) {
        movement = sneakClipMovement(movement);
    }

    movement = resolveCollisions(movement, original);
    updateFallState(movement.y);
}

// accumulates fall distance while airborne and deals damage on landing
void updateFallState(float movedY) {
    if (onGround) {
        if (fallDistance > FALL_DAMAGE_FLOOR) {
            dealDamage(ceil(fallDistance - FALL_DAMAGE_FLOOR));
        }

        fallDistance = 0;
    } else if (movedY < 0) {
        fallDistance -= movedY;
    }
}

// handles movement while submerged; drag differs between water and lava
void moveInFluid(float drag) {
    applyInput(moveStrafe, moveForward, FLUID_ACCELERATION);
    move(velocity);

    velocity *= drag;
    velocity.y -= FLUID_GRAVITY;

    // swim upward when pressing into a wall
    if (isCollidedHorizontally && isLiquidAt(velocity.x, velocity.y + 0.6, velocity.z)) {
        velocity.y = FLUID_WALL_BOOST;
    }
}

// main per-tick movement update: reads input, applies jumping, dispatches to the appropriate movement path
void tick() {
    // input axes are decayed every tick before new input is added
    moveForward *= INPUT_DECAY;
    moveStrafe *= INPUT_DECAY;

    // read raw keyboard state (player only; mobs set these in their AI code)
    moveForward += (forward ? 1 : 0) - (back ? 1 : 0);
    moveStrafe += (left ? 1 : 0) - (right ? 1 : 0);

    if (sneaking) {
        moveForward *= SNEAK_SPEED_MODIFIER;
        moveStrafe *= SNEAK_SPEED_MODIFIER;
    }

    if (jumping) {
        if (inWater || inLava) {
            velocity.y += FLUID_JUMP_BOOST;
        } else if (onGround) {
            velocity.y = JUMP_VELOCITY;
        }
    }

    if (inWater) {
        moveInFluid(WATER_DRAG);
    } else if (inLava) {
        moveInFluid(LAVA_DRAG);
    } else {
        // friction depends on the block underfoot; slippery blocks reduce both braking and acceleration
        float friction = onGround
            ? belowBlock.slipperiness * HORIZONTAL_FRICTION
            : HORIZONTAL_FRICTION;

        // acceleration is tuned so that on normal ground (slipperiness 0.6) it equals exactly 0.1
        float acceleration = onGround
            ? 0.1 * (NORMAL_FRICTION_CUBED / (friction * friction * friction))
            : AIR_ACCELERATION;

        applyInput(moveStrafe, moveForward, acceleration);

        if (onLadder) {
            velocity.x = clamp(velocity.x, -LADDER_MAX_HORIZONTAL, LADDER_MAX_HORIZONTAL);
            velocity.y = max(velocity.y, sneaking ? LADDER_SNEAK_DESCENT : -LADDER_MAX_DESCENT);
            velocity.z = clamp(velocity.z, -LADDER_MAX_HORIZONTAL, LADDER_MAX_HORIZONTAL);
            fallDistance = 0;
        }

        move(velocity);

        // climb upward when pressing into a ladder
        if (isCollidedHorizontally && onLadder) {
            velocity.y = LADDER_WALL_BOOST;
        }

        velocity.y -= GRAVITY;
        velocity.x *= friction;
        velocity.y *= VERTICAL_FRICTION;
        velocity.z *= friction;
    }
}

Knockback

java
// applies an impulse pushing the entity away from its attacker
void applyKnockback(Vector3f direction) {
    velocity *= KNOCKBACK_VELOCITY_DAMPENING;
    velocity.x -= direction.x * HORIZONTAL_KNOCKBACK;
    velocity.z -= direction.z * HORIZONTAL_KNOCKBACK;
    velocity.y = min(velocity.y + VERTICAL_KNOCKBACK, VERTICAL_KNOCKBACK);
}

Constants

NameValueDescription
GRAVITY0.08Subtracted from velocity.y every tick after the move, before friction is applied
VERTICAL_FRICTION0.98velocity.y is multiplied by this every tick after gravity, producing a terminal velocity (~3.92 blocks/tick downward)
HORIZONTAL_FRICTION0.91Base horizontal drag; used directly in air and multiplied by block slipperiness on the ground
JUMP_VELOCITY0.42velocity.y set to this on jump (~1.25 block jump height)
FLUID_JUMP_BOOST0.04Added to velocity.y each tick the jump key is held while in a fluid, allowing the entity to swim upward
FALL_DAMAGE_FLOOR3Falls of this many blocks or fewer deal no damage; each block beyond this deals 1 point
STEP_HEIGHT0.5Maximum height the entity can step up automatically without jumping
DEFAULT_BLOCK_SLIPPERINESS0.6Slipperiness value assigned to most blocks; ice uses higher values, reducing friction and acceleration
NORMAL_FRICTION_CUBED0.16277136(DEFAULT_BLOCK_SLIPPERINESS * HORIZONTAL_FRICTION)^3; used as a normalization constant so ground acceleration equals exactly 0.1 on normal blocks
AIR_ACCELERATION0.02Input acceleration while airborne; much lower than on the ground, giving the entity limited air control
INPUT_DECAY0.98moveForward and moveStrafe are multiplied by this every tick before new input is added; distinct from friction, which acts on velocity after the move
SNEAK_SPEED_MODIFIER0.3moveForward and moveStrafe are multiplied by this while sneaking, reducing ground speed

Fluids

NameValueDescription
WATER_DRAG0.8Per-tick velocity multiplier in water
LAVA_DRAG0.5Per-tick velocity multiplier in lava
FLUID_GRAVITY0.02Gravity applied per tick while submerged (replaces GRAVITY)
FLUID_ACCELERATION0.02Input acceleration while in water or lava
FLUID_WALL_BOOST0.3velocity.y set to this when pressing into a wall while submerged

Ladders

NameValueDescription
LADDER_MAX_HORIZONTAL0.15velocity.x/velocity.z clamped to ± this on a ladder
LADDER_MAX_DESCENT0.15Maximum downward speed on a ladder
LADDER_SNEAK_DESCENT0.0Downward speed locked to this while sneaking on a ladder
LADDER_WALL_BOOST0.2velocity.y set to this when pressing into a ladder wall

Cobwebs

NameValueDescription
COBWEB_HORIZONTAL_DRAG0.25Horizontal movement is multiplied by this in a cobweb
COBWEB_VERTICAL_DRAG0.05Vertical movement is multiplied by this in a cobweb

Knockback

NameValueDescription
KNOCKBACK_VELOCITY_DAMPENING0.5All velocity is halved before the knockback impulse is applied
HORIZONTAL_KNOCKBACK0.4Horizontal knockback impulse magnitude
VERTICAL_KNOCKBACK0.4Added to velocity.y on knockback; result is capped at this value