I am slowly realizing that my current “room defines what is in it” approach isn’t going to cut it. Thankfully, I didn’t get very far with it or add too much code. The main reasons is me wanting to make my life easier designing rooms. I would like to specify as few things as possible by hand. Perhaps locations where mobs spawn or props sit or loot can appear, but not actually how much and what kind for each room. I don’t want to micro-manage decisions on a per-room basis, but have the higher powers of the dungeon determine what loot and mobs and whatnot are necessary and then the rooms can place those things.
To sum up, I want to add a new dungeon generation layer that will “distribute gameplay-related stuff” around the dungeon. For example, my current level might require 60 mobs, 3 bosses, 20 loot items, and 1 artefact. And I might have 4 arenas and 10 hub rooms. So I can tell 3 of my arenas to have bosses and 1 to have an artefact, then sprinkle the mobs and loot in hubs or whatever. I’m thinking aloud here, so details will come as I write this entry. The takeaway point is that I can’t make such decisions on a room-by-room basis, because rooms don’t “know” that they need a boss and not an artefact spawned.
First of all, I would like to define on a per-level basis the sort of “content” I mentioned:
I can then reference it in my level (theme) and pass it to the dungeon generator. There, I’m adding an addition layer to convert my raw nodes into content-able nodes, which can store information about what sort of content they want. In my case, hubs become small content nodes and arenas (one of the 4 nodes) become large content nodes. And this works nicely (grey square on top of spheres designates the node as having content):
There are some design questions as to how I actually want to distribute my content. Some content, like bosses, would go one at a time into arenas. Other content, like enemies, could appear practically anywhere, including with other content, like loot. So I need at least several distribution “patterns” for these things. In fact, I might as well define them modularly, mainly so I can test changes quickly. A very simple version would look like this:
This is not yet sufficient to define what goes where. But it’s enough to know that enemies only go in hubs, for example. The problem here is selecting rooms for them as well as overlap between rooms with different content. How many rooms are dedicated to enemies, how many to loot, how many to both? I can answer this with a slider:
The slider describes the proportion of available rooms for that content that will actually contain that content. This means that if I have 100 rooms, 80 will be used for enemies and 40 for loot, of which 20 will be used for both. So this allows me to specify exactly how many rooms will contain each content (if any0 and how they will overlap (if at all). This has its limitations, but it should work fine for my purposes.
Actually implementing this is a somewhat tricky and takes several steps. During these I generate “content spots” which are basically locations of certain size/purpose that can take one or more content items of specified type and amount:
First, I need to collect all the nodes that I can actually use for content spotting (hubs and arenas):
Then I need to use the above distribution slider information to proportionally group content spots to their content:
Lastly, I need to distribute the actual content with the appropriate amounts to these rooms:
I can use the above debug output while I’m actively writing code for this, but I certainly need a actual in-scene debug for this (red flat rectangles – mobs; green – loot; split – both):
So this distributes the content in the dungeon. Now I can look at all these content spots and they will tell me exactly what they need, for example, 6 enemies and 3 loot items. The spots also reference the node(s)/room(s) they were made for, so I know which room(s) will need to accommodate these. So now I can go back to the actual rooms (definitions) and specify the locations for room-independent content.
Firstly, I can massive simplify how my rooms specify what goes in them. In fact, I can match their tiles to content types. I removed the previous areas I had and replaced it with specific content types (enemy, loot, player spawn):
For example (hub room with enemy locations and loot locations):
I no longer care to specify anything more about this content — not how many there are, not the enemy strengths or loot amounts, etc. This will now be provided by the content generation and the room just needs enough empty spots to place that content, whatever it is.
I guess I also don’t want to write special rules for spawning the player, so I might as well have a “player spawn” content type:
This is bureaucratic overkill, because I have 1 player distributed 100% in 1 room used for player only. But it’s better than writing custom logic just for this and it works fine as a particular edge case:
Now the final step is to actually place the content items in the room where they are wanted. This is–fundamentally–somewhat simpler than it used to be before. I need to go through every content node/spot I generated and then select enough tiles from the relevant room definition and just spawn the stuff there. For example, a two-exit hub room that got both enemies and loot:
Of course, now that content is expected, I have to actually set these areas for every room or the generator (rightfully) complains:
And this isn’t the “correct” content yet — I don’t specify which enemy types and kinds of loot I want. The big thing about my room design was choosing a particular enemy theme or strength for each room, so I don’t get silly mixes of stuff (like above). For that I need to go back to an earlier content generation step and consider how I choose which rooms get which specific content. And whether I want to distribute that as well in some way. This will have to be on a content-basis, that is, I cannot select enemies or bosses the same way as I select loot, and the player doesn’t get selected at all.
I already have the enemy selection asset:
I know which mobs to pick, sort of. And I previously had the enemy amounts (few, some, etc.), but that doesn’t translate into how I am doing it now level-wide: I don’t know which mobs to actually select. I can extend the enemy selection asset with a similar distribution logic:
This means that the level would select trash, regular, and special mobs with a 50-30-20 ratio. But this doesn’t feel like the proper way to do this. I want my dungeon layout to have some sort of logic to it, where trash and regular or special mobs are placed in appropriate sections. How would I choose boss mobs, for example? I think I am making too many different overlapping assets and categories here. I already have the content selection, so I should just extend that with different enemy types (I’ll leave “special” for later):
And, as proof of concept, this works fine and creates different enemies (pink – trash, red – regular):
And now I can pre-select the enemy I want for the entire room (“ghost” here) based on the desired category (regular enemy here):
Now, when I pre-select an “enemy”, I definitely don’t want random enemy types, but I might want relevant enemy types distributed in some logical way. For example, I might have a spider nest with 3 cocoons, 1 spider queen and a 70-30 mix of small and big spiders as needed. This is sort of my mob collection asset should be doing, so let me refactor it as enemy collection:
And, for the sake of having the pipeline able to produce the sort of scenarios as I mentioned above, I added some spider mobs and here’s the relevant values:
So now I can request this asset to provide me with a certain amount of mobs. The only problem is that this doesn’t account for when I need very few enemies. For example, if I need only 2 spiders, I don’t want it to be the queen and a cocoon (wait, is that how it’s spelled?), but rather a couple smaller ones. So I need to add restriction to the entries like so:
And this spawns enemies pretty well:
I also don’t want random enemy distribution around the dungeon. As an extreme example, I might have a themed section in the layout with special mobs and tiles. But even a simple balance issue like not having strongest mobs near the spawn point. There are again many ways I can approach this. The main choice is whether I let the layout generator or the content placer decide this. I think content generator will have to re-thread some of the layout steps to do this, so it makes more sense to put it in layout for now. So for that I’m specifying for each compatible layout generation step what the node’s purpose is, broadly speaking:
So first steps can generate nodes that are “close to start” and subsequent additions become “far from start”. This is a very simple use case, but this gives me an additional criteria by which to filter nodes in the layout for content purposes. Which means I can specify a cross-referenced placement for enemies based on both the node’s ability to support content (like a hub with mob spawn spots) and node’s purpose in the dungeon (like being far enough for strong mobs):
In this example the small rooms close to start only get trash mobs. While small rooms far from start can get both trash mobs and regular mobs. For example:
The green area is where the “close” nodes are, everything else is “far” nodes. Of course, with stuff branching and splitting, it’s not exact, but this isn’t supposed to be.
There are many things I need to work on still, but I think I will focus and cover them separately, like arenas and better adding of content to them (and related multi-room stuff). This should be a good start on the framework for content distribution in the dungeon. (And my posts sure are getting long with all these details…)
Okay, to recap for my own reference and for fun (with mobs as example):
- Build a dungeon layout from nodes
- Collect nodes from layout that can spawn mobs
- Distribute enemies between nodes as content pieces
- Select a random enemy group
- Place mobs in the node’s actual room
This is all getting quite convoluted, but I think it makes sense when broken down into steps. Most importantly, it lets me tweak things with a few values at the very highest level of detail and then it all auto-magically propagates throughout. And while all the steps are closely interlinked (and will fail horribly if something is missing), nonetheless any of the steps can be modified separately.