Last time, I explained what a core gameplay loop was, and identified Undersea Odyssey's as "avoid obstacles, collect treasure". The first step of that basically translates to "stay alive", which is necessary both to set a high score (part of the secondary loop) and to proceed to the second step at all. "Collect treasure" also helps fulfill a secondary loop, and will even be the main priority for players who identify as completionists rather than competitivists. I'll go into that more next time, but for now let's talk about obstacle spawning.
In my prototype, the player's submarine was stationary on the x-axis, while obstacles moved towards it. I couldn't give you a good reason why I chose to do it this way; I always just assumed that was how these types of games worked, but after thinking about it, I don't have anything real to base that assumption on. Anyway, it caused problems for me almost immediately. My simple system was to create a small chunk of level and put an obstacle in it, or multiple if they could co-exist without interfering with each other (like a swimming shark in between stationary stalactites). I called each one of these a gate, after the Flappy Bird mentality that the number you get through would correlate directly to your score. The problem I faced occurred any time I had independent movement, such as with the sharks I mentioned above. They needed to move back and forth inside of their gate which was also moving. I first needed to figure out the right combination of rigidbodies and parenting to even make this possible in Unity's system, but more importantly, this is a derivation of the "moving character on a moving platform" problem, which, common as it is in the gaming world, doesn't seem to have a universally-accepted solution. The most common solution I found involved manually adding the parent (the gate)'s velocity to the child (the shark), but first of all, this would involve a ton of extra component references all over my project to pull off. What really stopped me from pursuing this, however, was that I wanted to be able to increase gate speed as time went on, to make the game harder. So now I'd need to do those adjustments on a per-frame basis, potentially running into performance issues...not to mention how this exposed an even bigger potential problem: if gates spawned and started moving at different speeds, wouldn't they start running into each other?
So rather than find more workarounds for all of this, I pivoted and refactored to make the submarine be the thing that moves, rather than the gates. This still allows for swimming sharks, of course, but removes the need to take any other movement into consideration besides their own. The player can easily adjust their speed without affecting anything in the world, and in fact the only new issue that arose with this was figuring out when and where to spawn new gates (the prototype had a constant spawn point, a fixed position just offscreen in world space that they could poof into existence at, move across the screen, and poof out at another fixed spot, all possible because of the lack of other movement). I eventually settled on a system where each gate has a trigger that spawns the next one (or rather, one two ahead, for a buffer) when the player crosses it. Since the gates are all uniform size, the new spawn position is simply currentPosition + gateWidth, and by using the trigger rather than anything time-based, it doesn't matter what speed the sub is traveling at. I destroy gates automatically after they've left the screen, also based on a positional trigger, this one living on the sub itself.
There's just one more level of complexity to go into; my chosen gate width wasn't conducive to a seamlessly-repeating background. Either each gate would have exactly the same background and it would be extremely repetitive, I could make several interchangeable pieces (which would be difficult, all having the same connection points on either side with such a short width) and could still end up pretty repetitive, depending on the random spawn, or I could make the gates and background independent, which is what I did. One background section is now the width of three gates, which is also approximately the amount of content that fits on the screen at once. At this point, I used the Unity framework to my advantage, so that the spawning positioning and such is actually controlled by the backgrounds (which I called "sections"), with each containing three gates as children. When the section spawns, it automatically chooses random gates to be its children, and spawns them at the correct positions as well.
This all worked great until I came across another bug that involves a prefab storing a reference to itself. ...Or rather, not to itself, but to the prefab template it is a clone of. There's a subtle difference, you see? Because Unity doesn't. I'm honestly surprised that I never encountered this use case before, especially since some digging revealed that the problem was first logged way back in 2010. ...And then was marked as "by design" by the devs in 2014, which is basically the biggest "screw you" that you can ever get in software development. Since I needed my level sections to be able to create new copies of the original template (and not an exact copy of themselves, which may have changed via the player destroying obstacles or otherwise interacting with them), I ended up storing that good reference in my singleton Game Controller and grabbing it from there when I need to. These types of workarounds really annoy me because they're nothing but unnecessary added complexity and obfuscation, but all you can really do is leave a comment-rant and move on.
At the start of the game, the first two sections spawn automatically, and they're even special ones that don't contain gates, just to give the player some time to adjust before they need to start dodging things. Then, when the player crosses the triggers, more sections spawn using the offset described above, populating themselves with random obstacle gates. The Game Controller has a reference to the level which the player picked, so it knows what color to make the background and what pool of themed gates to pick from.
One thing I haven't gone into is the obstacle behaviors themselves, and I may do a more detailed post about that in the future. But I tried to come up with enough different combinations of movement patterns, speeds, and adverse effects on the player to make each one feel fresh and to keep people interested in what is in reality a pretty simple game. I'm going to purposely leave these details out of the "how to play" screens, to allow players to discover the obstacles' behavior and the best ways to avoid them all on their own.
Next time: the collectibles part of the core gameplay loop!
Comments