This week, I started on the next milestone, abilities. Abilities include special attacks, spells, utility skills, and passive effects. I have a list of around 90 abilities currently and expect the final count to be between 120 and 150. Each actor (player classes, NPC’s, enemies) will start with zero or more abilities. Players can gain more abilities over the course of the game. I haven’t worked out all the details, but the ability acquisition will be integrated into gameplay (i.e. they can be found like items, or learned from an NPC) as opposed to a skill tree. Like items, the abilities placed on the map get stronger the deeper the player goes. The abilities available to the player depend on the player’s class. Each class has a set of attributes and a maximum proficiency level for each attribute (basic, intermediate, advanced). Each attribute proficiency level is associated with a set of abilities. A class is able to use the abilities defined for each of its attributes, at or below the proficiency level defined for that attribute. For example, a Ranger has a Weapons (Intermediate) attribute that allows use of the abilities associated with the basic and intermediate proficiency levels of the Weapons attribute.
Reworked abilities and attribute classes. Basic ability and attribute functionality was already in place to display attributes on the class selection screen and display abilities available to the player. To fully support the ability system, the underlying code had to be extensively reworked. Most of this week’s time went into this rework. Active and passive abilities were split into separate classes inheriting from a common base class. Relationships between attributes, attribute proficiency levels, and abilities were rewired. Attribute ScriptableObject assets were condensed from one asset per combination of attribute and proficiency level to one asset per attribute to directly group abilities for each proficiency level (this is useful because classes have access to abilities for a maximum proficiency level and all proficiency levels below the maximum).
Improved Abilities Panel. The Abilities Panel previously displayed all available abilities in a single grid. Now the panel has a tab for each Actor Attribute and the abilities for the selected attribute are grouped by proficiency level.
Miscellaneous ability-related enhancements
Starting abilities automatically added to the hotbar.
Abilities can be inspected like entities and cells.
Contemplated replacing abilities with items. This is more of a side note than an update, but midweek I stopped development and seriously considered implementing abilities as items. The origin of the thought was my struggle to determine how resource consumption will work with abilities (I still haven’t figured this out, by the way). One option was to simplify and consolidate resource management to inventory. Players would find items that did the same things that abilities would do – a scroll to cast a spell, lockpicks to pick a lock, etc. This idea appealed to me because clever use of what you find is key to success in the game, and it avoids using time as a resource (waiting to regenerate mana/stamina). But, there are drawbacks: 1) the desired player behavior is to frequently use abilities, but having a finite number of uses and uncertainty about how many future uses the player will have encourages hoarding 2) it’s difficult to allot uses of large quantities of items over the course of the game. Because of the drawbacks, I rejected the idea and resumed work on abilities as planned.
Next week, now that the abilities system is complete, work will begin on the abilities themselves. The big challenge here is implementing many abilities in a scalable manner. I want to avoid creating a separate class for every ability, and I want to leverage existing objects like actions and effects as much as possible.
I’ve noticed that for at least several weeks, my todo list hasn’t gotten any shorter. I’m adding as many items to the list as I’m removing. The scope of the remaining UI work keeps growing, either because tasks are taking longer than expected or the features that have been added require additional features. It’s been time well spent, however; the UI is critical because one of my key goals is make the game as easy to learn and play as possible.
Added text to Action icons. UI best practice is generally to display labels with icons rather than icons alone.
Inspect Panel improvements.
When inspecting a cell, the Inspect Panel now displays all inspectable objects in the cell, including actors, items, and the cell type itself. This allows the player to see details and perform actions on a specific inspectable in the cell (previously only the top inspectable was accessible).
Actions that can be performed on the object being inspected but aren’t available for some reason (typically the player isn’t close enough to the object) are now displayed so that the player can see all potential actions.
Tooltips for empty hotbar, Quick Switch, and Quick Switch Deselect slots. Hovering over one of these slots now displays a tooltip indicating what the slot does and how to use it.
Actions that can be performed on an item or actor are defined as lists that can be edited in the Unity editor. This worked well until I created a new action that applies to all items. Since most items have the same actions, I created a ScriptableObject called ActionCollection that contains a list of actions and replaced the entity action list with a list of ActionCollections. This allows changes to be made quickly to the core set of items, enemies, or other related entities, while still allowing for variation.
Actors in a cell are stored in a typed list. Likewise, items are stored in a separate typed list. This was implemented before I modified actors and items to inherit from the same parent, Entity. Having two separate lists caused some problems. One of those problems was that the order of an actor relative to an item couldn’t be determined. The logic that occurred when a player clicked on a cell checked actors first and items second. So, if a cell contained a table and there was a health potion on the table, clicking the cell caused the player to hit the table rather than take the potion. I solved this problem by combining the actor and item collections into a single entity collection. This was easy to do because the interface remained the same.
The recent rework made it possible to use a data-driven for some actions that were previously hard-coded. For example, each item now has a Take action, replacing logic that automatically performed a take action if an entity was an item.
Started tracking my time with Clockify.me. It integrates with Trello, the tool I use for planning and issue tracking. I’ve started tracking my time to become more aware of how much time I’m spending on the project and where the time is going (enhancements, bugs, etc.).
Thought more about a new name. I came up with one name that I like and a short list of decent names. This process is going to take a few weeks at least.
Next week will likely consist of more UI refinements.
There’s not much of interest to report this week. Last week I implied that there wasn’t much work to do on Cell Context Menus because they technically already exist – the Inspect Panel lists the actions that can be performed on the entity being inspected. However, not all of the actions were listed. I couldn’t simply add the missing items because they are implemented in a different manner than the actions that were being listed. Specifically, the missing actions inherit from the UserCommand class and the included actions inherit from the ActionType class. I decided to simplify things by having actions inherit from ActionType, and limiting the purpose of UserCommand to mapping input to actions. This led to a lot of rework.
Item light sources for non-player actors. Actors other than the player now display light sources from the items they’ve equipped.
Dropped torches. When a lit torch is dropped, it now stays lit and maintains its light source. When the player picks up the torch, the torch is added to inventory as an unlit torch and the light source is removed.
Quick Switch Slot improvement. When no item is selected in a Quick Switch Slot, the deselect option, which is redundant in this scenario, is no longer shown.
Bug fixes. These were mostly issues introduced by this week’s extensive rework rather than long-standing issues.
Refactoring. I used the rework as an opportunity to refactor where it was easy to do. This included cleaning up some classes and method extraction.
Contemplated a name change. “Legend” is the third name that this game has had. I’m considering changing it after recently watching a video on naming your game. My concerns are that “Legend” is too generic, too hard to search for, and has copyright risk. I made a list of words that convey some aspect of the game this week. None of them jumped out at me. I’ll keep adding to the list and playing around with different combinations until something clicks.
Next week, there’s some more rework testing and cleanup to do. I also need to make minor refinements to some of the recently added features.
The big news this week is that the Quick Switch Slots and Cell Action Indicators are done. I started on these exactly one month ago. They shouldn’t have taken this long to complete, but did because I had less time than usual over the past month and I underestimated the effort to code the features. They are two of the remaining three major UI features that need to be added (the third being Cell Context Menus).
Completed Quick Switch Slots. Quick Switch Slots are similar to a hotbar, but are used to quickly change weapons and other held equipment, and enable automatic switching of melee and ranged weapons based on the target’s distance from the player.
CompletedCell Action Indicators. Cell Action Indicators tell the player what will occur when a particular cell is left-clicked. An icon representing the action the player will perform is displayed when the cursor hovers over a cell. If the player first needs to move closer to the cell to perform the action, the Cell Action Indicator will display two icons, one for movement and another for the action to be performed.
Player input and action refactoring. The above UI changes touched one of the uglier parts of the code that is responsible for triggering in-game actions from player input. In the interest of finishing this game, I’m being more selective on rework. However, some rework was unavoidable this time. Previously, there were Player Actions (actions mapped directly to player input, which could translate to in-game actions such as moving the player’s character, or UI actions such as opening the Inventory Panel), Action Types (in-game actions), and Actions (instances of Action Types, but still defined as separate classes for each Action Type due to the evolution of the code). It was messy and redundant. I renamed Player Actions to User Commands, which helped me mentally differentiate these from in-game actions. I added a minimum and maximum range attribute to each action so that the Cell Action Indicators can determine whether the player’s character has to move in range of an entity before interacting with it, replacing some hard-coded logic that only worked in certain scenarios. I standardized User Commands and Actions behavior. I moved some methods into more appropriate classes. I fixed all the little issues that cropped up from the changes. I even found a couple of bugs that predated the recent changes.
Wand of Magic Missile. The Wand of Magic Missile was primarily created as a proof of concept for getting wands working, but will most likely stay in the game, and perhaps be a starter weapon for the Wizard class. I’m still deciding how to configure wands in terms of resource consumption. I’m leaning toward unlimited charges, no cooldowns, and eventual destruction from use. Wands will have to be underpowered to support this.
Next week, I’ll start working on the final remaining UI feature: Cell Context Menus. Right-clicking a cell will display a Cell Context Menu that lists all of the possible actions that can be performed on the cell (left-clicking performs the default action, as indicated by the Cell Action Indicator).
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.
Most of the improved AI and game event response was built out this week.
Field of view for game events. Returns a list of the cells that can see an event. For each cell, the distance from the cell to the event is also calculated so that actor sight distance is taken into consideration.
Game event notification optimization. Previously, each actor received a notification for each event, and then determined whether the event was in the actor’s field of view. Now, only the actors in the game event field of view are notified, and only a quick sight distance check needs to be done.
Door opening and closing game event reactions. Actors now respond to doors that they can see open and close. They will walk to any door that opens or closes (other behaviors will be added in the future). This allows for player tactics like luring an enemy into a vulnerable location, or away from a guarded location.
Preoccupied and Observing awareness levels. Actors in the former state must wait one turn before reacting to a game event. Actors in the latter state can react in the same turn. So, when the player enters a room with guards who are actively observing, the guards can respond immediately.
Refactoring. Consolidated duplicate code and extracted some classes in the actor action code. I’ve been reading Clean Code and getting some good refactoring ideas.
Optimizations and bug fixes. The recent changes introduced slowness and some undesirable behavior (enemies attacking each other, enemies attacking doors, enemies attacking dead enemies).
11/30 is the target date for Release 3. The goals of Release 3 have largely changed since it was originally scoped six months ago. The last milestone is completing the AI game event response system. I should be able to complete this in the coming week.
Release 3 is scheduled to be completed at the end of this month. I started on this phase five months ago. I’ve completed around 30% of what I planned for this release, due to shifting priorities rather than underestimation. Much of the Release 3 scope consisted of adding – new monsters, new objects, new abilities, new items. Yet, the game wasn’t truly playable; it was buggy, there was no progression, and the main game loop was boring. In response, I stopped adding content and started concentrating on the fundamental experience. I improved game feel. I made combat more tactical. I refined procedural generation to produce more cohesively populated maps. I continuously asked myself what needed to be done to put this game in front of people. The game is now in better shape than it would have been had I stuck to the original Release 3 scope. Key achievements this week:
Fixed projectile raycasting. I originally implemented Bresenham’s line algorithm to determine the path of a projectile. This algorithm generally worked, but sometimes projectiles would pass right through obstacles, typically corner walls. On multiple occasions I stood waiting for an archer to come to me, thinking I was protected by the wall in between us, only to be shot by an arrow that passed through the wall. It turned out that Bresenham’s line algorithm wasn’t suited for identifying all cells on a grid between two points. This post illustrates the issue. I wrote a new cell traversal algorithm from scratch (I didn’t use the answer from the post) that determines every cell between two cells. The new algorithm fixed the issue.
Overhauled CellGetter. CellGetter is a utility class for getting cells in a specified pattern, such as adjacent cells, room perimeter cells, room corner cells, etc. It’s used extensively in procedural generation to place Map Elements in appropriate locations. It had become a 1,000 line class because a method was created for every variation of a pattern that was needed. For instance, there were separate methods for filled and unfilled rectangles and room perimeters and borders. I refactored the class by moving the variation to public, read-only C# Funcs that could be chained together as needed (e.g. all cells in a filled rectangle that can have objects placed on them). Then, I consolidated most of the methods into a handful of methods that have Func parameters. This will make it easier to add new Map Elements that place objects in patterns.
I’m still firming up plans for next week. I’ll likely focus on bug fixes and some minor user experience improvements. I also need to continue expanding the Map Elements.
It was a mentally challenging week, but ended on a good note. I did a project reality check and got discouraged by the amount of content I still have to create. I weighed some drastic alternatives including completely changing the vision to allow cutting most of the planned content, or (for a brief moment) giving up and focusing on my second biggest aspiration after game dev: writing a novel. I spent some time playing what I’ve built so far. While the current version succeeds at demonstrating the major mechanics, it’s a shallow experience. Many of the more subtle mechanics, largely pertaining to combat, aren’t implemented or are still broken from the refactoring I did a couple of months ago. That’s when it hit me that these details are the difference between boring and engaging gameplay. My highest priority is now completing these finer mechanics.
This Week’s Achievements
Damage modifiers based on physical material resistances and vulnerabilities. Damage is now increased when the target’s physical material has a vulnerability to the damage type, decreased when the physical material has a resistance to the damage type, and eliminated when the physical material is immune to the damage type.
Projectile-based spells. Spells that basically fire a projectile are partially implemented (they work, but I haven’t landed on a resource model yet).
Consolidated redundant code for shooting and throwing items. Much of the code for shooting and throwing was the same. To implement spell casting, I would need to create yet another copy of that code. I decided to refactor and put the shared code into a parent class.
Fixed miscellaneous minor bugs involving drag & drop, room generation, AI, combat, and pathfinding.
Next Week’s Goals
Next week, I’m focusing on combat mechanics including equipped item modifiers, terrain modifiers, visibility modifiers, and melee abilities.
It was an eventful week in terms of getting things done, but not in terms of things done that matter to the end user.
Significantly expanded the capability to configure object functionality in the Unity inspector using a custom event model. Basically, object types have a list, where each item contains a trigger and an event. Triggers are defined as enums and are tied to hooks in the code. Events are ScriptableObjects with custom methods. Any number of triggers and events can be added to an object type. This accelerates creating new object types and eliminates a lot of object type-specific code. I will use this to add the new object types planned for Release 3.
Overhauled hidden object handling. Hidden objects were broken by the refactoring done a couple of weeks ago. The implementation had to be overhauled. I used this as an opportunity to both simplify the implementation and make it more robust. Previously, each cell had a list for hidden actors and a variable for a hidden cell type. I didn’t put much thought into the general design; I added object-type logic as needed to support hidden objects such as hidden doors and traps. This was not scalable and added unnecessary complexity. The new implementation leverages the new custom event model to handle revealing and triggering hidden objects. By doing so, the hidden object variables and object type-specific code could be removed.
Removed and simplified code involving upcoming Release 3 features. The main theme was making more logic declarative and reducing complexity, made possible by the new custom event model.
Renamed some classes that had confusing or inaccurate names. Doesn’t sound like a big deal, but poorly named classes were adding to cognitive load.
New unit tests for actor visibility. These tests include actors being able to see other actors, cells blocking visibility, effects that affect visibility such as invisibility potions.
Hotbar research and design. I researched hotbars in games this week and began designing Legend’s hotbar. The design is around 75% complete. There will be a hotbar for consumable items and abilities, and possibly separate hotbars for switching equipment.
A lot of the work over the past few weeks was driven by the Save/Load features being added in Release 3. That work is coming to an end and I should be able to complete the Save/Load features in the coming week. I also plan to complete the hotbar design and start on implementation.
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.
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:
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.
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.