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

Entity Movement And Physics

NSDex edited this page Jul 9, 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.

Player Entity Movement

Player movement commands are captured once per tick by polling the state of the relevant keys on the keyboard. Movement commands in Minecraft are as follows:

  • Move Forward/Backward: moves in the direction that the player is looking.
  • Strafe Left/Right: moves perpendicular to the direction that the player is looking.
  • Jump: Applies a momentary impulse to player's vertical velocity if they are currently on the ground or in a fluid.
  • Sneak: Reduces the player height while also slowing movement. Also applies some other gameplay effects.

A player entity has moveForward and moveStrafe variables which are reset to 0 at the start of each tick. If the forward movement key is pressed, moveForward is incremented by one. If the backwards movement key is pressed, moveForward is decremented by one. If the strafe left key is pressed, moveStrafe is incremented by one. If the strafe right key is pressed, moveStrafe is decremented by one. If the sneak key is pressed, moveForward and moveStrafe are multiplied by 0.3.

To be continued...

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 handled 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
}

Clone this wiki locally