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 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.

Weekly Update – May 7, 2022

It was an ok week. I had the same issue that I had the previous week – nothing got done during the weekdays.

  • Abilities architecture overthinking. I’m spending way too much time figuring out the architecture for abilities. I have a vision for a clean, highly-extensible, Unity editor-driven solution but I haven’t been able to get there. This weekend I’m forcing myself to finish this and move on. There are plenty of ways for me to accomplish this goal that aren’t perfect but are workable.
  • Saving/Loading working again. Saving/loading has been a pain point throughout development. Many changes break it, causing me to wonder if I’m going about it in the correct manner. Since I don’t often test saving/loading, when I do test, there are always a few issues to fix, and these issues are usually not simple fixes. Testing saving/loading every time I make a change would be too cumbersome; maybe I’ll add automated testing of this feature in the future. Anyway, saving/loading is working again. Since I’m reworking the code less these days, I expect to have fewer saving/loading issues in the future.
  • Rework – cell selection support in actions. Many actions (melee attack, ranged attack, open, take, drink, etc.) require a target. In the main game context, the target is determined by where the player clicks. In other contexts, such as clicking an item in the hotbar, the target is determined by prompting the player to select a cell. Early on, I built the cell selection handling into the ranged attack action. I knew I’d have to eventually extract it but I wanted to get ranged attacks working quickly and I wasn’t clear on all the other potential uses of cell selection. This week I moved the cell selection code into its own class and made it easy to use from any action. 
  • Fixed some non-fatal, recurring exceptions. I got tired of seeing the same errors in the logs on every playthrough. They didn’t actually break anything in the game, but they cluttered the log. I removed the several most common errors.
  • Posted pixel artist ad on r/GameDevClassifieds. I still need to post on Pixel Joint and 1-2 other places.
  • Wrote the first half of a post on time-tracking. Last week I stated that I was going to post the findings from meticulously tracking my game dev time over the past four weeks. There are some interesting observations but I realized that I need a larger sample size for some of those observations to be meaningful. I’m going to collect another four weeks of data and reassess if I have enough for a post.

Next week, there will definitely be some new abilities in the game.

Weekly Update – November 20, 2021

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.
Library patterns
  • AI 2.0 Design. 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.

Weekly Update – July 16, 2021

Title Screen

One Release 3 feature completed this week: the Title Screen! It will likely completely change after I bring an artist onboard, but it gets the job done.

Title Screen

Finished Actor Refactoring

I dug myself into a deep hole last week by refactoring actors. Every unit test failed and there were a couple hundred compiler issues to fix. I’ve mostly climbed my way out of the hole since, and I think the actor architecture is solid enough now to get to the finish line. I now have:

  • Actors
    • The main actor class is a plain C# class for all actors. It contains all actor state and is therefore the only class involved in actor saving and loading.
  • Actor Types
    • A GameObject prefab is defined for each Actor Type. These prefabs are loaded into memory when the game starts. They use composition in a limited manner, typically having only Transform, SpriteRenderer, and Animator components. When a new actor is created, the corresponding Actor Type GameObject is instantiated and associated with the actor. 
    • A ScriptableObject prefab is defined for each actor type’s definition data. Composition is employed here as well, though it is not supported “out of the box” by Unity, at least not in the way I’m using it. The technique is to add a field to the ScriptableObject that is a parent class or interface, and create custom editors to enable an inherited class (or implementing class in the case of interfaces) to be selected from a dropdown. Reflection is used to get all of the subclasses and populate the dropdown. When an actor is created in the game, Activator.CreateInstance is used to instantiate the class. This allows me to define an actor’s AI and abilities, for example, in the editor instead of in code.

This isn’t an elegant solution, but it addresses the things that were bothering me about the previous architecture, namely redundant type data in each actor instance, having to use MonoBehaviours or ScriptableObjects for composition but not being able to easily save/load component state data, inadequate information hiding, circular dependencies, and unclear division of responsibilities between the different classes comprising actors. The drawbacks of this solution are having to maintain two prefabs for each actor type and not doing composition the “Unity way” with MonoBehaviours.

All Unit Tests Passing, More Unit Tests Added

I’m repeating myself from previous posts, but the unit tests have been well worth the investment in time.

Next week, the plan is to finish the class selection and load game screens. There are still some things that are broken from refactoring and I need to fix those too.

Weekly Update – July 9, 2021

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.

Weekly Update – April 2, 2021

My plan for this week was, among other things, to fix ranged combat. It became apparent that the fix required enemies to have their own inventories (so that ranged attacks could be based on ranged weapons that the enemies carried). This led me down the familiar refactoring path, but it was time well spent. Achievements this week:

  • Separate inventories for each actor. Each actor can now have its own inventory simply by adding a component. This enables the AI to determine possible actions from the items the actor has. It also allows for future features such as pickpocketing, running out of consumables, and dropping items on death.
  • To enable enemies to have their own inventories, I had to decouple inventory from UI. I was most of the way there already, so it wasn’t too much additional work. There’s still room for improvement – equipped items and carried items are separate classes and there’s some redundancy. Separating inventories and decoupling inventory from UI also makes it possible to implement a feature I’ve been thinking about adding – the ability to possess an enemy and view its belongings in the same inventory view used by the player.
  • Refactored effects/conditions. Effects (e.g. poison, paralysis, invisibility) were moved from an inheritance structure to a composition structure. This allowed me to merge tile effects and actor effects into a single set of effects that can be applied to anything.
  • Swapped out some multi-parameter method signatures with a single predicate parameter. In a few situations, there were multiple methods that applied different criteria to return a subset of values from a list. Using predicates instead of multiple parameters provides a lot more flexibility and is cleaner.
  • Item randomization class. There was enough randomization code for items specifically to warrant a randomization class solely for items. This class is primarily used to determine initial item placement. The main method uses a predicate to only certain items such as potions, or items that can be sold in shops.
  • Item database class clean up. There’s still some murkiness around prefabs, GameObject instances, plain old objects, and instances vs types for actors, items, and tiles. The item database class, which is responsible for loading and storing item types, received a good scrubbing.
  • Durability refactoring. Item durability code existed in a few places and had some unnecessary coupling. I again applied a composition model and created a class specifically to store item condition and process events that degrade an item’s condition. Since the class inherits from MonoBehavior and can be added as a component, it can be used for objects other than items, such as a wooden door that can be chopped down with an axe. Of course, this could be accomplished with the health attribute as well, and I may in the future eliminate durability altogether and just use health to track an item’s wear.
  • Dictionary class extension methods. I added some convenience extension methods to the Dictionary class: an IsEmpty method and a couple of methods to display the contents of entries and the entire dictionary in a readable format.

Next week, I’ll continue fixing ranged combat and some other combat-related bugs. 

Weekly Update – March 12, 2021

I made a lot of “under the hood” improvements this week, including:

  • Expanding testing configuration settings. In my ongoing effort to improve productivity, I realized that I needed a way to quickly reproduce and test specific features and objects. For example, there was a bug that allowed the player to take items in shops when the table the item was placed on was burned. I was wasting a lot of time reloading the map until a nearby shop appeared, then I had to find a fire potion that I could use to burn a table in the shop, and then walk back to the shop to reproduce the issue and confirm it was fixed. For this scenario, I added test configuration settings to increase the probability of shops and preload the player’s inventory.
  • Centralizing testing configuration settings in a single GameObject. Different settings, such as fog of war, spawning enemies, and visual map generation, were spread across different scripts and GameObjects. I put all of these into a single GameObject so that I can quickly see what’s enabled and disabled and reconfigure test settings.
  • Reusing random seeds for debugging. The game has the ability to reuse a random seed to generate the same map across multiple sessions, but I wasn’t using that feature much for fixing bugs. Capturing the random seed in my bug reports and regenerating the associated map has accelerated bug fixing.
  • Automatic class and method logging. All game log writes are performed by a custom class. Using .NET’s handy reflection capabilities, I was able to automatically include the class and method that called the log write method. This has been very handy for tracing through sequences of events.
  • Switch from binary to JSON serialization for saving/loading. I was using the .NET BinaryFormatter for serialization and didn’t learn until this week that the serialization method is now obsolete due to a security issue. Switching to JSON serialization was a quick and easy change.
  • Overhauled script and Unity resource folder structure. There are currently 145 scripts and 175 resources. Over time, the original folder structure got messier and it became more difficult to find things. I was starting to guess in some cases where a particular script was located. Even a sub-second delay was a concern because that added up over time, in terms of time and mental energy. I created a new folder structure that is much better fit for the current file set.
  • Many bug fixes, some recent and some long-standing. The bug backlog is finally shrinking, and after all the refactoring, there are far fewer occasions where I have to think hard about a solution; there’s now usually an obvious place for a fix to go and the fix is simple.

Next week, I’m adding enemies back to the map (they’ve been disabled for a while as I was focusing on other game aspects) to wrap up combat refactoring and close out enemy/combat-related bugs.

Weekly Update – March 5, 2021

I made a lot of progress on the big refactoring project this week. The work is gradually shifting from refactoring to fixing bugs in the backlog. Each bug that is easy to find and easy to fix is a sign that the refactoring is making a difference.

I struggled a bit with player input handling. The original solution was not great because player input handling was both event-driven (button OnClick events) and time-driven (checking for input in Update calls). This didn’t work with Unity’s event sequence, in which mouse input events are handled before Update calls. What would happen is that a button click would be handled in the OnClick event, and then the mouse click would be processed again in the Update method in the same frame. I ended up removing the OnClick events and only checking for input in the Update method.

I rewrote the player input handling twice. Not sure why exactly, but it was difficult to implement it in a way that was simple and followed best practices like single responsibility. The first rewrite was overly complicated because of excessive abstraction. Player inputs triggered input events, input events triggered input commands, input commands triggered either UI or player actor actions. I started to get a handle on things when I sketched out the state diagram for inputs. It was also helpful to list out the resulting actions for each input and separate these into UI actions, like displaying a prompt, and player actor actions, like moving to a new location. The solution I ended up with was to check for all input in the Update method of the main UI controller for the scene, create classes for each player action, and map inputs to player actions. 

With UI and player input in good shape, I’m working 50/50 on refactoring and bug fixes. The GameManager is down to 1,000 lines, half of its original size. I need to find a home for most of the remaining methods in that class still. This is the goal for next week, along with clearing the bug backlog.