[Unity] 良好手感的人物移动速率计算
我们先来看这个主要的计算移动的函数
protected override void OnStep(Player player)
{
// 获取当前输入的移动方向(相对于相机)
var inputDirection = player.inputs.GetMovementCameraDirection();
// 判断是否有输入
if (inputDirection.sqrMagnitude > 0)
{
// 获取输入移动方向相对于当前移动方向的偏移量
var dot = Vector3.Dot(inputDirection, player.lateralVelocity);
// 这里是触发制动状态的条件判断,下面画图的时候进行讲解
if (dot >= player.stats.current.brakeThreshold)
{
player.Accelerate(inputDirection);
player.FaceDirectionSmooth(player.lateralVelocity);
} else
{
player.states.Change<BrakePlayerState>();
}
}
}
首先根据注释我们讲这个计算速率分成几个步骤
- 通过InputSystem获取Vecotr2输入值算出当前输入相对于摄像机的移动方向
- 在非制动状态下通过
Accelerate(Vector3 direction)方法计算速率 - 平滑转向指定方向
进入函数
获取方向
// 这个函数的作用是:获取玩家的输入方向,并将其转换为相对于摄像机朝向的方向。
public virtual Vector3 GetMovementCameraDirection()
{
// 获取移动方向
var direction = GetMovementDirection();
// 由于direction返回的是一个Vector3,这里就取模长
if (direction.sqrMagnitude > 0)
{
// 这里计算出相机在Y轴上的偏航角度(Yaw)
var rotation = Quaternion.AngleAxis(m_camera.transform.eulerAngles.y, Vector3.up);
// 根据摄像机的朝向修改人物的移动方向,例如人物现在按W,摄像机看向相对右手边,此时人物不是绝对移动向前,而是向摄像机的偏航角方向移动
direction = rotation * direction;
// 归一化,只取方向
direction = direction.normalized;
}
return direction;
}
public virtual Vector3 GetMovementDirection()
{
if (Time.time < m_movementDirctionUnlock)
{
return Vector3.zero;
}
// 从InputSystem里获取Vector2
var value = m_movement.ReadValue<Vector2>();
return GetAxisWithCrossDeadZone(value);
}
public virtual Vector3 GetAxisWithCrossDeadZone(Vector2 axis)
{
var deadZone = InputSystem.settings.defaultDeadzoneMin;
// Abs仅仅判断输入的值是否大于死区的值
axis.x = Mathf.Abs(axis.x) > deadZone ? RemapToDeadzone(axis.x, deadZone) : 0;
axis.y = Mathf.Abs(axis.y) > deadZone ? RemapToDeadzone(axis.y, deadZone) : 0;
// 这里仅控制平面移动
return new Vector3(axis.x, 0, axis.y);
}
// 死区的值如果设置成0.2,当ABS==0.21时,如果不调用这个方法则启动时输入的值就是0.21,会非常的突兀
// 这里除法的作用就是归一化,防止出现速度比预期少,例如输入value为1,没有除法的话最大速度就是(value - deadzone)
private float RemapToDeadzone(float value, float deadzone) => (value - deadzone) / (1 - deadzone);
计算速率
有了Vector3方向之后我们就可以进行速率的计算了
让我们进入Accelerate方法当中去
public virtual void Accelerate(Vector3 direction)
{
// 转向时的摩擦力
var turningDrag = isGrounded && inputs.GetRun()
? stats.current.runningTurnningDrag
: stats.current.turningDrag;
// 速率
var acceleration = isGrounded && inputs.GetRun()
? stats.current.runningAcceleration
: stats.current.acceleration;
// 最快速度
var topSpeed = inputs.GetRun()
? stats.current.runningTopSpeed
: stats.current.topSpeed;
// 最终得到的速率
var finalAcceleration = isGrounded ? acceleration : stats.current.airAcceleration;
Accelerate(direction, turningDrag, finalAcceleration, topSpeed);
}
public virtual void Accelerate(Vector3 direction, float turningDrag, float acceleration, float topSpeed)
{
if (direction.sqrMagnitude > 0)
{
// 代表“我当前的速度(lateralVelocity),有多‘顺着’我想去的新方向(direction)”。
// 如果数字很大很正:非常顺路。
// 如果数字是0:正好垂直,不顺路也不逆路。
// 如果数字是负数:完全是反方向,在“开倒车”。
// speed 的初始值,就是你当前速度 lateralVelocity 在你想去的新方向 direction 上的“贡献值”。
var speed = Vector3.Dot(direction, lateralVelocity);
// direction 是一个只有方向没有大小的“路标”,speed 是一个只有大小没有方向的“油门大小”。两者一乘,就得到了一个既有正确方向,又有合适大小 的速度向量。这就是我们分解出的“好速度”。
var velocity = direction * speed;
// 坏速度,需要被摩擦力抵消的速度
var turningVelocity = lateralVelocity - velocity;
var turiningDelta = turningDrag * turningDragMultiplier * Time.deltaTime;
// 当前能达到的最大移速
var targetTopSpeed = topSpeed * topSpeedMultiplier;
// 如果当前速度未达到最高移速或者速度小于0
if (lateralVelocity.magnitude < targetTopSpeed || speed < 0)
{
// 持续加速
speed += acceleration * acclerationMultiplier * Time.deltaTime;
// 限制速度,在当前可达到最大速度区间
speed = Mathf.Clamp(speed, -targetTopSpeed, targetTopSpeed);
}
// 当前的移动向量(这里不仅需要方向,而且要有大小了)
velocity = direction * speed;
// 每次调用方法将当前的坏速率向量慢慢消除
turningVelocity = Vector3.MoveTowards(turningVelocity, Vector3.zero, turiningDelta);
// 将当前的速率更新为计算过后的速率
lateralVelocity = velocity + turningVelocity;
}
}
这里Dot之前一直存在一个误区,单纯把Dot拿来当作判断位置的值,但其实dot值的大小还是受到模长的影响,并非只有[-1,1]
- 它的正负号 依然告诉我们方向关系(前方(>0)还是后方(<0)或者是垂直(=0))。
- 它的绝对值 告诉我们“有多顺路”或“有多逆路”,这个值的大小直接受到 lateralVelocity 模长的影响。

浙公网安备 33010602011771号