-
Notifications
You must be signed in to change notification settings - Fork 312
Entity Movement And Physics
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.
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 theyOffset
, from the minimum extent of entity's bounding box along the Y-axis. Typically an entity'syOffset
equals its eye-height (1.62 for a player entity). Sneaking, sleeping, dying, and other actions can change an entity'syOffset
. -
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 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 incrementsvelocity.y
by 0.04. -
Sneak: Reduces the player height while also slowing movement by 70% (multiplies the
moveForward
andmoveStrafe
properties by 0.3).
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.
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)
}
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).
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.)
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.
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).
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
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).
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.)
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 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.
- 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;
}
- 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.
- 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
}