Architecture and code design for history procedural generation is 75% complete. History generation is turning out to be a feature that seemed simple at an abstract level but has proven to be very difficult at a concrete level. It wasn’t part of the original vision, but I had considered it early on and chosen to exclude it because I didn’t think it was necessary. My opinion only recently changed, when I realized that dungeon stocking needed to be more contextual. Now history generation is a must-have, though I’m still uncertain about how well it will work in practice.
Map generation optimization. Map generation was further optimized this week, bringing memory allocation to ~50 MB (from 5.5 GB originally). Optimization is taking longer now. The low-hanging fruit has been picked; the remaining inefficiencies are more spread out and smaller percentages of the whole. Good gains came from removing some logging calls, which are expensive in Unity. Another boost came from reworking the graph shortest path algorithm, which gets called for every unique pair of rooms. When I optimize, I focus first on memory allocation and second on duration. Memory is in good shape, but it is still taking 5-6 seconds to generate a map (the 3-5 seconds I reported last week was incorrect; I wasn’t looking at the actual duration data). My goal is under 3 seconds, so there’s still some optimization work to be done. I’m targeting code that is frequently executed. The worst case is slightly over 1,000,000 executions.
History procedural generation design. The conceptual design is done. The next step is software architecture/code design.
Next week, I’ll focus on the history generation implementation.
Map generationoptimization. I knew as I wrote Map Generator 2.0 that some of the code was horribly slow and wasteful and would need to be optimized later. Map generation had ballooned to 10-15 seconds and .5 GB of memory. It’s now down to 3-5 seconds and 200 MB of memory, and there’s much more room for improvement. The optimization techniques were converting LINQ statements to for loops and reducing use of temporary lists. One optimization example involved how connections between rooms are stored. I started with one list that stored all original connections between rooms. Then I created a new list for connections that constructed loops and another new list for connections that joined sections. I used separate lists instead of the original list because I needed to do different things with the items in these lists, and it was more expedient to create new lists (though a little voice inside my head was telling me to slow down and do it the right way). I added a fourth list when I realized I needed to track each connection in each room that used the connection (as opposed to only the room that originated the connection). Because it was sometimes necessary to get all of the connections, I created a property that combined all four lists into one new list. Yikes. The allocations… The solution was to combine the lists into one and add an attribute indicating the type of connection. This caused way more rework, and troubleshooting issues caused by the rework, than I anticipated. At least the rework made the code simpler and easier to understand, which is always beneficial.
Movementoptimization. Enabling actor actions to be displayed simultaneously exposed a problem: the movement code took a long time to run, causing actors to instantly move to the next cell rather than moving incrementally over multiple frames. Linear interpolation is used to calculate how far an actor moves each frame, with the actor’s movement speed and elapsed time since the last update as inputs. I ran the Unity profiler and identified the main causes: dynamic lighting and excessive Unity log calls. The log calls are easy enough to deal with; they won’t be in production releases. Dynamic lighting, which uses the Smart Lighting 2D asset, is a dilemma. I want to keep it in the game but I’m not sure how much I can optimize it. Temporarily disabling the lighting and logging fixed movement between two cells, but there was still an issue when moving across multiple cells. Actors momentarily stopped at each new cell before moving again. I had seen this before and knew the source: the state logic in the Update method caused some frames to skip movement. For example, an Update call would determine that all actions had finished in the previous Update and it would update the turn state. Movement wouldn’t resume until the next Update. With nested state logic (there are turn phases, actions phases, and action step phases), several frames passed before movement resumed. This was resolved by modifying the state logic to process state changes in the same update when applicable. For example, when an action step finishes, the logic will start the next action step in the same update.
Displaying actor actions simultaneously. I reverted the changes I made last week to present actor actions simultaneously. It became clear that an enormous amount of rework was needed to separate action logic and presentation. Fortunately, a much simpler solution had been right in front of me the whole time: asynchronous actions. Instead of waiting for each action to finish being presented, I’d simply start off each action at the same time. I didn’t consider this initially because one actor’s actions can affect another; I believed that all actors’ actions had to be resolved before any of them could be presented. For example, if the player hits an enemy, and that enemy dies, the enemy shouldn’t be able to move. I still had to make some modifications to get this working, such as checking that an actor is still alive, and tracking cells that actors are moving to before they reach the cell (so that other actors don’t attempt to move into the same cell).
Pathfinding improvement. Over time, I’ve changed my mind on how actors interact with other actors and objects that are diagonally adjacent. I may change my mind again, but what’s certain is that there needs to be a way to allow interactions with adjacent objects in ordinal or cardinal directions, depending on the object. Currently, a melee attack can be performed from an adjacent diagonal cell, but opening a door cannot. Until this week, the latter was not possible because of a limitation in the pathfinding code – since actors can move diagonally, and the pathfinding code finds the shortest route, paths end at a cell diagonal to the cell containing the object being interacted with. The fix for this was to change the path destination based on the interaction range of the object. An object with a range of 1 can only be interacted with if the actor is adjacent to the object in a cardinal direction.
Better debugging statements. It just occurred to me that I’ve written a lot of bad debugging statements. I typically add debugging statements while troubleshooting a particular issue. They make sense when working within the context of an issue, but not on their own. Without context, they do more harm than good because they increase cognitive load, which is already high from being in troubleshooting mode. I improved these statements by adding more relevant state information to them. I also rearranged the statement in some cases so that the subject of the statement (actor, item, etc.) was at the beginning of the statement. This made it easier to skim through the debug log.
Inspector improvements for ScriptableObjects using Odin. To reap the full benefit of Odin, I added Odin attributes to all classes inheriting from ScriptableObject. These objects are now easier to view and edit in the Unity Inspector.
Duplicate door bug fix. Doors recently stopped opening when they were clicked. Actually, most doors didn’t open but a few did. I reviewed the pertinent code but couldn’t find a problem. I started a game and right-clicked on a door to open the Inspect Panel, which shows everything in the cell. Nothing appeared to be out of the ordinary, and the door opened when I clicked it. Then I clicked another door. This one didn’t open. I opened the Inspect Panel and found the problem: there were two doors on the cell. It turns out that the recent change to track connections between rooms in both rooms caused most doors to be added twice. The fix was trivial; I just had to exclude door creation on the duplicate connections.
Next week, I’ll further optimize map generation. Possibly, I’ll start coding the procedural history generation, which I’ve been slowly designing over the past month.
I thought I could get away with coupling logic and presentation for actor actions. For example, a melee attack works as follows:
Start the attack animation
When the attacker appears to make contact with the target, determine if the attacker hit and, if so, the damage done; start particle effects and display the damage amount as floating text
When particle effects are almost done, add damage residue (blood) to nearby cells
If the target’s HP <= 0, replace the target with a corpse
These steps are triggered by the action itself, animation callbacks, or event handlers.
Despite being an anti-pattern, this design has worked well. I would have gotten away with it had I not recently decided to display every actor’s actions simultaneously. I, and most players, don’t have the patience to sit and watch every other actor on the screen act, sequentially, before being able to act again. Presenting the player’s action and other actors’ actions at the same time solves this problem. Note that actions are still turn-based; selection and resolution still occur for one actor at a time.
It’s been a challenging change to make. I can’t simply add or remove; I have to untangle. My solution, in short, is to split turns into two phases, one to decide and resolve actions, and another to present all actions. There are complications to work through. For instance, if a melee attack causes enough damage to kill the target, I now have to postpone the event-driven death animation and corpse creation until the presentation phase. Events are used extensively, so there are likely many issues I haven’t encountered yet. So far, movements and melee attacks can now be presented simultaneously.
History generation brainstorming
Map generator work came to a halt when I tried coming up with a contextual and meaningful way to stock the dungeon. It’s disappointing because I’ve had great momentum in this area over the past two months. I’m reconsidering an idea I previously tossed out – dungeon history generation. The basic concept is that a series of randomly generated events would result in a collection of related places, actors, objects, items, and quests that would be used to theme and populate the dungeon. I spent a good chunk of time researching and brainstorming on this topic this week.
Next week will be tight time-wise. I’ll continue working toward full support of simultaneously presenting actor actions and fleshing out the history generation.
Legend is now entering its fourth year of development. It took the first two years to build the foundation. Systems were stood up, then overhauled once, twice, or even three times in some cases. In 2022, rework dropped significantly (evidence that systems were stabilizing and meeting design needs), enabling me to focus on refinement and content in preparation for a public release. The release didn’t happen due to the unplanned Map Generation 2.0 work, but the game is much, much closer to this goal.
With the focus on getting a playable version out to the public, UI/UX received a lot of attention.
Quick equipment switching. Melee weapons, ranged weapons, ranged weapon ammo, and light sources can be quickly changed using slots next to the hotbar.
New inventory screen with separate equipment slots, stats, and inventory items.
Visual indicators for the cell the mouse is hovering over and the default action that will be performed by clicking on the cell.
Improved inspect panel now displays each tile and entity in the cell and actions that can be performed on those.
Status effect icons shown for player and enemies.
Improved Select Class screen.
Stamina and magic bars.
Added a loading screen.
Changed the main font to Merriweather. Not sure if I’ll keep it, but it’s preferable to the low resolution, fixed-width font I was previously using because it’s more compact and looks less retro.
Using the Smart Lighting 2d Unity asset, dynamic lighting, shadows, ambient lighting, and entity light sources were added. In addition to giving dungeons more ambience, the new lighting creates some interesting gameplay. Players should proceed with caution because who knows what could be lurking in the shadows… Lighting example.
One of the trickier features implemented this year was partial wall hiding. This was needed because walls between rooms occupy a single cell. The player shouldn’t be able to see the wall in the other room. Partial wall hiding solves this problem by only drawing the portion of a wall cell that is visible to the player. Partial wall hiding example.
The slowdown in systems development, and maturation of those systems, made it possible to create a lot of new content. Dozens of new enemies, items, and objects were added. Abilities were finally added to the game as well.
At the beginning of November I realized that my map generator was too limited to achieve the game’s vision and that it needed to be replaced. Map Generation 2.0 had four objectives:
New structuring methodology – layout of walls and floors in rooms, corridors, caverns, and other shapes
Sections – map partitioned into separate areas with discrete structures, themes, and content
Data-driven stocking – replace the existing hardcoded dungeon stocking with a data-driven implementation
Node pattern-based stocking – identify the best locations on the map to place specific types of content using node patterns on the map graph
All four objectives were completed. Much of the code from original map generation was still usable, but had to be repackaged. Some code, such as the BSP code, was scrapped. The new generator is much cleaner and, most importantly, is capable of producing the kinds of maps I originally envisioned (with some more fine-tuning).
I acquired some fantastic Unity assets in 2022:
Odin Inspector and Serializer – I can’t recommend this asset enough; it’s a must-have for Unity developers. It can greatly increase your productivity when using the Unity inspector. It’s very easy to learn and start using.
History Inspector – super handy. It lists recently viewed assets and allows you to go to those assets with a single mouse click. I was spending a lot of time going back and forth between assets and finding assets before I got this.
Recently Used Assets – this wasn’t useful because it just tracked assets that changed. History Inspector (see above) is what I was actually looking for.
Behavior Designer – this seems like a great tool, but after I bought it I realized I was trying to solve the wrong problem. Legend’s AI meets present requirements, and doesn’t require the sophistication of behavior trees. But, if more complex AI is required in the future I will reconsider this asset.
I wanted to understand how many hours I was working on Legend and what that time was spent doing. In April, I started tracking my time using Clockify.me. Since Clockify integrates with Trello, the tool I use to track my work, the overhead added by time tracking was negligible.
In 2022, I spent 538 hours working on Legend, averaging 10.3 hours per week.
Over half of this time was spent on enhancements (new features, feature improvements). 25% of my time went to testing and bug fixing. While I didn’t track time in prior years, I suspect the refactoring time was much higher in those years compare to the 8.7% in 2022 because I was doing a lot more rework.
I’m confident that more people than me and my kids will play this game in 2023. There’s some work to do to get a public release out, and it will be far from finished (and will very likely still contain the Oryx stock art rather than original art). That work includes:
Improved Dungeon Stocking
Dungeon stocking using Map Elements is currently done on a per-room basis. I want to add multi-room and multi-level Map Elements to provide more cohesion across levels and the dungeon as a whole. An example is placing a locked door and key in different map locations.
Playing the game doesn’t feel great currently. It’s clunky and unresponsive at times, especially when there are multiple enemies on the screen. A big issue is the way turns are handled. Actor actions are animated sequentially, so the player has to wait for every other visible actor to move before moving again.
Much more content is needed, primarily objects, Map Elements, and map section types, to ensure that maps are varied enough. Some more enemies and items are needed too.
Balancing improved in 2022 but still has a ways to go.
Once these tasks are completed, I’ll distribute the game to a small group of people who are interested in trying out Legend and providing feedback. After incorporating that feedback, I’ll create a Steam page and publish an early access version.
Node pattern-based stocking. This identifies the best locations on the map to place specific types of content using node patterns on the map graph. To my pleasant surprise, this was easy to implement. The difficult pieces – constructing the map graph and identifying various patterns – were already in place. I just need to tie it all together and make it configurable through the Unity inspector. All four of the Map Generation 2.0 objectives are now complete. A stretch goal, story-driven map content placement, is still out there.
Symmetry and alignment in room / corridor generation. A preference for symmetry and alignment is now built into the generation, resulting in maps that look more human-made.
Priority randomizer. This is a new randomizer that randomly selects items from a prioritized list. In practice, the priorities are used as categories rather than a stat-ranking (the latter wouldn’t be random). This randomizer is used to randomly select a list of items in order of priority, forcing preferred options to be selected first.
9-slice sprite support. Unity has 9-slice support built in, but it didn’t apply to my use case. Now 9-slice objects such as rugs and pools can be created in the editor.
Map Generation 2.0 code clean-up. Some of the cell locator classes, which identify specific cells to place content, were combined, reducing the number of classes from seven to three. Two different sets of classes had emerged for specifying Map Element placement criteria. One set of these classes was removed.
Next week’s goals are:
Multi-area Map Elements. This allows Map Elements involving multiple map areas, such as a locked door and key, to be added.
Simultaneous actor animations. Each turn, all actors’ actions should be shown at the same time, instead of sequentially.
At the close of 2022, three of the four Map Generation 2.0 objectives have been completed:
Structuring – layout of walls and floors in rooms, corridors, caverns, and other shapes (done)
Sections – map partitioned into separate areas with discrete structures, themes, and content (done)
Data-driven stocking – replace the existing hardcoded dungeon stocking with a data-driven implementation (done)
Node pattern-based stocking – identify the best locations on the map to place specific types of content using node patterns on the map graph (in progress)
There’s also an aspirational fifth objective, which is to use generated background stories for each map to select, place, and customize content.
I’ve been working on map generation exclusively for the past two months. It’s been fun and challenging but I’m starting to feel burned out on it. I need to complete objective 4 and switch over to something else.
Accomplishments this week:
New Room Type Map Elements: Barracks, Bedchamber, Bone Pile, Corpse
New Objects: Beds (Plain, Dirty, Fancy), Prison Door, Blood Fountain
Data-driven Map Elements. Map Elements, the procedural generation objects used to stock the dungeon, are now data-driven. Previously, a class was created for each Map Element. For the most part, Map Elements define what objects go in a room, and where those objects are placed. So, a separate class was needed for each room type. Now, new room types can be created from the Unity Inspector. With this new capability, I was able to quickly recreate the class-based Map Elements and add some new ones. Odin has been an incredible tool for this.
Expanded object placement capabilities. Objects can now be placed in grids with random element sizes and rows and columns. Objects can now also be placed in clusters, which is useful for objects that typically appear next to each other such as barrels. Parameters have been added to some existing placement patterns for more flexibility. For example, corners can now be offset relative to the edge or the center of the room. Placed objects can now be grouped, with conditions for placing groups (such as minimum room size). Groups can be configured to place all of the objects they contain or a single, randomly selected object. These improvements provide many more ways to populate rooms in the dungeon.
Section-based structure. Map sections can now have distinct structures. For example, catacombs sections have longer corridors and smaller rooms.
Map generation performance improvement. The recent additions and changes to map generation really slowed it down. A quick and impactful fix was adding some caching. There’s more work to be done here, but the current performance is tolerable again.
Next week, I’ll start on the node pattern-based dungeon stocking. I believe the design work is done; I’ve cataloged many patterns using the graphs generated during testing. Some pattern recognition already exists, as it was implemented before I had the big picture. For instance, sequences of rooms and required rooms are identified during generation.
Map generation robustness was this week’s theme. Code was cleaned up, errors were eliminated, and improvements were made throughout the map generation process.
Next week, I’ll move the Map Elements, which are the objects that stock the dungeon, to a fully data-driven implementation. My goal is to finish the year with a fully working version of Map Generation 2.0, which includes separate map sections, new map structuring, and new map content populating.
Map generation refinements. Map sections now connect to each other (most of the time; smarter connecting corridors are still needed). The starting section locations are better spread out across the map. This helps balance the section sizes, but doesn’t eliminate a frequent issue I’m seeing in which a particular section grows disproportionately and blocks other sections from growing. The solution I’m considering is to check the size of each section as the map is being generated and dynamically adjust the growth of sections that are too large or small relative to the other sections.
Catacombs and Crypt themes. Map sections can now be themed as catacombs or crypts, in addition to the existing bastion and cavern themes. It’s just a tile swap at the moment, but future updates will alter structure and content.
Refactoring. Some enums have been converted to classes that inherit from ScriptableObject so that they can be referenced in the Unity inspector. MapElements, which are responsible for stocking the dungeon, are also in the process of being converted to ScriptableObjects. This will accelerate creating new MapElements, which will soon be needed because the upcoming section-based stocking requires a wide variety of room types.
Started using Odin Inspector. On multiple occasions I’ve run into the limitations of the out-of-the-box functionality of the Unity inspector. While it is possible to customize the inspector, a relatively high level of effort is required to do so (coding). I originally heard about Odin from a Jason Weimann video. In the video, Jason said that if you can only get one Unity asset, make it Odin. That set my expectations pretty high, but so far Odin is living up to those expectations. It’s extremely easy to use and is very well documented. In a nutshell, I’m using it to make the Unity inspector more useful, refining the controls and presentation.
I’m on vacation for the rest of December and looking forward to making huge progress with all that time. Map generation refinement will be the main focus. If time allows I will also tackle speeding up game turns by enabling all actors to move at the same time.
I was on vacation most of the week and took a break from my laptop. Today was productive though – my goals for the week were completed across a couple of solid time blocks.
Map sections. Dungeon levels can now be partitioned into separate sections. Each section can be populated and styled from a different theme, such as a bastion, cavern, or tomb.
Map loops. Looping has been added. It’s pretty arbitrary at the moment; all rooms that share a wall but aren’t connected are identified, and doors are added in a random number of instances.
Map graph visualizer improvements. The starting node is now shown at the top of the graph, the node number is only incremented for rooms (not corridors), and secret and blocked doors are indicated by different edge styles.
Next week, I’ll refine map sections and loops. Map sections don’t connect to each other because they’re generated independently. Otherwise, they’re working well. Loop generation needs greater control. Rather than adding a random number of loops in random locations, I want to control the size, position, and quantity based on the generated map structure. I’ll also add another theme (there are currently only two – bastion and cavern) to better demonstrate the new map sections capability.