Under Construction

Click to Continue

Minecraft Clone – Updating Blocks

TODO

In the previous post I covered how blocks are grouped into chunks before being converted into triangle meshes to be rendered. This allowed us to infinitely explore the procedural terrain. While this is nice, traversing a static world quickly gets old.

In this post I go over how the player can modify the world and see it react to their changes.

Block Updates

I use the term block update to refer to any action that modifies the underlying voxel data of the world. This includes both direct and indirect actions from the player.

DirectIndirect
Placing blocksFalling blocks
Mining blocksFlowing water
Growing plantsPlanted constraint

Modifying voxels is as easy as changing an integer value in a 3D array. The difficulty comes in propagating changes and rebuilding chunk meshes.

Since I render each chunk as a singular mesh, I have to rebuild a chunk’s mesh every time it’s modified. For example, if the player removes a block then that entire chunk must be rebuilt. In addition, if that block was on a chunk border then the neighboring chunk may need to be rebuilt as well. Both of these rebuilds must occur near instantaneously to avoid lag spikes.

Example of TNT explosions and water flow triggering block updates.

Block Update Cycle

Some blocks need to perform an update when a neighboring block has changed (sand, water, sugar cane, etc.). These kind of blocks introduce a unique performance challenge.

When one of these blocks is updated, it often requires the surrounding blocks to be updated as well. I can’t update all of these blocks at once as that could lead to an infinite chain of updates. Therefore, I use an update cycle.

Whenever a block is modified, that block and its neighbors are marked to be updated. Then every 0.25 seconds I scan over all the marked blocks and perform their updates in a single batch. Afterwards, I rebuild the updated chunk meshes.

Block Gravity

Blocks like sand and gravel are affected by gravity, which means they fall smoothly whenever no block is beneath them. This effect provides some realism and dynamics to the game, which is why I thought it’d be fun to include it in my clone.

Example of gravel blocks being affected by gravity.

However, this effect is tricky to implement as it breaks two previous assumptions:

  1. Blocks can only exists at discrete positions.
  2. Blocks are rendered per-chunk rather than individually.

Like Minecraft, my solution was to temporarily convert the falling block to an entity. This entity is rendered separately from the chunks and has a rigidbody so it can be affected by physics (i.e. gravity). The trick is to seamlessly swap the block for an entity when it starts falling, and then vice versa when it lands.

Example of sand blocks being converted to entities when falling. Entities are colored red to make the transition obvious.

Flowing Water

Representing fluid with voxels poses a tradeoff between realism and performance. The difficulty comes in that fluid is inherently continuous and dynamic, while voxels are discrete and expensive to change.

The continuity problem can be solved by discretizing the possible water levels into a manageable amount of quantities. I tried implementing 16 different quantities and used shaders to offset the resulting water block heights. I also experimented with finite water (ex. a lake can be drained by flowing into a ravine).

I was able to achieve some neat results with this, but unfortunately I had to scrap it as it could easily cause massive performance issues. So instead I opted for a single quantity of water that will create copies in adjacent blocks.

How does Minecraft handle water flow?

Minecraft tackles the continuous problem by discretizing water blocks into about 8 different quantities of water. Blocks with a higher quantity will create lower-quantity water blocks adjacent to it.

As for the dynamic issue, that is resolved with the block update cycle I mentioned eariler.

Example of water flowing through a channel. The water can move into horizontally adjacent blocks, but cannot pass over holes.