In Ludus we are trying to make the code completely modular, so all the systems exist on their own with as few dependencies as possible. The systems fetch all their information from the world, process it, and then put the information back into the world instead of sending information around to different systems.
We don’t have too many things in the architecture that we are strict about but this part is important because we want to be able to replace and improve the systems independently without affecting too much of the code-base. The bigger the code base becomes and the more sub-systems that are put into the simulation, the more important it becomes to be able to work on something without breaking something else. It also helps make it easier to implement and debug just one aspect of the game, and also means by serializing just one class we can save and load everything we need simply by ‘saving the world’.
In our case, the world object currently keeps track of only a handful of primary things:
-The characters in the world.
-The relationship between those characters.
-The upcoming set of gladiator games.
-The active rumours in the world.
It also has a bunch of helper methods to for example find a character with a certain profession or to get the asymmetrical relationship between two characters (I like you but you don’t necessarily like me).
Almost all the characters in our game are generated at runtime and as I mentioned are stored as data in the world object. The world object is persistent between the scenes and so when we for example need to load the slave trader into the market scene, we first use a simple proxy object to place the character visually in the level editor. The proxy object has a little replacement script on it that finds the world object and requests any character in the world that fits the description, in this case: the occupation ‘slave trader’. The world object finds the most appropriate character or generates a new one with the required properties so the important professions will always available in the game. This is extra important because in our dynamically changing world with senators vying for position and fighting out personal vendettas, some of them have a tendency to lose their heads, both figuratively and literally. In other words, if something unfortunate has befallen Quintus the slave trader; Julia, whom just so happened to recently move here from Aegyptus, will be there to replace his function in the game. She may not be as friendly on the prices, but she’s got a keen eye for gladiator talent and you can start a brand new professional relationship with her. That’s a good thing because Quintus had been getting on your nerves and that was why you told everyone he was sleeping with the magistrate’s wife .
As you trade and talk with this new character, the state of your relationship is updated from the conversation system to and from the world object, but the character placement system only knows that there has to be a character in this 3D position with the occupation ‘slave trader’ and the corresponding conversation trigger on it.
Now a small anecdote which originally lead to this blogpost:
Upon realizing that I needed to store all the things in the world in some way, I adapted the already existing inventory system to use as a kind of world state for the prototype. Gradually I added more things and realized I needed to store arrays of characters in there. This lead to conceptually peculiar parts of the code-base like Inventory.AddSlave() and Inventory.GetCharacter(occupation as string). The principle of idea of the world object was good though, and it’s still called Inventory in the codebase.