This page is part of the 3D Game Kit example
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 sealed 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;
private void Awake()
{
_QuickTurnLeft.Events.OnEnd =
_QuickTurnRight.Events.OnEnd =
() => Character.Animancer.Play(_LocomotionMixer);
_FootFall = new AnimatedFloat(Character.Animancer, "FootFall");
}
public override bool CanEnterState => Character.IsGrounded;
private void OnEnable()
{
Character.Animancer.Play(_LocomotionMixer);
}
private 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 var currentAngle, out var targetAngle))
return;
if (Character.ForwardSpeed > _QuickTurnMoveSpeed)
{
var deltaAngle = Mathf.DeltaAngle(currentAngle, targetAngle);
if (Mathf.Abs(deltaAngle) > _QuickTurnAngle)
{
var 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;
var 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;
}
}
}