Target Selection

Selecting and prioritizing targets using the Apex Utility AI

Introduction

Targeting is a critical part of many games where computer driven AI and AI controlled by the player has to select a target from a list of possible targets for a given attack. Across game genres, from real-time-strategy (RTS) to first-person shooters (FPS), targeting is essential in great combat. Utility AI is especially good at prioritization and evaluation given many complex inputs.

Design

Targeting can be trivial, such as selecting the closest unit or the strongest unit. However, often targeting requires many more inputs to be intelligent.

Take the scenario of a squad based game, where the approaching enemy squad has both grenades, sniper rifles and machine guns. Who should we target first? If the grenade thrower throws the grenade, our entire squad might be wiped out. However, if our squad is spread out or if we have a visual to the grenade thrower (the grenadier), the grenade might be very ineffective. Hence, perhaps we should concentrate on the machine gun? Unless we are in cover, and we can easily move out of the killzone of the machine gun, but then what about the sniper? The sniper will pick our team off with high accuracy, unless we are in very close range to the enemy, in which case the sniper’s scope will be ineffective and the large sniper rifle will be more of a hindrance than an asset.
In such scenarios, selecting the target is context dependent, with no clear rules for which of the different targets is the most dangerous one.

Implementation

We start by implementing an ActionWithOptions<T> that takes a GameObject as its type argument.

The ActionWithOptions has a GetBest() method that takes the Context and a List<T> of the type specified in the ActionWithOptions<T> type as argument as input.

The above code shows an attack target selection minimal example (using the ExampleContext introduced in “Context”).

The observations that the AI stores in its context are thus a list of GameObjects of potential targets. These targets can be sampled using e.g. a scanner (See separate tutorial on Vision and Hearing).
To make an implementation for the above scenario, we need at least three (3)

OptionScorers.

Scorers Explanation
IsGrenadierHoldingGrenade If the grenadier is actually holding a grenade, he might throw it soon, and we should consider targeting the grenade thrower.
IsSniperPointingTowardsUs If the sniper is pointing our way, he might be aiming at us, and we should consider prioritising him.
AreWeInMachineGunKillZone If we are in the killzone of the machine gun, that means not too close, but still close enough for the machine gun to be accurate, we should prioritise the machine gunner.

However, in addition, we also add an OptionScorer that scores based on the proximity of the target. This means that if no other scorer returns more than zero (0) then we will select the closest target.

Note that the following code examples assume that a component can be found on all AI units called ‘WeaponComponent’, with certain methods for getting which weapon is in use. Note that for optimal performance the WeaponComponent should be cached, e.g. in the Context, through a custom data type encapsulating the GameObject or a (singleton) manager, rather than being ‘get’ on every option scorer execution.

The first option scorer, for evaluating whether a given GameObject is holding a grenade, gets the weapon component for the GameObject being evaluated and performs a simple method call to check whether a grenade is being held. Please note that components should not be get in an option scorer as this can lead to undesirable performance drain, especially before Unity 5.

The next option scorer evaluates whether a sniper is pointing towards ‘us’, meaning the AI unit performing the evaluation of which GameObject to select as attack target. First the weapon component is accessed to evaluate whether the GameObject in question is a sniper or not. If the sniper check returns true, a simple facing evaluation is executed to know whether the sniper is facing the ‘self’ GameObject (us) or not.

The third scorer returns true if the AI unit is within a certain range of the machine gun wielder, without being too far away for the machine gun to have effect. Thus, once again the weapon component is queried for whether the GameObject being evaluated has a machine gun, and if so, the distance is calculated and compared to a lower and an upper threshold, of which it must be between, for the scorer to return its score.

The final scorer simply returns a variable score from zero (0) to the set score (defaults to thirty (30)), depending on the distance to the GameObject being evaluated. A weight can be applied to decrease or increase the effect of this particular scorer compared to the others. The intention for this scorer, however, was to ensure that if two potentially equally dangerous targets are identified, the closest one should be chosen. Additionally, if none of the other scorers return their score, the closest GameObject will always be chosen as the attack target, thanks to this final proximity option scorer, as long as at least one GameObject is within 30 meters. Targets beyond 30 meters can be handled in different ways. The AI could consider completely different actions for targets at this range. Instead of a linear score, an exponential score could be used, so targets will always a score returned, even though this is approximating zero (0). The exact implementation should be dependent on the game.

Testing The AI

To test the AI we set up a truth table, testing different scenarios, and adjust the OptionScorers as necessary.

Input data from the game world

The table shows the input data for each target.

The IsHoldingGrenade can either be zero (0) if the Grenadier is not holding a grenade, or one (1) if the Grenadier is holding a grenade.

The angle of the Sniper is the angle between the Sniper’s forward direction and the direction to the AI. This can between 0 and 90 degrees, with 90 degrees meaning that the Sniper has his side towards the AI. We have capped the angle to maximum 90 degrees, indicating that the Sniper is not expected to e.g. turn his back to us.

The distance to the machine gun is the same as the distance to the Gunner. So this input can be reused for both OptionScorers. The distance to the Grenadier and the Sniper must be calculated as well.

Is Holding Grenade Sniper Angle Gunner Distance Grenadier Distance Sniper Distance
0 89 8 12 1
1 21 17 2 19
1 2 19 10 14
0 90 3 14 9
0 89 9 6 4
1 10 18 12 2
0 84 27 26 21
1 82 16 28 20
0 85 18 20 11
1 68 27 13 15
1 66 14 26 10

The scores returned from each scoring point per target

The input data is fed to the OptionScorers, which subsequently calculate a score that is returned to the Qualifier.

For the IsHoldingGrenade, the input of zero (0) results in a score of zero (0), and an input of one (1) results in a score of fifty (50).

For the Sniper, any angle under 30 degrees is counted as the Sniper facing towards the AI, and is given a score of forty (40). Any angle over 30 degrees is given a score of zero (0).

For the KillZone Score, any distance to the Machine Gun / Gunner between five (5) and fifteen (15) meters results in a score of thirty (30) being returned. Any distance below or beyond that results in a score of zero (0) being returned.

The scores for distance to the Grenadier, Sniper and Gunner are calculated the same way. The different between the score of fifty (50) and the distance is returned.

Is Holding Grenade Sniper Angle KillZone Score Gunner Distance Grenadier Distance Sniper Distance
0 0 30 18 29 22
50 40 0 28 11 13
50 40 0 20 16 11
0 0 0 16 21 27
0 0 30 24 26 21
50 40 0 18 28 12
0 0 0 9 4 3
50 0 0 2 10 14
0 0 0 10 19 12
50 0 0 17 15 3
50 0 30 4 20 16

The scores added for each target

Finally, the scores are added up for each target. For the Grenadier the IsHoldingGrenadier score is added with the GrenadierDistance score. For the Sniper the SniperAngle score is added with the SniperDistance score. For the Gunner the KillzoneScore score is added with the GunnerDistance score. The selected target is the highest scoring target.

Grenadier Sniper Gunner Result
18 29 52 Gunner
78 51 13 Grenadier
70 56 11 Grenadier
16 21 27 Gunner
24 26 51 Gunner
68 68 12 Grenadier or Sniper
9 4 3 Grenadier
52 10 14 Grenadier
10 19 12 Sniper
67 15 3 Grenadier
54 20 46 Grenadier

Notice that in the row where the Grenadier and Sniper receive equal scores. Even though this might not happen very often, we need to consider a potential tie breaker for this scenario, or we can simply select a random target in this case.

Drawing Gizmos

Drawing on screen Gizmos is a very helpful way to understand how in-game positioning of targets affects the AI. Using the Unity editor Scene View, specific situations can be tested out.

Shows the AI agent’s target selection while the grenadier is holding a grenade, under these circumstances the grenadier scores higher than the others.

Shows the AI agent’s target selection while the grenadier is not holding a grenade, now the machine gun wielder scores highest because the agent is within the “machine gun kill zone”.

Please refer to the Visualizers tutorials to see how visual debugging can be implemented in the Apex Utility AI.

Extensions

The target selection AI can be extended with virtually any OptionScorer imaginable. We can for example prioritise the grenade thrower if he has actually pulled the pin on the grenade and is ready to throw. We can prioritise any target with its side or back to us. We can prioritise the sniper if he has had more than five (5) seconds since his last shot, indicating that he might have a good aim at us or one of our allies.
The following table outlines some common OptionScorers relevant for target selection

OptionScorer Usage
TargetProximityScorer Scores higher for targets that are near, farther targets score lower
TargetRangeScorer Scores discretely, e.g. if the range is over a threshold a fixed score is returned
IsAttackTarget Scores if the evaluated GameObject is already set as our attack target.
IsTargetFacingUs Scores if the target GameObject is facing towards us.
IsTargetWeaponLoaded Scores if the target has a loaded weapon that is ready to fire.
HasLineOfSightToTarget Scores if there is free line of sight to the evaluated target.
IsTargetCloseToCover Scores if the target is close to a cover, which could protect him from our fire.
IsTargetMoving Scores if the target has a velocity above zero (0).
IsAllyCoveringTarget Scores if the target is being covered by one of its allies.
HasDesiredRangeToTarget Scores higher if the range to the target is closer to a set desired range.
IsTargetOfType Scores if the target evaluates to a certain type, e.g. ‘Player’, ‘Enemy’ or ‘Sniper’ (depending on game.
HasTargetWeapon Scores if the target has a certain weapon or possibly just any weapon at all. Possibly scores higher for some weapons (more dangerous ones).
IsTargetWounded Scores if the target is wounded, possibly scores higher depending on the gravity of the wounds of the target.
DoesTargetHaveArmorOfType Scores if the target has a certain armor equipped. Possibly scores higher for some armors.
DoesTargetHaveAbilityOfType Scores if the target possesses a certain ability, e.g. a magic spell, a or an ability provided by a certain item, etc.
HasCash Scores if the target is carrying an amount of cash more than a set threshold, or possibly scores higher for those with more cash.
TargetProximityToNearestAllyScorer Scores higher if the target is near to its nearest ally, or possibly the reverse, depending on what is desired.

Instead of linear scorers, we can consider using other mathematical functions to calculate the score. The grenade thrower might thus be really dangerous at close range, have a zone around the edge of the throwing range where the danger quickly drops off, and in ranges beyond the throwing range he is virtually harmless. A logit function or other S-curves might thus be beneficial here to calculate the return score.

If we add a large number of OptionScorers, we could consider to use “Sum while above threshold”, to avoid many small return scorers to cloud significant scorers. Whether the grenade thrower has pulled the pin on the grenade might thus be significant, whereas the exact locations and angle of the sniper and machine gun might be less relevant under these circumstances.

Significant OptionScorers can return a score an order of magnitude higher, e.g. 10, 100, 1000 to ensure that they are prioritised. If e.g. a grenade has landed close to us, we might then want to score any point outside of the blast range with 1,000, to ensure that those destinations are prioritised above all other considerations.

Avoid oscillations by introducing a fixed scorer that assigns a fixed value to the current target. Several linear range Scorers interacting can for example create oscillations where multiple targets receive the same score. To ensure that the AI stays on target, existing targets can be given a fixed score to ensure that they are prioritised unless there are compelling reasons to change target.

Consider a separate action explicitly setting the existing target to null, if the existing target goes out of scope and no other targets are available. This can aid debugging and avoid strange behavior where the AI tries to shoot or chase targets that are out of range or no longer available at all.

For different weapons, you might consider different target rules. Grenades and artillery might for example be able to fire over walls and obstacles. Heavy machine guns can fire through walls. Grenades and rockets might do area damage. Finding good area targets can be put into a parallel Utility AI, identifying for example targets with multiple other targets in range. If this AI returns possible area damage targets, these can be given a special OptionScorer that prioritises them.

Conclusion

In this tutorial we saw how target selection can be implemented using the Apex Utility AI. The tutorial showed a simple scenario, where prioritising between three different types of adversaries can be done in a simple and intuitive way even under complex input data, and we showed how the AI can be tested and decisions validated using a truth table. Furthermore, we explain the numerous possibilities for extending the target selection AI by adding additional OptionScorers to the ActionWithOptions, additionally we showed a number of techniques to avoid common pitfalls such as oscillations, ignoring significance and the potentially clouding scores.