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 put limited time into game dev this week, and a large portion of the time went into some difficult code design/architecture thinking rather than coding itself.
New Map Elements: Library, Alchemy Chamber, Armory. These are new room types that contain objects arranged in simple patterns (tables, bookcases, weapon racks) and applicable random assortments of items. I want to improve the variety of the object placement patterns. I sketched out some patterns for the library (below) to visualize the end result and work backwards to develop the generation logic. It got me wondering about the feasibility of an alternative approach: training a machine learning algorithm to generate patterns from a set of example patterns. I’m going to research this next week.
AI 2.0Design. Last week’s addition of AI states and actor responses to game events has necessitated more rework than anticipated. The original AI was player-centric; other actors only cared about what the player was doing. The end goal was always to allow actors to respond to a variety of events, but I limited the initial implementation to player events for simplicity. I’m now modifying the design so that actors can potentially act on any event. This requires some optimization as well because, for each event, a check needs to be performed to determine if the actor notices the event. It’s further complicated by the fact that each event may be detected by sight or hearing.
Next week, I’m continuing working on the AI 2.0 coding.
AI State and Game Event Detection. One of the planned Release 3 features was to display icons next to enemies based on whether the enemies had seen the player. In addition to displaying the icons, I needed to fix some long-standing AI issues that cause enemies to react too soon, or not soon enough, when seeing the player for the first time. I also needed a way for enemies to respond to events other than seeing the player, such as a door opening, and to respond to events that are heard but not seen. To handle all of this, I implemented a simple finite state machine to manage enemy state. I added listeners to enemies that receive all game events, determine whether they are able to perceive the event, and trigger state transitions based on perceivable events. The solution is working well with the first set of test cases; I now need to apply it to all game events.
New Decor Map Elements: Braziers, Bookcases, Tables. These Map Elements place these objects in logical patterns (for example, in corners, in a grid, in a rectangle, in the center) in a specified area, typically a room. They are used by different room types such as Libraries and Shrines.
Procedural generation improvements. I changed the basis for some enemy/item quantities from a percentage chance per cell to a random number within a range. This prevents situations in which a large room has too many enemies or items. I reduced the number of combat encounters in a level (too much combat nade the game overly difficult and tedious to play).
Finished implementing treasure chests. Chests contain random or specific items now and chest contents get transferred to inventory. It wasn’t as straightforward as it sounds. Some odd bugs were introduced (and subsequently fixed). For example, the player started getting two copies of each item in the chest. I spent a few hours trying to figure out what was happening. It turned out that when the chest changed from an open chest to an empty chest in the process of taking the contents of the chest, the chest was being destroyed and dropping its contents (routine behavior).
Next week, I’ll finish the AI State / Game Event feature. I’ll add a few more Map Elements as well (my goal is to add at least a few each week).
I stuck to the plan this week (for a change) and finalized the Release 3 features. I’ll publish a summary in next week’s post.
Added cell-based status effects. These are effects that are added when an actor moves onto a cell and removed when an actor leaves a cell. An example is a Darkness effect that limits the player’s visibility. This builds on the existing capability to add status effects by using an item or interacting with an object. This feature was unexpectedly difficult to add; a lot of the status effect code and Unity configuration needed to be reworked.
Room features. Each room has a room type that determines what’s in the room. For example, a Shop Room contains a shop and a Treasure Room contains treasure. I needed to be able to apply different features to a room to do things like placing a trap inside of a treasure room. I added a base RoomFeature class and some child classes like ShopRoomFeature and TrapRoomFeature. This makes it possible to mix and match different features can create a wider variety of rooms than was possible using room types. I still need to fully incorporate this into the map generator, specifically by replacing the existing room generation with feature-based generation.
Overhauled effect triggering. I was never comfortable with the way I originally implemented the configuration of status effects caused by an object or item. Effects inherited from MonoBehaviour so that they could be added to game objects in the Unity editor. It worked, and it was designer-friendly, but it was unintuitive (it wasn’t obvious the purpose of the component was to cause an effect) and too heavyweight (multiple effects required multiple components). I converted effect types to ScriptableObjects and created a new class containing a list of effect types. I called this class TriggeredEffectCollection and used it to replace the individual effect components. Adding effects is now easier (add/remove from a list editor in the Unity inspector) and the purpose of the component is more clear.
Removed the limit of one static object per cell. Now cells can have any number of static (not moving) and dynamic (moving) actors/objects. For example, a cell can now have a floor trap and a statue. This also simplified the code because now there’s just a list of actors/objects instead of a list plus the static object.
Extensive actor AI logging. Now the log very clearly shows why an actor chose a particular action and not others. Very handy for troubleshooting. The basic AI process is to 1) identify the potential actions and 2) select the best action. The logging explicitly indicates why potential actions were available or unavailable, and why potential actions were rejected or selected.
Added new effects: Cure Poison, Darkness, and Paralysis.
Added new items: Cure Poison Potion, Throwing Axe.
Damage type resistance. Physical materials now have explicit resistances to different damage types. This will be used to do things like making an axe more effective against a wooden door than a short sword.
The plan for next week is to replace Room Type-based room generation with Room Feature-based room generation and start on two new features: Enemy Alerts and Knowledge. The former provides visual indicators for whether enemies have seen the player and the former varies the descriptions of things based on the player’s knowledge.
Improved cell highlighting. When selecting a target cell to shoot at or throw an object at, highlighting now excludes cells that can’t be seen
Improved actor tracking. Enemy and NPC AI controllers were hardcoded to track the player. This caused a bug where other actors weren’t tracking the player because they were being instantiated during map generation, before the player was instantiated. But, the real problems were that a) the AI should determine what is being tracked instead of hardcoding it and b) the decision to track should be made when the other actor becomes visible, not when the tracking actor is created. I removed the hardcoding, extended the AI to determine what an actor tracks, and moved adding tracked actors to the point when the tracking actor sees the actor to be tracked.
Improved health bar visibility logic. Health bars used to appear over every damageable, dynamic actor. Now they only appear when an actor has been damaged, and appear for static actors like doors.
Miscellaneous refactoring and code improvement, which includes:
Changing variables from public to private
Removing unused classes and code
Consolidating redundant code
Many bug fixes. When things break, I’m spending more time on the fixes to lower the chance of the same things breaking in the future. I’m making progress but I have a lot of room for improvement.
Trimmed scope. I moved some planned enhancements from Release 2 to Release 3. I’ve been working on Release 2 for 15 months. That’s a lot longer than I want to spend on each release, so I pushed out the remaining planned enhancements to the next release.
Next week, play testing and bug fixing continues as I work toward wrapping up Release 2.
Highlighted cells for shooting/throwing. When selecting a cell to shoot at, or throw an object at, eligible cells based on the shooting/throwing range are now highlighted.
Fleshed out effects design. Effects (poison, invisibility, paralysis, etc.) weren’t completely or cleanly implemented. I needed to think through all the ways effects will work in the game. Effects can be caused by using an item (quaffing a potion), in combat (stabbing a target with a poisoned blade), or by moving onto a cell that causes effects (a paralysis trap). They can last for a specific number of turns, or only while the actor is standing on the cell causing the effect. Also, I had “cell effects” like fire and poisonous gas that caused effects on actors but also behaved like actors (fire can spread to adjacent cells with flammable contents). After filling in the gaps in my understanding about how effects work, I was able to clean up and complete the effects code.
Synchronized attack and damage animations. Combat animations didn’t feel right. I recorded some combat and watched it back in slow motion. I discovered the issue: the damaged animation didn’t start until the attack animation finished. The problem with this is that the point of attack in the attack animation is in the middle of the animation sequence. I had to reduce the damage animation delay so that it started in the middle of the attack animation. Another issue was that actors that were just hit were starting their own attacks while their damage animation was still playing. This didn’t look right and caused combat to play out too quickly to discern the sequence of events. I fixed this by preventing the next actor from acting until the damage animation finished. Now combat looks much better.
Basic object interaction AI. An unintended behavior was occurring where actors were interacting with objects as they moved. For example, a skeleton archer in pursuit of the player would stop to smash a pile of bones. I think actors should be able to interact with some objects, but AI needs to dictate which objects an actor will choose to interact with. I implemented a simple check (“if object = X, don’t interact with it”) to control which objects an actor can interact with. I’ll add more intelligence later on. For now, I just needed to prevent actors from getting distracted.
Design incorporated into iterative coding and refactoring. I’ve written a design document for Legend but it’s not complete. It’s more of a semi-organized collection of ideas. At some points during coding, I get stuck because I haven’t fully thought through the design of a particular feature. As I’ve done with refactoring, I’ve started to incorporate game design in an iterative manner. I step away from the code editor, open up the design document, and write out a detailed design for the topic in question. My new process is small cycles of design, refactoring, and coding. It’s not quite Agile Software Development, but it’s similar.
Ranged combat improvements. If arrows hit a target that they can’t damage, such as a stone wall, they now hit the wall and land on an adjacent cell. I also added minimum ranges to prevent arrows from being shot at point-blank range.
Minor code clean up and bug fixing.
Next week, my main goal is to finalize the scope for Release 2 and work on the todo’s within that scope. Release 2 won’t be a public release, but I’ll share some gameplay footage.
Player health and gold indicators got a visual upgrade. They used to be shown in a single text control as numbers, which worked but wasn’t visually appealing. I moved player health to a health bar. I moved the gold counter to the inventory panel. I don’t think it’s necessary for players to always see their gold amount, so I removed it from the main screen. For both the health bar and gold counter, linear interpolation (lerp) is used to smoothly transition to new values.
Now that basic AI is in place for enemies, I occasionally encounter (pleasantly) surprising behavior during testing.
For example, a skeleton archer kept trying to shoot the player when the player ran out of the room and was no longer visible to the archer. I checked all of the associated code and still couldn’t figure out why the archer was shooting. Then I realized the archer wasn’t shooting at all; it determined it couldn’t shoot the player, so it was throwing the arrows in its inventory instead (the player visibility check for thrown items wasn’t implemented yet).
Another time, an archer ran out of arrows and started moving toward the player for a melee attack. It walked over an arrow that had missed the player, picked it up, and shot it at the player. I had no idea that would happen. Turns out, the item pick up code was used by both the player and non-players.
Lots of small-scale refactoring, improvements, bug fixes this week.
I haven’t planned out next week yet. One thing I need to get done soon is the scope of the next playable version. Without a specific set of features to work toward, I’ll never get to “done.”
Improved Enemy AI. Enemies will now look through their inventory to determine the best action to perform. If an enemy has both a bow and a sword, and the player is far away, the enemy will shoot at the player. The criteria is currently range and damage, but the criteria and logic can be expanded to make the AI more sophisticated. Also fixed ranged combat in the process (main goal for this week).
Random item drops when enemies die. Experimenting with this, not sure how it will work in the final game. This also applies to items inside of objects like barrels and piles of bones.
Enemies drop their inventory items when they die. Another experiment; may be difficult to balance.
Destructible objects that block vision. This is similar to the Vegetation in Pixel Dungeon. Not sure how I’ll use it, just a fun mechanic I was able to quickly add while working on some related items.
Miscellaneous refactoring, including downsizing the number of variables in object types by moving the variables into the appropriate component (for example, moving health to a Damageable component).
Many, many bug fixes, mostly still lingering from the recent major refactoring.
Next week, the focus will be on bug fixes and improving performance when multiple actors are acting as I work toward completing MVP2.