Weekly Update – July 15, 2022

The bugs keep crawling out. I’m fixing them and finding new ones at about the same pace. On the bright side, they’re getting easier to find. I usually know where to go or at least where to start now. I don’t know if that’s the result of the code improving or me starting to recognize where the common problem areas are. Bugs are also getting easier to fix. Often I simply have to change a configuration value or modify a single line of code rather than having to do major rework or implement a system I didn’t anticipate. However, I’m spending 25% of my dev time on bugs and that is too high. To lower this percentage, I’m allocating more time for automated test development. My strategy is to incrementally add tests from a master list of test cases, and add tests related to the bugs I find.

A major achievement this week was reworking interactions between entities in the same cell. This was originally handled in the following manner:

  1. When an entity enters a cell, it applies its interaction effects to all existing entities in the cell, and all existing entities in the cell apply their interaction effects to the entering entity.
  2. Each turn that entities co-occupy a cell, their AI reapplies interaction effects. For example, a fire will continue applying a burning status effect to each entity in the cell.
  3. When an entity exits a cell, the interaction effects it caused are removed from the remaining entities in the cell, and the interaction effects caused by the exiting enemy are removed from the remaining entities in the cell.

This implementation had a few limitations. It prematurely removed some status effects, such as poison, that have a multi-turn duration. It required effects to be applied by the AI, which never conceptually made sense. Because it relied on AI to apply effects each turn, it hurt performance because it increased the number of entities that acted in each turn. For example, in a room filled with poisonous gas, the gas in each cell acted in each turn. To address these limitations, I did the following:

  • Added a game service to manage per-turn interactions between entities occupying the same cell, allowing efficient interaction handling on each new turn.
  • Added a new effect trigger that only occurs when two entities interact for the first time. This is used when the player walks onto spikes, for example. The player is damaged when walking onto the spikes, but will not be further damaged while remaining in the cell containing the spikes.
  • Removed the logic that removes effects when an entity leaves a cell.
  • Removed the AI logic that applies effects each turn (this is now handled by the interaction manager).

I added a new Map Element, Darkness. When the player walks into a room with Darkness, visibility becomes limited to the adjacent cells. This is intended to be an unnatural or magically-induced darkness, as opposed to normal darkness. It’s not fully working yet. When the player opens the door into the room, all of the room cells the player can see from the doorway are visible. I need to fix this.

The most difficult bug I fixed this week was in the grid traversal raycast algorithm, which is used for line of sight and projectile calculations. Sometimes, arrows didn’t travel to the cell the player clicked. It wasn’t obvious why this was happening, and it wasn’t a major problem, so I didn’t do anything about it for a long time. Now that I’m fixing all of the known bugs, I decided to tackle it. I traced the issue to a method that calculates the final position of a ray (a projectile or light) given a start and end point. The algorithm used is similar to Bresenham’s line algorithm, but it includes every cell that the ray passes through. I can’t remember when I added this algorithm, but it’s been in use long enough for me to have forgotten when I started using it. So, I was surprised to find that there was a problem in it (I should have written unit tests for it). I spent a couple of hours adding debugging statements and tracing through the code. I could see that the algorithm was generating the wrong points, but I couldn’t figure out what I needed to do to fix it. I got out some graph paper and sketched an example. The visual representation was much more helpful than the code. There were actually two errors in the algorithm. The code fixes were trivial at that point. 

Next week will be 40% bug fixing, 40% automated testing, and 20% feature development. The feature development will involve expanding the types of interactions between entities. There’s already a framework for this based on the primary physical material that an entity is made of, but more interactions between materials need to be defined (for example, when fire hits a cell containing a puddle).

Weekly Update – July 8, 2022

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.

  • New Abilities
    • 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 configured to 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.

Weekly Update – July 1, 2022

It was another light week due to being on vacation for the first half of the week. More bugs were squashed and some new abilities were added. Abilities continue to require a surprising amount of work, including rework and expansion of the supporting systems.

  • New AI Type: Fear. This AI Type causes the actor to select a fleeing action each turn, even when cornered.
  • New Status Effect Type: Changed AI. This Status Effect temporarily changes the target actor’s AI Type. It’s used by the Fear spell to change the target’s AI from attacking to fleeing. When the effect ends, the target’s original AI is restored.
  • New Abilities: Fear and Mass Fear. The former causes Fear in a single target. The latter causes Fear on all actors in the area of effect.
  • Refactoring: Action consolidation. The recent rework on Actions, in which Actions were reduced to a series of Action Steps, made a number of Actions identical. For example, the Eating, Drinking, and Reading Actions consist of a single Action Step that causes the effect of the selected item. Since Actions are uniquely defined by their Action Step sequence, the number of Actions can be reduced to the number of unique Action Step sequences.
  • Bug fixes.

Next week will be more of the same, but I’ll have more time available. There are still many known issues to fix, and many more, I’m sure, that are still unknown. After a few weeks of mainly bug fixing my motivation is waning. It’s been fun creating and trying out new Abilities and I may spend more time on that next week than anything else.

Weekly Update – June 24, 2022

Time was limited this week and mostly went into bug fixing. I add a new map element and a new ability to keep content creation going.

  • New Map Element: Barrel of Water. This is a variation of a standard barrel that contains water. Destroying the barrel will create a puddle. It can be used to reduce the impact of explosions and put out fires.
  • New Ability: Run. The Run ability enables an actor to move across multiple cells in one turn. I’m thinking all or most player classes will get this ability.
  • Item type configuration validation. A few recent bugs were caused by improperly configured item types, for example a ranged weapon having a maximum range of 0. I added some checks to catch these issues when the items are initially loaded.
  • Bug fixes.

Next week, time will again be short and bug fixing will again be priority one. I will squeeze in one or two new abilities too.

Weekly Update – June 17, 2022

I designated this week a bug-fixing week. I do this when the list of little (and big) issues that I can tolerate during play-testing but players won’t tolerate gets too large. I have found this to be a productive way of dealing with bug-fixing. When I have to fix bugs in the middle of feature development, it’s a hassle. When I allocate a large block of time just for bug-fixing, I’m more motivated to do it and it’s a great feeling at the end because I’ve made the game more playable and moved it closer to launch. Here’s a sample of bugs that were fixed:

  • AI now responds to getting hit. Previously, AI response was based on seeing the player. If an invisible player hit an enemy, the enemy would just stand there. Adding a new observation type for getting hit fixed this.
  • Minimum projectile range works properly now. This was hard-coded originally because all bows had a minimum range of two cells. Then I added other projectiles such as thrown weapons and wands, and the two-cell minimum no longer applied to every ranged attack. I added a minimum range attribute to item types to fix.
  • After adding a regeneration status effect and discovering that one-time use healing potions were healing the player every turn, I realized I couldn’t rely solely on a number of turns item attribute to determine how many turns to apply an effect (the value 0 was used for one-time use and infinite turns; the value 1 caused a one-time effect to be applied twice, once when immediately used and again at the start of the next turn). To fix this, I added a duration type enum to status effects. The possible values are Once, Finite, and Infinite.
  • Tooltips were no longer appearing. I discovered this resulted from some code improvement I did a while ago in which I changed how tooltips appeared. Instead of code explicitly calling a method to display and populate a tooltip, I had the tooltip respond to an event instead. This reduced coupling. However, the tooltip GameObject is inactive when the game starts, so the Awake event that adds the event listener wasn’t being called. The solution was to add a wrapper GameObject that is active.
  • Tooltips were appearing behind the inventory panel instead of in front of it. I couldn’t fix this with layers or z position, and I couldn’t easily reorder the GameObjects because the tooltip is a child of a GameObject that appears before the inventory panel. I took the path of least resistance by changing the inventory panel layout so that it didn’t overlap the location of the tooltip.
  • When the player teleported into a room with enemies, the enemies didn’t act until the player moved. This was fixed by registering a new observation when an actor teleports. Enemies that observe the player teleporting into the room are awakened.
  • Similar to the previous bug, creatures that hatched from eggs didn’t act until the player moved. However, the solution was different in this case. Since the creature didn’t have an observation, it didn’t awaken. By changing the creature’s starting state to active in its configuration, the creature can act after hatching.
  • The teleport scroll stopped working. 

For the next couple of weeks, game dev time is limited due to travel. As time permits, I’ll continue fixing bugs.

Weekly Update – June 10, 2022

Implementing the Vampire’s AI was a lot of work. The process revealed the need for additional capabilities to support more varied and advanced AI. With the new AI framework, creating AI for new enemies in the future will be faster. The Egg, also added this week, is evidence of that.

  • New Enemies
    • Vampire. The Vampire is a dangerous enemy that has the special ability to transform into a bat and flee when its health is low. It returns to Vampire form after its health regenerates.
    • Egg. When the Egg detects the player nearby, it starts to hatch. The Egg can be destroyed in one hit if the player gets to it in time. But, if the player doesn’t get to it in time, a creature hatches from it.
Vampire
  • New Objects
    • Coffin. A Coffin can be opened or closed and can contain a Vampire and/or items, or be empty. 
    • Puddle. This is a single cell containing shallow water. It’s only found in caves. It’s just decorative at the moment. I plan to leverage the existing Physical Materials framework to add interactivity.
  • New Map Elements
    • Coffin Area. This map element places a single Coffin in a significant location in a room.
    • Egg Area. The Egg Area Map Element contains a cluster of Eggs.
  • Built new reusable capabilities. The above features required the implementation of new capabilities including a new action type to move an actor and trigger an effect at the same time (used when the Vampire flees to move away from the player and transform into a bat), a health regeneration status effect (used by the Vampire), the ability to prevent health bars from animating in specific situations (used when a Vampire transforms into a bat and health is transferred), generate particle effects when effect types are triggered (used to display a cloud of smoke when the Vampire transforms), and a new AI for actors that triggers an effect when the player is seen (used by the Egg to transform into a Cracked Egg), with an optional delay (used by the Cracked Egg to hatch after a number of turns).

`Next week will be a mix of new content and bug fixing. 

Weekly Update – June 3, 2022

In my relentless pursuit of increasing software development productivity, I started the week off pondering what is slowing me down the most. I kept coming back to aspects of object-oriented programming – encapsulation, abstraction, inheritance/composition, polymorphism. OOP has always been a double-edged sword for me, providing both solutions and problems. Certainly some of my issues are the result of my shortcomings as a developer, but I believe there are inherent shortcomings in OOP as well. A frequent challenge is determining where things belong, and a frequent source of bugs is putting things in the wrong place. I began questioning whether data and functionality belonged together in the same class (I was quite deep into the rabbit hole at this point) and if I could reduce complexity by separating the two. I also considered making data and functionality, once separated, completely public (I know, OOP heresy) and using either immutable or versioned data. I googled these ideas to see what already existed and found something very close: Data-Oriented Programming (DOP). Now, it would be impractical to go back and rewrite 2+ years of code using a DOP paradigm. But, I’m going to experiment with it for some of the new code I’m writing (see the AI example below). 

  • AI Overhaul part 2. I thought I was done with AI rework after last week, but I put even more time into it this week. To make the new composition-based AI configurable in the Unity editor, I added AIType classes (implementing the Type Object pattern). inheriting from ScriptableObject, I also made the pluggable components of AIType, such as the observation and action deciders, ScriptableObjects. The legacy AI classes were gutted and consolidated. AI state data was moved into a separate generic data structure (see below) and AI functionality was moved into the AIType classes. I added general AI behaviors such as offense and flee, and mapped actions to the behaviors. This simplifies the action decider code because only the behavior has to be specified; the behavior class will return all of the applicable actions to the action decider. With these improvements, I can assemble AI’s in the Unity editor, provided that the pluggable components have been written. I may need to move to data-driven behavior trees if the AI logic becomes too complicated, but for now I’ll stick with conditional statements.
  • Generic Data Structure. To support my data-oriented programming experiment, I created a class to act as a general-purpose data container. It’s essentially a map data structure, but contains three dictionaries to store values of different types (bools, ints, and objects). It’s not sophisticated but it works. I’m now using this to store AI state data, which varies by AI type. The syntax for accessing data within the structure is more cumbersome than individually defined variables, but that drawback is outweighed by flexibility and ease of serialization/deserialization. I also like that the syntax makes it obvious which variables are part of the state.

Next week’s goals are the same as last week’s goals: add the vampire and 1-2 more enemies to test the new AI, and add a few new abilities.

Weekly Update – May 28, 2022

Legend

Website | Twitter | Youtube

I started to add a new enemy this week, vampires. This revealed a problem with my AI framework. Vampires have the same move and attack behavior of a normal enemy, but they have some additional behaviors as well. For instance, they can change into a bat and will use that ability when their health is low to temporarily flee and regenerate health. Prior to vampires, the AI framework worked fine. Each time I needed to give an actor a different behavior, I’d simply add a new AI class. I had a single enemy AI class, a neutral NPC class, and a few classes for actors that do something but aren’t sentient, like fire and gas. This worked because the logic for each class was completely different. The vampire AI revealed a problem because it needed some of the standard enemy behaviors and its own unique behaviors. I spent a couple of days thinking about what to do about this. The solution came to me when I identified the pieces of the AI class that needed to change with each enemy: choosing which actors to track, choosing which observations to react to, and choosing an action from a list of potential actions. I defined interfaces for each of these and created standard enemy and vampire implementations. I extracted shared logic, such as determining all of the potential attacks an enemy has, into new classes so that the logic could be reused. I reduced the enemy AI class to the logic that was applicable to all enemies, which was mainly state management. I can now easily add new enemy behaviors without having to replicate code.

Rework can be a discouraging exercise, especially this far into a project. It doesn’t add anything to the game from a player standpoint. It doesn’t concretely move the project closer to the finish line, but there’s an expectation that it will save time in the long run. It can feed self-doubt (if I had written good code the first time around, I wouldn’t have needed to rework it). There’s a risk of over-engineering or building capabilities that you’ll never need. In this case, I almost scrapped my entire AI framework and considered implementing it using a Unity asset, Opsive Behavior Designer. I actually bought the asset and read the documentation. It seems like a great tool that provides a visual designer for AI behavior trees. It also supports utility AI within a behavior tree, which is essentially what my AI framework is doing in code currently. However, I decided to rework my existing code instead because it took less time to do.

With the AI rework filling up the week, the vampire wasn’t completed. I should be able to easily finish adding it next week. I’ll add one or two more enemies to test out the reworked framework. I will do the same with abilities, which went through a similar process recently of having to be reworked to support new types.

Weekly Update – May 20, 2022

This week’s focus was on expanding Action Steps and Abilities. A big chunk of time went into solving an issue with Action Step sequencing. An action’s Action Steps are instantiated and queued when the action is instantiated. A manager class then executes the Action Steps in order. This is a problem when an Action Step depends on the outcome of a previous Action Step, for example selecting a cell. I solved this problem by passing the action to each Action Step constructor and using the common properties in every action to pass values (every action has an actor performing the action and optionally, a target entity and entity that the action is performed with). It’s an imperfect solution but I needed to move on.

I also resumed the practice of adding at least one new Map Element each week.

  • New Ability: Charge. The Charge ability causes an actor to move up to several cells in a straight line and perform a melee attack within a single turn.
  • New Action Steps: Move and Melee Attack. These Action Steps enable an action to move an entity to another cell and perform a melee attack, respectively.
  • New Map Element: Spikes. Spikes simply cause damage when an actor walks on them. The player will have different ways of dealing with these, and they can also be useful in some situations, for example pushing an enemy onto them. They’re currently placed in a random row or column within a room. 
Spikes
  • Refactoring. Redundant parameters.

Next week’s goals are undecided. I’m due for a round of bug fixing; there are a number of known issues that I’m able to work around during playtesting but must be removed before release. I also need to keep the pixel artist search moving forward by posting in a couple more places and contacting the best artists from the original post.

Weekly Update – May 14, 2022

Momentum is picking up after a couple of slow weeks puzzling out the abilities architecture. With the architecture determined, I was able to implement the ability that triggered the architecture exercise in the first place, Heavy Strike. This ability is a melee attack that does more damage, uses a different animation and sound effect, and shakes the screen more. I wanted to be able to reuse the existing melee attack action for this ability. The solution was to add more parameters to the melee attack action and set the parameters from the Heavy Strike ability, which is a Unity ScriptableObject.

After finishing Heavy Strike, I started working on another ability, Charge. This ability performs two actions in a sequence, moving the player and attacking a target. It’s one of many abilities that perform multiple actions. This presented a new dilemma – actions were designed to be executed once per actor per game turn. The rework required to enable multiple actions per actor per turn was significant. I found a better alternative: Action Steps. Action Steps are now the basic building blocks of actions. They enable a series of actions to be performed within a single game turn action and make creating new actions and abilities a lot easier. Creating the Action Steps involves extracting code from existing actions. This is in progress. So far, Action Steps have been implemented for selecting a cell and shooting a projectile.

To control the execution sequence of Action Steps, I introduced another new object, Action Phases. Action Phases define the sequence in which Action Steps are performed. Each Action Phase contains one or more Action Steps. Action Steps within the same Action Phase are performed concurrently. This allows Action Steps to be performed sequentially, in parallel, or with a combination of the two. Some actions, such as pushing and pulling, require parallel execution of Action Steps. 

Next week, I’ll build a few more Action Steps (moving and attacking), which will allow the Charge ability to be created. I should be able to quickly add some more abilities. I’ll also post the pixel artist ad in a few new places.