Friday, August 14, 2015

Enemy spawns / Game data persistence

Since the last update, there has been a substantial amount of progress made on two different, but related fronts. Specifically, the enemy spawning system has been more or less finished (that's programmer parlance for "I finished 80% of it, but I don't know what the hell to do with the other 20%" - also known as the Pareto Principle.. or a modified version of it, anyhow). Additionally, we have the framework in place to save/load game data, which actually turned out to be quite simple once one gets past the initial fear of having to implement something so crucial to the overall game. Rather, the fear of badly messing it up.

So, first, I want to explain enemy spawning mechanics, starting with the old method. Previously, we would literally drag the enemy prefabs into the scene. Then, one (or both of us) would prepare an arrangement of five or six scented candles in a crude pentagram-like fashion with its centerpoint being my laptop. Depending on how we lit the candles, sometimes the scene would work. Other times, Lucifer himself would appear in the corner of the room for a brief moment, and we'd be like, 'clean rebuild and try again'.

Now I know you're all like, "this didn't happen, it's clearly embellished. There is no way they made a pentagram with six candles." And you would be so, so wrong.

The new method is much better and does not, at the moment, rely on any kind of ritual. The first step was to break up all scene elements into two categories: those that were scene-dependent and those that would persist through scene loading. We already had something like this, but it was highly unorganized. We began shuffling around objects that clearly did not need to be duplicated--characters, for instance, never need to be duplicated. Previously, we had each playable character present in each scene within the Unity editor itself, but this was not the best idea, and was mainly to avoid issues that we didn't quite understand at the time. Also, we were really coked out in June/July, but we've cut (get it?) back a lot.

A similar fate befell the cameras that we are using--no need to duplicate these in every scene. Additionally, each enemy object contained its own battle scene transition, which is completely needless, considering that every transition would essentially take you to the same place. Much better to have a single transition object and activate it once battle collisions occur.

As for enemy spawns, these were previously non-existent. What I mean by this, is that we would simply add the enemy prefab to the scene (as stated before prior to the bullshit segue about incantations and whatnot). This enemy prefab contained, among other things, its own spawn parameters and its own waypoint collection. This would be fine and dandy, but we elected ultimately to have a random enemy spawn system. You can see the issue there, when you want to spawn a spider that only chases you for 10 meters, or doesn't chase you at all, et cetera. This meant that we would have to modify the actual object within the scene itself and pray to god that we never applied the prefab. To explain, this would have overwritten every other spider's parameter. The point being, it was a bad idea.

If you recall the update regarding the world map battle zone spawning (you don't and neither do I, so it's fine), the idea was to randomly spawn an encounter once a certain number of steps had elapsed. The great thing about this is that we could pretty much reuse this same framework--instead of spawning a battle group randomly (the term for the collection of enemies that you actually engage when the battle transition occurs), we could spawn a bunch of them within the scene itself, with an extra layer of logic sitting on top that actually just instantiates the enemy prefab at the location. World map battle encounters do not need to bother with this step since enemies are not visible.

Essentially, the relevant parameters were then copied outside of the enemy prefab itself and into the battle spawn object. Much cleaner, and much more flexible (like my fourth wife). The next goal was to actually persist the list of enemy spawns through battles. This means that they would retain their previous locations and spawn status upon leaving a battle (were they defeated in battle? did you flee from them?). Additionally, they would not need to be re-instantiated each time (this is really the big one). For a scene with a few enemies, sure, this is trivial, but for more involved scenes, this will save a lot of time.

We then realized that it isn't just enemies that would benefit from this, but treasure chests/resource nodes as well. Not sure if we want to handle NPCs like this yet, but we'll find out soon enough. What happens then is that these nodes are actually placed under a special node within the scene called LevelManager. The nice thing about this is that we can actually inherit from this class if we want to add level-specific callbacks. For instance, when you first enter Cephaline, it's dark, but it never is again after that. Currently we are handling this in an... uh... hacky way. But no longer, damn it.

Then when you enter a new scene (not the world map or battle scenes, they's special) we graft the nodes from the non-persistent root (LevelManager) to the persistent root (GameManager) and instantiate the enemies accordingly. This has the added benefit of allowing the scene objects to be specified within the level itself. This also has the benefit of putting these persistent objects in a nice centralized location, which is useful for serialization.

As for the enemy spawn mechanics themselves, these are still being finalized, but the basic idea is this: every scene has a number of potential spawn points. When the scene is loaded for the first time, we iterate through these spawn points and check whether or not an enemy should be created at this location. This is mostly time-based, but can be conditional-based as well. Imagine ole' mama spider only popping up after gettin' pissed that our intrepid adventurers decapitated ten of her children.

Now, I know you're thinking, "spiders have millions of babies. Who gives a shit about ten dead spiders?" You be quiet with that type of logic.

Once an enemy is defeated, depending on whether it is a one-time spawn (like mama spider up there, although you could have grandma spider show her ass once ten mama spiders get killed, ad infinitum, all the way back to amoeba), it will be marked as inactive until its spawn timer comes up again. This should be pretty familiar to anyone who has played (MMO)RPGs with similar systems.

The big question then, is how to persist this across multiple reloads. As it turns out, C# makes it fairly easy and straightforward to serialize and encrypt any object. As it also turns out, Unity doesn't and is like, "hell naw, whatchu tryna do here boy?" Unity asks us this multiple times per day, which is why we both wear headphones.

So, even though it's overkill, it would have been extremely nice to have just taken the entire GameManager (the persistent node) and serialized it once a scene was exited and deserialized it once a scene was entered. But again, that would have meant that we would have saved off every attribute (even the ones we did't want saved). Also, it wasn't possible without paying some amount of money for an extension script. I wish I were kidding.

The problem is that C#'s serializer can only serialize primitive types... which is to be expected. Furthermore, it requires you to wrap any attributes that you want to serialize under a special tag. Generally, the idea is to wrap an entire class or structure within this. Unfortunately, all game objects (including GameManager) inherits from Monobehaviour which cannot be serialized.

Thus, the solution, as much as I hate it, is to serialize a nested structure within the class itself. Normally, this would involve duplicating all of your variables. For instance, you would have the forward-facing variables that are exposed to the rest of the game (and most importantly, the Unity inspector). and then you would have a separate 'chunk' that are able to be serialized. Whenever you serialize or deserialize, these would be copied back and forth each time in an explicit step that individually sets/gets variables. Puke.

The trick to get around this was to use properties, including setters and getters that redirect to the internal serializable data structure. The only real issue with this is that you lose access to these variables within the Unity inspector. Thankfully, since they are not meant to be modified, you shouldn't really see them there anyhow. Furthermore, we plan on having custom inspector scripts at some point (any volunteers out there?) that could optionally expose them anyhow.

You know what, talk is cheap. Let me show you what a directory looks like before we save data and afterwards. Put your sunglasses on.



Looks like a pretty normal, empty directory, huh?

I know it's hard to believe, but that is that very same directory AFTER saving game data. Pretty sweet, huh?

All joking (and badly cropped pictures of nothing) aside, we do have a functioning saving/loading system, which is a pretty big f'ing deal. (I get one f word per blog post, right?) I could go on and on about this, but all that matters is that it works.

Next up: revamping the skill/inventory system. *shudder*











No comments: