All posts
cover image
Making of OPCraft (Part 1): Building an on-chain voxel game
last year By alvarius

We like Autonomous Worlds.

Last month we launched OPCraft, our first foray into creating an Autonomous World. It's an on-chain 3D voxel world where every aspect of the World - every river, blade of grass, and patch of snow atop a mountain - exists on-chain. A world where every action is executed as an Ethereum transaction.

The first part of this series discusses how we built the core physics of OPCraft. The second details our on-chain procedural terrain generation. The final part chronicles the hilarious drama of our two-week open playtest.

MUD: an engine for autonomous worlds

OPCraft is built using MUD, the open-source on-chain game engine we're developing at Lattice. We recommend reading the MUD announcement if you're interested in details. Here is a summary to get you up to speed.

We built MUD to solve all those pesky low-level challenges that come with building complex on-chain applications (like games). MUD allows developers to create ownerless data namespaces called Worlds. It separates data and logic into Components and Systems. Components are contracts that assign typed data packages to Entities. An Entity is represented by a numeric ID. Systems contain the application's logic and modify data in components (that have granted them write access). Entities, Components, and Systems are registered in the World contract, which is the entry point to the application. Since data is stored in a standardized format, MUD synchronizes contract and client state without application-specific indexers or network code.

Let's go over the steps we took to build OPCraft.

A minimum viable on-chain voxel game

Step 1: Components

The first step of creating a new application with MUD is to model the components to represent the application state.

In a voxel-based world, the central entities are blocks. Every block has a position and a type (Grass, Dirt, Stone, etc.). To represent this in MUD, we create two components: Position and Item. The Position component maps each entity to its position, while the Item component maps each entity to its item type.

Every entity is represented by a numeric id. Data can be attached to entities via components.

Every entity is represented by a numeric id. Data can be attached to entities via components.

Attaching the Position component places blocks in the world. But what about blocks in a player's inventory? We can use an OwnedBy component to store a mapping from each entity to the address of its owner.

Placed blocks shouldn’t have an owner, and blocks in an inventory shouldn’t have a position. To handle this logic, we will require systems.

Step 2: Systems

Systems represent the physics of the world. Every system has the ability to edit a set of components, and its logic defines the canonical rules valid data modifications.

The only physics in our minimal voxel game are mining blocks and placing blocks. To mine blocks, we create a Mine system: it takes a coordinate and block type as input, verifies that the given block type exists at the given coordinate, removes the block entity's Position component, and in turn sets the block entity's OwnedBy component to the sender of the transaction.

To place blocks, we create a Build system: it takes a coordinate and the entity id of the block to be placed as input, verifies the given block is owned by the sender of the transaction, verifies the given coordinate is not occupied by another entity, removes the block entity's OwnedBy component, and in turn sets the block entity's Position component to the given coordinate.

At this point, we have an on-chain game that anyone can play by sending transactions to the system contracts. And while manually sending transactions is a valid way to play an on-chain game, most players would prefer a graphical client to show the game state and send transactions.

Step 3: Hooking up the client

The frontend of an on-chain game does not differ much from the frontend of a traditional video game. MUD is a backend engine compatible with any rendering engine. For OPCraft, we used fenomas' NOA engine, a browser-based voxel engine built on top of Babylon.js.

MUD synchronizes the state of all component contracts with the client. It also includes a typescript-based reactive ECS library, which stores component data on the client in the same format as on the blockchain. This lets us create client systems that dynamically react to component updates.

After connecting client components to the contract components, we can create a client system that renders entities with Position and Item components as blocks.

Lastly, we enable left clicking, which calls the Mine system, and right clicking, which calls the Build system.

Calling the Mine system updates the Position and OwnedBy components on the blockchain. MUD propagates the updates to the client, and the client removes the block from the terrain and adds it to the player's inventory. The inverse happens when calling the Build system.

That's it - we now have a fully on-chain voxel game in an infinite world.

An infinite world of flat grassland.

An infinite world of flat grassland.

Adding features

Mining and placing blocks is not sufficient for a fun game. We need crafting. MUD makes it easy to add functionality like this. All we do is create a Crafting system contract. It takes a 2D array of block entities as input, fetches their types from the Item component, and verifies that the hash of these item types matches a recipe. If so, it burns all input entities, creates a new entity of the recipe’s type, and sets owner to the system's caller. The client knows how to represent block entities via the Item and OwnedBy components, so the only client-side change needed is UI to call the Crafting system.

The last mechanic we need is to protect players' buildings from being destroyed by other players. We want to avoid selling plots of land for money but instead design an in-game mechanic to claim land. We also want to prevent inactive players from claiming popular plots of land and then leaving them unused, so first-come-first-serve is not a good distribution mechanism. Our solution is to divide the world into 16 by 16 block chunks and let players stake rare resources found in the world in them. The player with the highest stake in a given chunk can claim it. In a claimed chunk, only the claimer can mine and build. But another player can stake more resources and claim the chunk from the previous owner. This creates an in-game market in which sought-after chunks, like those close to the spawn point, are more expensive than unpopular ones.

Any valid Ethereum address can claim a chunk. Although the core mechanics require the chunk's claimer to be one address, it is easy to create a proxy contract to claim a plot of land on behalf of a group of players. The players can then send mine and build transactions to the proxy contract, which forwards them to the lower-level Mine and Build system contracts, allowing the players to own the chunk together. This simple construction is an example of how players can extend the core functionality of the game - the beauty of building on-chain.

Links and acknowledgements

Thanks to 0xhank, biscaryn, CipherSovereign, frolic, and ludens for feedback on this article.

You can find the full source code of OPCraft on GitHub, including instructions on how to deploy your own instance locally or on an EVM compatible blockchain of your choice.

Continue with Part 2: On-chain procedural terrain generation or skip to Part 3: What happened in two weeks of OPCraft.