Adding a new ability to summon a swarm of giant rats nearly derailed my plans for this week (and potentially the next few months) when I tried out the ability and, to my surprise, the rats started attacking each other. I realized that there was no way of indicating that the rats were both allies of the player and each other; I needed to be able to specify relationships between actors. Factions were the first thing that came to my mind. They’re overkill for this single use case, but I’ve always intended to have factions in the game and reasoned that I could use them to solve the rat problem. As I’ve done with other aspects of the game design, I start with a flurry of research and brainstorming. I read up on factions in game design and roguelikes specifically, including a FAQ Friday on the topic, and wrote down all of the ideas and details I could think of. And, as I always do in this situation, I got lost in the subject and created a conceptual design that far exceeded what I likely needed and what I had time to build. That’s not a bad thing, as long as you can quickly pull yourself back into reality and reduce a grandiose design to something practical. I was able to do that in this case. My solution was to add an actor attribute indicating whether the actor is friendly, neutral, or hostile to the player. It’s a compromise, and there’s a good chance it will be thrown away in the future when I add a true faction system, but it’s the right solution right now, because it was extremely simple to implement and I don’t have a clear vision for how factions will work.
New Ability: Summon Giant Rat Swarm. Summons a swarm of giant rats to fight on the player’s behalf.
New Item: Fire Sword. Sets enemies and objects it strikes on fire. It’s overpowered at the moment, but I haven’t decided how to nerf it.
Chests and corpse item access. The items contained in chests and corpses can now be viewed and taken from the Inspect Panel. A “Take All” button lets all items in the container be taken at once.
Inventory Panel is automatically displayed when inspecting a container. When viewing a container, such as a chest, the player inventory is now displayed automatically. This allows items to be dragged from the container into inventory and vice versa.
Weighted observation selection. One of the configurable AI components is the ObservationDecider. This component examines the actor’s observations since the last turn and chooses an observation to react to. There are two implementations of this component, one that selects the first observation involving a tracked actor, and another that randomly selects an observation (used by the Fear status effect). These implementations are primitive. They don’t work well when there’s more than one suitable observation to respond to. To address this, I modified the ObservationDeciders to weight each observation and select the highest weighted observation.
New automated tests: Eggs. I’ve repeatedly broken the functionality of eggs, so this was a great automated test to add. The unique behavior of eggs (turning into a cracked egg upon detecting the player, waiting 10 turns to hatch, hatching a creature at the end of waiting) tests a number of systems.
Installed History Inspector from the Unity Asset store. My project asset list has gotten enormous. ScriptableObjects are used extensively, and many of these ScriptableObjects reference other ScriptableObjects. I often have to follow a trail of ScriptableObject references to fully understand how something is working, or go back to the previous asset I was viewing. The time spent scrolling through the asset tree, and the effect of navigating the structure on cognitive load, is impacting my productivity. I looked for a built-in Inspector history viewer in Unity but couldn’t find one. So, I turned to the Unity Asset Store and found the History Inspector asset. It’s already become an indispensable tool that’s saving me a lot of time.
Next week, I’m play-testing combat and overall level difficulty. I’ll adjust and fix based on what I find. The overarching goal for the year remains getting to a version of the game that I can distribute to others.
Over the course of the week dev time shrunk as my work days got longer. Great progress was made on automated testing, and it’s already helping to find bugs. The biggest benefit I’ve gotten from automated testing has been with “whack-a-mole” issues, where I fix a bug in one spot and create a new bug in another spot.
Automated tests added for Abilities. The eight Abilities that have been created so far now have automated tests. Because Abilities are tied into many systems, they are a high risk area for bugs. I no longer have to test each Ability individually. This will be a major time saver, especially after the 100+ planned Abilities are all in the game.
Non-player actors can now use Abilities. Abilities are no longer limited to player classes. Any number of Abilities can be assigned to any actor. I still need to convert some enemy powers, such as transforming into a Vampire Bat, to proper Abilities, and incorporate Abilities into AI.
Next week, automated testing continues with Items. Also, I want to close out more bugs than I was able to this week. Finally, I want to put at least one new thing into the game (an item, enemy, or object).
The two days I had off from work this week doubled my available dev time. Note those weren’t full days; they were just two extra 3-4 hour blocks of time similar to weekend work. A wide assortment of to-do’s got done, including minor tweaks and enhancements, bug fixes, refactoring, and some miscellaneous items. The general theme was getting the game to a stable, bug-free experience.
Turn Undead. Classic cleric ability that causes nearby undead to flee.
Heal. Heals the caster.
Self-Targeted Abilities. Abilities, such as Heal, can now be configuredto target the user by default.
Entity collection-specific Abilities. Abilities can now target specific entity or entity collection types. This capability was used to implement the Turn Undead Ability (it’s basically the Fear status effect, limited to undead actors).
Interesting bug fixes
Some enemies were fleeing from the player unexpectedly. Upon further inspection I determined that this only occurred when multiple enemies were present. Even stranger, when a new enemy spawned, an enemy that previously attacked the player started fleeing. It turned out that enemies were reacting to the movement events of other enemies. This shouldn’t happen because enemy AI contains an actor tracker component that limits the actors that the enemy will react to (typically, enemies only react to player actions). The actor tracker wasn’t being used by the enemy’s movement generator.
I wanted to display bones on some of the spikes so I configured the map generator to randomly add a bone pile actor on top of some of the spikes. When I tested this, ghosts appeared where bones should have been. I knew instantly what had happened. The bones are configured to spawn ghosts when they are destroyed. The spike damage destroyed the bones in the first game turn, causing the ghosts to appear. This was an easy fix. I changed the effect trigger for spikes from Touch, which is applied each turn, to DidEnterCell, which is only applied when an actor enters a new cell.
Other bug fixes
Tooltips remain on screen when the Inventory and Ability panels are closed.
Exception thrown when displaying the Abilities panel.
The default player action for Rugs and Summoning Circles was attack.
Cracked Eggs stopped hatching.
Eggs weren’t changing into Cracked Eggs.
Exception thrown when removing the Ring of Invisibility.
Heal Ability wasn’t working.
Closing the Select Cell prompt ended the player’s turn.
Poisonous gas, once spawned, would spread throughout the entire dungeon instead of a limited area.
Run Ability wasn’t working.
Exception thrown when Fires burned out.
Minor tweaks and enhancements
Eggs now have 1 HP so they are killed in one hit.
Destroyed eggs now leave behind the corpse of the creature inside the egg.
Added descriptions for Cauldrons and Crystals.
Grass is no longer damaged by arrows or other piercing weapons.
Bones are now randomly added to some spike cells.
When the game crashes before play starts, unusable saved game files are created. These are now automatically removed.
The default action type for an Open Door was changed from Close to Move. Having Open Doors close when clicked was annoying because 99% of the time the desired action is to move into the doorway rather than close the door. Closing a door can still be done through the Inspect Panel.
Refactored / cleaned up several classes, including the Cell class, which had gotten up to 1,000 lines. There was a lot of code that didn’t belong in Cell – handling projectiles landing on the Cell and handling actors taking items in the Cell, for example. I pulled this code into new static handler classes. This is a poor long-term solution, but it’s an effective way of quickly extracting code that belongs elsewhere.
New automated tests: Ring of Invisibility. I haven’t added any new automated tests in a while. The Ring of Invisibility provides a good set of tests not just for the ring itself but for the Invisibility status effect as well. I intend to write automated tests for all Abilities.
Improved ergonomics (for me). I’ve been having some pain in my right arm lately. I’m not an ergonomics expert, but after identifying the positions that caused my arm to hurt, I concluded that I needed to reduce the rotation of both my forearm and wrist. My mouse was too far off to the side, and I was holding it with a pronated grip (the normal mouse grip). I bought a smaller keyboard (the Logitech MX Keys Mini) to move my mouse closer to me. I bought a vertical mouse (the Logitech MX Vertical Advanced Ergonomic Mouse) to maintain a more natural, neutral grip when using my mouse. I’m very happy with the result. In the first week of use, the pain has mostly subsided.
Next week, and for the near future, stability and bug removal will remain the focus.
Feeling a bit overwhelmed this week… I had to do some major rework (again) instead of working on new features. I find myself battling with Unity again, specifically where to put things – prefabs, components/MonoBehaviours, ScriptableObjects, plain vanilla classes. When I first started using Unity a couple of years ago, I tended to write code for everything, because that’s what I was familiar with. As I gained familiarity with Unity, I pushed myself to embrace it and fully leverage the editor capabilities. However, that produced a lot of constraints, and now I’m back to relying on code more (though not as much as in the beginning). Anyway, a lot happened this week due having a couple of days off:
Finished the save system. I mentioned last week on reddit that I was struggling to determine the best way to code the save system in Unity. I ended up pulling all state data out of MonoBehaviours and into plain classes for each object type. Nested objects are supported as well (serialized attributes have to be explicitly declared). All objects that need to be saved are nested in the Map class, so saving the game is as simple as serializing this class. Loading is a tad more complicated because, after deserialization, game objects have to be instantiated.
A byproduct of the save system was changing actors and items to inherit from the same base class. There’s a lot of commonality between actors and items – they’re in-game objects, they can be damaged, have status effects, etc. I was handling this through composition. Attributes were spread across multiple MonoBehaviours. Because Monobehaviours can’t be serialized, capture/restore state code needed to be written for each component. It made more sense to move all state attributes into a single serializable class. This was a case where inheritance made more sense than composition.
Another byproduct was pulling health into the base class from a MonoBehaviour. Previously, the Damageable component made an actor or item damageable. This component tracked health. However, each actor and item ended up needing this component so it didn’t need to be optional. Also, this was done to address the issue in the previous bullet. This change broke a lot more than I expected, but fortunately the unit tests helped pinpoint the issues quickly.
Test map generator. The map generator can now generate a map with designated layout, actors, and items. This will allow a greater degree of automated testing.
New map generation capabilities. Map generation now fully supports different sets of parameters. These parameter sets can be statically predefined in the Unity editor as ScriptableObjects, or dynamically generated in the initial steps of the map generator, enabling additional layers of procedural generation.
Next week, the goal is to finish the load game and select class screens, and start on the hotbar time permitting.
This week was mostly consumed by writing unit tests. The goal of completing the detailed feature list for Release 3 was missed.
Took a deep dive into unit testing. 20 months into development of Legend, the first unit test was written. This week I wrote about 30 unit tests, with each test performing anywhere from 1 to 10 assertions. The unit test setup fully loads a level first. This is an unconventional way to do unit testing, but is preferable in my case because there are so many interdependencies and most of the bugs occur due to some sequence of prior events (and therefore wouldn’t manifest when testing a class in isolation). The process of creating the unit tests led to some refactoring and a few new methods that will likely only be used for testing purposes, for example getting a count on a private collection. I also discovered some bugs right out of the gate.
Overhauled the message log. One of the planned Release 3 features is a better message log, both from an end-user and a code quality standpoint. Instead of having explicit message log writes throughout the code, game events are raised, and an event handler determines 1) is the player aware of the event and 2) does the player care about the event. When the answer to both of these questions is yes, an event is generated to log a message. The message log UI controller listens for these events and displays new messages in the log. I also wrote a message formatter to determine the appropriate subject and object to display in the message (depending on whether the message refers to the player or another actor or object). For some messages, the message formatter also determines the appropriate verb, for example “am” for first-person, “are” for second-person, and “is” for third-person.
Kept using Rider. I’m totally sold on Rider at this point. It took some getting used to, but I’m getting a ton of value out of it now that I’ve gained some proficiency with it. The code recommendations have helped tidy up my code. The refactoring, debugging, testing capabilities have greatly improved my productivity.
I’m 75% through writing the initial set of unit tests. Next week, I’ll finish these and get back to finalizing the detailed feature list for Release 3.
Release 2 is done! This is a non-public release that represents the second major development milestone. I shared a mind map summarizing the features earlier this week on Reddit. Whereas Release 1 got the core mechanics working, Release 2 significantly expands mechanics and content, resulting in fully playable levels (though not well-balanced and still very rough around the edges).
Next week, I’ll complete the detailed feature list for Release 3. I’ll also start adding automated testing. I haven’t used unit tests or any other form of automated testing yet because I wanted to build out the basic game quickly, and the code has been highly volatile. I’m at the point where testing is very time-consuming and I’m sometimes spending more time finding and fixing bugs than writing new code, especially when refactoring.