In this recipe, we will create a control that is going to steer an AI character. Using Control
to do this is beneficial since it can add the AI functionality and be used together with other Controls
in the game. We can use GameCharacterControl
from Chapter 2, Cameras and Game Controls for both the player and AI characters by adding AIControl
to its spatial. To get a quick and visual result, we'll apply it to the bullet-based BetterCharacterControl
class in this recipe.
We need to perform the following steps to get a basic, but functional attacking (or following) AI:
AIControl
, extending AbstractControl
. The core of the recipe will be based around an enum (enumeration) called state
. For now it only needs two values: Idle
and Follow
.BetterCharacterControl
, called physicsCharacter
, Booleans forward
and backwards
, a Vector3f
field for walkDirection
, and another for viewDirection
. If it's going to follow something, it also needs a target
field, which can be Spatial
.switch
statement in the controlUpdate
method, as shown in the following code. The first case is Idle
. In this case, the AI shouldn't do anything:switch(state){ case Idle: forward = false; backward = false; break;
Follow
case, we should first check whether target
is set. If there is a target, we find the direction to the target and make the AI face it by setting viewDirection
, as shown in the following code:case Follow: if(target != null){ Vector3f dirToTarget = target.getWorldTranslation().subtract(spatial.getWorldTranslation()); dirToTarget.y = 0; dirToTarget.normalizeLocal(); viewDirection.set(dirToTarget);
5
the AI will try to get closer. If the distance instead is less than 3
, it will try to back up a bit. The AI can also lose track of the target if it is more than 20 units away. In this case, it also changes state to Idle
, as shown in the following code:if (distance > 20f){ state = State.Idle; target = null; } else if(distance > 5f){ forward = true; backward = false; } else if (distance < 3f){ forward = false; backward = true; } else { forward = false; backward = false; }
Vector3f modelForwardDir = spatial.getWorldRotation().mult(Vector3f.UNIT_Z);
setWalkDirection
on the BetterCharacterControl
class with the result shown in the following code:if (forward) { walkDirection.addLocal(modelForwardDir.mult(3)); } else if (backward) { walkDirection.addLocal(modelForwardDir.negate().multLocal(3)); } physicsCharacter.setWalkDirection(walkDirection);
setViewDirection
, as shown in the following code:physicsCharacter.setViewDirection(viewDirection);
Using BetterCharacterControl
, we get a lot of functionality for free. We only need a couple of Booleans to keep track of movement, and two Vector3f
instances for directions. Target is what the AI will focus on (or follow, for now).
If we're familiar with TestBetterCharacter
from jMonkeyEngine's test examples, we can recognize the movement handling from that class. For now, we only use the forward
/backward
functionality. It is a good idea to keep the rotation code as well, just in case we would like it to turn more smoothly in the future. The walkDirection
vector is 0
by default. It can either be sent as it is sent to physicsCharacter
, in which case the character will stop, or be modified to move in either direction. The viewDirection
vector is simply set to look at the target for now, and passed on to physicsCharacter
.
The logic in the Follow
case mentioned previously is mostly there to have something to test with. Even so, it's AI behavior that seems to be sufficient for many MMOs. Once a target has been acquired, it will try to keep itself at a certain distance. It can also lose track of the target if it gets too far away. In this case, it falls back to the Idle
state.
By linking this recipe together with Chapter 4, Mastering Character Animations, we can easily make Jaime play some animations while he's moving.
Start by adding the AnimationManagerControl
class to the AI character using the following code:
aiCharacter.addControl(new AnimationManagerControl());
We need to tell it to play animations. In AIControl
, find the forward and backwards brackets inside the controlUpdate
method and add the following lines:
if (forward) { ... spatial.getControl(AnimationManagerControl.class).setAnimation(AnimationManagerControl.Animation.Walk); } else if (backward) { ... spatial.getControl(AnimationManagerControl.class).setAnimation(AnimationManagerControl.Animation.Walk); } else { spatial.getControl(AnimationManagerControl.class).setAnimation(AnimationManagerControl.Animation.Idle); }
Let's create a test case world we can use for both this and many of the following recipes. First we need a world with physics:
BulletAppState bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState);
We will need some kind of object to stand on. The PhysicsTestHelper
class has a few example worlds we can use.
We load up good old Jaime. Again, we use the BetterCharacterControl
class since it offloads a lot of code for us. Since the Bullet physics world is different from the ordinary scenegraph, Jaime is added to physicsSpace
as well as to the rootNode
, as shown in the following code:
bulletAppState.getPhysicsSpace().add(jaime); rootNode.attachChild(jaime);
We also need to add our newly created AI control using the following code:
jaime.addControl(new AIControl());
There's one more thing we need to do for this to work. The AI needs to track something. The easiest way we can get a moving target is to add a CameraNode
class and supply cam
from the application, as shown in the following code:
CameraNode camNode = new CameraNode("CamNode", cam); camNode.setControlDir(CameraControl.ControlDirection.CameraToSpatial); rootNode.attachChild(camNode);
We set camNode
to be the target, as shown in the following code:
jaime.getControl(AIControl.class).setState(AIControl.State.Follow); jaime.getControl(AIControl.class).setTarget(camNode);
If we're familiar with cameras in OpenGL, we know they don't really have a physical existence. A CameraNode
class in jMonkeyEngine gives us that. It tracks the camera's position and rotation, giving us something easy to measure. This will make it easier for us when we want the AI to follow it, since we can use the convenience of it being spatial.
For this reason, we can set CameraNode
to be its target.
3.139.240.244