How It Works
The Canisters
There are three types of canisters in the World Engine:
World canister: The game server canister
World Hub canister: The indexing canister
User Node canisters: The user data canisters
World canister
A World canister is essentially your game server written as a canister smart contract. You can deploy your own through our website. Your game client communicates exclusively with this canister. Its main purpose is:
Act as the game server for your game
Manage the Configs of your game
Validate the actions that players are doing in your game
Ensure players aren’t cheating
World Hub
The World Hub indexes World canisters and User Node canisters. It tells the World canisters which User Node canister they need to call.
User Node
UserNode canisters store the data of all users. User data is stored as pieces of data called Entities.
The Data
There are four types of data stored across the World, World Hub, and User Nodes:
Action: Actions enforce what players can do in your World
Config: Configs define metadata, usually about Entities
Entity: Entities are data representing the objects or things in a World
Permissions: Defines which Worlds have permission to alter Entities in your World
Action
An Action is something a player can do in your game that gets saved to the database.
Example: If a player spends 100 Gold they receive 1 Sword
Action
This is what an Action looks like in code:
public type Action =
{
aid : Text;
callerAction : ?SubAction;
targetAction : ?SubAction;
worldAction : ?SubAction;
};
aid: The action id that identifies this specific Action
callerAction: The SubAction to apply to the user calling the Action
targetAction: The SubAction to apply to the user being targeted by the Action
worldAction: The SubAction to apply to the World executing the Action
SubAction
This is what a SubAction looks like in code:
public type SubAction =
{
actionConstraint : ?ActionConstraint;
actionResult : ActionResult;
};
actionConstraint: The requirements for a player to do this Action
actionResult: The result of this Action
Config
A Config defines static metadata for your game. Configs are stored in the World canister, and are fetched by the Unity game client when the game starts. They can be changed in the canister without requiring a new game build. They look like this:
public type Config = {
cid : Text;
fields : Map.Map<Text, Text>;
};
cid: The config id for this Config
fields: A hashmap of "fields" and values. This allows for flexibility to define whatever fields you want to store in this Config.
Entity
In its simplest definition, an Entity is a piece of data in the database. They look like this:
public type Entity = {
wid : TGlobal.worldId;
eid : TGlobal.entityId;
fields : Map.Map<Text, Text>;
};
wid: The world id in which this entity exists (aka the canister id of the World canister)
eid: The entity id that uniquely identifies this Entity (eg. awesome_item_01)
fields: A hashmap of fields and values contained in the Entity
This standardized data format for Entities can represent almost any data type imaginable.
The most common use cases for Entities include Items, Buffs, and Stats. But they can be used for more complex data like Characters, User Profiles, Purchases, Tech Tree etc.
Entities are the building blocks of your game economy.
Here are some example implementations:
Item Entity:
{
eid="item_01" //entity id
wid="awcae-maaaa-aaaam-abmyq-cai" //world id (aka the canister id of your world)
fields = [
("quantity", "420"), //quantity of item user holds
];
};
Buff Entity:
{
eid="buff_01"
wid="awcae-maaaa-aaaam-abmyq-cai"
fields = [
("expiration", "1695954265797"), // expiration timestamp
];
};
Stat Entity:
{
eid="stat_01"
wid="awcae-maaaa-aaaam-abmyq-cai"
fields = [
("games_played", "420"),
];
};
Character Entity:
{
eid="warrior_01"
wid="awcae-maaaa-aaaam-abmyq-cai"
fields = [
("attack", "420"),
("defense", "69"),
("health", "9000"),
];
};
Permissions
Permissions allow other games to alter your game's database.
There are two types of permissions:
GlobalPermission allows another game World to change all the Entities in your World.
public type GlobalPermission = {
wid : Text; // The World Id of the World that you're giving permission to
};
EntityPermission allows another game World to change a specific Entity in your World.
public type EntityPermission = {
wid : Text; // World Id of the World that you're giving permission to
gid : Text; // Group Id of the Entity
eid : Text; // Entity Id of the Entity
};
These are the functions in your World canister that allow you to add and remove permissions:
public shared ({ caller }) func grantEntityPermission(permission : EntityPermission) : async () {
assert (isAdmin_(caller));
await worldHub.grantEntityPermission(permission);
};
public shared ({ caller }) func removeEntityPermission(permission : EntityPermission) : async () {
assert (isAdmin_(caller));
await worldHub.removeEntityPermission(permission);
};
public shared ({ caller }) func grantGlobalPermission(permission : GlobalPermission) : async () {
assert (isAdmin_(caller));
await worldHub.grantGlobalPermission(permission);
};
public shared ({ caller }) func removeGlobalPermission(permission : GlobalPermission) : async () {
assert (isAdmin_(caller));
await worldHub.removeGlobalPermission(permission);
};
Below is a diagram of how game World canisters interact with the World Hub and User Node canisters:
Last updated