Having AI using cover is a huge step towards making characters seem more believable and it usually makes them more challenging as they don't die as quickly.
There are many ways to implement this functionality. In the simplest form, the AI is not aware of any cover. It's simply scripted (by a designer) to move to a predefined favorable position when they spot an enemy. A player playing the sequence for the first time can't possibly notice the difference between an AI taking the decision by itself. Hence, the task of creating a believable AI (for that situation) is accomplished.
A much more advanced way would be to use the same principles for cover, which was established in Chapter 2, Cameras and Game Controls. However, evaluating options also becomes far more complex and unpredictable. Unpredictable AI might be good from the player's perspective, but it's a nightmare from a designer's perspective.
In this recipe, we'll go for a middle ground. First of all, we will base the AI on the FSM created in the previous recipe, and add a new state that handles finding cover. We will then add cover points to a scene, from which the AI can pick a suitable one and move there before attacking.
Let's begin by defining a class called CoverPoint
, extending AbstractControl
by performing the following steps:
Vector3f
called coverDirection
. With getters and setters, that's all that's needed.SeekCoverState
, extending our AIState
class from the previous recipe.CoverPoints
called availableCovers
, and a CoverPoint
called targetCover
.stateEnter
method, it should look for a suitable cover point. We can do this with the following piece of code. It parses the list and takes the first CoverPoint
where the dot product of the direction and coverDirection
is positive:for(CoverPoint cover: availableCovers){ if(aiControl.getTarget() != null){ Vector3f directionToTarget = cover.getSpatial().getWorldTranslation().add(aiControl.getTarget().getWorldTranslation()).normalizeLocal(); if(cover.getCoverDirection().dot(directionToTarget) > 0){ targetCover = cover; break; } } }
controlUpdate
method, the AI should move towards targetCover
if it has one.targetCover
should be set to null, indicating it should switch to AttackState
.stateExit
should tell the AI to stop moving.PatrolState
, where it can switch to SeekCoverState
instead of AttackState
when it spots a target.CoverPoints
to a scene and see what happens.The CoverPoint
class we created adds the behavior to any Spatial
instances to act as a cover. In a game, you would most likely not see the CoverPoint
spatial, but it's good for debug and editing purposes. The concept can be expanded to cover other types of interest points for AI, as well as modified to handle volumes, rather than points using the spatial's geometry.
Once the SeekCoverState
is enabled, it will try to find a suitable cover point that's relative to the target's position (at that time). It does this using the dot product between coverDirection
and the direction to the target. If this is positive, it means the target is in front of the cover, and it picks this as targetCover
.
When the AI reaches this, it sets targetCover
to null
. This means that when controlUpdate
is called the next time, it will exit the state and enable AttackState
instead. In a real game, the AI would most likely use some kind of navigation or pathfinding to get there. You can get an introduction to navigation in the next recipe. There is also the Pathfinding: Our own A* pathfinder recipe that covers implementing pathfinding later in the chapter.
With the current implementation of the AI, the result might be a bit erratic, since it doesn't remember the target's position. It might very well be that it doesn't see the target once it reaches the cover and instantly switches to PatrolState
.
18.119.19.174