In the previous recipe, we built the basics for an animation by managing the Control
class. This would be fine for many types of games, but for a game where a character is in focus, let's say an FPS, we would want a more detailed control. This is where the concept of AnimChannel
comes in handy. AnimChannel
is a way of dividing a skeleton into different groups of bones and applying an animation only to them. As we will find out in this recipe, this means we can have different animations playing on different parts of the body at the same time.
Applying animations only to certain channels can help reduce the workload tremendously for a character-focused game. Let's say we're making an FPS or RPG where the character can wield a number of different items and weapons, both one– and two–handed. Making full-body animations for all the combinations, including standing, walking, running, and more, is not feasible. If instead, you are able to apply the weapon animation only to the upper body and a walk animation to the lower body, you get a lot more freedom.
This recipe will also describe some other tricks that might help in developing the game.
We can have different animations playing on different parts of the body at the same time by performing the following steps:
ActionListener
and AnalogListener
interfaces in our animation's manager
class. This will allow us to receive input directly from an input-handling class and decide which animations to play.AnimChannels
: one for the upper body called upperChannel
and one for the lower called lowerChannel
. We also create a Channel
enum to easily choose whether to play an animation in a separate channel or the whole body, as shown in the following code:public enum Channel{ Upper, Lower, All, }
setSpatial
method, we create the upper and lower channels in AnimControl
. We let AnimChannel
add all the bones recursively using the addFromRootBone
method, as shown in the following code:public void setSpatial(Spatial spatial) { super.setSpatial(spatial); animControl = spatial.getControl(AnimControl.class); upperChannel = animControl.createChannel(); lowerChannel = animControl.createChannel(); upperChannel.addFromRootBone("spine"); lowerChannel.addBone("Root"); lowerChannel.addFromRootBone("pelvis");
AnimEventListener
to AnimControl
to receive events when animations change or cycle, as shown in the following code:animControl.addListener(this);
setAnimation
, which takes an animation and Channel
(enum) as the input, as shown in the following code:public void setAnimation(Animation animation, Channel channel){ switch(channel){ case Upper: setAnimation(animation, upperChannel); break; ... } }
onAction
method, the control can receive input directly from InputListener
and apply the logic on its own before setting the animation, as shown in the following code:public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("StrafeLeft")) { leftStrafe = isPressed; } ... } else if (name.equals("Jump") && isPressed) { jumpStarted = true; setAnimation(Animation.JumpStart); } if(jumpStarted || firing){ // Do nothing } else if(forward || backward || rightStrafe || leftStrafe) { setAnimation(Animation.Walk); } else { setAnimation(Animation.Idle); } }
AnimChannels
, we can implement ActionListener
in our SimpleApplication
instance and bind some keys to it, as shown in the following code:public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("Anim1") && isPressed) { jaime.getControl(AnimationChannelsControl.class) .setAnimation(Animation.Walk, Channel.All); } ... }
AnimChannels
can be used to create new animations out of combined ones, create a new application and set the walk animation on Jaime's lowerChannel
while applying the jumping animation on upperChannel
. Jaime will now commence a zombie walk impression.We can see that the Animation
enum has had a field called key
added. This is not necessary but is part of a way to not have to hard-code animation names.
By using the addFromRootBone
method, the channel will automatically add all the bones recursively, starting with the bone that is supplied first. After adding spine to upperChannel
, it will continue down the chain, adding shoulders, neck, arms, and hands, as shown in the following screenshot:
Since we implemented ActionListener
, there's also an onAction
method in the class, which can receive an input from a number of external sources, such as InputListener
. This also means it can apply logic by itself before deciding on what to play and not simply being an animation-playing control. We can recognize the pattern used here from the GameCharacterControl
class from Chapter 2, Cameras and Game Controls.
By supplying a Properties
file that maps the animation names, it's possible to use models with different naming conventions. It's also easier for a designer or artist to try out a number of different animations without consulting a programmer to make changes.
3.144.232.189