We will create a control that will handle the animations of a character. It will follow jMonkeyEngine's control pattern and extend AbstractControl
. We won't actually use most of the functions of AbstractControl
right away, but it's a neat way to offset some of the code from a possible Character
class. It will also be easy to add functionalities later on.
To create a control that will handle the animations of a character, perform the following steps:
CharacterAnimationManager
and have it extend AbstractControl
. This class should also implement AnimEventListener
, which AnimControl
uses to tell our class when animations have finished playing.AnimControl
should take to blend to a new animation using the following code:public enum Animation{ Idle(LoopMode.Loop, 0.2f), Walk(LoopMode.Loop, 0.2f), Run(LoopMode.Loop, 0.2f), ... SideKick(LoopMode.DontLoop, 0.1f); Animation(LoopMode loopMode, float blendTime){ this.loopMode = loopMode; this.blendTime = blendTime; } LoopMode loopMode; float blendTime; }
We need two fields as well: an AnimControl
field called animControl
and an AnimChannel
called mainChannel
.
setSpatial
method, as shown in the following code. Don't forget to add the class to the AnimControl
field as a listener, or we won't receive any calls when animations are finished:public void setSpatial(Spatial spatial) { super.setSpatial(spatial); animControl = spatial.getControl(AnimControl.class); mainChannel = animControl.createChannel(); animControl.addListener(this); }
setAnimation
in the following code. Inside this, we set the supplied animation to be mainChannel
as the current one if it's not the same as the one playing now. We also set loopMode
according to how it's defined in the enum:public void setAnimation(Animation animation) { if(mainChannel.getAnimationName() == null || !mainChannel.getAnimationName().equals(animation.name())){ mainChannel.setAnim(animation.name(), animation.blendTime); mainChannel.setLoopMode(animation.loopMode); } }
onAnimCycleDone
method, we create a control so that all animations that don't loop return to the idle animation, with the exception of JumpStart
, which should switch to Jumping
(as in midair) as shown in the following code:public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { if(channel.getLoopMode() == LoopMode.DontLoop){ Animation newAnim = Animation.Idle; Animation anim = Animation.valueOf(animName); switch(anim){ case JumpStart: newAnim = Animation.Jumping; break; } setAnimation(newAnim); } }
jaime.addControl(new AnimationManagerControl());
The AnimControl
class is responsible for playing and keeping track of the animations. AnimChannel
has a list of Bones
that the animation should affect.
Since we let the enum decide the animation parameters for us, we don't need much code in the setAnimation
method. We do however need to make sure we don't set the same animation if it is already playing or it could get stuck, repeating the first frame in a loop.
The onAnimCycleDone
method is called from AnimControl
whenever an animation reaches the end. Here, we decide what will happen when this occurs. If the animation is not looping, we must tell it what to do next. Playing the idle animation is a good choice.
We also have one special case. If you look at the animation list, you will notice that Jaime's jump animation is split into three parts. This is to make it easier to handle jumps of different lengths or the falling animation.
We will tell AnimControl
to change the animation to a jumping action once JumpStart
is done. We never change to JumpEnd
once the jumping action has taken place however. Instead, this should be called from elsewhere when Jaime hits the ground after he jumps. How this is measured is dependent on the game logic, but since we're using the Control
pattern, we could use controlUpdate
to check Jaime's whereabouts.
3.21.104.183