[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>();
            }
        }
    }

首先根据注释我们讲这个计算速率分成几个步骤

  1. 通过InputSystem获取Vecotr2输入值算出当前输入相对于摄像机的移动方向
  2. 在非制动状态下通过Accelerate(Vector3 direction)方法计算速率
  3. 平滑转向指定方向

进入函数

获取方向

// 这个函数的作用是:获取玩家的输入方向,并将其转换为相对于摄像机朝向的方向。
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 模长的影响。
posted @ 2025-07-29 21:13  MingHaiZ  阅读(22)  评论(0)    收藏  举报