This page is part of the 3D Game Kit sample
The character has several animations that can be blended to allow movement at any speed as well as to turn quickly when needed.
Mecanim
The Mecanim character's LocomotionSM
state is a Sub State Machine containing a main Locomotion
Blend Tree to allow for movement at any speed, left and right quick turn animations, and the Idle
state mentioned on the Idle page:
Animancer
With Animancer, all the state logic is defined in the LocomotionState
script which handles everything that applies while the character is moving along the ground except for things that also apply in other states as well (such as the Character.OnAnimatorMove
method described on the Movement page). It uses a Mixer State in place of the Blend Tree used by Mecanim:
using Animancer;
using Animancer.Units;
using System;
using UnityEngine;
public class LocomotionState : CharacterState
{
[SerializeField] private LinearMixerTransition _LocomotionMixer;
[SerializeField] private ClipTransition _QuickTurnLeft;
[SerializeField] private ClipTransition _QuickTurnRight;
[SerializeField, MetersPerSecond] private float _QuickTurnMoveSpeed = 2;
[SerializeField, Degrees] private float _QuickTurnAngle = 145;
private AnimatedFloat _FootFall;
protected virtual void Awake()
{
_QuickTurnLeft.Events.OnEnd =
_QuickTurnRight.Events.OnEnd =
() => Character.Animancer.Play(_LocomotionMixer);
_FootFall = new AnimatedFloat(Character.Animancer, "FootFall");
}
public override bool CanEnterState => Character.IsGrounded;
protected virtual void OnEnable()
{
Character.Animancer.Play(_LocomotionMixer);
}
protected virtual void FixedUpdate()
{
if (Character.CheckMotionState())
return;
Character.UpdateSpeedControl();
_LocomotionMixer.State.Parameter = Character.ForwardSpeed;
UpdateRotation();
UpdateAudio();
}
private void UpdateRotation()
{
if (!_LocomotionMixer.State.IsActive)
return;
if (!Character.GetTurnAngles(Character.Brain.Movement, out float currentAngle, out float targetAngle))
return;
if (Character.ForwardSpeed > _QuickTurnMoveSpeed)
{
float deltaAngle = Mathf.DeltaAngle(currentAngle, targetAngle);
if (Mathf.Abs(deltaAngle) > _QuickTurnAngle)
{
ClipTransition turn = deltaAngle < 0 ? _QuickTurnLeft : _QuickTurnRight;
if (turn.State == null || turn.State.Weight == 0)
{
Character.Animancer.Play(turn);
return;
}
}
}
Character.TurnTowards(currentAngle, targetAngle, Character.CurrentTurnSpeed);
}
[SerializeField] private UnityEvent _PlayFootstepAudio;
private bool _CanPlayAudio;
private bool _IsPlayingAudio;
private void UpdateAudio()
{
const float Threshold = 0.01f;
float footFallCurve = _FootFall.Value;
if (footFallCurve > Threshold && !_IsPlayingAudio && _CanPlayAudio)
{
_IsPlayingAudio = true;
_CanPlayAudio = false;
_PlayFootstepAudio.Invoke();
}
else if (_IsPlayingAudio)
{
_IsPlayingAudio = false;
}
else if (footFallCurve < Threshold && !_CanPlayAudio)
{
_CanPlayAudio = true;
}
}
}