• Weekly Update – June 2, 2023

    This month I’m planning to conduct Legend’s second play test ever. The testers will be my wife and kids (I’m taking baby steps). The first play test was performed by one of my sons. He discovered a game-breaking bug 30 seconds into the test. I’m hopeful that I’ll get more feedback this time around.

    In preparation for the play test, I added a bunch of new content this week. I’ve historically limited content creation so that I could focus on functionality and to minimize the need to update existing content when system changes are made. However, encounter variety is a key game feature. I want to ensure that there’s enough variety for the play test sessions.

    • New Map Elements
      • Eight new room types were added for Catacombs sections.
      • Miscellaneous Map Elements including a dead adventuring party, a gelatinous cube blocking a corridor, and a spider-infested room.
    • New enemies. Wisps, Giant Floating Skulls, Unarmed Skeletons, Skeleton Warriors, Spiders.
    Giant Floating Skull
    • New items
      • Copper Coins. Only Silver and Gold Coins previously existed; players were accumulating money too quickly.
      • Starting equipment. I decided to add a new equipment tier, Tier 0, for starting equipment. The equipment in this tier will be only slightly better than having no weapon and armor. This makes finding Tier 1 equipment in the first few levels an upgrade and aligns with the narrative of the player being a poor, fledgling adventurer.
    • Finalized weapon and armor lists. The item stats have also been determined after many hours of modeling progression and balance in spreadsheets. The stats are subject to change depending on how well they work in practice.
    • Improved map room population. I was having an issue with maps being too sparse. I partially fixed this this week by increasing the number of history events by 50%. More rooms are populated now, but there’s now more repetition. Repetition is easily solved by adding more room types. Another problem to solve is disproportionate room counts between different level sections. One section may consist of a single room while others have dozens of rooms. It’s ok for one section to be larger than another, but I need to limit the degree to which section sizes can vary. Also, tying the number of history events generated in a section to the number of rooms in that section will help populate the section with the appropriate amount of content.
    • Minor enhancements
      • Leave the Inspect Panel open after clicking the Take All button.
    • Bug fixes
      • The player occasionally gets stuck when opening a door.
      • Silver coins can’t be picked up.
      • Bandits only drop one coin.
      • When the player uses an ability that requires selecting a cell, enemies are able to attack before the cell is selected.
      • Lighting doesn’t work when playing the Wizard class.
      • Ranger class not animated.
      • The player’s default non-weapon melee attack doesn’t work.
      • Vampire no longer transforms into a bat when fleeing.
    • Optimizations
      • Particle effects aren’t being destroyed. Unsurprisingly, this caused severe performance degradation over time.
      • Rectangle game objects used during map generation for debugging aren’t being destroyed.

    Next week there will be another cycle of finding and fixing bugs and adding content. 

  • Weekly Update – May 26, 2023

    It was a light week due to vacation.

    • Revamped movement with object interaction. When the player is too far away from an object to interact with it, clicking on the object causes the player to walk to the object and then interact with it. The recent open/close door changes broke this in some cases. Since I couldn’t find a simple fix and the logic was hard to follow, I reworked the logic on paper and updated the code accordingly. The root issue was overly-complicated branching. It should be a lot easier to work with this code in the future.
    • Damage and damage reduction stat progression design. Combat is currently broken because damage and damage reduction stats are completely unbalanced. I resumed working on a spreadsheet I started last year containing damage calculations and weapon/armor stats. It’s slow-going. I have solid numbers for how many hits the player can take before dying based on all combinations of weapon and armor tiers. Based on these numbers, I also have minimum net damage per weapon/armor tier combination. I’m now struggling with deriving armor damage reduction values from net damage.
    • Summoner and demon corpses.
    Demon and Summoner corpses
    • Bug fixes
      • Missing stat modifiers on some armor items.
      • Error when attempting to ascend stairs on the first level of the dungeon.

    Next week, my top goal is to fix combat with decent first-iteration item damage/damage reduction stats. And, there are still major bugs in the main game loop to remove.

  • Weekly Update – May 19, 2023

    I never thought I’d say this, but it felt great to get back to bug fixing this week. Procedural generation is my favorite part of roguelike development, but after four months of working almost exclusively on history generation, I was eager for a change. It was very satisfying to focus on making the main game loop better. Many bugs were removed and small, though important, improvements were made.

    Before resuming work on the main game loop, I had one big, elusive bug to squash in history generation. With enhancements to the history generation logs (details below), I was able to find the bug and remove it, and for the first time I can say that history generation is fully functional (though needing much more content). The bug prevented some event types from being selected by the history generator. After removing it, the average percentage of distinct event types used by the history generator rose from 30% to 40%. This seems about right for the current number of events being generated (70 on average) with 66 event types.

    • History event selection major bug fix. This bug fix warrants its own bullet because of how difficult it was to fix and its impact. I spent most of last week fixing an event selection bug that limited the number of eligible event types by limiting the combinations of entities that could be bound to an event. I expected that would solve the event selection problem, but it was only half of the solution. The other half was a bug fix in one of the commonly used entity binding criterion classes. This binding criterion compares a supplied value to the value of a specified entity attribute. Sometimes the entity’s Id needs to be compared to a value. For instance, if there are two actors in an event, the actors’ Ids are compared to ensure that the same actor isn’t used twice. However, Id isn’t an attribute; it’s a variable in the entity class because every entity has an id, whereas attributes are optional and vary by entity type. To allow Id to be used in binding criteria, I wrote logic to cause the Id to be used as an attribute. The source of the bug was a method in which I forgot to include the special case for the Id. Instead of adding the special case, I converted Id to an attribute. This was a harsh reminder that cutting corners sometimes saves time up front but wastes more time in the long run.
    • Easier history event troubleshooting. History event troubleshooting is time-consuming and mentally demanding because of the massive quantity of log entries produced. I tried to make it easier by simplifying the text of some log entries, adding more detail and context to other log entries, and using rich text formatting to highlight key info. It helped with troubleshooting, but only marginally. Since troubleshooting usually involved one or a few history event types, I added a history event type flag to enable/disable extended logging. Then, I wrapped most of the log history generation log statements with a check for the flag. This allows detailed logging on the event types I’m interested in while keeping the overall log size low.
    • History event type weighting. History event types can now be weighted to control how often the event type is selected relative to other event types. I foremost added this for testing purposes.
    • Fixed walk animation. Last year I introduced a bug where an actor’s walk animation finished before the actor finished moving, causing the actor to appear to slide momentarily. I can’t recall what change was responsible. Now that I’m working on the main game loop, it was time to fix this. I tweaked movement speed and animation duration to no avail; there was no magic combination that worked. One thing I noticed was that the outcome depended on how far the actor moved. One set of values worked well when moving two cells, another set when moving three cells, etc. There was no issue when the actor moved one cell at a time, and that got me thinking. Each time the actor moved to a new cell, Animator.Play() was called to start the walk animation. It turned out that the animation from the previous cell was still running when the actor entered the new cell. To correct this, I called Play() with the normalizedTime parameter (I wasn’t using this parameter) and set the value to zero to restart the animation.
    • Map generation analysis. There are two major issues with the map generator: 1) too many rooms are directly connected to each other rather than by corridors and 2) too many rooms are empty. I captured screenshots of generated maps to better understand the behavior and look for patterns. Fortunately, the system that generates the map structure was redone at the end of last year and should be capable of correcting the first issue with configuration changes. The second issue is, on the surface, similarly simple to fix. History events cause rooms to be populated, so increasing the number of events will populate more rooms. However, if the number of room types is limited, the level will feel repetitive. Additionally, if the rooms aren’t arranged in a logical fashion, the level will feel like a random, incohesive assortment of rooms. The former concern can be addressed by adding more room types. The latter concern requires more use of existing capabilities that relate rooms, such as history event triggers and multi-room map elements. The next step is to improve the map structure.
    • Actors no longer cut corners when moving. Actors could previously move in 8 directions. This worked fine except in the case where the actor was moving diagonally and one or both of the cells adjacent to the destination cell was blocked. In this case, the actor appeared to walk through the wall. Preventing this was surprisingly easy. In the A* pathfinding algorithm, when retrieving neighbors, diagonal cells are now conditionally retrieved based on whether their adjacent cells are blocked. This was surprisingly easy to correct. I just had to modify the GetNeighbors method to exclude diagonal neighbors when they are adjacent to a blocked cell.
    • New Actor Type attribute: Skeletonized Form. This is used to determine what object a corpse changes into after decomposing (typically a pile of bones).  This is needed for adding the remains of actors that died a long time ago in the generated history.
    • Added three new Mushroom types. Cave sections lack variety. They can be populated with stalagmites, crystals, or mushrooms at the moment. I recently purchased a large collection of game sprites. One of the sprite sets in the collection is mushrooms. I used three of the sprites to create three more mushroom types. For each type, I created new physical materials  (because the mushroom types have some different physical properties), new damage particle effects (I just set the particle color to the most prominent color in the sprite), and new actor types (objects are implemented as actors).
    Mushroom varieties
    • Doors no longer automatically close when an actor passes through them. Originally, I configured doors to automatically open when an actor moved into a door cell and close when an actor moved out of a door cell. Later on, I split opening a door and moving into two separate actions, each requiring a turn. I did this because I wanted the player to have to stop and look into the room first, and because opening the door and moving should be two separate actions performed in sequence. I didn’t change the closing behavior. While testing a bug where enemies pursuing the player weren’t opening doors, I realized that when the player exited a door cell and closed the door, the enemy had to spend its next turn re-opening the door, allowing the player to get one cell farther away from the enemy. To address this, I removed the automatic door closing. Now doors will remain open after an actor passes through them. Actors still have the option of closing doors, but this requires a turn to do.
    • Improved Actor Inventory Profile editing. The Actor Inventory Profile defines an actor’s default items and random items and gold. Using Odin, I better organized the field layout and controls.
    Inventory Profile editor
    • Minor optimizations. The slowness and choppiness I observed after upgrading Unity was caused by console logging and dynamic lighting. With both of those systems disabled, I ran Unity Profiler to identify any other sources of poor performance in the main game loop. There weren’t any significant ones. Pathfinding and line of sight are the most expensive operations, though I doubt there’s that much more performance improvement to squeeze out of those. I applied lazy loading and object reuse in a few spots to reduce memory allocations.
    • Minor enhancements
      • Message log entries for actor death, ghosts appearing from bones, drinking.
      • Items randomly placed in debris.
      • Some objects such as barrels and debris now drop the items they’re holding. This is in contrast to corpses, which must be searched.
    • Bug fixes
      • Colors are incorrect for stalagmite and mushroom damage particle effects.
      • Error when mouse hovers over an unreachable cell with an interactive object.
      • Some objects don’t emit damage particle effects.
      • The default action for a fountain is a melee attack.
      • When inspecting an inventory item, the item tooltip remains on the screen.
      • When an object containing items is destroyed, the items disappear.
      • Summoner doesn’t attack the player.
      • Multiple enemies are able to move onto the same cell.
      • Actors can’t attack diagonally.
      • Drink action disabled when inspecting a fountain.
      • Error when drinking from a fountain.
      • Witch and Debris descriptions missing.
      • Enemies not opening doors when pursuing the player.

    Next week will be light because I’ll be on vacation for most of the week. I plan on doing more main game loop testing.

  • Weekly Update – May 17, 2023

    I noticed that some history event types were never selected by the history generator. These events involved multiple existing entities, for example the leader of a faction attacking the leader of an opposing faction. After a laborious review of the history generation logs, I pieced together what was happening. An event type’s selection eligibility is based on the triggers it can respond to and its entity binding criteria. The latter determines which existing entities can be bound to entity placeholders in the event. If there are no existing entities that meet the binding criteria, the event type cannot be selected. The problem was that when an entity was bound to an entity placeholder, it could limit the bindable entities for other placeholders in the event. In the above example, entities were selected in the following order: attacking faction, attacking actor, defending faction, defending actor. If the attacking faction was selected, but the leader of that faction was dead, the history event type would be immediately excluded from selection. Other factions would not be evaluated. To fix this, every entity combination needed to be checked. I spent most of the week implementing a solution. The code works, but it’s relatively complicated and grossly inefficient.

    The rest of the week went to an assortment of minor bug fixes, enhancements, and refactorings.

    Next week, I’ll do the tasks that were originally planned for this week (fixing the issues introduced by upgrading Unity, adding more history generation event types, and addressing open bugs in the main game loop).

  • Weekly Update – May 5, 2023

    Huge progress this week. The website was redesigned, Unity was upgraded (first time in over two years), and there were many history/map generation improvements.

    • History generation improvements and fixes
      • Multiple events from triggers. A trigger can now spawn more than one event. An example use is a settlement. When a faction settles in a section of the map, the rooms in the section need to be populated with the room types used by the faction. This is easily achieved by defining each room type as an event that responds to the Settled trigger and queuing multiple instances of the trigger.
      • HTML history file. The text logs output by history generation were tedious to review. I wanted to be able to click on an entity and see its full history, see entities arranged by type, and see entities by section. I added an HTML output file to do all these things. It’s much easier to understand what the history generator did now.
      • Event validator. All history event types are now scanned before generation to catch and report configuration issues. This reduces troubleshooting time.
      • Binding criteria not applied to triggered events. A history event that can respond to a trigger must verify that the trigger entities meet the event’s binding criteria. I thought this was happening but it wasn’t.
      • Refactoring. Event triggers were converted from derived classes to a single class inheriting from ScriptableObject. All of the trigger subclasses did the same thing (mapping entities to trigger parameters), so separate classes were unnecessary.
    • Map generation improvements and fixes
      • Chain Map Element Types. The map graph analysis that occurs during map generation identifies all the sequences of rooms on the map (“chains”). The new Chain Map Element Type enables these chains to be populated with specific sequences of room types. For example, a treasure room could be placed at the end of a 3-room chain, with a trap room and monster room protecting the treasure.
      • Dead end removal not working. I broke this a while ago but didn’t want to fix it because I was consumed with history generation. However, now that I’m doing more playtesting again, repeatedly running into dead ends is a real annoyance.
    • Rethemed website. The Legend website has had some long-time minor formatting issues due to limitations in the WordPress theme it uses. I could’ve directly edited the CSS to fix those issues, but didn’t want to expend effort. A recent issue cropped up, I assume from an update, that changed the font of much of the body text. That was the final straw; time for a new theme. I looked around a bit but couldn’t find any that suited the purpose. I wanted a theme that was barebones, fully configurable, and worked with minimal images and content. I landed on the WordPress Twenty Twenty-Three theme. This theme uses the new design tools in WordPress 6.1, which I was unfamiliar with. Though there was a bit of a learning curve, I enjoyed working with the new theme. A few things were unintuitive, but overall, the new design tools are more elegant and easier to use than the previous tools.
    • Updated Unity and Rider. I was using the LTS version of Unity from over two years ago (2020.3.15). The 2023 GDC Unity Roadmap presentation, particularly the mentions of performance improvements, inspired me to update to the latest version of Unity. The upgrade process was surprisingly smooth. There were a few hurdles with build version, assets and Rider integration, but nothing too time-draining. In-game, there are some animation, movement, and lighting issues I need to fix. The game runs fine otherwise. I was blown away by how much faster the game builds and loads now. This is going to save me so much time! It appears Unity has made improvements in this area, but I also benefited by moving from an Intel to Apple Silicon Unity binary.

    Next week, I’ll fix the issues introduced by upgrading Unity, add more history generation event types, and address open bugs in the main game loop.