【Unity】角色沿着地形边缘滑行效果实现

204 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

如图,游戏角色在地形的边缘,且沿移动方向(蓝色Z轴方向)继续移动将要掉落地形时,控制角色沿着地形边缘滑行移动,而不是让角色直接停在原地(这样处理游戏体验极差,滑行相对好一些)。

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

public class Player : MonoBehaviour
{
    private Rigidbody roleRigidbody;
    private int groundLayerMask;
    private float speed = 300;

    private void Start()
    {
        groundLayerMask = LayerMask.GetMask("Ground");
        roleRigidbody = GetComponent<Rigidbody>();
    }

    Vector3 moveVelocity;
    private void Update()
    {
        moveVelocity = Vector3.zero;
        if(Input.GetKey(KeyCode.A))
        {
            if(Input.GetKey(KeyCode.W))
            {
                moveVelocity.x = -1;
                moveVelocity.z = 1;
            }
            else if(Input.GetKey(KeyCode.S))
            {
                moveVelocity.x = -1;
                moveVelocity.z = -1;
            }
            else
            {
                moveVelocity.x = -1;
                moveVelocity.z = 0;
            }
        }
        else if(Input.GetKey(KeyCode.D))
        {
            if(Input.GetKey(KeyCode.W))
            {
                moveVelocity.x = 1;
                moveVelocity.z = 1;
            }
            else if(Input.GetKey(KeyCode.S))
            {
                moveVelocity.x = 1;
                moveVelocity.z = -1;
            }
            else
            {
                moveVelocity.x = 1;
                moveVelocity.z = 0;
            }
        }
        else if(Input.GetKey(KeyCode.W))
        {
            moveVelocity.x = 0;
            moveVelocity.z = 1;
        }
        else if(Input.GetKey(KeyCode.S))
        {
            moveVelocity.x = 0;
            moveVelocity.z = -1;
        }

        if(moveVelocity != Vector3.zero)
        {
            moveVelocity = moveVelocity.normalized * speed * Time.deltaTime;
            if(IsPlayerCanMove(moveVelocity))
                Move(moveVelocity);
            else
                SidewipeMove(moveVelocity);
        }
    }

    public void Move(Vector3 velocity)
    {
        roleRigidbody.velocity = velocity;
        if(velocity != Vector3.zero)
            transform.forward = Vector3.Lerp(transform.forward,velocity.normalized,0.5f).normalized;
    }
    public void Move(Vector3 velocity,Vector3 forwardDir)
    {
        roleRigidbody.velocity = velocity;
        transform.forward = Vector3.Lerp(transform.forward,forwardDir.normalized,0.5f).normalized;
    }

    public void StopMove()
    {
        roleRigidbody.velocity = Vector3.zero;
    }

    public bool IsPlayerCanMove(Vector3 moveVelocity)
    {
        //根据移动速度计算下一帧移动位移*安全系数
        moveVelocity = moveVelocity * Time.fixedDeltaTime * 1.2f;
        //最小预判距离为1
        if(moveVelocity.sqrMagnitude < 1)
            moveVelocity.Normalize();
        //预算下一帧移动后的位置
        moveVelocity += transform.position;
        //检测点向上偏移2单位
        moveVelocity.y += 2;

        //检测下一帧位置是否在可移动范围内
        if(Physics.Raycast(moveVelocity,Vector3.down,5,groundLayerMask))
            return true;

        Debug.DrawLine(moveVelocity + Vector3.up * 5,moveVelocity,Color.red);
        return false;
    }

    //角色遇到障碍物/地图边缘,在可行走区域内贴边滑行
    const int predictAngle = 80;
    int offsetAngle, leftAngle, rightAngle;
    Vector3 tempVelocity, targetVelocity;
    public void SidewipeMove(Vector3 velocity)
    {
        if(velocity == Vector3.zero)
        {
            StopMove();
            return;
        }

        //当前移动方向往右偏移80度
        offsetAngle = predictAngle;
        tempVelocity = Quaternion.AngleAxis(offsetAngle,Vector3.up) * velocity;

        if(IsPlayerCanMove(tempVelocity))
        {
            //可往右侧滑行
            leftAngle = 0;
            rightAngle = offsetAngle;
        }
        else
        {
            offsetAngle *= -1;
            tempVelocity = Quaternion.AngleAxis(offsetAngle,Vector3.up) * velocity;
            if(IsPlayerCanMove(tempVelocity))
            {
                //可往左侧滑行
                leftAngle = offsetAngle;
                rightAngle = 0;
            }
            else
            {
                //不可滑行,转向、停止移动
                //transform.forward = velocity.normalized;
                transform.forward = Vector3.Lerp(transform.forward,velocity.normalized,0.05f);
                StopMove();
                return;
            }
        }

        int count = 0;
        //二分法判断可滑行方向
        targetVelocity = tempVelocity;
        while(rightAngle - leftAngle > 10)
        {
            offsetAngle = (leftAngle + rightAngle) / 2;
            tempVelocity = Quaternion.AngleAxis(offsetAngle,Vector3.up) * velocity;
            if(IsPlayerCanMove(tempVelocity))
            {
                if(offsetAngle > 0)
                    rightAngle = offsetAngle;
                else
                    leftAngle = offsetAngle;
                targetVelocity = tempVelocity;
            }
            else
            {
                if(offsetAngle > 0)
                    leftAngle = offsetAngle;
                else
                    rightAngle = offsetAngle;
            }
            count++;
        }

        //减慢速度
        targetVelocity *= 0.75f;
        //滑行时方向保持摇杆方向,与移动方向不同向
        Move(targetVelocity,velocity);
    }

}