That’s the problem I’m working on right now. I think this is the last big piece I need to complete before starting in earnest on my game. And it’s definitely a big piece. I wouldn’t be surprised if it took a few months.
Mostly I’m flying blind here, but these are the things I know I need to handle/am trying to solve:
- Managing entities coming into and out of existence (with respect to them being represented in the Rendering system, or Collision system) as you move around the world
- Efficient saving/loading support (e.g. save games)
- Ways for entities to communicate with each other (e.g. send the “fire” message to everything withing 10 meters of some position)
- Easy integration with world editor
I’ve made some progress here, but I don’t feel like I have the right abstractions yet, and I need to take a step back. This is one of those times where I’d really like to have another programmer around to trade ideas with.
In my implementation so far, I have a GameObject base class that serves as a generic property bag. Specific functionality is added by sub-classing the GameObject class. I’m ok with requiring a recompile to create a new type of object – this isn’t something that needs to be done often.
GameObject is simply a property bag. It contains no standard properties per se, and nor does anything that inherits from it. All object state is in the property bag (accessed by GetValue/SetValue type methods). This means I don’t write any per-type serialization. There is just a single save/load functionality for all objects.
Properties have metadata associated with them (e.g. type, name, etc…). In addition, each object type has a schema that defines the properties it can have, what the default values for that property are, and whether the property is static (never changes), persisted (it can change), or live (meaning it can change, but we only persist it if the object is currently active).
My game world is divided into Regions. Every part of the world is part of some Region, and they don’t overlap (though I suppose they could). When you near a Region, we look at the persisted/static state for that Region and activate GameObjects for all the entities in it. When you leave, we persist that state back to a blob. There are multiple sets of state for a Region: static, persisted, and (potentially) live. For instance, in the live state we might store “object 123 is at position x/y/z”. In the persisted state we might store “object 123 has been used by the player”. In the static state we might store “object 123 is green”. All stores are composed together when activating the GameObject.
A Region represents a “simulation group”. For instance, an NPC might be walking from one location to another within a Region. In order for that to happen reliably, all the objects in that Region need to be activated so that the collision/path-finding for the NPC work properly.
Properties are stored in a sort of global database (I use that term loosely) for each property instead of in the GameObject, though this is sort of an implementation detail. I figured that would lessen memory allocations. It arguably makes things easier when I want to query all objects within a certain area, for instance (since all positions are in one list).
One of the goals is to minimize save game size. To do this, I never save “static” properties for an object. I also don’t save property values that are the default value for that property for that object.
Part of the reason for the Region concept was also that it would allow me to be more efficient when saving state. I only need to save certain state for active Regions. For in-active Regions, I need to save “persisted” state. If I haven’t loaded that region since the last save game, all I need to do is copy the old blob directly over (I’m probably not explaining this very well).
One of the main blocking issues I have now is how to handle global state (state that is independent of Region). While the Region concept may be convenient for knowing when to activate/deactivate objects, it presents problems when manipulating state. For instance, performing an action in one Region might affect the state in another Region. My system isn’t well setup to handle this now. Perhaps all state should be global, and the only information associated with a Region is which objects are a part of it?
So I’m not quite satisfied with the way I have things set up. I feel like I’m missing some important “conceptual leaps”.
I do have a loosely-organized list of requirements that I’m using to drive design. Some examples are:
- The player buys a ship
- The player encounters a spawned monster, kills it, gets its loot
- The player encounters a unique monster, kills it, gets it loot
- The player harvests a plant that “refreshes” every n hours
- The player uses a shovel to unearth a unique item
- A unique object gets moved from one Region to another
- An NPC joins the player’s group
- A player talks to an NPC and the NPC checks to see if the player has a certain item in his inventory
- Conversing with an NPC adds a word-trigger to another NPC’s conversation system
- A fireball ignites nearby objects
- Streetlamps come on at a certain time of day
Of course, you shouldn’t infer anything about my game from the above requirements. They are sort of generic RPG things, but may not necessarily apply. I find they are useful for vetting designs though.
So currently I’m taking a step back and reading up on various other implementations of entity systems (such as this one).