[Unity] 使用 CharacterController 的冲刺 Dash 逻辑

580 阅读1分钟

获取鼠标位置:

gamedevbeginner.com/how-to-conv…

一般使用 RigidBody 的冲刺写法:

gist.github.com/bendux/aa8f…

我觉得这个挺不错的,但是就是 InputSystem 有一个微妙的逻辑。假设 Player Input 使用 Invoke Unity Event 的方式调用角色的控制脚本,移动是 WASD,冲刺是右键,在我按下 WASD 之中几个键的时候,如果我按下右键冲刺,那么冲刺完之后如果设当前运动方向为 0,那么角色之后就不会动了,即使冲刺之后你依然按着 WASD。

这就和一般人的游戏直觉相悖,但是这是因为 Input System 默认只在按键按下或者松开的时候才 Invoke,虽然你可以改成 Hold 方式……但是那个响应有点慢感觉

所以还需要考虑在冲刺之前缓存当前运动方向,冲刺完之后再恢复

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerHandler : MonoBehaviour
{
    #region Move Var

    [Header("Move")]

    [SerializeField]
    private float walkSpeed = 2f;

    [SerializeField]
    private Vector3 moveDir = Vector3.zero;

    [SerializeField]
    private Vector3 curVelocity = Vector3.zero;

    [SerializeField]
    private float stopThreshold = 0.01f;

    private bool isMoving
    {
        get => curVelocity.magnitude > stopThreshold;
    }

    protected CharacterController character;

    #endregion

    #region Dash Var

    [Header("Dash")]

    [SerializeField]
    private bool canDash = true;

    [SerializeField]
    private bool isDashing;

    [SerializeField]
    private float dashSpeed = 8f;

    [SerializeField]
    private float dashingTime = 0.2f;

    [SerializeField]
    private float dashingCooldown = 1f;

    [SerializeField]
    private TrailRenderer tr;

    Vector3 worldPosition = Vector3.zero;

    Plane plane = new Plane(Vector3.up, 0);

    Vector3 walkDirCached = Vector3.zero;

    #endregion

    private void Start()
    {
        character = this.GetComponent<CharacterController>();
    }

    private void FixedUpdate()
    {
        if (isDashing)
        {
            curVelocity = Vector3.Lerp(curVelocity, Vector3.zero, Time.fixedDeltaTime);
        }
        else
        {
            curVelocity = moveDir * walkSpeed;
        }

        character.Move(curVelocity * Time.fixedDeltaTime);
    }

    #region InputSystem Callback

    public void TryMove(InputAction.CallbackContext context)
    {
        if (isDashing)
            return;

        Debug.Log("Trying Move!");

        Vector2 moveInput = context.ReadValue<Vector2>().normalized;
        moveDir = new Vector3(moveInput.x, 0, moveInput.y);
        moveDir = Quaternion.AngleAxis(45, Vector3.up) * moveDir;
    }

    public void TryDash(InputAction.CallbackContext context)
    {
        if(canDash)
            StartCoroutine(Dash());
    }

    #endregion
    
    private IEnumerator Dash()
    {
        canDash = false;
        isDashing = true;
        tr.emitting = true;
        walkDirCached = moveDir;

        float distance;
        plane.SetNormalAndPosition(Vector3.up, transform.position);
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (plane.Raycast(ray, out distance))
        {
            worldPosition = ray.GetPoint(distance);
            moveDir = new Vector3(worldPosition.x - transform.position.x, 0, worldPosition.z - transform.position.z);
            curVelocity = dashSpeed * moveDir;
        }

        yield return new WaitForSeconds(dashingTime);
        tr.emitting = false;
        isDashing = false;
        curVelocity = Vector3.zero;
        moveDir = walkDirCached;

        yield return new WaitForSeconds(dashingCooldown);
        canDash = true;
    }
}