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).
- 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.
- 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.
Leave a Reply