Been a bit since I’ve been able to take the time to put out a blog and so much has happened.
First, our Kickstarter Campaign was successful! Super nervous about the Kickstarter not being funded because everyone on the team put in so much work to make the content shown on the Kickstarter. Rod spent a ton of time getting the Kickstarter Trailer just right even though he had to use the old build of Mortal Rite (0.5.3) with the newer content in it and that build had bad framerate issues. But that’s expected, right? Sill in development and we haven’t been able to optimize yet.
Let’s dive in to what I’ve been up to on the game itself.
Editor Time Persistence
Ran into an issue where our placers weren’t persisting our settings between edits. What would happen – sometimes – is that an edit would be made (such as rotating an enemy via placer or changing a spawn setting on the enemy via placer) and then (at runtime in editor or when the level was reloaded) the change wouldn’t take effect. So, I tracked this down to the edits not marking the placer or the enemy dirty so that the changes are persisted, figured out how to mark the needed bits as dirty, and now that all works as intended. This is one of those things that isn’t exciting and it took a while to figure out, but was necessary and now things that were already supposed to be done are now done – kind of. There is more Placer and Ai settings changes coming soon to support new gameplay that we need in the new build.
Part of our damage calculations takes target armor into account as most people would expect. The placeholder armor contribution to the damage calculations was a flat damage reduction that didn’t scale well. Example of flat damage reduction is 100 incoming damage and 20 armor would yield 80 damage. Literally Damage In – Armor = Damage Out. This doesn’t scale well because you can outpace incoming damage with armor fairly easily. There is a place for flat damage reduction, but I don’t think armor is it.
After doing some research, we now have armor calculations that can scale infinitely, takes armor penetration into account, which we didn’t have before, and scales well with target level. No specifics on how this works to keep things hush, hush for now.
Generic Ai Ability Traces
People are often curious how the Ai makes decisions. As soon as I gather my thoughts about how our Ai decides what to do to try to explain it, I kind of give up on explaining it because there is so much going on at an implementation level. So, it roughly comes down to:
- Ai can run abilities. Abilities range from an advance ability that moves the Ai into melee range to use a melee attack to a special attack that is only used when it makes sense to be used.
- Ai will, basically, run the ability with the highest score. Filtered abilities have a 0 score and are rejected.
- Ai collect information from their environment about targets: range, LOS, various statuses, etc.
- Some abilities have cooldowns.
- Ai have a Tag Relationship Mapping between target statuses and abilities that determines which abilities it can use from a pool of abilities each time it evaluates what ability it should be using.
- Ai can – now – also use generic traces to determine which abilities to use.
In the previous project, Ai would do a base pass on each ability to determine a if it could run an ability. It would reject abilities based on settings, such as if a target was not in line of sight and the ability needed the target to be in line of sight, the Ai would reject the ability immediately.
Old Project (Mortal Rite 0.3.x through Mortal Rite 0.5.x): Evaluate base score and filter abilities based on ability criteria in C++. If this filtration passed and there is a Blueprint implementation for the scoring function, run the Blueprint Scoring Function to get the final score of the ability. If this filtration passed and there is no Blueprint Scoring Function, use the base score as the final score.
Downsides here were having to kick out to a Blueprint function to get a final score. This was actually used quite a bit in order to get final scores. It was convenient because Blueprints are convenient, but Blueprints have a higher overhead compared to C++, and it was generally hard to maintain. Each ability ended up having its own Blueprint Scoring Function implementation and while there were some that were similar to each other so you could copy and paste between abilities, most were unique implementations with completely unique logic.
One of the most important things done in the Blueprint Score Function in the old project was tracing to determine if an ability should be used (not LOS. LOS is part of an earlier check). A lot of the Ai use abilities that can damage a specific area or need to know if it will be able to damage one or more targets if used. Example of this is you have an ability like the Sword Knight Explode Ability where if the Sword Knight felt overwhelmed he would use explosion to push everyone back and recover. One of the deciding factors to use this ability is if one or more targets is within the range of his explosion. If there is nothing within range, he should not use the ability even if he feels overwhelmed. Intelligent.
New Project (Mortal Rite 0.6.x+): Filter using Tag Relationship Mapping, score based on criteria, if there are traces configured run the traces. If a trace fails the ability cannot be run. If all traces succeed, the ability score is returned so that this ability can be sorted.
Currently, I have 100% of all Ai ability decision making in C++.
The goal is to allow everything that was being done in Blueprints in a non-reusable way to be done in C++ via configuration in a reusable way.
So, what happens when this is all put together? Let’s find out.
Sword Knight that is setup to acquire targets and then can only use his Explode Ability. The Explode ability is setup to be used whenever a sphere trace in front of the Sword Knight (X: 200, Y: 0, Z: 0 relative to the Sword Knight’s forward vector) is triggered with a hostile Pawn (in this case it’s me as Dawksin).