Previous: Hello Material,
Next: Hello Picking This tutorial shows how to add an animation controller and channels, and how to respond to user input by triggering an animation in a loaded model.
package jme3test.helloworld;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
/** Sample 7 - how to load an OgreXML model and play an animation,
* using channels, a controller, and an AnimEventListener. */
public class HelloAnimation extends SimpleApplication
implements AnimEventListener {
private AnimChannel channel;
private AnimControl control;
Node player;
public static void main(String[] args) {
HelloAnimation app = new HelloAnimation();
app.start();
}
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(ColorRGBA.LightGray);
initKeys();
DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());
rootNode.addLight(dl);
player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
player.setLocalScale(0.5f);
rootNode.attachChild(player);
control = player.getControl(AnimControl.class);
control.addListener(this);
channel = control.createChannel();
channel.setAnim("stand");
}
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
if (animName.equals("Walk")) {
channel.setAnim("stand", 0.50f);
channel.setLoopMode(LoopMode.DontLoop);
channel.setSpeed(1f);
}
}
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
// unused
}
/** Custom Keybinding: Map named actions to inputs. */
private void initKeys() {
inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addListener(actionListener, "Walk");
}
private ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("Walk") && !keyPressed) {
if (!channel.getAnimationName().equals("Walk")) {
channel.setAnim("Walk", 0.50f);
channel.setLoopMode(LoopMode.Loop);
}
}
}
};
}
You create animated models with a tool such as Blender. Take some time and learn how to create your own models in these Blender Animation Tutorials. For now, download and use a free model, such as the one included here as an example (Oto Golem, Ninja).
Loading an animated model is pretty straight-forward, just as you have learned in the previous chapters. Animated Ogre models come as a set of files: The model is in Oto.mesh.xml
, and the animation details are in Oto.skeleton.xml
(plus materials and textures). Check that all files of the model are indeed in the right Model
subdirectory.
/* Displaying the model requires a light source */ DirectionalLight dl = new DirectionalLight(); dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal()); rootNode.addLight(dl); /* load and attach the model as usual */ player = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); player.setLocalScale(0.5f); // resize rootNode.attachChild(player);
Don't forget to add a light source to make the material visible.
After you load the animated model, you register it to the Animation Controller.
/* Load the animation controls, listen to animation events,
* create an animation channel, and bring the model in its default position. */
control = player.getControl(AnimControl.class);
control.addListener(this);
channel = control.createChannel();
channel.setAnim("stand");
Add implements AnimEventListener
to the class declaration. This interface gives you access to events that notify you when a sequence is done, or when you change from one sequence to another, so you can respond to it. In this example, you reset the character to a standing position after a Walk
cycle is done.
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { if (animName.equals("Walk")) { channel.setAnim("stand", 0.50f); channel.setLoopMode(LoopMode.DontLoop); channel.setSpeed(1f); } } public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { // unused }
There are ambient animations like animals or trees that you may want to trigger in the main event loop. In other cases, animations are triggered by user interaction, such as key input. You want to play the Walk animation when the player presses a certain key (here the spacebar), at the same time as the avatar performs the walk action and changes its location.
simpleInitApp()
).initKey()
convenience method from simpleInitApp()
.Walk
to the Spacebar key.Walk
action.private void initKeys() { inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE)); inputManager.addListener(actionListener, "Walk"); }
To use the input controller, you need to implement the actionLister: Test for each action by name, and set the channel to the corresponding animation to run.
private ActionListener() { public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("Walk") && !keyPressed) { if (!channel.getAnimationName().equals("Walk")){ channel.setAnim("Walk", 0.50f); channel.setLoopMode(LoopMode.Cycle); } } } };
Make the a mouse click trigger another animation sequence!
for (System.out.println(anim); }
Open the skeleton.xml
file in a text editor of your choice.
You don't have to be able to read or write these xml files (Blender does that for you) – but it is good to know what the xml files are there for and how skeletons work. "There's no magic to it!"
Add the following code snippet to simpleInitApp()
to make the bones (that you just read about) visible!
SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton()); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.setColor("Color", ColorRGBA.Green); mat.getAdditionalRenderState().setDepthTest(false); skeletonDebug.setMaterial(mat); player.attachChild(skeletonDebug);
Can you identify individual bones in the skeleton?
Now you can load animated models, identify stored animations, and trigger by using onAnimCycleDone() and onAnimChange(). You also learned that you can play several animations simultaneously, by starting each in a channel of its own. This could be useful if you ever want to animate the lower and upper part of the characters body independently, for example the legs run, while the arms use a weapon. Now that your character can walk, wouldn't it be cool if it could also pick up things, or aim a weapon at things, or open doors? Time to learn more about the secrets of picking!