In certain games, players traverse dangerous areas where a fall off from a ledge could lead to their deaths. Sometimes, in these games, the player is not meant to fall off and their movement is restricted when they are close, or the player gets an extra warning before they plummet.
The control we'll develop can be used for any of those things, but since this chapter is about animations, we'll use it to play a special animation when the player gets too close to the edge.
The recipe will use similar patterns that have been used before in this chapter and we'll also use the animation manager control from earlier in the chapter. Any animation control will be fine to use, but it should have separate channels for the upper and lower parts of the body.
We can implement almost everything we need in a single class as follows:
EdgeCheckControl
, which extends AbstractControl
and contains the following fields, as shown in the following code:private Ray[] rays = new Ray[9]; private float okDistance = 0.3f; private Spatial world; private boolean nearEdge;
setSpatial
method, we instantiate them and aim them downwards, as shown in the following code:for(int i = 0; i < 9; i++){ rays[i] = new Ray(); rays[i].setDirection(Vector3f.UNIT_Y.negate()); }
controlUpdate
method, we begin by placing one of the rays at the center of the character, as shown in the following code:Vector3f origo = getSpatial().getWorldTranslation(); rays[0].setOrigin(origo);
checkCollision
method. If it doesn't, we don't need to check the rest and can exit the loop using the following code:float angle; for(int i = 1; i < 9; i++){ float x = FastMath.cos(angle); float z = FastMath.sin(angle); rays[i].setOrigin(origo.add(x * 0.5f, 0, z * 0.5f)); collision = checkCollision(rays[i]); if(!collision){ break; } angle += FastMath.QUARTER_PI; } private boolean checkCollision(Ray r){ CollisionResults collResuls = new CollisionResults(); world.collideWith(r, collResuls); if(collResuls.size() > 0 && r.getOrigin().distance(collResuls.getClosestCollision().getContactPoint()) > okDistance){ return true; } return false; }
if(!collision && !nearEdge){ nearEdge = true; spatial.getControl(AnimationManagerControl.class).onAction("NearEdge", true, 0); } else if(collision && nearEdge){ nearEdge = false; spatial.getControl(AnimationManagerControl.class).onAction("NearEdge", false, 0); }
if (binding.equals("NearEdge")) { nearEdge = value; if(nearEdge){ setAnimation(Animation.Jumping, Channel.Upper); } }
With each update, the nine rays we create are placed in a circle around the character, with one in the center.
They will check for collisions with a surface below them. If any of them (this might be changed to two or three) does not hit something within okDistance
, it will be reported as the character being close to a dangerous edge.
The okDistance
has to be set to something suitable, higher than a step on a stair, probably at a height where the player could take damage.
When this happens, the animation manager will be called with the NearEdge
action set to true
. This will apply the jumping animation (wild flaying of the arms) to the upper body of the character while still allowing other animations to be played on the lower part.
The NearEdge
Boolean is used to make sure that we only send the call to the animation manager once.
When doing collision checks, one should be careful about the amount and shape of the objects that are being collided. If the world is large or constructed from complex shapes (or even worse, if it has MeshCollisionShape
), we should try to find optimized ways of applying the method. One way could be to separate the world into parts and have an auxiliary method to select which part to collide against. This method might use contains
on BoundingVolume
to see the part the player is located in.
3.139.97.202