V(ulcanix)FX (Part III)
Updated: Aug 22, 2019
To wrap up my series on Vulcanix, there's one last thing I want to talk about: the cows. Ever since I first dreamed up this planet, I had a vision of a cow slowly sinking into a lava pool, after which it was removed from the game entirely. While there aren't really any strategic gameplay reasons to do this, it's a comedic effect along the same lines of watching them slide off a barn roof, and it fits in perfectly with the aesthetic of the game. But even something as deceptively simple as "destroy the cow when it touches the lava" came with a lot of considerations.
Starting from the beginning, the very first point was to make sure that the lava around the edges of the map was treated as edges. Using Unity's parenting/transform system, I was able to offset the lava parts even further away, making sure that they would not be affected by the main ground collider. So, if you drop a cow while on the island, it falls to the ground and wanders like normal. If you drop one over lava, it falls right through...and continues falling to eternity. (Side note, there's no navmesh over the lava either, so COM UFOs can't go there at all, and players with their fixed-y will just hover over it with no problem.) By adding a trigger collider to the lava, I can at least detect the cow, and do everything I need to at that point: slow its fall for the "sinking" effect, instantiate the lava particles I talked about last time, and set a clock to destroy the cow after enough time to let it completely disappear from view.
None of that is so bad, but in a game of this size, you always need to be thinking about everything. For one, what about my special teleporting cows? I can't just destroy the Hunt Mode cow (the one where it's the only one on the entire map), because that would be game over. Or in CTC, dropping an opponent's cow into the lava would actually benefit them if it were removed. My solution was to just teleport them again, the Hunt to a random location like normal, and the CTCs back to their bases. It was just a matter of clearing the right variables at the right time, then calling functions I already had written. Another factor was the Frenzy Mode. Since I'm keeping track of all of the cows, and decreasing the total remaining every time one is captured, I figured that destroying the cows also needed to decrease that number. The last main thing I needed to account for was my achievement list. Since I have two that involve looking at the state of every cow at the end of the game, I needed to determine how removing cows should effect them. When a Unity GameObject is destroyed, if it was present in a list or other data structure, the reference goes null. That's it; it's not deleted automatically or anything else you might expect. So I could potentially have a whole bunch of "dead" cows hiding in my list. It's simple enough to check (though it does increase the size of the function by about three lines every time), but it was more about the principle behind the thing: if there are literally no cows left on the map, does that mean that "every cow is in a single barn"? How about "no cows in generic pens"? I decided no to the first and yes to the second.
I also wanted to be able to drop cows into the tendrils leading out from the volcano to the edges. This was a lot more difficult. I mentioned the ground collider that stretches over the entire map, which was something I didn't want to mess with. So, during design, I purposely made the tendrils stick up out of the ground high enough for a cow to fall into and be completely obscured by. I figured I could trigger-collide with them in the same way, and have the cows sink straight down until they could be safely deleted. But this didn't work quite as I planned. First, I needed a physical collider on this lava. It was a requirement of the navmesh generation, to keep normal ground cows from wandering too close and destroying themselves. At that point, having a trigger collider too wouldn't help, as I would have still needed to ignore the normal one somehow. So I had to bite the bullet, and use Unity's IgnoreCollision function. There's nothing really wrong with that, I just have a personal aversion. And it does get more complicated when you throw the teleporters into the mix, because now at some point you've got to un-IgnoreCollision, or else the same cow dropped into the lava later won't behave properly. Okay, even with all that technical stuff taken care of, there was still the visual to worry about. Unity colliders are indiscriminate with their detection, so if I manage to clip the very tiniest edge of one, the collision event still fires. My setup looked good when a cow was dropped squarely in the middle of a tendril, where it had enough room to sink, but off to either side, the cow will look really weird sinking in lava that it's only half-in, then eventually it'll just pop out of existence. This problem took a lot more thinking to solve.
But here's the answer: I placed a list of transforms on each lava tendril, spaced relatively evenly along them. The OnTrigger event finds the closest one in the list to where the cow made contact, and now, instead of sinking straight down, the cow gets pulled inward to the relevant transform. Imagine it getting pulled at a diagonal towards the center of the lava, so at least it's always completely hidden from view before it's destroyed. With enough different transforms to choose from, this works surprisingly well.
Eventually, I changed my edge lava to work the same as my lava tendrils, for reasons that had absolutely nothing to do with the cows themselves. Though the code is more complicated, it's probably better anyway to just have a single function that always does the same thing. It'll be better for if I ever encounter problems in the future to only have to look in one place, or even if I just need to refresh myself the next time I have a far-reaching change to make.