Wouldn’t it be cool…

Ai Vision is what determines whether an enemy can see a target or not, and it’s trickier than I thought.

First, when I started working on Mortal Rite, I had not ever worked on creating Ai for a game so creating an Ai that works well and meets or exceeds the Ai in other established games was an order taller than Shold. Shold is tall. Bigly tall. I was eager to make a really good Ai so I went for it and have been learning the whole time.

Mortal Rite Steam update about the Road Map being released.
Mortal Rite Steam update about the Road Map being released. Shold(left) is super tall compared to normal sized humans, and this image doesn’t even do his height justice!

What does Ai Vision do exactly?

Enemies need to be fair and predictable for players to have a good gaming experience. This means that a player should expect an enemy to acquire their character as an attackable target when it is in line of sight(LOS) and within a certain range.

Easy enough: The Ai just needs to do a line trace to see if a target is visible (e.g.: Not blocked by something that should block sight). Trace from the location of the enemy’s eyes to the location of the potential target. Record whether or not the target is in line of sight and that helps determine if the enemy should attack that target or not.

Line Trace Logic, sort of.

Hol’ up. What are potential targets?

You, the reader.

Right. What are potential targets?

Potential targets are… targets that are attackable. Done.

Me

Real talk: How do we know what targets to trace against? We use the teams system built into Unreal Engine to control what is hostile to what. Entities on the same team are friendly. Entities on different teams are hostile. There are roughly two types of targets that an Ai should worry about:

  • Characters – Other enemies that are hostile or players that are hostile.
  • Destructibles – Objects in the world that can be destroyed (such as barrels, obstacles (walls potentially)) that are hostile. By default all destructibles are hostile to all characters.

Characters are fairly easy because we maintain a list of the characters in the world that the Ai can iterate through and choose the hostile characters. EZ.

Destructibles are fairly easy to get as well. I chose to use a sphere trace so that each Ai only needs to worry about destructibles within its attack range. Originally I used a sphere trace for Characters as well, but that’s silly when I have a curated list of characters at my disposal.

Get Targets. Are targets in LOS?

Non-Characters are more tricky. If we want an Ai to be able to do damage to a destructible and have the capability of destroying a destructible, we need that destructible to be hostile relative to the Ai. Hostility gates direct damage. This presents a new problem which is that if we put a lot of fun destructibles all over the world, the Ai will potentially run around killing those destructibles rather than fighting the player.

Solution to the Ai running around killing destructibles is to not allow the Ai lock on to those targets. Destructibles that the Ai cannot lock onto are destructibles like barrels, torches, some walls, etc.

Lock On Interface

The Lock On Interface exists to determine what entities can lock on to other entities such as when a player locks on to a Sword Knight and you can see the little white dot that shows you are locked on to that target. Abilities (everything you can do in Mortal Rite is an ability, basically. Even melee attacks.) can then be made to reference the lock target or not.

Locked on to a Sword Knight

The Lock On Interface uses one or more Target Points, which are literally placed on the model by a developer, to indicate what parts of a target can be locked on to. In the case of the Sword Knight the target point is in the middle of his

Target Point (the RGB Axis Gizmo) on the Sword Knight in the UE5 editor.

I updated destructibles to allow them to be locked on to by:

  • No one – For things like barrels (fodder)
  • Only Ai – Player-created asset like Shold’s Wall
  • Only Players – A barrier that only a player needs to destroy to get somewhere
  • All

This means that we can have destructibles that only exist to be environmental fodder that accidentally gets destroyed or a player can choose to destroy just because. But most importantly an Ai will ignore it.

Updated logic flow becomes…

Ai Sight Logic with Hostile and Lock On checks.

Wouldn’t it be cool…

Someone in the office

Probably both the best and worst thing to hear around the office.

…if an enemy couldn’t reach its target because of a destructible it would swap to the destructible and destroy it to get to its target?!

A younger me

How hard could it be? Destructibles are already tracked by the Ai and attackable by the Ai. Now I just have to figure out what is blocking the Ai’s current target and, if the blocker is attackable, increase the blocker’s threat so that the Ai prefers that target over the Ai’s current target! Genius.

Updated logic flow!

Logic flow with Block threat and Blocker Threat resetting.

So this basically works. Other Alex showed off the Ai at this point in a TikTok video a while ago where an Evil Initiate attacked a wall in order to get to a target. It was a short clip, but it worked at that point.

The original scope of “The Ai should attack obstacles to get to targets” was Shold’s Great Wall that cuts off vision to the Ai’s target and also cuts off Navigation Mesh so that the Ai knows that it cannot move through the wall.

What’s navigation mesh, precious?

Gollum

Navigation Mesh is a procedurally generated mesh based on rules (such as radius and height (size) of an agent using the navigation mesh) that determines where an Ai can path. Navigation Mesh can be pre-computed so that at runtime it doesn’t have to be computed. Navigation Mesh can also be updated at runtime when obstacles move around so that Ai knows that it cannot path through moveable objects at runtime. Pathing calculations use Navigation Mesh in order to know where it can potentially path instead of having to compute pathing against the world’s geometry itself. Navigation Mesh is basically a simplification.

Green bit is the Navigation Mesh.

New Ai Vision Smell

What’s new is:

  • Better Multiple Target Point Evaluation
  • Better Base Target Threat
  • Better Reachable Evaluation

Multiple Target Point Evaluation

In the time long, long ago we determined that it was necessary to have multiple target points for larger targets such as the Constructor Boss. It was bad that players could only lock on to a single point on the Constructor Boss because it caused bad camera interaction. Solution was to add multiple Target Points and Alex updated player lock on logic to allow smooth cycling through those Target Points. It sounded like a good solution, but honestly wasn’t something that I thought I would like. But I do like multiple target points and I am happy that we have it.

What it means for the Ai is that the Ai can also have access to multiple target points for targets that are large. Since everything is modular and target points are defined by the Lock On Interface, it’s really simple to add N Target Points as needed.

Random Wall Destructible with multiple Target Points (yellow hand drawn Xs)

What’s really useful about multiple target points for the Ai is that it helps address a problem that most people probably wouldn’t think about: Whether an Ai can reach a target to attack it.

Half of this game is 90% mental.

LOS is only half of the “Can the Ai attack a target” equation. The real question is “Can the Ai see a target and can that Ai get to a location that allows it to attack that target”. The Ai has two types of attacks at a really high level: Melee attacks and Ranged attacks. When an Ai decides what it can do it needs to know if it can use a melee attack or a ranged attack against a target. Part of Ai Vision Logic is determining if a target is Melee Reachable or Ranged Reachable.

  • Melee Reachable: True if there is a pathable location that is within the Ai’s attack range to a target.
  • Ranged Reachable: True if the target is in LOS and within Missile Range.

It might be becoming clear that all of the above Ai Vision logic talk was really just scratching the surface of what the Ai is actually doing, but I felt I needed to cover all that to setup a foundation for the real problems that I’ve solved recently.

A problem caused by needing to be within range of a target in order to attack it is that for large targets that have one target point the Ai may or may not be able to get within range of the target point, but it can get in range of the target’s location. This looks like the Ai should be able to attack the target, but the Ai, trying to be smart, is overthinking the situation and rejecting the target because it can’t reach the target point.

One target point near the pivot of the asset is attackable.
Level designer flips the asset 180 degrees for visual reasons

Multiple Target point handling allows the Ai to dynamically choose the best target point for a target. This happens every evaluation so that the Ai can handle a moving target that has multiple target points and have a better chance to always be able to attack that target.

Evaluate the best target point and use that.
Ai can’t see the two target points that are near the bottom because they are below the terrain. If these were the only target points then the Ai wouldn’t be able to see the target at all.
Multiple Target Points being evaluated by Sword Knight.

Base Target Threat

Originally the threat table assumed that any target with 0 threat should be removed. Everything on the Ai’s threat table was either Threat greater than 0 or Threat equal to 0. After talking over all of these issues with Alex, Alex suggested adding an way to define base threat per target. So, a player would have a higher base threat than a destructible, but the destructible still had to have a base threat of 1. This would allow an Ai to choose a player, which is a more desirable target for the Ai to attack, over a player. And BAM, now the Ai chooses the Players over the Destructibles when they are evaluated at the same time.

The original base threat change was good, but the ultimate solution was to allow Ai to track targets that have 0 threat and not reject them. The new paradigm is everything with Threat greater than or equal to 0 stays on the threat table and anything less than 0 is removed. Also, anything that has a threat of 0 is not chosen as a target.

Boiling this down with everything else talked about:

  • Targets can have multiple target points that allows them to be attacked because at least one target point will be in LOS and reachable
  • Destructibles start with 0 threat, which allows the Ai to track them, but prevents Ai from attacking them when they are the only targets.
  • Targets gain threat when they are a blocker of a target that the Ai wants and therefore becomes the Ai’s priority when they should be.
  • Blockers reset their threat when they are no longer blocking a target so that the Ai resumes its pursuit of the real target: the player.

Better Reachable Evaluation

Multiple Target Points allow for the Ai to better see targets that it could not see before due to a single target point being obstructed and also partially addressed the reachability issue because multiple target points increases the chance that a target point will be in range for the Ai (optimization for static targets would be to place fewer target points in better places than to just have a bunch of target points. Don’t worry: we’re not going to be stupid about the number of target points we use because performance is important).

It was because everything was working better that a new problem appeared: there can be obstacles on the ground that make a target partially unreachable for melee attacks.

In a setting where there is debris on the ground or anything that blocks a direct path to a target that is reachable in other ways the Ai would still decide to not attack that target, which is bad and makes the Ai look dumb. And we don’t want dumb Ai.

Nav blockers do not block LOS to Target, but do prevent the finding of a reachable location within range of the target.

The solution that I’ve put in place for this is to test a half circle of reachable locations from the chosen target point. This means that in the above case additional reachable locations would be tested to the sides of the red location.

The Ai can now reach the target due to the extra testable locations. As expected.

Conclusion. Finally.

This has become a long post. Hopefully interesting. Now for the system in action and potentially the TLDR of the whole post.

Everything in motion. Performance pretty horrible due to the unique way that Unreal Engine handles drawing debug shapes. Performance without the debugging is fine.