There was something a very insightful playtester (thanks zivi!!
Now this is fine for a "survivors" style game, a genre in which I have some experience! But Iris Blade is supposed to be the opposite from a survivors game.
So it was time to redo enemy spawn logic!
Mulling it over, and the notebook
I'll go off on a bit of a tangent. I like to avoid "the topic" in my writing because we're all very tired, especially me, but there is one thing that keeps coming up when discussing with the cultists. They all bring up the usefulness of bouncing off ideas against the proverbial deus ex wall and how effective that is at organizing one's thoughts.
And you know what, I gotta give it to the cultists.
So, I devised a contraption that lets me do the same thing with a heavily reduced carbon footprint: The notebook. Now, I didn't invent the notebook. As far as I can tell, this was invented by YouTuber ThinMatrix, of Equilinox fame and Java gamedev extraodinaire. If only he had told us how key the notebook was to his success during the devlogs. Instead, he just hinted at us by showing quick montages of his pages upon pages of doodles every now and then.
Anyway, seriously now: The next time you're struggling with some problem and feel the urge to consult with a talking pile of tensors, try talking to your notebook instead! Oh and drink that glass of water instead, you've earned it!
Alright, It seems I am incapable of seriousness, so goofiness will have to suffice for now. Now, to better illustrate my point, I'm about to let you take very blurry peek into my mind Those are the first two pages, to fully converge on a design for this I wrote about six pages. If you've tried reading a bit of this, you'll notice it's just me rambling. I'm literally thinking and writing it down as I go, stream-of-consciousness style. I find the act of writing helps me connect the dots and steers me away from tangents in a way that's almost impossible to do with freestyle thinking.
You'll also notice my writing style is a bit wasteful, we're lucky paper literally grows on trees, eh?
And the notebook nerds in the room may also have noticed how I like my notebooks clear: no grids or lines. That's just my style and I don't really have strong arguments for it, other than the fact that no lines gives me complete freedom to doodle things, even if this page ended up being a bit more text-y. Maybe you're also having strong opinions about the fact a grown-up is using a pencil. You can even see it in the picture, a 0.7mm Bic Velocity mechanical pencil (to save you the digging). The reason? I find it useful, and even cathartic to be able to conveniently "erase" a thought once I realize something didn't make sense.
The economics of spawning a bunch of slimes
Back to the topic, if there ever was one: The Game Director, which is what I've been calling the bit of code responsible for spawning enemies and making some other interesting gameplay decisions, has now acquired two tasks: (1) Fill up the world with enemies at the start of the level and (2) Respawn enemies as they die so you get to catch a break, but never really run out of them.
In retrospective, this was much simpler than I thought. Anytime you need to do something "procedural", the answer is always chunks and noise functions. What I did is split the game world into chunks (yet again). Each chunk stores two new properties:
- Enemy count
- Heat
With this, we can define what I called the density of a chunk. Now, hopefully you'll excuse phpbb's lack of LaTeX formulas:
Code: Select all
density = enemyCount / heat
So, by stability, what we mean is that we want to achieve a constant value for... some metric? Let's call it tension, even though it's a bit of a misnomer, like all the other terms here:
Code: Select all
tension = targetDensity / density
- If the chunk is at the target enemy density, it has a value of 1.
- If the chunk has a lot more enemies than it should, its value gradually go towards zero.
- If the chunk has less enemies than it should, its value will quickly raise
The mathematicians are screaming into the monitor as I am explaining division to the crowd. "Look at what gamedevs need to mimick a fraction", or something
But tension was the main ingredient here: When it's time to spawn an enemy, the Game Director will rank chunks by tension, and favor those with higher tension. Picking an element from a list at random with weighted probabilities was a recent topic of discussion, so I won't delve into that.
There's some twists I added to the idea, for extra chaos. Heat is typically computed by checking things in that chunk, like chests and whatnot. Each chest adds some heat, barrels add some heat too, and so on. But then I added a noise function on top, so even if two chunks have the same amount of stuff in it, some chunks will tend to be a lot more densely populated than others, just like life itself: And to you, the player, the real reason behind this variance is that I'm sure those slimes have a plan which makes them have some areas more heavily guarded than others.
I mentioned at the start of the section the game director had two goals: It turns out, the two goals are actually one and the same. A nice property of this system is that Initial spawn and respawning are two sides of the same coin. You start with an empty map, and you keep spawning enemies until you're satisfied!
It's hard to show this in action since the point is to feel it over the course of the run, but this was my best attempt in a short looping gif: And then the FPS dropped
But no tale is complete without some performance testing.
What's this I see? 45 FPS in my debug build? No, that won't do.
Spawning enemies near the player has some advantages. You get to play smoke and mirrors with them and despawn enemies once they venture too far off. When it's you against the horde, nobody will notice a missing bat.
But now that there's enemies all over, I couldn't pull off this trick anymore. It only took a quick glance at the profiler to notice the issue. Hear me out now: Invest some time into having a good profiling system!
The solution? Simple! Every performance issue can be solved in one of two ways: (1) Do it faster, (2) Do less work. Since my enemy logic is a port from Carrot Survivors, I know it was pretty optimized already: zero-allocation, uses a fast path for the spatial partitioning logic that's specific to enemies... So (2) was the obvious choice.
The next best thing you can do when you can't despawn faraway enemies is to freeze them. And that turned out reasonably well! I also took the chance to add culling logic to my Sprite component, which I hadn't had the need to until now (because most things in the game are tiles or grass and those are culled!).
Closing
Anyway, I hope you enjoyed the devlog!
I tried to go for a longer one this time, but I think it was time well spent. If I get to be a little selfish, please do let me know if you enjoy these. Gamedev can feel a bit lonely sometimes and despite what it might seem, morale among the carrot ranks hasn't been the highest.