- Map generation optimization. Map generation was further optimized this week, bringing memory allocation to ~50 MB (from 5.5 GB originally). Optimization is taking longer now. The low-hanging fruit has been picked; the remaining inefficiencies are more spread out and smaller percentages of the whole. Good gains came from removing some logging calls, which are expensive in Unity. Another boost came from reworking the graph shortest path algorithm, which gets called for every unique pair of rooms. When I optimize, I focus first on memory allocation and second on duration. Memory is in good shape, but it is still taking 5-6 seconds to generate a map (the 3-5 seconds I reported last week was incorrect; I wasn’t looking at the actual duration data). My goal is under 3 seconds, so there’s still some optimization work to be done. I’m targeting code that is frequently executed. The worst case is slightly over 1,000,000 executions.
- History procedural generation design. The conceptual design is done. The next step is software architecture/code design.
Next week, I’ll focus on the history generation implementation.
I missed the 11/30 target date for Release 3 but expect to complete the remaining work this weekend. As with the prior two releases, this release is internal (i.e. I’m the only person playing the game). Release 4 will be the first release available to others, though I’m not sure in what form yet. Certainly the game won’t be ready for Steam Early Access, but I need to start getting feedback from other players.
- Completed AI 2.0. Actors now determine actions from their observations. Observations are generated from the game events that an actor perceives as well as significant actors/objects/items in an actor’s field of view. The AI determines which observations an actor cares about and the action to take in response to the observations. The game events actors respond to include actors moving, doors and chests opening/closing, and projectiles hitting targets or landing on a cell. A number of small improvements were made to optimize performance. The most impactful optimization has been to reduce the number of actors running the AI code each turn. Also, performance was improved by organizing the AI code so that the most expensive operations are performed last and preceded by inexpensive checks that allow methods to exit before running expensive code.
- Fixed a lot of bugs. Half of the bugs were introduced by the AI changes and the other half were random bugs found during playtesting and fixed to tighten up Release 3.
Next week, I’ll create the Release 4 feature list. The focus will be refinement rather than new capabilities and content. New features and changes will be smaller; I don’t plan to implement any new systems or do any major refactorings. More time will be spent on bug fixing and testing than in prior releases. I also plan on finding an artist and replacing the stock images that are currently in the game.
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.
I’m still focused on testing and bug fixing as I try to wrap up the second release by end of May.
- Switched from Visual Studio for Mac OS X to JetBrains Rider. I’m forcing myself to give Rider another try after quickly dismissing it the first time I installed it. Rider is highly recommended by some well-respected Unity developers. I didn’t like it at first because I felt that it excessively decorated and annotated the code displayed in the editor. And, some key features I constantly used in VS were missing or worked differently (keyboard mappings, search box, finding usages). But, Rider’s growing on me. Debugging is much better; being able to see the current values of variables inline with the code is awesome. Expensive method calls and heap allocations are highlighted. It’s better integrated with Unity.
- Optimization. There was a brief but noticeable stutter each time the player moved onto a new cell. I fired up the Unity Profiler and found the cause: on the start of each new turn, every actor checked which other actors it could see. The check was expensive; it involved getting all the cells between the two actors (using the Bresenham line algorithm) and iterating through the cells until reaching the other actor or hitting a cell that blocked line of sight. The performance was terrible because the line method was allocating a new list every time, and the check was being performed many times (O(n2)). Initially I tried using the ArrayPool class to avoid the new list allocations, but it didn’t work well for a few reasons I won’t go into. Then I realized a list wasn’t needed at all; I just needed the last point. I wrote a modified version of the Bresenham line algorithm that started from the origin and stopped when the current cell matched the specified predicate and returned the current cell. I reran the Profiler and the performance was much better, but could still be improved. Having each actor call the “CanSee” method for each other actor was simple to code but very wasteful; most of those checks weren’t needed. I changed the code from checking each actor in the list to checking for actors only in the other cells the actor could see. This made performance worse, because the checks against all the cells were costly, and the map is relatively sparse in terms of actors. I removed that change, and went back to checking the list of actors, but added a new check to determine if an actor is in the viewing range of the other actor before running the line algorithm. This optimization significantly reduced the number of times that the line algorithm ran and greatly improved performance.
- Created an instant status effect for adding/removing money. It seems a little strange to have an effect for money, but it basically does the same thing the instant healing effect does in that it’s changing an attribute by some amount. This allowed a few hard-coded references to money item types (like gold coins) to be removed.
- Added the ability for items to cause status effects as soon as they are picked up. This was needed for the money status effect and future instant effects.
- Fixed the health bar display logic. I kept having issues with when health bars were displayed and hidden. I’d fix one issue and create a new one. After sketching out the logic on paper the solution was obvious. The basic rule is: health bars are only visible when an actor/object is damaged and visible. However, when an actor/object is first damaged, the health bar needs to appear at 100% and animate down to the % of health after being damaged, so the basic rule doesn’t apply. And, when an actor/object dies, the health bar shouldn’t be removed (even though health is at 0%) until the animation finishes and the health bar shows 0%.
- Peeled some more code off of the GameManager class.
- Fixed a lot of bugs found during playtesting.
Next week my goal is to complete testing on Release 2. I’ve found and fixed 100+ issues over the past few weeks, but it’s still going to be tight.