SoC::003 - Data-Driven Mech Generation
July 4th, 2018, 3:54pm
After taking a bit of a hiatus from working on # m e c h g a m e, I'm back with a little update, with a new and "improved" method of storing and loading mechs and their components.
I'd previously spent a lot of time figuring out a good method of storing mech weapons and editing their properties easily. My last post goes into detail on this. After some time away from it though, I started to dislike the ScriptableObject approach. It started to feel like it might not be as extensible as I'd like it to be, and I don't like the idea of having to open Unity every time I need to make a minor stat change.
Now, instead of SOs I've moved to storing all mech parts in a SQLite database. This gives me a lot more flexibility in terms of how I can edit the component data. It also forces me to think about mechs in a much more data-oriented way, which is a good thing IMO. Also also, storing mechs and mech parts purely as data means I can use that data to procedurally generate mechs on the fly and allow for things like player mech editing and saving/loading custom builds (this last one is a big deal).
An initial version of my mech database involved a separate table for each type of component. It kept things slightly more organized, but started to result in some redundant info. Every table had a field for hit points, prefab name, and mass. Plus, I started to realize I wanted to have the flexibility to have parameters I thought would only apply to weapons on things like legs or cockpits.
I decided to have a single table for every component that a mech is made of, with all possible properties available to each component. Only a few are used on every part:
|Name||The name of the component. This'll get used in-game in the mech editor(!) as well as when items are found(!!)|
|Description||Same as the name, though might not get used as much. Makes room for in-game lore and stuff.|
|ComponentType||This one's an Enum stored as an int that represents what sort of component it is.|
|Prefab||A string representing the base prefab of the part. This is used to load the part from an AssetBundle.|
|Mass||The mass of the object. I'm gonna use this in conjunction with the Engine force to make some mechs feel heavier than others, depending on the loadout.|
|PassiveSystems||A set of bit flags stored as an integer. This represents the Passive Systems the part uses.|
|ActiveSystems||Same as above, but for Active Systems (duh)|
There's a number of additional properties that get used either by the component manager (if it has one) or by the Active and Passive Systems, depending on whether those systems have been enabled or not. This includes:
One thing I forgot to mention: in keeping everything data-driven, I'm able to create generic systems that act on the data stored in these components. Things like health, heat management, and even weapon controls will be handled by these systems. And since it's all based on the data passed to them, I can have a single instance of each system class handle every component and mech in the game. I imagine this'll save on memory management, and (hopefully) make debugging easier.
Those last two parameters in the above table are what ensure a component is acted on by these systems. On mech creation, bit flags are checked and the systems will have the parts registered to them so they know to do something with them. Probably worth noting I haven't put this part totally together yet, so we'll see how it works.
Just as a quick note, there's a number of component types currently in the project.
|CHASSIS||The base structure of the mech. Currently it's an armature imported from Blender with no meshes on it. When you bring an armature into Unity from Blender, it imports as a collection of empties in a parent-child structure that matches the connections of the bones of the armature. This is super handy, as I can then take (almost) all the other components and attach them to their appropriate empties as child objects. This is handled by a ChassisManager script that holds references to the empties and attaches the components it's told by the MechBuilder script.|
|ENGINE||This is a prefab that holds a mesh and the EngineManager script. That handles the movement of the mech (max force) as well as the rotation of the chassis.|
|COCKPIT||The cockpit has a mesh, a CockpitManager script, and bunch of empty children representing accessory mount points. On generation, the cockpit will receive each of the accessories listed in the MechData table and connect them in the order received.|
|THIGH_L, CALF_L, FOOT_L, THIGH_R, CALF_R, FOOT_R||These are all basically the same from a dev standpoint. Just a mesh with no manager (yet).|
|ACCESSORY||Accessories get mounted to the cockpit. These can be a number of things, from weapons to heat sinks to energy boosters to whatever else I can think of. Meant to be a generic part that adds the real spice to a mech.|
Much like the components, mechs are stored in a database table as well. This allows for saving builds by myself and the player. Things like common mech enemy types or bosses could be stored in a read-only database, and player builds could be saved in a separate db pretty easily. The MechData dable is pretty straightforward, since every component in the MechCoponentTable has a unique ID. There's a column for each component that goes into the base mech, plus fields for name, description and six accessories.
With mech data implemented and the MechBuilder script actually properly building basic mechs, the next part is making some accessories and attaching those. After that, I'll likely start working on some basic mech AI to make them move around and shoot at each other.
Hopefully the wait between now and the next SoC post won't be as long as it was for this one. Also, I won't be posting a build just yet, as the build right now is less interesting than the last one. Hopefully I'll have some mechs for you to shoot at/evade/etc.