- Map generation optimization. I knew as I wrote Map Generator 2.0 that some of the code was horribly slow and wasteful and would need to be optimized later. Map generation had ballooned to 10-15 seconds and .5 GB of memory. It’s now down to 3-5 seconds and 200 MB of memory, and there’s much more room for improvement. The optimization techniques were converting LINQ statements to for loops and reducing use of temporary lists. One optimization example involved how connections between rooms are stored. I started with one list that stored all original connections between rooms. Then I created a new list for connections that constructed loops and another new list for connections that joined sections. I used separate lists instead of the original list because I needed to do different things with the items in these lists, and it was more expedient to create new lists (though a little voice inside my head was telling me to slow down and do it the right way). I added a fourth list when I realized I needed to track each connection in each room that used the connection (as opposed to only the room that originated the connection). Because it was sometimes necessary to get all of the connections, I created a property that combined all four lists into one new list. Yikes. The allocations… The solution was to combine the lists into one and add an attribute indicating the type of connection. This caused way more rework, and troubleshooting issues caused by the rework, than I anticipated. At least the rework made the code simpler and easier to understand, which is always beneficial.
- Movement optimization. Enabling actor actions to be displayed simultaneously exposed a problem: the movement code took a long time to run, causing actors to instantly move to the next cell rather than moving incrementally over multiple frames. Linear interpolation is used to calculate how far an actor moves each frame, with the actor’s movement speed and elapsed time since the last update as inputs. I ran the Unity profiler and identified the main causes: dynamic lighting and excessive Unity log calls. The log calls are easy enough to deal with; they won’t be in production releases. Dynamic lighting, which uses the Smart Lighting 2D asset, is a dilemma. I want to keep it in the game but I’m not sure how much I can optimize it. Temporarily disabling the lighting and logging fixed movement between two cells, but there was still an issue when moving across multiple cells. Actors momentarily stopped at each new cell before moving again. I had seen this before and knew the source: the state logic in the Update method caused some frames to skip movement. For example, an Update call would determine that all actions had finished in the previous Update and it would update the turn state. Movement wouldn’t resume until the next Update. With nested state logic (there are turn phases, actions phases, and action step phases), several frames passed before movement resumed. This was resolved by modifying the state logic to process state changes in the same update when applicable. For example, when an action step finishes, the logic will start the next action step in the same update.
- Displaying actor actions simultaneously. I reverted the changes I made last week to present actor actions simultaneously. It became clear that an enormous amount of rework was needed to separate action logic and presentation. Fortunately, a much simpler solution had been right in front of me the whole time: asynchronous actions. Instead of waiting for each action to finish being presented, I’d simply start off each action at the same time. I didn’t consider this initially because one actor’s actions can affect another; I believed that all actors’ actions had to be resolved before any of them could be presented. For example, if the player hits an enemy, and that enemy dies, the enemy shouldn’t be able to move. I still had to make some modifications to get this working, such as checking that an actor is still alive, and tracking cells that actors are moving to before they reach the cell (so that other actors don’t attempt to move into the same cell).
- Pathfinding improvement. Over time, I’ve changed my mind on how actors interact with other actors and objects that are diagonally adjacent. I may change my mind again, but what’s certain is that there needs to be a way to allow interactions with adjacent objects in ordinal or cardinal directions, depending on the object. Currently, a melee attack can be performed from an adjacent diagonal cell, but opening a door cannot. Until this week, the latter was not possible because of a limitation in the pathfinding code – since actors can move diagonally, and the pathfinding code finds the shortest route, paths end at a cell diagonal to the cell containing the object being interacted with. The fix for this was to change the path destination based on the interaction range of the object. An object with a range of 1 can only be interacted with if the actor is adjacent to the object in a cardinal direction.
- Better debugging statements. It just occurred to me that I’ve written a lot of bad debugging statements. I typically add debugging statements while troubleshooting a particular issue. They make sense when working within the context of an issue, but not on their own. Without context, they do more harm than good because they increase cognitive load, which is already high from being in troubleshooting mode. I improved these statements by adding more relevant state information to them. I also rearranged the statement in some cases so that the subject of the statement (actor, item, etc.) was at the beginning of the statement. This made it easier to skim through the debug log.
- Inspector improvements for ScriptableObjects using Odin. To reap the full benefit of Odin, I added Odin attributes to all classes inheriting from ScriptableObject. These objects are now easier to view and edit in the Unity Inspector.
- Duplicate door bug fix. Doors recently stopped opening when they were clicked. Actually, most doors didn’t open but a few did. I reviewed the pertinent code but couldn’t find a problem. I started a game and right-clicked on a door to open the Inspect Panel, which shows everything in the cell. Nothing appeared to be out of the ordinary, and the door opened when I clicked it. Then I clicked another door. This one didn’t open. I opened the Inspect Panel and found the problem: there were two doors on the cell. It turns out that the recent change to track connections between rooms in both rooms caused most doors to be added twice. The fix was trivial; I just had to exclude door creation on the duplicate connections.
Next week, I’ll further optimize map generation. Possibly, I’ll start coding the procedural history generation, which I’ve been slowly designing over the past month.