1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DS
{
/// <summary>
/// 动画处理器 - 负责管理角色动画参数的更新、根运动(Root Motion)的提取,
/// 以及角色旋转状态的控制。
/// </summary>
public class AnimatorHandler : MonoBehaviour
{
[Header("组件引用")]
public PlayerLocomotion playerLocomotion; // 玩家移动控制器引用,用于传递根运动数据
public Animator animator; // 动画器组件引用
[Header("动画参数哈希")]
private int _vertical; // "Vertical" 动画参数的哈希值,用于高效设置动画参数
private int _horizontal; // "Horizontal" 动画参数的哈希值
[Header("旋转状态")]
public bool canRotate; // 是否允许角色旋转,在动画事件中控制
/// <summary>
/// 初始化函数 - 获取 Animator 组件并计算动画参数的哈希值。
/// 哈希值用于替代字符串名称,提高性能。
/// </summary>
public void Initialize()
{
// 获取当前 GameObject 上的 Animator 组件
animator = GetComponent<Animator>();
// 使用 Animator.StringToHash 将字符串参数名转换为整数哈希值
// 这样在 SetFloat 时使用哈希值比使用字符串更高效
_vertical = Animator.StringToHash("Vertical");
_horizontal = Animator.StringToHash("Horizontal");
}
/// <summary>
/// 更新动画参数值 - 将输入的移动数值映射为动画混合树使用的离散值。
/// 输入值范围通常为 -1 到 1,将其量化为 -1, -0.5, 0, 0.5, 1 五个档位,
/// 以实现从慢走到快跑的平滑过渡。
/// </summary>
/// <param name="verticalMovement">垂直方向的移动输入(前进/后退),范围 -1 ~ 1</param>
/// <param name="horizontalMovement">水平方向的移动输入(左移/右移),范围 -1 ~ 1</param>
public void UpdateAnimatorValues(float verticalMovement, float horizontalMovement)
{
#region Vertical — 垂直方向(前进/后退)的动画参数处理
float v = 0; // 存储映射后的垂直动画参数值
// 判断垂直移动输入,将其量化为几档:
// 0~0.55 之间视为慢速行走(0.5),大于 0.55 视为奔跑(1)
// 负值同理,处理后退动作
if (verticalMovement > 0 && verticalMovement < 0.55f)
{
v = 0.5f; // 慢速前进
}
else if(verticalMovement > 0.55f)
{
v = 1; // 快速前进/奔跑
}
else if (verticalMovement < 0 && verticalMovement > -0.55f)
{
v = -0.5f; // 慢速后退
}
else if(verticalMovement < -0.55f)
{
v = -1; // 快速后退
}
else
{
v = 0; // 无输入或死区范围内,保持静止
}
#endregion
#region Horizontal — 水平方向(左/右)的动画参数处理
float h = 0; // 存储映射后的水平动画参数值
// 同样将水平移动输入量化为几档,用于控制侧移或转身动画
if (horizontalMovement > 0 && horizontalMovement < 0.55f)
{
h = 0.5f; // 慢速向右
}
else if(horizontalMovement > 0.55f)
{
h = 1; // 快速向右
}
else if (horizontalMovement < 0 && horizontalMovement > -0.55f)
{
h = -0.5f; // 慢速向左
}
else if(horizontalMovement < -0.55f)
{
h = -1; // 快速向左
}
else
{
h = 0; // 无输入或死区范围内,保持静止
}
#endregion
// 将计算后的参数值设置到 Animator 中
// 第三个参数 dampTime 为 0.1f,表示在 0.1 秒内平滑过渡到目标值
// 第四个参数 deltaTime 用于确保平滑速度与帧率无关
animator.SetFloat(_vertical, v, 0.1f, Time.deltaTime);
animator.SetFloat(_horizontal, h, 0.1f, Time.deltaTime);
}
/// <summary>
/// 动画事件回调 - 允许角色在动画的特定帧开始旋转。
/// 通常在动画的某个时间点(如转身动画的起始帧)由动画事件调用。
/// </summary>
public void CanRotate()
{
canRotate = true; // 启用旋转
}
/// <summary>
/// 动画事件回调 - 停止角色的旋转。
/// 通常在动画的某个时间点(如转身动画的结束帧)由动画事件调用,
/// 防止角色在不需要旋转时继续旋转。
/// </summary>
public void StopRotation()
{
canRotate = false; // 禁用旋转
}
/// <summary>
/// MonoBehaviour 生命周期函数 - 在 Animator 更新动画时每帧调用。
/// 用于处理根运动(Root Motion)数据,将动画驱动的位移传递给 PlayerLocomotion。
/// 需要勾选 Animator 组件的"Apply Root Motion"选项才能触发此回调。
/// </summary>
private void OnAnimatorMove()
{
// 获取当前帧的根运动位移向量
var rootMotion = GetRootMotion();
// 输出调试日志,用于查看根运动数据
Debug.Log(rootMotion);
// 将根运动数据传递给 PlayerLocomotion 组件
// PlayerLocomotion 会将这些位移应用到角色控制器上
playerLocomotion.rootMotion = rootMotion;
}
/// <summary>
/// 编辑器辅助函数 - 在 Scene 视图中绘制调试线条,
/// 可视化显示根运动的方向和大小(放大 2 倍以便观察)。
/// 仅在编辑器中有效,不会影响运行时性能。
/// </summary>
private void OnDrawGizmos()
{
// 从角色当前位置绘制一条线到 当前位置 + 根运动方向 * 2
Gizmos.DrawLine(transform.position, transform.position + GetRootMotion() * 2f);
}
/// <summary>
/// 提取根运动向量 - 从 Animator 的当前速度中提取水平方向上的位移,
/// 忽略垂直方向(Y 轴)的运动。
/// 这样可以防止动画中的微小垂直位移影响角色位置(如地面抖动)。
/// </summary>
/// <returns>返回一个 Vector3,表示投影到水平平面上的根运动位移</returns>
private Vector3 GetRootMotion()
{
// 假定地面法线始终朝上(即世界坐标的 Y 轴方向)
// 注意:如果角色在斜坡上运动,此处需要根据地面法线动态调整
var normalVector = Vector3.up;
// 将 animator.velocity(动画驱动的速度向量)投影到法线定义的平面上
// Vector3.ProjectOnPlane 会去除向量中沿法线方向的分量
// 这样只保留水平方向的位移,避免 Y 轴上的微小波动影响角色位置
return Vector3.ProjectOnPlane(animator.velocity, normalVector);
}
}
}
|