ECS and saving/loading

Started by
6 comments, last by Zipster 6 years, 11 months ago

Hello forum!

I'm running an ECS but still have problems with planning loading/saving.

Let's look at a specific case, a dragon-entity for example:


graphics_component = 
{
texture_path = "..."
}

other_component = 
{
...
}

So, this is an enemy, it will be used more than once, it is literally a class-concept that shall be instantiated during run-time. Therefore, it does not require a transform-component describing its location within the playable world just yet.

But what happens with the dragon-entity once it gets instantiated? Well, at some point, a transform-component will be added, that looks somewhat like this:


transform_component = 
{
x = 100
y = 20
width = 64
height = 64
}

But wait, where do we get the width and height from? So let's just add a minimal version of the transform-component to our dragon-entity.


graphics_component = 
{
texture_path = "..."
}

other_component = 
{
...
}
transform_component =
{
width = 64
height = 64
}

Now, when we save our game, what are my possible strategies? Keeping track of all entity-files within a table and assigning instantiated entities to their fitting ID?


index = 
{
[0] = "file/path/dragon.entity"
[1] = "file/path/rabbit.entity"
}

So a dragon-entity would obtain an id of 0.

And now, only writing the x- and y-coordinates into the save-file? Is this common? Assuming that the original level-file still contains a version of the transform-component that lacks information (x, y)?

My prior idea was to separate constant components (e.g. graphics) from variable components (e.g. transform). Keeping constant ones inside the blue-print and variable ones inside the save-file. This seems to be not doable without adding more components which would result in more complex code.

Is this it? Or are there better strategies?

Thanks for taking your time : )

Advertisement
I've previously tackled this by having components and "component templates". Think of component templates as the properties of an component that do not change during the course of the game. For example, how much health a power up gives, or the dexterity requirement of a weapon. The component templates are bundled together into an entity template with a unique identifier.
When instantiating an entity, you pass in the entity template and create each of the components it requires, using the relevant component template.
Then, on serialisation you only serialise the dynamic properties defined in each component. Eg: transform, current health value etc. None of the properties in the component template can change, so there's no need to serialise these. But what you do need to know is which entity template was used to create the entity. So, you also store the unique identifier for the entity template. On deserialisation you read the entity template identifier, instantiate a new entity exactly as it was orignally spawned, and then deserialise the components' properties into it.
A note on unique identifiers: I found using a string ID that resembles a namespace was much more flexible and robust than using an integer. For example: "core.entities.longsword" rather than "123".
It's easy to assign meaningful id's and also handy if you want to offer content expansions or themed content. In my example, I used "core." to represent the core (always included content), and you could use "halloween." to include content that should only exist if the game is played during halloween. Enabling/disabling this content at runtime is then trivial.
I also wanted to support modding, and having a namespace like structure makes id collisions almost impossible. Eg: "mods.mikesweaponsmod.rifle" vs "Id = 4567". How likely is it that a mod creator is going to be able to guarantee the Integer Id's they've picked can never conflict with other mods?
In addition, a saved game may reference entity templates that are defined in a mod that is no longer installed, and you have a much better chance of handling the situation gracefully if the templates are identified by a string rather than an Id. eg: missing entity : "Id = 1234" vs missing entity : "mods.extratrees.birch".
[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

Wow, that gives me a different point of perspective onto the whole thing. Sameish idea of using a somewhat unique identifier but different usage.

But one issue is still there, what if a component shares dynamic and static/constant values? As said with the transform-component, owning its static bounding box/size but also dynamic coordinates of position.

Should I simply implement them in such a way, that the component-serialiser only saves dynamic values and the deserialiser loads the bounding-box from the template and the coordinates from the save-file?

Should I simply implement them in such a way, that the component-serialiser only saves dynamic values and the deserialiser loads the bounding-box from the template and the coordinates from the save-file?

That's pretty much what I was saying. Serialise the properties that are dynamic. Assume the properties that are static.
For a more concrete example, I had an "EdibleComponent", which was given to entities that could be eaten. I also had an "EdibleComponentTemplate", which defined the food and water values you'd get from eating it. These values are static, so I didn't serialise them. The edible item can spoil over time, so there was an Age property in the EdibleComponent which ticks at different rates depending on how it is stored (refrigerated vs not etc). Since that property is dynamic, it is serialised.
So a mocked up example of how the serialised file might look:
The entity template:

Name = core.entities.redberries
<Edible>
    FoodValue = 15
    WaterValue = 20
</Edible>

Serialised Entity:


Template = core.entities.redberries
<Edible>
    Age = 123
</Edible>

Obviously these can be stored in whatever format you like, this is just in text for clarity.

You can replicate the properties from the template in the component or have the component reference the template. Eg:

entity.GetComponent<EdibleComponent>().Template.FoodValue;

vs


entity.GetComponent<EdibleComponent>().FoodValue;

I prefer the former as it makes it easier to tell which properties you should be serialising in the EdibleComponent class. Hint: everything! :) In the latter you have to remember which is which.

[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

But one issue is still there, what if a component shares dynamic and static/constant values? As said with the transform-component, owning its static bounding box/size but also dynamic coordinates of position. Should I simply implement them in such a way, that the component-serialiser only saves dynamic values and the deserialiser loads the bounding-box from the template and the coordinates from the save-file?

I wouldn't even make the differentiation between "dynamic" and "static" values. Why force yourself to think about whats dynamic and whats static, and why limit you in such an obvious way (hint: in my 2d-game, on multiple occassions hitboxes can dynamically resize in a matter that needs to be saved ie. in a savefile; also position need not be saved for a static entity, etc... ).

I'd instead perform a simple delta-compression: You compare the runtime-entity to its definition, and store each field that is different - you can do that for savegames, also if you use some sort of template/prefab, you can do the same thing.

I don't have a graphics component. Just position, orientation, model/mesh, which stores texture, is_active, and shader info and a few other related components.

I then save all component data (pos and orientation are quats) out to the file. The scene graph keeps the transform and I have no reason to save it out, it can be regenerated easily. Each system, position, model/mesh, etc write out their data in turn themselves. They handle loading different versions too (get a version number passed in) so that they're fully self contained.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

In the ECS system I built, each component type knows how to say if its equal to another of the same type. So when serializing an entity, I compare it to its original entity template (each entity stores its template id, so I can look this up. The template id is just a hash of the template name).

For any components that are the same, I don't need to serialize anything. Then for any components which are different, I serialize that whole component (the component itself participates in this, so it might compress its data too). And of course I also have to take into account deleted/added components that weren't in the entity template.

I would save the type/template data every time, regardless of what's changed. The last thing you want to do is break backwards compatibility with your save files between versions, but that's a very real risk if you make assumptions about type data that might not be the same when a save game is loaded again. However if all that data is readily available, it's relatively easy to load older save files and have the data remain valid and self-consistent.

This topic is closed to new replies.

Advertisement