AnimatorStateMachine: Anim Layers, Blend Trees & State Machine Behaviours

Hello everybody,
below the demonstration demos on the use of AnimatorStateMachine:

It is a prototype, but I hope it will be useful for you. In the Wiki section you can already find some explanations on how it works. Report any bugs to me. If you have any ideas on how to write a graphics editor or improvements just let me know.

Thanks to the community for their help and inspiration.
Have fun.

5 Likes

Cool :+1:

1 Like

Hello everybody,
last stages of testing of the new features for the support of animations on multilevel state machines configurable through AvatarMask. Coming soon on github.

In this video, I used two layers to apply animations. The first layer acts on the whole body. The second layer acts only on the upper body by using a mask on the bones of the skeleton.

5 Likes

Hello everybody,
I uploaded the latest version of the AnimatorController to github. The code is commented and on the account you can find the test-cases and the class-diagram of the API

Provides:

  • Animating different body parts with different logic
  • Layering and masking features
  • Animation parameters
  • Animation state machines
  • Animation states
  • Animation transitions
  • State machine behaviours
  • State machine listeners
  • Blend Trees

As you may have noticed, the API is based on the one provided by Unity and is completely generic and customizable. Unfortunately it doesn’t have the same graphic editor yet, but if anyone wants to try, I wrote the API with that in mind too. In the absence of the editor, the code needed to create a State Machine is really simple and minimal.
If you are wondering how and when you can use the API remember that now you can take advantage of the hundreds of Unity videos and convert them to jMonkeyEngine more easily (not all features are supported yet).

I hope this idea can help you manage the difficult world of animations. If you have any ideas or suggestions, please let me know.

Thanks to all the community members for the support and work you do.
Have fun :wink:

Edit: Class-Diagram and Wiki section updated with last API version.

4 Likes

Hello everybody,
new Blend Tree feature with blendspace 2D plus more optimizations coming soon. :wink:
Visit the wiki page to find out more. Blend Trees · capdevon/jme-capdevon-examples Wiki · GitHub

3 Likes

Hi everyone,
I uploaded the new features to github. New class diagram:

Let’s talk about the technical details after the video:

+Shoulder camera.
+Dynamic FOV when aiming.

For the implementation of the BlendTree 2D, I used as a reference the source code of the Godot Engine which is completely free. Not having a graphic editor, I discarded the functions that involved the use of the Delaunay tringulation algorithm. Maybe I’ll add it later.

When to use this system:

"Used when your motions represent different directions, such as “walk forward”, “walk backward”, “walk left”, and “walk right”, or “aim up”, “aim down”, “aim left”, and “aim right”. Optionally a single motion at position (0, 0) can be included, such as “idle” or “aim straight”

After setting the Blend Type, the first thing you need is to select the two Animation Parameters that will control this Blend Tree. In this example, the parameters are velocityX (strafing) and velocityZ (forward speed).

The positions in 2D blending are like the thresholds in 1D blending, except that there are two values instead of one, corresponding to each of the two parameters. Their positions along the horizontal X axis correspond to the first parameter, and their positions along the vertical Y axis correspond to the second parameter. A walking forward animation might have a velocityX of 0 and a velocityZ of 1.5, so those values should be typed into the Pos X and Pos Y number fields for the motion."

// Create the controller and the parameters
AnimatorController animator = new AnimatorController(animComposer);
animator.addParameter("velocityX", AnimatorControllerParameterType.Float);
animator.addParameter("velocityZ", AnimatorControllerParameterType.Float);
player.addControl(animator);

AnimatorStateMachine sm = animator.getLayer(0).getStateMachine();

BlendTree tree = new BlendTree();
tree.setBlendType(BlendTreeType.SimpleDirectional2D);
// Configure the name of the parameter that controls the mixing of animations.
tree.setBlendParameter("velocityX");
tree.setBlendParameterY("velocityZ");
// Configure the animations and their position in the 2d graph
tree.addChild("IdleAiming", new Vector2f(0, 0));
tree.addChild("Move_FW", new Vector2f(0, 1));
tree.addChild("Move_BW", new Vector2f(0, -1));
tree.addChild("Move_L", new Vector2f(-1, 0));
tree.addChild("Move_R", new Vector2f(1, 0));
tree.addChild("Move_FWL", new Vector2f(-1, 1));
tree.addChild("Move_FWR", new Vector2f(1, 1));
tree.addChild("Move_BWL", new Vector2f(-1, -1));
tree.addChild("Move_BWR", new Vector2f(1, 1));
// Create the state from the blend tree
AnimatorState walk = sm.createBlendTree("BlendTree-Walk", tree);

// set the initial state.
sm.setDefaultState(walk);

Now in your PlayerMovementControl you can control animations through the parameters defined in your AnimatorController.

    public class PlayerMovementControl extends AbstractControl implements ActionListener, AnalogListener {

        public float m_MoveSpeed = 1.8f;
        public float m_TurnSpeed = 2.5f;
        public BitmapText crosshair;

        private Camera camera;
        private AnimatorController animator;
        private BetterCharacterControl bcc;
        private final Vector3f walkDirection = new Vector3f();
        private final Vector3f viewDirection = new Vector3f(0, 0, 1);
        private boolean _MoveForward, _MoveBackward, _StrafeLeft, _StrafeRight;
        
        private MainCamera _MainCamera;
        public float nearClipPlane = 0.01f;
        public float farClipPlane = 100f;
        private float fov = 0;
        private float aimingSpeed = 5f;
        private float aimFOV = 45;
        private float defaultFOV = 60;
    	boolean isAiming;

        /**
         * Constructor.
         * @param app
         */
        public PlayerMovementControl(Application app) {
            this.camera = app.getCamera();
        }

        @Override
        public void setSpatial(Spatial sp) {
            super.setSpatial(sp);
            if (spatial != null) {
                this.animator = spatial.getControl(AnimatorController.class);
                this.bcc = spatial.getControl(BetterCharacterControl.class);
                
                _MainCamera = new MainCamera(camera, defaultFOV, nearClipPlane, farClipPlane);
            }
        }

        @Override
        public void onAnalog(String name, float value, float tpf) {
            // TODO Auto-generated method stub
        }

        @Override
        public void onAction(String name, boolean isPressed, float tpf) {
            if (!enabled)
                return;

            if (name.equals(InputMapping.MOVE_FORWARD)) {
                _MoveForward = isPressed;
            } else if (name.equals(InputMapping.MOVE_BACKWARD)) {
                _MoveBackward = isPressed;
            } else if (name.equals(InputMapping.MOVE_LEFT)) {
                _StrafeLeft = isPressed;
            } else if (name.equals(InputMapping.MOVE_RIGHT)) {
                _StrafeRight = isPressed;
            } else if (name.equals(InputMapping.AIMING)) {
            	isAiming = isPressed;
            	crosshair.setCullHint(isPressed ? CullHint.Never : CullHint.Always);
            }
        }

        @Override
        protected void controlUpdate(float tpf) {
            // update physics
            walkDirection.set(0, 0, 0);
            float hSpeed = 0;
            float vSpeed = 0;

            if (_MoveForward || _MoveBackward) {
                Vector3f dz = spatial.getWorldRotation().mult(Vector3f.UNIT_Z);
                vSpeed = _MoveForward ? 1 : -1;
                walkDirection.addLocal(dz.multLocal(vSpeed));
            }

            if (_StrafeLeft || _StrafeRight) {
                Vector3f dx = spatial.getWorldRotation().mult(Vector3f.UNIT_X);
                hSpeed = _StrafeLeft ? -1 : 1;
                walkDirection.addLocal(dx.multLocal(-hSpeed));
            }

            walkDirection.normalizeLocal();
            bcc.setWalkDirection(walkDirection.multLocal(m_MoveSpeed));

            // update view direction
            Vector3f lookDir = camera.getDirection();
            lookDir.y = 0;
            lookDir.normalizeLocal();
            Quaternion lookRotation = FRotator.lookRotation(lookDir);
            spatial.getLocalRotation().slerp(lookRotation, m_TurnSpeed * tpf);
            spatial.getLocalRotation().mult(Vector3f.UNIT_Z, viewDirection);
            bcc.setViewDirection(viewDirection);

            // update animation
            animator.setFloat("velocityX", hSpeed);
            animator.setFloat("velocityZ", vSpeed);
            
            updateWeaponAiming(tpf);
        }
        
    	private void updateWeaponAiming(float tpf) {
    		if (isAiming) {
    			fov += tpf * aimingSpeed;
    		} else {
    			fov -= tpf * aimingSpeed;
    		}
    		
    		fov = FastMath.clamp(fov, 0, 1);
    		_MainCamera.setFieldOfView(FastMath.interpolateLinear(fov, defaultFOV, aimFOV));
    	}

        @Override
        protected void controlRender(RenderManager rm, ViewPort vp) {
            //To change body of generated methods, choose Tools | Templates.
        }

    }

This animation management system with other much more advanced features is present in all engines such as Unreal, Unity and Godot.

Let me know if you like it.
Have fun

5 Likes