Cartel is a SpatialOS project by indie developers Bold Conjectures aiming to create a complex, massively multiplayer, city building game…with a dark twist. In Cartel, Players are cast as down-and-out entrepreneurs in a dysfunctional metropolis. They must struggle against crime, corruption, and their rivals in an attempt to make their business empire the largest and most powerful in the city.
In this, the first episode of the project’s developer diaries, Zach Caceres from indie developer team Bold Conjectures takes us through what the benefits of porting a game from Unity’s UNET platform to SpatialOS were for him, his team, and their intriguing idea.
Porting Object-Orientated Programming (OOP) to SpatialOS’s Entity-Component-Worker (ECW) Approach
Bold Conjectures first step was to deconstruct our traditional object-oriented class hierarchy. We needed to take the functionality we already designed and map it to SpatialOS’s Entity-Component-Worker architecture.
Some of the more experienced game devs reading this are undoubtedly thinking, “You used an OOP hierarchy for your game?!”. Yep. If you’re not from a game design background, an object-oriented design seems like a no-brainer. Many university courses and books still recommend this approach.
They’re not completely without reason. A good object-oriented (OO) class hierarchy makes your code easier to maintain. OOP uses inheritance and it neatly encapsulates the data and behaviours of your game objects. It feels oh-so-easy-and-fun to make a new building or character in your game by inheriting from another object and adding a few new methods or variables to make the new object unique.
This works great at first, but it doesn’t make sense for applications that radically change. When a game evolves – as it inevitably will – your class hierarchy can quickly become a prison, as critics of OO design in games will be quick to point out.
As we talked with prospective customers and tested our game, we wanted to add new features and create new objects in the world. As these changes became more radical, we were forced to awkwardly restructure the hierarchy or refactor at ever more airy levels of abstraction.
For example, at first ‘Building’ seemed like a reasonable class. It’s a game about cities, after all. But when players wanted vehicles to travel long distances more quickly, we realized that we needed to refactor up to ‘OwnableObject’ so that vehicles could access functionality from Building without inheriting from it.
Later, OwnableObject was subordinated to ‘DamageableObject’, as we discovered certain aspects of gameplay required objects in the world that could be destroyed but not owned. If this sounds boring and far from a focus on gameplay, well, it was!
SpatialOS’s Entity Component Worker (ECW) architecture really shines here. In ECW, everything in the gameworld is an Entity. These entities are made of components, which are compact units of behaviour and states that can be plugged into an entity. Anything in a component is automatically synchronized across the system so, if a server were to fail or a new client joins the simulation, players receive everything in its proper state. SpatialOS enables and disables components to update states and fire events as the game world unfolds.
When this is first explained, some people react by saying: “This sounds just like OOP by another name! You have an object (entity) and it has methods and member variables (components with behaviours and states)!”
Not so fast. In OOP, each object embodies a class with behaviour and data that defines its place in the hierarchy. In ECW, entities are essentially empty containers defined by the components you put in them. There is no inheritance, just a clustering of components plugged in as needed.
Anything in a component is automatically synchronized across the system so, if a server were to fail or a new client joins the simulation, players receive everything in its proper state. SpatialOS enables and disables components to update states and fire events as the game world unfolds.
Let’s look at a concrete example of why SpatialOS’s ECW works well for a game like Cartel.
From Classes to Components
Our first step was to identify core entities in our game world. Since Cartel is about cities, a lot of entities are building-like. It’s easy to slip back into object-oriented thinking here. Remember, there is no inheritance and although lots of entities in the game world will be building-like, they are not part of a common class, “Building”. A ‘core entity’ is just conceptual: it’s a cluster of components that are likely to appear together.
Starting with this idea of a core entity, our next step was to decompose class properties and methods so that data and behaviour likely to ‘fire together’ during gameplay are grouped in a component. When a component is activated by SpatialOS, messages – required to tell the rest of the simulated world about the impact of this change, and vice-versa – are sent across the network. For efficiency purposes, it’s best to cluster messages that are likely to fire together so that a complete update occurs in a single network message.
I went through our class hierarchies and made a spreadsheet of any class member variables or important methods. As you do this, you can start to see where you can cluster functionality around components.
For example, one base class, “OwnableObject” handled data and behaviour for ownership in the game world. Aspects of this class could be converted into a component: Ownable.
The Ownable component clusters together data elements (states) and events that are likely to fire together during gameplay. It made sense to cluster the bool “ForSale” in the same component as the ID of the player that owns the object. When a building is bought or sold, both the ForSale state and the Owner will change, so we can update it at the same time.
However, it did not make sense to cluster the price in Ownable, even though it had been in the same OwnableObject class previously. So we made a separate component, called “Priced”. This arrangement makes far more sense from a gameplay perspective. Many things, like an entity’s condition or proximity to a negative or positive event can affect its price. So let’s do that separately.
Here’s a few examples of components drawn from OwnableObject.
|OwnerId||Spatial Entity ID|
The next step was to take this spreadsheet and define components using SpatialOS’s schema language.
Here’s an example:
id = 1003; bool for_sale = 1;
int32 base_cost = 2;
EntityId owner_id = 3;
This is just the first step of implementing the logic of our game world, but already we’re on a firmer foundation to prototype Cartel. By defining our game world one component at a time, we can layer gameplay and complexity as it makes sense, without worrying about refactoring an OOP hierarchy at some future date.
Since components are reusable, we no longer need to worry about adding vehicles, pets, or whatever else we decide we need in the game! Earlier I mentioned the hassle of adding vehicles to our game world. If we did that now, we could simply use components like “Name” “Ownable” “Damageable”, “Flammable”, or “Color”. Even though these were originally designed for building-like entities, no refactoring needs to be done.
We don’t need to try to figure out how “Vehicle” can inherit from “Building” just so that we can access functionality first designed for buildings. We just plug these universal components into our vehicle entities and then add some new components to make a vehicle ‘Driveable’ or ‘Moveable’.
SpatialOS’ ECW architecture puts Cartel on a much stronger foundation for future development. As we discover more interesting ways to expand our world, we can spend more time focused on prototyping and gameplay, not fretting over our class hierarchy or limiting the scope or richness of the game world to fit within traditional computation limits.
Thanks for reading!
If you liked this blog, why not discuss it on the official SpatialOS community forums?