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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
|
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.VisualScripting;
using UnityEngine;
namespace DS
{
/// <summary>
/// 玩家角色移动/旋转控制器
/// 负责处理玩家的移动、旋转,并与输入系统、动画系统、物理系统进行交互
/// </summary>
public class PlayerLocomotion : MonoBehaviour
{
// ============================================================
// 根运动混合参数
// ============================================================
/// <summary>
/// 根运动(Root Motion)与程序化移动的混合比例。
/// 0 = 完全使用程序化移动(手动计算位移),
/// 1 = 完全使用动画中的根运动(由动画驱动位移)。
/// 建议在跑步、行走等需要动画精细控制位移时使用根运动;
/// 在需要响应式操控(如闪避、冲刺)时可降低此值。
/// </summary>
[Range(0, 1)] public float rootMotionBlending = 1f;
/// <summary>
/// 便捷属性:将 rootMotionBlending 映射为布尔值。
/// true = 使用根运动(blending > 0.5),
/// false = 使用程序化移动(blending <= 0.5)。
/// </summary>
public bool usingRootMotion
{
get => rootMotionBlending > 0.5f;
set => rootMotionBlending = value ? 0 : 1;
}
// ============================================================
// 公开字段
// ============================================================
/// <summary>
/// 当前帧从动画中提取的根运动位移向量(由 AnimatorHandler 提供)。
/// 单位:米/帧。
/// </summary>
[HideInInspector] public Vector3 rootMotion;
/// <summary>
/// 当前角色的 Transform 引用(缓存避免重复查找)。
/// </summary>
[HideInInspector] public Transform myTransform;
/// <summary>
/// 动画处理器引用(负责驱动 Animator 参数和根运动提取)。
/// </summary>
[HideInInspector] public AnimatorHandler animatorHandler;
/// <summary>
/// 角色的 Rigidbody 组件引用(用于物理驱动移动)。
/// 使用 new 关键字隐藏父类 Rigidbody 字段以避免混淆。
/// </summary>
public new Rigidbody rigidbody;
/// <summary>
/// 常规第三人称摄像机对象的引用(用于旋转跟随等逻辑)。
/// </summary>
public GameObject normalCamera;
// ============================================================
// 私有字段
// ============================================================
private Transform _cameraObject; // 主摄像机的 Transform 缓存
private InputHandler _inputHandler; // 输入处理器引用
private Vector3 _moveDirection; // 计算出的移动方向向量
private Vector3 _normalVector; // 地面法线向量(用于投影移动方向到地面)
private Vector3 _targetPosition; // 目标位置(当前未使用,保留备用)
// ============================================================
// 参数配置
// ============================================================
[Header("Stats")]
[SerializeField] private float movementSpeed = 5; // 移动速度(单位:米/秒)
[SerializeField] private float sprintSpeed = 5; //冲刺速度
[SerializeField] private float rotationSpeed = 10; // 旋转速度(单位:度/秒,实际用于 Slerp 插值)
public bool isSprinting;
// ============================================================
// 初始化
// ============================================================
private void Start()
{
// 获取并缓存 Rigidbody 组件
rigidbody = GetComponent<Rigidbody>();
// 获取并缓存输入处理器
_inputHandler = GetComponent<InputHandler>();
// 在子物体中查找 AnimatorHandler 组件并缓存
animatorHandler = GetComponentInChildren<AnimatorHandler>();
// 缓存主摄像机的 Transform
_cameraObject = Camera.main.transform;
// 缓存当前物体的 Transform
myTransform = transform;
// 初始化 AnimatorHandler(例如获取 Animator 组件引用等)
animatorHandler.Initialize();
}
// ============================================================
// 每帧更新(使用 Update 处理移动逻辑,因为需要与输入同步)
// ============================================================
// ============================================================
// 固定更新(当前未使用,但保留以备未来物理相关逻辑)
// ============================================================
private void FixedUpdate()
{
// 当前未在此处处理逻辑
// 若需要基于物理的移动,可将 rigidbody.velocity 赋值移到这里
}
// ============================================================
// 移动与旋转相关方法
// ============================================================
#region Movement
/// <summary>
/// 处理角色旋转逻辑。
/// 根据输入方向和摄像机朝向,平滑旋转角色面向移动方向。
/// </summary>
/// <param name="delta">帧时间(秒)</param>
private void HandleRotation(float delta)
{
Vector3 targetDir = Vector3.zero; // 目标朝向向量
// 注意:当前未使用 moveOverride,保留备用
float moveOverride = _inputHandler.moveAmount;
// ----------------------------------------------------------
// 1. 根据输入计算目标朝向
// ----------------------------------------------------------
// 目标朝向 = 摄像机正方向 * 垂直输入 + 摄像机右方向 * 水平输入
targetDir = _cameraObject.forward * _inputHandler.vertical;
targetDir += _cameraObject.right * _inputHandler.horizontal;
// 归一化并置平 Y 轴
targetDir.Normalize();
targetDir.y = 0;
// 如果没有输入(目标方向为零向量),则保持当前面向不变
if (targetDir == Vector3.zero)
{
targetDir = myTransform.forward;
}
// ----------------------------------------------------------
// 2. 通过 Slerp 平滑旋转至目标方向
// ----------------------------------------------------------
float rs = rotationSpeed; // 旋转速度
Quaternion tr = Quaternion.LookRotation(targetDir); // 目标旋转四元数
// 使用球形插值(Slerp)实现平滑旋转
Quaternion targetRotation = Quaternion.Slerp(myTransform.rotation, tr, rs * delta);
// 应用旋转
myTransform.rotation = targetRotation;
}
// ============================================================
// 角色移动更新(核心逻辑)
// ============================================================
public void UpdateCharacterMovement(float delta)
{
if (_inputHandler.rollFlag)
return;
// ----------------------------------------------------------
// 1. 计算移动方向(基于摄像机朝向)
// ----------------------------------------------------------
// 前方向量 = 摄像机正方向 * 垂直输入(W/S)
_moveDirection = _cameraObject.forward * _inputHandler.vertical;
// 侧方向量 = 摄像机右方向 * 水平输入(A/D)
_moveDirection += _cameraObject.right * _inputHandler.horizontal;
// 归一化,确保对角线移动时速度不叠加(保持长度 1)
_moveDirection.Normalize();
// 将 y 分量置 0,确保移动只在水平面上
_moveDirection.y = 0;
// ----------------------------------------------------------
// 2. 应用移动速度
// ----------------------------------------------------------
float speed = movementSpeed;
if (_inputHandler.sprintFlag)
{
speed = sprintSpeed;
isSprinting = true;
_moveDirection *= speed;
}
else
{
_moveDirection *= speed; // 现在 _moveDirection 是速度向量(单位:米/秒)
}
// ----------------------------------------------------------
// 3. 混合根运动与程序化移动
// ----------------------------------------------------------
// 根据 rootMotionBlending 在根运动位移和程序化速度之间做线性插值
var baseVelocity = Vector3.Lerp(rootMotion, _moveDirection, rootMotionBlending);
// 将速度向量投影到地面法线平面上(防止角色飞起或陷入地面)
Vector3 projectedVelocity = Vector3.ProjectOnPlane(baseVelocity, _normalVector);
// 设置 Rigidbody 速度(物理驱动移动)
rigidbody.velocity = new Vector3(
projectedVelocity.x,
// 保留原始的 Velocity Y
rigidbody.velocity.y,
projectedVelocity.z);
// ----------------------------------------------------------
// 4. 更新动画参数
// ----------------------------------------------------------
// 将玩家的移动量(0~1)传递给 Animator,用于混合行走/奔跑动画
animatorHandler.UpdateAnimatorValues(_inputHandler.moveAmount, 0,isSprinting);
// ----------------------------------------------------------
// 5. 更新翻滚参数
// ----------------------------------------------------------
if (_inputHandler.rollTriggered)
{
animatorHandler.TriggerRoll();
_inputHandler.rollTriggered = false;
}
// ----------------------------------------------------------
// 5. 处理旋转(仅在允许旋转时执行)
// ----------------------------------------------------------
if (animatorHandler.canRotate)
{
HandleRotation(delta);
}
}
public void HandleRollingAndSpringting(float delta)
{
if (animatorHandler.animator.GetBool("isInteracting"))
{
return;
}
if (_inputHandler.rollFlag)
{
_moveDirection = _cameraObject.forward * _inputHandler.vertical;
_moveDirection += _cameraObject.right * _inputHandler.horizontal;
if (_inputHandler.moveAmount > 0)
{
animatorHandler.PlayerTargetAnimation("Roll",true);
_moveDirection.y = 0;
Quaternion rollRotation = Quaternion.LookRotation(_moveDirection);
myTransform.rotation = rollRotation;
}
else
{
animatorHandler.PlayerTargetAnimation("step_back",true);
}
}
}
#endregion
}
}
|