本文最后更新于2 天前,其中的信息可能已经过时,如有错误请留言
Official document:Animancer – Home
Unity自带的画控制器Mecanim动存在许多设计缺陷,内部逻辑不透明,使用时需反复试验,手动操作且无法重用,修改可能导致其他问题。
相比之下,Animacer完全允许播放动画并通过代码控制,简化了使用流程。在检查器中提供实时控制,
Istate
IState
是一个状态接口,在有限状态机(FSM, Finite State Machine)的架构中,用于定义一个状态类所需实现的标准行为。每个实现了 IState 接口的类都代表一个具体的状态,并定义在进入、更新、动画变化和退出该状态时要执行的逻辑
public interface IState
{
//状态被激活或进入时调用,初始化状态相关的数据、播放动画、重置参数等。
void OnEnter();
//每帧更新时调用。通常用于持续检测输入、控制状态行为或判断是否应当切换状态。
void OnUpdate();
//在动画更新时调用(例如每帧同步动画位移或动画事件)。常用于处理动画驱动的逻辑。
void OnAnimationUpdate();
//状态退出前调用。可在这里清理资源、重置标志或停止动画等。
void OnExit();
//动画播放结束时调用,适合做状态转换、触发下一动作等操作。
void OnAnimationEnd();
}
StateMachineBase
public class StateMachineBase
{
public IState currentState;
public IState lastState;
/// <summary>
/// 状态切换的API
/// </summary>
/// <param name="targetState"></param>
public virtual void ChangeState(IState targetState)
{
//调用当前状态的 OnExit()(如果当前状态存在)
currentState?.OnExit();
//更新lastState和currentState
lastState = currentState;
currentState = targetState;
//调用当前状态的OnEnter()(如果当前状态存在)
currentState?.OnEnter();
}
/// <summary>
/// 动画状态退出的接口
/// </summary>
public void OnAnimationEnd()
{
currentState.OnAnimationEnd();
}
/// <summary>
/// Update状态API
/// </summary>
public void OnUpdate()
{
currentState?.OnUpdate();
}
/// <summary>
/// 按动画帧来更新
/// </summary>
public void OnAnimationUpdate()
{
currentState?.OnAnimationUpdate();
}
}
CharactorBase
CharacterBase
是一个通用的“角色物理控制核心类”,处理了角色的重力、落地、动画运动、空中运动、斜坡适配等逻辑,供子类Player
使用
using System;
using UnityEngine;
[RequireComponent(typeof(Animator),typeof(CharacterController))]
public class CharacterBase : MonoBehaviour
{
public CharacterController controller { get; private set; }
public Animator animator { get; private set; }
//重力的配置
[Header("重力设置")]
[SerializeField] public float gravity = -12;
[SerializeField] public Vector2 velocityLimit = new Vector2(-20, 60);
[SerializeField] public LayerMask whatIsGround;
[SerializeField] private float groundDetectedOffset = -0.06f;
[SerializeField] private float groundRadius = 1.2f;
private Vector3 detectedOrigin;
public BindableProperty<bool> isOnGround { set; get; } = new BindableProperty<bool>();
//角色垂直速度
public float verticalSpeed { get; set; }
private Vector3 verticalVelocity;
//角色的水平速度:不包含动画位移
private Vector3 horizontalVelocityInAir;
private Vector3 animationVelocity;
public Vector3 AnimationVelocity => animationVelocity;
//角色的运动
private Vector3 moveDir;
public Vector3 animatorDeltaPositionOffset{ get; set; }
public bool applyFullRootMotion { get; set; } = false;
[SerializeField,Range(0.1f,10)] public float moveSpeedMult =1;
public bool disEnableRootMotion { get; set; }//不采用任何根运动信息,禁用OnAnimatorMove方法
public bool ignoreRootMotionY { get; set; } = false;//忽视根运动的Y量
public bool disEnableGravity { get; set; } = false;//是否禁用程序重力
public bool ignoreRotationRootMotion { get; set; } = false;//是否忽略根运动的转向
protected virtual void Awake()
{
animator = GetComponent<Animator>();
controller = GetComponent<CharacterController>();
}
protected virtual void Update()
{
CheckOnGround();
CharacterGravity();
CharacterVerticalVelocity();
ResetHorizontalVelocity();
}
#region 重力的处理
/// <summary>
/// 地面检测
/// </summary>
private bool CheckOnGround()
{
//获取检测球体的中心位置
detectedOrigin = transform.position - groundDetectedOffset * Vector3.up;
//检测是否与 whatIsGround 图层有接触,不受那些“只触发、不碰撞”的 Trigger Collider 影响。
var isHit = Physics.CheckSphere(detectedOrigin, groundRadius, whatIsGround, QueryTriggerInteraction.Ignore);
/*
verticalVelocity在 CharacterGravity() 方法中更新,每帧都会加速下落,直到最大速度 velocityLimit.x
*/
isOnGround.Value = isHit && verticalSpeed < 0;
return isOnGround.Value;
}
private void CharacterGravity()
{
//如果当前角色设置为不受重力影响(如飞行、跳跃帧锁等状态),直接返回,不执行下面逻辑。
if (disEnableGravity)
{
return;
}
//如果角色站在地面上,verticalSpeed 设置为 -2(略微向下)这是一种常见技巧,防止角色贴地后“悬空”或者和地面产生穿插。
if (isOnGround.Value)
{
verticalSpeed = -2;
}
else
{
//模拟自由落体
verticalSpeed += Time.deltaTime * gravity;
//限制下落速度不要超过最大限制(避免速度过快穿模)
verticalSpeed = Mathf.Clamp(verticalSpeed, velocityLimit.x, velocityLimit.y);
}
verticalVelocity = new Vector3(0, verticalSpeed, 0);
}
#endregion
#region 玩家移动
/// <summary>
/// 角色落地后清零空中速度,防止跳跃或空中位移的惯性导致角色落地后仍然“漂移”
/// </summary>
private void ResetHorizontalVelocity()
{
if (isOnGround.Value)
{
if (horizontalVelocityInAir!= Vector3.zero)
{
horizontalVelocityInAir = Vector3.zero;
}
}
}
/// <summary>
/// 应用垂直与空中水平速度,驱动角色移动
/// </summary>
private void CharacterVerticalVelocity()
{
//如果当前角色处于“无重力状态”
if (disEnableGravity)
{
verticalVelocity = Vector3.zero;
}
//CharacterController组件处于启用状态,则将垂直方向速度和水平方向的速度加在一起,得到完整的运动方向 + 速度。
if (controller.enabled)
{
controller.Move((verticalVelocity + horizontalVelocityInAir) * Time.deltaTime);
}
}
/// <summary>
/// 控制角色是否使用动画自带的移动Root Motion,或在禁用 Root Motion 时,手动采样动画位移数据并进行地形适应处理
/// </summary>
protected virtual void OnAnimatorMove()
{
//Unity 会在播放动画且 Animator 有启用 RootMotion 时每帧调用此方法
//不启用角色根运动
if (disEnableRootMotion)
{
return;
}
//开启角色的根运动,应用动画自带的位移量
if (applyFullRootMotion)
{
animator.ApplyBuiltinRootMotion();
}
//不启用根运动,但是采样的也是角色根运动信息(位移)
else
{
Vector3 animationMovement = animator.deltaPosition+ animatorDeltaPositionOffset;
if (ignoreRootMotionY)
{
animationMovement.y = 0;
}
moveDir = SetDirOnSlop(animationMovement) * moveSpeedMult;
UpdateCharacterMove(moveDir,animator.deltaRotation);
}
}
public void UpdateCharacterMove(Vector3 deltaDir,Quaternion deltaRotation)
{
if (!ignoreRotationRootMotion)
{
if (deltaRotation != Quaternion.identity)
{
transform.rotation = deltaRotation * transform.rotation;
}
}
//每帧移动Dir个单位
if (controller.enabled == true)
{
animationVelocity = deltaDir;
controller.Move(deltaDir);
}
}
public float ChangeVerticalSpeed(float verticalSpeed)
{
return this.verticalSpeed = verticalSpeed;
}
public void AddHorizontalVelocityInAir(Vector3 vector3)
{
horizontalVelocityInAir = new Vector3(vector3.x,0, vector3.z);
}
public void ClearHorizontalVelocity()
{
horizontalVelocityInAir = Vector3.zero;
}
#endregion
#region 斜坡的处理
private Vector3 SetDirOnSlop(Vector3 dir)
{
//检测脚下是碰撞体
if (Physics.Raycast(transform.position, Vector3.down, out var hitInfo, 1))
{
//有碰撞体,返回碰撞体的法向量。
if (Vector3.Dot(hitInfo.normal, Vector3.up) != 1)
{
//法向量和世界坐标中的竖直向上方向点积不是 1,说明碰撞体不是水平的
//因此将[移动的方向]投影到碰撞体平面上。
return Vector3.ProjectOnPlane(dir, hitInfo.normal);
}
}
//法线是 (0,1,0),平面是水平面
return dir;
}
#endregion
private void OnDrawGizmos()
{
if (CheckOnGround())
{
Gizmos.color = Color.green;
}
else
{
Gizmos.color = Color.red;
}
Gizmos.DrawWireSphere(transform.position - groundDetectedOffset * Vector3.up, groundRadius);
}
}
PlayerStateMachine : StateMachineBase
有限状态机,缓存状态,驱动并更新状态类
public class PlayerStateMachine : StateMachineBase
{
//缓存状态
public Player player;
public PlayerIdleState idleState;
public PlayerMoveStartState moveStartState;
public PlayerMoveLoopState moveLoopState;
public PlayerMoveEndState moveEndState;
public PlayerJumpState jumpState;
public PlayerClimbState climbState;
public PlayerLedgeClimbState ledgeClimbState;
public PlayerMoveToWallState moveWallState;
public PlayerFallLoopState fallLoopState;
public PlayerPlatformerUpState platformerUpState;
public PlayerLandState landState;
public PlayerStateMachine(Player player)
{
this.player = player;
idleState = new PlayerIdleState(this);
moveStartState = new PlayerMoveStartState(this);
moveLoopState = new PlayerMoveLoopState(this);
moveEndState = new PlayerMoveEndState(this);
jumpState= new PlayerJumpState(this);
climbState = new PlayerClimbState(this);
ledgeClimbState = new PlayerLedgeClimbState(this);
moveWallState = new PlayerMoveToWallState(this);
fallLoopState = new PlayerFallLoopState(this);
platformerUpState = new PlayerPlatformerUpState(this);
landState= new PlayerLandState(this);
}
public override void ChangeState(IState targetState)
{
base.ChangeState(targetState);
player.ReusableData.currentState.Value = targetState.GetType().Name;
}
}
Player : CharacterBase
功能模块 | CharacterBase 的职责 | Player 的职责 |
---|---|---|
✅ 基础组件 | 初始化 Animator 和 CharacterController | 初始化 AnimancerComponent |
✅ 重力与地面检测 | 提供重力逻辑、地面检测、下落速度控制 | 直接继承使用 |
✅ 空中运动控制 | 控制空中水平速度、落地惯性清除 | 提供接口如 AddHorizontalVelocityInAir() 等,供子类调用 |
✅ RootMotion处理 | 控制是否使用动画自带位移、方向投影斜坡等 | 添加 OnAnimationUpdate() 给状态同步动画信息 |
✅ 角色移动 | 使用 controller.Move() 执行物理移动 | 使用动画和状态控制移动逻辑 |
✅ 状态管理 | ❌ 不涉及 | 管理 PlayerStateMachine ,控制角色各种行为(Idle、Jump等) |
✅ 动画播放 | ❌ 使用原生 Animator | ✅ 使用 Animancer 动态播放动画剪辑 |
✅ 输入/服务接入 | ❌ | 初始化 InputService 和 TimerService |
✅ 数据与逻辑封装 | ❌ | 使用 ReusableData 储存运行数据,ReusableLogic 封装通用逻辑 |
PlayerReusableData
它是一个“数据容器类”,非 MonoBehaviour 组件,Unity 游戏角色控制器中用于缓存和复用运行时变量的数据结构,帮助管理一些易变的(动态)信息,例如动画混合参数、状态切换数据、角色速度等。
所有字段在同一角色状态机中的多个状态之间共享数据,比如移动、跳跃、攀爬、锁敌等。避免重复创建和销毁,提高效率与一致性。
PlayerSO
public class PlayerSO : ScriptableObject
{
[field:SerializeField] public PlayerMovementData playerMovementData { get; private set; }
[field :SerializeField] public PlayerParameterData playerParameterData { get; private set; }
}
PlayerParameterData
public class PlayerParameterData
{
[field: SerializeField] public StringAsset standValueParameter { get; set; }
[field: SerializeField] public StringAsset rotationValueParameter { get; set; }
[field: SerializeField] public StringAsset speedValueParameter{ get; set; }
[field: SerializeField] public StringAsset LockValueParameter { get; set; }
[field: SerializeField] public StringAsset Lock_X_ValueParameter { get; set; }
[field: SerializeField] public StringAsset Lock_Y_ValueParameter { get; set; }
[field: SerializeField] public StringAsset moveInterruptEvent { get; set; }
[field: SerializeField] public StringAsset cancelClimbEvent { get; set; }
}
PlayerMovementData
集中管理玩家各种动作状态下的配置数据
public class PlayerMovementData
{
[field: SerializeField] public PlayerIdleData PlayerIdleData { get;private set; }
[field: SerializeField] public PlayerMoveStartData PlayerMoveStartData { get; private set; }
[field: SerializeField] public PlayerMoveLoopData PlayerMoveLoopData { get; private set; }
[field: SerializeField] public PlayerMoveEndData PlayerMoveEndData { get; private set; }
[field :SerializeField] public PlayerClimbData PlayerClimbData { get; private set; }
[field:SerializeField] public PlayerHangWallData PlayerHangWallData { get; set; }
[field: SerializeField] public PlayerJumpFallAndLandData PlayerJumpFallAndLandData { get;private set; }
}