Skip to content
This repository has been archived by the owner on Nov 19, 2018. It is now read-only.

Entity Movement And Physics

NSDex edited this page Jul 10, 2016 · 7 revisions

Analysis is based on the decompiled source of the vanilla b1.7.3 client.

Note: Minecraft Entity movement and physics code is a tangled mess. To describe it well, I will need to disclose more of the actual Minecraft code structure than usual. Method and variable names will be changed where practical.

Entity Overview

All entities have the following properties related to movement and physics:

  • position: The current X/Y/Z position of the entity in world space. The position of an entity lies at the center of its bounding box along the X-axis & Z-axis, and at a variable offset, referred to as the yOffset, from the minimum extent of entity's bounding box along the Y-axis. Typically an entity's yOffset equals its eye-height (1.62 for a player entity). Sneaking, sleeping, dying, and other actions can change an entity's yOffset.
  • velocity: Movement in Minecraft is measured in meters-per-tick. An entity's velocity carries over between ticks. Almost all changes to an entity's velocity are performed by adding or multiplying other values with the existing value.
  • size: The entity's width and height. The width value is used for both the width and depth of the entity, thus the bounding boxes of all entities has a square base. An entity's size is set once during initialization. It is 0.6w x 1.8h for a player entity.
  • boundingBox: Axis-aligned bounding box of the entity in world space. Because the bounding box is in world-space, Notch must keep it synchronized with entity's position (and vis-versa). Therefore updating an entity's bounding box should also be considered to update the entity's position (and the reverse) unless otherwise noted.
  • rotationYaw: The rotation of the entity around the Y-axis. An entity's yaw is zero when facing in the direction of the positive Z-axis and increases as the entity rotates clockwise.

Living entities (players, mobs) have additional properties:

  • moveForward: The speed of movement in the direction the entity is currently facing within the horizontal plane. Positive if moving forward, negative if moving backwards.
  • moveStrafe: The speed of movement perpendicular to the direction the entity is currently facing within the horizontal plane. Positive if moving left, negative if moving right.

Both values are reset each tick based on the current keys pressed (for players) or the actions of the A.I. (for NPCs). They are then converted into a vector based on the entity's rotation and applied to the entity's velocity after which, they are not used any further for the remainder of the tick.

Entity Movement

Handle Input

Entity movement begins by capturing the entity's action state for the current tick. This involves polling the current keyboard state (for player entities) or running the A.I. (for NPCs). An entity can perform the following movement-related actions:

  • Move Forward/Backward: moves the entity in the direction it is currently facing. For a Player entity, the moveForward property is set to 1 if the forward movement key is held, -1 if the backwards movement key is pressed, and 0 if both or neither keys are pressed.
  • Strafe Left/Right: moves the netity perpendicular to the direction it is currently facing. For a Player entity, the moveStrafe property is set to 1 if the left movement key is held, -1 if the right movement key is pressed, and 0 if both or neither keys are pressed.
  • Jump: Applies a momentary impulse to entity's vertical velocity if the entity is currently on the ground. For all entities, jumping increments velocity.y by 0.42. If the entity is in water or lava, jumping increments velocity.y by 0.04.
  • Sneak: Reduces the player height while also slowing movement by 70% (multiplies the moveForward and moveStrafe properties by 0.3).

Apply Heading

The Entity::applyHeading method performs different actions depending whether the entity is currently in water, lava, or neither. Prior to branching, the entity's moveForward and moveStrafe properties are multiplied by 0.98. The purpose of this is not clear.

Convert a Heading Into A Velocity

Notch uses the following implementation to convert an entity's moveForward and moveStrafe properties into a velocity vector.

Entity::ConvertHeading(strafe, forward, multiplier)
{
    var speed := SQRT(strafe * strafe + forward * forward);
    if speed < 0.01 then
        return Velocity.Zero
        
    speed := multiplier / MAX(speed, 1.0)
        
    strafe := strafe * speed
    forward := forward * speed
        
    let yawYComponent := sin(rotationYaw)
    let yawXComponent := cos(rotationYaw)
     
    let xComponent := strafe * yawXComponent - forward * yawYComponent
    let zComponent := forward * yawXComponent + strafe * yawYComponent

    return Velocity(xComponent, 0, zComponent)
}

Water Movement

The entity's heading is converted into a velocity using a multiplier of 0.02, and added to the entity's current velocity. The Entity::moveEntity method is invoked to move the entity using the current velocity as the proposed deltaX/Y/Z (See Physics below).

Apply Drag & Gravity

After the entity has been moved, its velocity is multiplied by 0.8 to simulate drag and 0.02 is subtracted from its velocity.y to simulate gravity. (Yes, both of these happen after the entity has been moved for the current tick.)

Surface Breaching

If the entity is colliding with an object next to it (e.g, a wall) ¿and the top of the entity is above the water?, 0.3 is added to the velocity.y. This allows the entity to jump out of the water when it reaches a shoreline.

Lava Movement

The entity's heading is converted into a velocity using a multiplier of 0.02, and added to the entity's current velocity. The Entity::moveEntity method is invoked to move the entity using the current velocity as the proposed deltaX/Y/Z (See Physics below).

Apply Drag & Gravity

After the entity has been moved, its velocity is multiplied by 0.5 to simulate drag, and 0.02 is subtracted from its velocity.y to simulate gravity.

Note: Surface breaching from lava is identical to water

Normal Movement

If the entity is on the ground, a multiplier is computed from the slipperiness of the block it is standing on using the implementation shown below. Otherwise a constant 0.02 is used as the multiplier. The entity's heading is converted into a velocity using this multiplier and added to the entity's current velocity.

// Default block sliperiness is 0.6
var slipperiness := World.getVoxelAt(floor(position.x), floor(boundingBox.minimumY) - 1, floor(position.z)).blockType.slipperiness
slipperiness := slipperiness * 0.91

let multiplier := 0.1 * (0.1627714 / (slipperiness * slipperiness * slipperiness))

The Entity::moveEntity method is invoked to move the entity using the current velocity as the proposed deltaX/Y/Z (See Physics below).

Apply Friction & Gravity

After the entity has been moved, 0.08 is subtracted from its velocity.y to simulate gravity and then its velocity.y is multiplied by 0.98 (presumably to simulate air drag). Its velocity.x and velocity.z are multiplied by the slipperiness of the block was standing on (before it moved), or 0.91 if not on the ground. (Yes, both of these happen after the entity has been moved for the current tick.)

Physics

Minecraft lacks a discreet physics system. Gravity, collision, and translation of input into movement are often handled in the same methods as non-physics entity updates such as dealing fire damage when inside of a burning block.

Collision (Movement Inhibiting)

Collision detection and resolution is primarily handled in the Entity::moveEntity method for collisions that inhibit entity movement. Other types of collision, such as those that generate events, are detected elsewhere.

Given the entity's current bounding box and a proposed deltaX/Y/Z (translation) for said bounding box, this method determines what blocks or entities exist in the in-between space and modifies the deltaX/Y/Z to avoid a collision. It then translates the entity's current bounding box by the (potentially modified) deltaX/Y/Z thereby moving the entity to its new position.

  1. Create a copy of the entity's current bounding box. Extend this bounding box by the proposed deltaX/Y/Z using the implementation shown below:
BoundingBox::extend(dx, dy, dz)
{
  if dx < 0.0 then
      minimumX := minimumX + dx;
  if dx > 0.0 then
      maximumX := maximumX + dx;
      
  if dy < 0.0 then
      minimumY := minimumY + dy;
  if dy > 0.0 then
      maximumY := maximumY + dy;
      
  if dz < 0.0 then
      minimumZ := minimumZ + dz;
  if dz > 0.0 then
      maximumZ := maximumZ + dz;
}
  1. Collect all potentially colliding bounding boxes from blocks or entities within the extended bounding box from [1]. When searching the world for potentially colliding entities, Notch temporarily expands the query bounding box (from [1]) by 0.25 in all directions.

For a player entity, the only type of entity that will be found by this query are Boats. Although the player can collide with many other entities, a Boat is the only one that inhibits movement. Collision with entities that can be pushed are handled later (after the pushing entity has been moved). However, a Boat or Minecart colliding with another entity will inhibit its movement. Therefore, the query for entities potentially colliding with a Boat or Minecart will return the bounding boxes of all entities within the query bounding box.

  1. Iterate over all the bounding boxes from the previous step. For each bounding box, determine whether its distance to the bounding box from [1] along the Y-axis is less than the proposed deltaY using the implementation shown below. If the computed distance is less than the proposed deltaY, reduce deltaY to the computed distance. At the end of this step, deltaY should be equal to the distance to the nearest bound box from the previous step along the Y-axis. Offset the entity's bounding box (not the copy) by the final deltaY. Repeat this step for the X-axis and Z-axis in that order.
// NearbyBoundingBox is the bounding box from the current iteration
// QueryBoundingBox is the bounding box from [1].
BoundingBox::calculateYOffset(NearbyBoundingBox, QueryBoundingBox, deltaY)
{
  // Bail out if not within the same X/Z plane.
  if QueryBoundingBox.maxX <= NearbyBoundingBox.minX or QueryBoundingBox.minX >= NearbyBoundingBox.maxX then
      return deltaY
  if QueryBoundingBox.maxZ <= NearbyBoundingBox.minZ or QueryBoundingBox.minZ >= NearbyBoundingBox.maxZ then
      return deltaY
      
  // The entity is moving UP and is currently below NearbyBoundingBox.
  if deltaY > 0 and QueryBoundingBox.maxY <= NearbyBoundingBox.minY then
  {
      let difference := NearbyBoundingBox.minY - QueryBoundingBox.maxY
      
      if difference < deltaY then
          deltaY := difference
  }
  
  // The entity is moving DOWN and is currently above NearbyBoundingBox.
  if deltaY < 0 and QueryBoundingBox.minY >= NearbyBoundingBox.maxY then
  {
      let difference := NearbyBoundingBox.maxY - QueryBoundingBox.minY
      
      if difference > deltaY then
          deltaY := difference
  }
  
  return deltaY
}