For some time now, I’ve been able to load resources from meta files, which allowed me to change my resources without the need of code recompiling. As long as I didn’t need any new kind of resources or attributes, at least. In fact, I do need to recompile when I want to create or change my testing entities, their components or their attributes, and this is getting really annoying.
I could use a scripting language to build my objects on the fly, but in the end, making a prefab loading system seemed a better choice. The reason is simple: I won’t have any early glue dependency with any kind of scripting language, which is something I want right now.
So… where do I start? A prefab system is a whole scary system – that’s why there is the word SYSTEM in there.
As usual, when something is so huge that we can’t easily choose a starting point, and the team (of one) size is small, or available (free) time is too short, a good place to start is deciding on behavior, desired features, and then applying some constraints and compromises. Having said this, here is the rule set I came up with:
- [r1]: Prefabs can be loaded from any type of yaml string;
- [r2]: A single yaml string must have a single prefab;
- [r3]: I must be able to load prefab’s yaml from files. A file must be guaranteed to have only a single prefab;
- [r4]: Prefabs can be a hierarchy of entities. There must be a single root entity, and the hierarchy will be built on top of Transform components (warning: transforms are optional components on the entities. A single, transformless root, is valid. Hierarchy without transforms on all entities should be impossible);
- [r5]: Prefabs components and entities can only make references to other components of the same prefab;
- [r6]: Prefabs can make references to another prefab only if referring to its root entity (components would be nice, but I’m making a compromise here);
- [r7]: Any real game entity can only make references to a prefab root entity.
- [r8]: Prefabs won’t be manipulated at runtime, and thus can only have “clone entities” instantiated;
- [r9]: A prefab must have a special (not yaml) representation in memory. This representation must not be bound to any loading type or persistent storage type / technique. This is the actual “prefab” in memory.
- [r10]: When instantiating a “clone entity”, all references must be properly set, and all loading events of the components must be properly called.
Rules r1, r2 and r3 are straightforward in making me dependent of YAML. Limiting a single prefab per yaml “complete” string, and a “single” yaml string per file, I reduce some flexibility of information loading in order to speed up feature development – by means of simplification.
Rule r4 says that I can have prefabs that are actually a hierarchy of one or more entities. However, the topmost entity (the root) must be a single one - so, no two-or-more-headed-prefabs. This allows some flexibility in the system, while the single root constraint will simplify a lot features that are about prefabs referencing, such as rules r6 and r7. By allowing other prefabs and entities to refer only to roots, a lot of the complexity about many special situations is immediately cut out from the system.
Rules r5 and r8 are sanity rules, intending to cut out even more flexibility in order to simplify the development. Prefabs changing on the fly can induce headaches from situations such as roots suddenly missing some components, or having absurd values on attributes. Also, if prefabs had dependency on specific components of other prefabs, the indirect dependency would result in special cases when instancing the actual entities.
Rule r9 is very important, as it will force the prefab system to have its own way of storing the prefab in memory. Think of this as the “core” – the way of loading the prefabs are simply outer layers of this core, and thus I can change the technology without needing to change the prefab system itself. If I succeed in being smart enough, it might be possible to make an internal representation that can itself be changed without affecting the engine.
Finally, r10 is the whole point of doing all this. Looking back, this system sure will need a loooot of planning before going to coding phase. I’ll have to postpone this a bit more, in order to have ideas on how to structure this on classes and, maybe, interfaces. Please, do note, I didn’t make a single rule about “being fast”. I’m new to planning this kind of system from scratch, so it feels like “being fast” would instantly lead to premature optimization.
Hope I don’t overengineer this…