11 February 2019

Oops I Just Accidentally a Roguelike

I've created a simple level generator based largely on Lived 3D's random level generator
Two of my pet projects are a game about building spaceships and a system for procedural game worlds using 3D chunks (imagine Minecraft's chunks, but as cubes so that they can generate an unlimited world vertically as well as horizontally).
The spaceship game needs a system with which the user can select, attach, and change parts on a ship, and one of the best ways I've seen for doing this is a "node attachment" system that connects parts to one another based on attachment nodes, each of which has a position and orientation (and other optional properties). If an object contains one or more of these, when requested (e.g. when spawned by a player or when the player presses a "connect" button), it will match itself with an attachment node on another object and cause its own assigned object to be moved and rotated so as to attach to the other rather like Legos:
I've shamelessly borrowed this image from Bac9 as it's just such an awesome illustration of the concept. I also take no credit for the concept itself, as it's been done beautifully before by games such as Kerbal Space Program (a hardcore addiction of mine for much of the last few years, and part of the inspiration for my spaceship game).
Had I the forethought, I should rightly have made a post about my adventures trying to get this system running and the silly math problems I encountered (turns out the best way to make a quaternion facing the opposite direction from another quaternion is... to rotate it 180 degrees! Wow!). In any case, this system is functional (though not without room for improvement) and allows the player to slap together arbitrary collections of objects, provided they've been set up with attachment nodes, be they pipes, dungeon rooms, spaceship parts, etc.
Now my other project, the procedural world system, lends itself happily to generating terrains, starfields, and even cellular automata. I mean to go into more detail about it later, but for now here's more eye candy:
Beautiful. Such terrain, very procedural. Wow. I should make a whole game about driving Thomas the Tank Engine a cool sci-fi rover around on an alien landscape. It'll be the next Minecraft and I'll make 7 billion dollars and retire to Sweden in a mansion with a cola fountain.
So naturally I had procedural generation on the brain and got to thinking "what if my dungeon builder could run automatically?"
And that's the story of how I accidentally made a roguelike.
It turned out that iterating through all the attachment nodes was a fairly straightforward process, and I only had my own shortsighted design choices as easily surmountable obstacles to overcome. Within a day I had a neat system that could slap new rooms onto the doorways of existing ones as many times as I wanted until I had a huge level to explore, rich in exciting features such as "zombies" (capsules that follow the player and make the "HP" number go down), "chests" (cubes that make the "score" number go up), and... unreachable areas due to some rooms growing straight through existing ones.
I knew what the solution was even before getting this far: when preparing to place a new room, the algorithm must first make sure it can fit in the desired location, i.e. any solid objects comprising it, such as walls, don't intersect (except maybe a smidgen) with any solid pieces of another room. Lived 3D showed in beautiful gif form how to do this with bounding boxes, but I'm overly ambitious and wanted to do it without boxes. What if my rooms are noodley cave segments?
What I needed was a way to check in advance whether a collider (a "hit box" or "collision mesh" except not always a box or a mesh) would intersect with another collider if placed in a given location. Fortunately my old friend the Unity engine has added a feature that happens to contain this functionality (although to my very minor disappointment it comes with a bunch of other overhead I don't need for this purpose) in the form of Physics.ComputePenetration. Come to think of it I should probably make a post serving as a tutorial for how to use this thing. The fact that I discovered it and want to help make sure the word gets around is like half the reason for this post, after all. Also I had a whole other set of adventures making dumb mistakes relating to checking for collisions in the right places.
I had to do a lot of debugging ^^;
Now why, one might ask, did I insist on using cerebral physics queries instead of something basic like OnCollisionEnter that every competent Unity developer knows?
OnCollisionEnter is called on any object that already exists in the scene if, between the previous physics timestep and the current one, the object went from not touching some other collider to touching some other collider. Developers can configure their code to accept information about what colliders were involved, how much they intersected and where, what was attached to them, etc. which is very useful for doing things in response to things bumping into each other, say for example lowering the player's HP if it touches a zombie. Theoretically, this function even gets called if the object in question wasn't touching anything the previous timestep because it didn't exist.
But one might see what I saw as a problem at this point: A room has to be made to exist, then it can be fed information by the physics engine about what has come into contact with it - and in most cases it even has to wait for a physics update, which is roughly the same interval as a "normal" frame update. The long and short of it is that the room has to actually exist in the world for as much as an entire frame before OnCollisionEnter would be useful to it.
Now I as a developer could account for this by designing all of my room prefabs to not do anything when they get spawned and instead wait for their location to be confirmed. But on one hand that's something I'll have to be careful to do when I get creative about room prefabs (spawning hordes of zombies on load, loading saved items from a file, etc.), and on the other hand, I want to be able to distribute this system to other developers without having to deal with them not following this extra rule. So what I needed was a way for the generator to, in a sense, imagine the room being in place without actually sticking anything there just yet. I was pleased to find that despite its apparent complexity, ComputePenetration ran surprisingly quickly and only when piles and piles of rooms had been spawned did it provide any noticeable performance cost (you might notice that the last few frames of the gif up top show the generator slowing down due to the number of rooms in play).
What I hope to figure out next is how to link up connections between existing rooms so that the level can contain loops and how to make level sections generated in one place connect their exits to the entrances of sections generated somewhere else in the world so that I can combine this with my chunk system...

No comments:

Post a Comment

Sorry this isn't a real post :C

I've entered graduate school and, as I expected, suddenly become far busier than I had been before, leaving no time to maintain my blog,...