Saving levels in an in-game level editor

Two advices.

Keep your code modular. Just because your objects have some unique scripts and runtime behavior, it doesn’t mean that the data you need to serialize is different. For example you might have a bunch of different button like objects: a button on a wall, pressure button on floor, switch requiring a key. They might each have different scripts attached for handling the activation of them (using keyboard, stepping on, using interact button while having a key). But they all have a common part that the activation signal can be connected to activation receiver (door or some other mechanism). The part which needs to be serialized is the same. So instead of each type of button having a completely unique script, duplicating the connection logic, you split them into two MonoBehaviors where the part describing connection and serialization of it is shared, alternatively you can use inheritance for sharing the common part. Structuring your code this way will not only help with serializing, but can also simplify the rest of runtime logic (like the UI code for connecting a button like object to a door).

Use interfaces. If the amount different kind of serializable data is still large, create an interface for serializing/deserializing the object specific properties, and implement it in each script with unique serializable data. That way top level level saving/loading code doesn’t need to know what exact properties each object have. When you create new type of object, you only need implement this interface for the new object, without touching the rest of level saving/loading process.

As for references, you might have to split the loading process into two stages: first create all the objects, then apply the object specific properties (including reference to other objects). That way once you try to restore object reference, the target object is already created.

Alternative approach is to use lazy references (within final game objects), which only get resolved when you need to use them. This way you don’t need multistage loading process. Downside is that you need to modify the gameplay logic, and there is also some performance penalty of doing the reference lookup each time you interact with a thing. Depending on the type of game this might be a none issue. For example in a turn based puzzle game this is probably not a problem, even in an action game if it’s something which happens once every few seconds cost of resolving integer/string based reference probably won’t matter. One more downside to lazy reference resolving is that if level is broken, and stuff is referencing to objects that don’t exist, you will only notice it when interacting with specific objects. With the previous approach, you would detect all the bad references immediately at load time or possibly even when saving the level. With lazy references you might have to play through whole level, even worse there is a high chance of not noticing the problem (before it gets to player) as the level author might not fully retest every possible interaction with optional objects which are not necessary for completing the level. With all those drawbacks why would you ever choose lazy reference resolving? Two stage loading is fine for flat structures, but if you have a complex hierarchical structure of subobjects referencing each other, then separating object creation from property initialization and deserialization might be challenging. One benefit of having weak references, is that it can make it easier to directly use of the shelf serializing interfaces without having to invent your own thing, or introducing additional layer of indirection serialized_data ↔ deserialized structure ↔ actual game object.

There is a tough choice is between detecting problems early or doing it lazily and having half broken levels. On one hand you don’t want players to be downloading broken levels created by other players and wasting time just to find out after finishing half of it that it’s broken and impossible to finish. On the other hand it’s equally annoying when large amount of user content is “broken” and unplayable after a game update even though the change was mostly cosmetic and the levels would be otherwise fully playable.

4 Likes