It was a highly productive week. 100% of my time went into features and fixes that are required for the initial release (not always the case).
Status effect icons. Enemy status effect icons now appear above the enemy. Player status effects are now represented as icons as well, located next to the health bar.
Loading screen. New levels now display a loading screen, complete with a progress bar and text describing what is currently happening. At first, the progress bar was based on AsyncOperation.progress, which tracks the progress of an asynchronous scene load in Unity. The bar quickly got to 90% and then hung for around ten seconds. This delay was, of course, the procedural generation. Luckily, when I made the map generation visualizer last year, I had to reduce map generation into hierarchical, independent units of work – maps are generated by a series of processors, processors are composed of stages, stages are composed of substages, and substages are composed of tasks. The tasks are pre-generated and placed in a queue. This enables the map generation visualizer to display the result of one task at a time and interactively fast forward through substages, stages, and processors. It also makes it easy to calculate the progress percentage. I only had to make a slight modification to make it work with the loading screen progress bar, which was to wrap it in a coroutine and have it yield after each substage so that the UI could update the status.
Cell selection improvements. The area of effect is now highlighted and the cell indicator (not just the mouse cursor) is now shown.
15 sound effectsadded.
Next week I’ll take a break from new features to shorten the bug list. Saving and loading is broken again.
It was a solid week with lots of smaller, but important, improvements and fixes. I added a few new map features and need to make sure that I do this every week, because map content variety is one of the key aims of the game.
Fibonacci weighted randomization. This is an extension of the existing weighted randomizer that adds the capability to automatically set weights based on fibonacci numbers. I created this because I kept needing a random number generator where each outcome was less common than the outcome before it, and the number of possible outcomes was variable. Fibonacci numbers naturally worked for this. I start at the second 1, so the outcome weights are 1, 2, 3, 5, 8, 13 and so on, and the weights are assigned in reverse order so that the most common outcome has the largest weight. I use this primarily for rarity-based random selection.
New sound effects. I added sounds for hitting webs, stalagmites, and bones and an ambient looping sound for braziers. The physical material-based sound effect configuration, in which the sound played is based on a combination of the weapon used and the physical material of the target, is becoming unwieldy. I will probably remove weapon-specific sound configuration and create a global configuration based on combination of weapon damage type (slashing, chopping, bludgeoning, piercing), weapon physical material, and target physical material.
New Map Elements. Added statues, grass, pots, and rugs.
Removed redundant Map Elements. Some common patterns emerged as I continued to create new Map Elements and it became evident that I could combine some of the elements. For example, there were dedicated elements to place a single actor such as a fountain or a shrine. The only difference between the elements was the actor. So, I removed these variations and created a single Actor Map Element that takes an actor type as a parameter.
Rubble tile variations. I got really tired of seeing the same rubble tile everywhere. I bought the Oryx 16-bit Sci-Fi sprite set primarily because it has two more rubble variations (that’s how much I like making my own art). The variation helps, but some of the rocks are too big. I’ll live with it because all the artwork is going to be replaced anyway.
Player corpse. When the player dies, there’s no visible indicator beyond the “Game Over” message. So, I made a corpse sprite (for the Knight; I still need to do the other player classes). As with the enemy corpse sprites I made, I used a simple process of rotating the sprite 90 or 180 degrees, slightly shearing it, and dropping the blood sprites above or below. The results are ugly but serviceable. Now when the player dies, a corpse will be displayed.
Animated Bones improvements. Animated Bones, which are piles of bones that come to life when the player approaches them, were improved. Now they animate when the player is diagonally adjacent to them and when the player attacks them from a distance with a ranged weapon.
Equipment hotbar design. Over the past few weeks, I’ve been thinking a lot about how melee weapon switching and ranged weapons should work from a UI standpoint. This week I finally figured out how this should be designed. I still need to implement the design to validate it.
Added unit tests for select utility classes. I practiced some test-driven development this week, which is a rarity. I found some issues with the Fibonacci randomizer right out of the gate, so the new unit tests already paid for their effort.
Bug fixes. Fixed ~10 issues that directly affected gameplay, which felt very satisfying.
Next week, I expect to mostly work on UI, specifically the melee and ranged weapon hotbars.
Legend has been in development for 2.3 years. It’s hard to believe that that much time has passed since I started working on the game. I don’t know if anyone else has experienced this, but how I felt at the two-year mark was in stark contrast to my feelings at the one-year mark. After the first year of development, I was thrilled with how much I had accomplished and excited for the future. At two years, panic set in. How could I possibly finish at the rate I was going? Was I wasting my time? Was this game even any good?
Ultimately, the mid-gamedev crisis was a good thing. This was my brain telling me to reassess and correct course. That’s exactly what I did in the latter part of the year. The reality was that, at the rate Legend was progressing, it would need at least several more years to release. I don’t want to wait that long (I have more games to do!). I cut a large chunk of planned features while preserving the original vision. I forced myself to make decisions. I have a tendency to postpone decisions as long as possible to avoid limiting possibilities. It’s been a sure-fire way of keeping completion in the distant future.
Development thus far has consisted primarily of building the game system framework, and rebuilding many facets of the framework as my Unity knowledge increased and my ideas crystalized. Heading into 2022, the framework is done and development shifts to using the framework to flesh out the actual game.
What I said I was going to do in 2021:
Replacing the stock art
I’m still using Oryx. I still have some uncertainty about the exact 2D perspective that will be used. I also feel that the art doesn’t need to be replaced until the game is ready for a public release.
A handful of new enemies, items, and objects were added. Every piece of existing content was reworked in some way, for example extracting a parent class for actors and items, and moving from room-based to element-based map generation.
At the beginning of 2021, I never would have expected to accomplish as much as I did in this area. I considered polish something you do at the end of development. That largely is the case, but I’m using “polish” loosely here to refer to any visual and audio effects beyond the bare minimum, and refinement of any sort, such as fine tuning procedural generation and balancing combat. The game looked bad and felt dull whenever I did testing. Even though I knew I’d improve the look and feel before launch, I was still getting discouraged. For my own psychological benefit more than anything else, I added some game juice, including:
This did the trick; I could finally envision other people playing the game.
I posted a weekly dev update on the website and Sharing Saturday on r/roguelikedev. I posted the link to each Sharing Saturday update on Twitter. I occasionally posted videos on Youtube. I have tiny followings on those channels. I continued to spend a minimal amount of time on community in favor of game development.
The new map generator, started in 2020, was finished in January. I built some dev tools for procedural generation analysis, tuning, and troubleshooting: an interactive map generation visualizer and a map graph visualizer. These proved to be very handy.
I made another major change to map generation later in the year with the addition of Map Elements. These are the basic building blocks for populating a map with content after the map structure’s been created. They’re hierarchical and modular, enabling rooms to be constructed from layers of interchangeable components and reuse common components. For example, many room types can include an Abandoned Map Element that adds cobwebs and debris to give the appearance that the room is abandoned.
Maps became both more varied and more playable. Map configuration parameters, which dictate a map’s structure (number of rooms, room sizes, room distances, room themes, etc.) are now randomized. This additional layer of procedural generation increased the variety of maps while maintaining consistency within a map. Map configuration parameter ranges were fine-tuned to avoid problematic maps. Room type probability changed from a linear distribution to a weighted distribution based on rarity to give maps a more logical composition of rooms.
Significant UI work was done. New screens were added, a hotbar was added, and the main game UI was refined.
AI was expanded. Now, actors can have their own AI controllers and behave differently than other actors. AI controllers can be reused across multiple actor types. Actors can now track any number of other actors, enabling them to do much more than charging the player. They can attack, defend, and interact in other ways with tracked actors as dictated by their AI controller. Actors now gain awareness of events based on their vision and hearing ranges and act based on the type of event occurring. Each actor has its own inventory and the ability to pick up items and use them. When an actor dies, other actors can acquire the items it was carrying.
In light of my time constraints and the mountain of work remaining, I made a deliberate effort throughout the year to increase my productivity. The ways in which I did this include:
Reducing and simplifying code.
Moving logic from code to configuration (physical material interactions, game events).
Adding unit testing.
Adding an in-game console for spawning objects to accelerate playtesting.
Adding more granular logging so that I have more information to troubleshoot.
Consolidating test settings into a single design-time editable object.
Finally, code rework occurred throughout the year. Most of it was necessary to keep the code maintainable, but I also know that I am overly eager to rework code and sometimes create more problems for myself than I solve. As the year progressed, the rework did slow down. There are two reasons for this: 1) the framework reached maturity and 2) I became more selective with when I reworked code. Before I make changes or add new features, I now consider what compromises I can make and what I can do within the existing framework to avoid rework.
This year’s goals are essentially the same as last year’s. However, the priorities have changed. An early access release is now the primary goal. Original art, a requirement for the release, is also a top priority. Effort will be concentrated on what is absolutely necessary for public release, including tightening the game loop, balancing, and clearing the bug list.
The first week of the year is always busy work-wise and this year was no exception. Hence, progress was limited.
Figuring out lighting. I started to get the hang of the Smart Lighting 2D Unity asset. I set up a light source from the player and tilemap lighting so that light collides with walls.
Item rarity. Items already had a rarity attribute, but it wasn’t used. Item rarity values have been populated and rarity is now considered when placing random items.
Next week, I’ll keep working on the lighting and some miscellaneous gameplay improvements/features like reducing the player input required to shoot arrows and making the weapon hotbar functional.
It was a great finish to the year. The main gameplay loop is coming together, levels are more balanced, changes have gotten easier.
Dungeon level-based equipment drops. Each dungeon level now contains equipment appropriate to the player’s power curve. Prior to this change, dropped equipment was random. A player on level one could find the best armor in the game, and a player on level twenty could find a copy of the short sword that they started with. Now the player will typically get equipment on the power curve, occasionally equipment one tier above or below the curve, and rarely equipment two tiers above the curve.
Map element placement overhaul. Previously, map elements were added by calculating the number of elements to place based on the number of map nodes and then randomly selecting a node and an element for each iteration. This was quick to implement but it unevenly populated the map with elements. I changed this to iterating over each map node to guarantee that it received one and only one map element. Additionally, I moved enemy placement responsibility from the map generator to the map elements themselves because the available enemies varies based on the map element. I also added a way to control the relative frequency of map elements, so that some occur more or less often that others.
Map element hierarchy. After creating many map elements it became evident that there’s a natural hierarchy – some elements define an entire map node while other elements perform a subordinate function such as decoration. I organized the folder structure and added inheritance into the classes. This also helped identify which map elements should be used directly by the map generator and which ones should be used by parent map elements.
Most destructible objects are now destroyed with one hit. Walking into a room full of barrels and crates and having to hit each one a few times to destroy it was realistic but tedious. I made these and similar objects destructible in one hit. Some objects that are not routinely destroyed and should require some effort, such as wooden doors, still take multiple hits.
Combat modifiers for standing on corpses. Standing on a corpse now causes a slight combat disadvantage, the thinking being that an actor can’t maneuver as well. More importantly, it adds another tactical option for combat.
Enabled Unity’s Universal Render Pipeline. I honestly didn’t know about this (embarrassed to say that with two years of Unity experience under my belt now) and stumbled upon it while researching lighting in Unity. To use some of the lighting features, the URP was required. It wasn’t too difficult to enable. I had some issues with materials in a couple of third party assets. I’m still fuzzy on the URP but it seems like the right choice given that I have an aspirational goal of releasing on multiple platforms and the game is 2D. Ironically, I ended up using a lighting asset from the Unity Asset Store rather than the built-in lighting because the latter didn’t have all the functionality I needed.
Added lighting, then removed it (temporarily). I set up basic lighting (darkened dungeon, player visibility, torch light) using Unity out-of-the-box lighting and it looked pretty good, but I ran into some limitations. I bought a Unity asset, Smart Lighting 2D, to get more functionality. I got it partially working, but I’m still learning how to use it. I had to put it on hold because I had some more important things to do this week. I’ll pick it back up in January.
Fixed some bugs that had been hiding for a long time. I discovered a few bugs that causing problems I didn’t even know the game had! For example, one of the methods for fetching cells in a pattern was returning all of the cells in a map node rather than the room inside the map node. This explained the occasional mysterious placement of objects outside of a room. I also realized that enemy AI was executing some of the code for handling player clicks. This was ultimately benign, but still totally wrong so I corrected it.
Increased the frequency of enemies and items for the standard room. Most rooms use a generic map element that sometimes adds enemies and items. Sometimes wasn’t enough; there were too many empty rooms, and maps often felt dull. Simply by increasing the frequency of enemies and random items, the game was funner to play.
Next week, I’m making some more minor tweaks to gameplay and map generation with the end goal of making level one challenging and fun.
My main goal for the week was to create the Release 4 feature list. As I mentioned last week, Release 4 is focused on refining gameplay/user experience through small adjustments and improvements. To create the feature list, I started a new game and jotted down ideas as I played. It was helpful to play the game with the mindset of looking for small changes; it enabled me to find problems and opportunities I hadn’t noticed before. This process also revealed that creating the complete Release 4 feature list is impossible. In previous releases, creating the feature list up front was doable because the goals were to create specific systems, mechanics, and content. Release 4 is more iterative and uncertain; I’m going to play, identify adjustments, implement adjustments, test, and repeat.
Game juice for movement. I’m trying to make moving look and feel better using various animation techniques (a great resource is Jeremiah Reid’s excellent talk on game juice given at this year’s Roguelike Celebration). I want actors to have a slight bounce as they move from cell to cell to indicate that movement is cell-based rather than pixel-based and to make the movement look more interesting. I spent far too much time on this and have little to show for it. I can’t seem to get the animation right – it’s either too bouncy, too slow, or too choppy. This experience highlights a drawback of solo development – the potential inefficiency and subpar results when working outside of one’s areas of expertise. I’m concerned about how much time I will sink into this. Part of the challenge is not having a clear picture of what the animation should look like – I’m adjusting parameters in the code, recompiling, and rerunning the game to see if I like the result. Next week I’m going to either make a custom GUI or move the parameters into a Unity game object so that I can edit the parameters at runtime and iterate faster. The latter isn’t currently possible because the parameters are in actor type data, which is loaded and cached up front.
Game juice for combat. There’s already some game juice for combat in the way of particle effects, floating damage numbers, and sound effects. I made some small adjustments this week to the floating text. When I originally added this, I had the text rise up and then drop back down, resembling a bounce. I don’t recall why I did it this way; I think I just liked how it looked at the time. But now, I feel that this is unintuitive and unconventional. Why would text indicating damage taken float away and then return to the actor? I changed the animation so that the text now just floats and fades away. I also changed the color from white to red to make it clearer that the number represents damage. I also tried adding a black outline to the text to make it stand out more. But, it didn’t really work because the text uses an NES-style font that looks weird with an outline.
Map generation tuning. The map generator randomizes generation parameters to produce more varied maps. Sometimes the results don’t pass muster – rooms are too large or small, room density is too high or low, etc. I played around with the minimum and maximum parameter values to more consistently generate higher quality maps. The key takeaways from this exercise were to increase the minimum room size (a level packed with 3×3 rooms isn’t fun) and to more vary room density (the original parameters caused rooms to be tightly packed into the map, virtually eliminating corridors).
Random name generation. A remaining Release 3 to-do was to flesh out the player info window. This window displays the player sprite, name, class, and stats. The player name isn’t currently set anywhere, or stored for that matter. While implementing the name attribute, I visited the Unity asset store and looked at the available random name generators. None of them jumped out at me. I ended up downloading a free asset that was easy to integrate. The asset is essentially a single script that has a hard-coded list of names. It will work for now, but I’ll need to replace it with a more robust random name generator before the game is publicly released. The good news is that the interface now exists, so swapping out another random name generator should be easy.
Next week, I’ll continue to work off of the list of ideas I produced this week, with the main themes being game juice and map generation.
I put limited time into game dev this week, and a large portion of the time went into some difficult code design/architecture thinking rather than coding itself.
New Map Elements: Library, Alchemy Chamber, Armory. These are new room types that contain objects arranged in simple patterns (tables, bookcases, weapon racks) and applicable random assortments of items. I want to improve the variety of the object placement patterns. I sketched out some patterns for the library (below) to visualize the end result and work backwards to develop the generation logic. It got me wondering about the feasibility of an alternative approach: training a machine learning algorithm to generate patterns from a set of example patterns. I’m going to research this next week.
AI 2.0Design. Last week’s addition of AI states and actor responses to game events has necessitated more rework than anticipated. The original AI was player-centric; other actors only cared about what the player was doing. The end goal was always to allow actors to respond to a variety of events, but I limited the initial implementation to player events for simplicity. I’m now modifying the design so that actors can potentially act on any event. This requires some optimization as well because, for each event, a check needs to be performed to determine if the actor notices the event. It’s further complicated by the fact that each event may be detected by sight or hearing.
Next week, I’m continuing working on the AI 2.0 coding.
AI State and Game Event Detection. One of the planned Release 3 features was to display icons next to enemies based on whether the enemies had seen the player. In addition to displaying the icons, I needed to fix some long-standing AI issues that cause enemies to react too soon, or not soon enough, when seeing the player for the first time. I also needed a way for enemies to respond to events other than seeing the player, such as a door opening, and to respond to events that are heard but not seen. To handle all of this, I implemented a simple finite state machine to manage enemy state. I added listeners to enemies that receive all game events, determine whether they are able to perceive the event, and trigger state transitions based on perceivable events. The solution is working well with the first set of test cases; I now need to apply it to all game events.
New Decor Map Elements: Braziers, Bookcases, Tables. These Map Elements place these objects in logical patterns (for example, in corners, in a grid, in a rectangle, in the center) in a specified area, typically a room. They are used by different room types such as Libraries and Shrines.
Procedural generation improvements. I changed the basis for some enemy/item quantities from a percentage chance per cell to a random number within a range. This prevents situations in which a large room has too many enemies or items. I reduced the number of combat encounters in a level (too much combat nade the game overly difficult and tedious to play).
Finished implementing treasure chests. Chests contain random or specific items now and chest contents get transferred to inventory. It wasn’t as straightforward as it sounds. Some odd bugs were introduced (and subsequently fixed). For example, the player started getting two copies of each item in the chest. I spent a few hours trying to figure out what was happening. It turned out that when the chest changed from an open chest to an empty chest in the process of taking the contents of the chest, the chest was being destroyed and dropping its contents (routine behavior).
Next week, I’ll finish the AI State / Game Event feature. I’ll add a few more Map Elements as well (my goal is to add at least a few each week).
Thanks to a very rainy weekend and a day off in the middle of the week, I got a lot done this week. The focus remained largely on procedural map content generation.
Added a debugging console. A key press displays a prompt. Typing the name of an entity (actor/object/item) creates an instance of the entity in a cell near the player. It’s a time saver because I no longer have to look for an instance of the entity on the map. Why didn’t I add this sooner?! I will extend it in the future as needed.
New actor action, Transform, that transforms an actor into another actor. This is used to animate a pile of bones as a skeleton and replace a treasure chest with a mimic.
New object, Animated Bones, that changes into a skeleton when the player comes near it.
New Map Elements: Animated Bones Area, Debris Area, Storage Cluster. Animated Bones Area places a few piles of animated bones in the specified area. Debris Area adds rubble and other debris to an area. Storage Cluster places storage objects (barrels, crates, etc.) in clusters for more natural-looking placement.
Added more Treasure Area Map Element variations. Treasure Areas can be actively used or inactive (abandoned / forgotten), and looted or not looted. The combinations of these parameters produce different effects. For example, an abandoned and looted area will have only empty, open chests, with cobwebs and debris. Conversely, an active, not looted area will have all of its treasure items and chests intact and no cobwebs or debris.
New enemies (partially): mimic, gelatinous cube, cultist, bandit, witch. The Unity prefabs, animations, and definition data were created. I still need to add different behaviors for each. I also need to incorporate some of them into Map Elements. As of now, the mimic can appear as a chest in a treasure room, and some shrine rooms may contain cultists.
Combat modifier visibility. The cell inspect panel now displays combat modifiers applied by the cell. For example, a cell with rubble, or a cell that is an open door, will modify the chance to hit and/or chance to evade modifiers for any actor occupying the cell.
Procedural generation tuning. I fine-tuned some parameters such as probability of caves, numbers of objects and enemies, and non-combat vs combat Map Elements. The final values used for each map are still randomly generated, but the ranges of possible values were adjusted. There’s still a lot of work to do in this area. Some rooms are way too big, or filled with way too many enemies. Some maps have too many rooms, and some don’t have enough.
Bug fixes, mostly around map generation. In the course of troubleshooting an AI bug, I discovered that actor tracking was extremely inefficient. Each turn, each actor was checking to see if it could see every other actor. This was occurring because the visibility check was executing before the check for whether the actor was tracking the other actor.
Next week, I need to build expand the utility class responsible for getting cells in different patterns (e.g. room corners, rows/columns, rectangles) to accelerate the creation of new Map Elements that place objects in patterns (e.g. Library, Armory). I also plan on building out the behaviors for the new actors added this week.
More proc gen work this week involving the new Map Elements framework. I’m starting to see the difference this technique is making. Dungeons feel slightly less random and more coherent.
Moved existing map features to Map Elements. Hidden doors, blocking doors, shrines, bone pile, storage room, chest, and treasure room map elements were added. These map features were already being generated, but generating them within the Map Elements framework provides a lot more flexibility and control.
Object variations. Different types of shrines, bone piles, and blood splatters were added to increase visual variety.
A lot of bug fixes. Resolved some long-standing map generation bugs (objects placed on top of each other, placing a difficult encounter in the starting room, etc.). I also fixed an odd pathfinding issue where some player paths weren’t generating. The root cause was simply that the player’s pathfinding map was no longer getting initialized. This wasn’t obvious at first because most paths worked; as the player moved around, the pathfinding map got updated and corrected itself.
Next week, I’ll add more Map Elements, primarily combat-based. I also need to revisit the roadmap. The next internal release (Release 3) is scheduled for the end of November and there’s still a lot to do. For the past two months I’ve deviated from the roadmap to focus on some core aspects, such as combat and map generation, that I decided were more important than the objectives that were on the Release 3 list.