三、人物運動控制:Rigidbody

人物的運動有很多種,最簡單的可以直接用transform.translate或者直接改變?nèi)宋锏腜osition,這種方法在2D游戲可能會有采用。
另外還可以用導(dǎo)航系統(tǒng)進(jìn)行運動。但用得更多的是另外兩種方式:Rigidbody和CharacterController。
CharacterController能很快做出人物的運動方案,但是相比Rigidbody,物理特性表現(xiàn)沒有那么好,接口也相對少。

結(jié)合視角控制的內(nèi)容,采用了第三人稱固定視角,搖桿控制,并加入了一個小型事件分發(fā)系統(tǒng)。實現(xiàn)搖桿控制人物的移動,懸崖邊界檢測,地面檢測,相機第三人稱視角跟隨。

其中移動模塊代碼如下:

using UnityEngine;

public class MoveMotor : MonoBehaviour
{
    public Animator animator;
    public Rigidbody selfRigidbody;//自身剛體組件
    public float speed = 17f;//移動速度
    public float rotSpeed = 17f;//旋轉(zhuǎn)速度
    public Transform[] groundPoints;//地面檢測點
    public LayerMask groundLayerMask = ~0;//地面LayerMask
    public LayerMask wallLayerMask = ~0;//墻壁LayerMask
    int mIsMoveAnimatorHash;//移動Animator變量哈希
    private Camera mainCamera;

    public bool IsMoving { get; private set; }
    public Vector3 MoveDirection { get; private set; }


    void Start()
    {
        mIsMoveAnimatorHash = Animator.StringToHash("IsMove");
        EventManager.AddEvent<Vector2>(EventTypeList.EventTouchDir, InputDir);
        mainCamera = Camera.main;
    }

    void InputDir(Vector2 v2)
    {
        horizontal = v2.x;
        vertical = v2.y;
    }

    float horizontal;
    float vertical;
    void Update()
    {
        const float INPUT_EPS = 0.2f;
        //horizontal = Input.GetAxis("Horizontal");//橫向軸的值
        //vertical = Input.GetAxis("Vertical");//縱向軸的值

        var inputDirection = new Vector3(horizontal, 0f, vertical);//輸入向量
        var upAxis = -Physics.gravity.normalized;//up軸向
        var moveDirection = CameraDirectionProcess(inputDirection, upAxis);//相機輸入方向修正
        MoveDirection = moveDirection;
        if (inputDirection.magnitude > INPUT_EPS)//是否有輸入方向
        {               
            var raycastHit = default(RaycastHit);
            var groundNormal = Vector3.zero;
            var groundedFlag = GroundProcess(ref raycastHit, ref moveDirection, out groundNormal, upAxis);//地面檢測

            if (groundedFlag)
            {
                var cacheMoveDirection = moveDirection;
                var wallFlag = WallProcess(ref raycastHit, ref moveDirection, groundNormal, upAxis);//墻壁檢測
                var cliffFlag = false;
                if (!wallFlag)
                    cliffFlag = CliffProcess(ref raycastHit, ref moveDirection, groundNormal, upAxis);//懸崖檢測
                if (!cliffFlag)
                    selfRigidbody.velocity = moveDirection * speed * Time.fixedDeltaTime;//更新位置
                UpdateGroundDetectPoints();//打亂地面檢測點順序
                RotateProcess(cacheMoveDirection, upAxis);//更新旋轉(zhuǎn)
            }
            animator.SetBool(mIsMoveAnimatorHash, true);//更新Animator變量
            IsMoving = true;
        }
        else//沒有移動
        {
            MoveDirection = Vector3.zero;
            animator.SetBool(mIsMoveAnimatorHash, false);//更新Animator變量
            IsMoving = false;
            Debug.Log("沒有輸入方向");
        }
    }

    Vector3 CameraDirectionProcess(Vector3 inputDirection, Vector3 upAxis)
    {
        mainCamera = Camera.main;
        var quat = Quaternion.FromToRotation(mainCamera.transform.up, upAxis);//不同重力的up軸修正
        var cameraForwardDirection = quat * mainCamera.transform.forward;//轉(zhuǎn)換forward方向
        var moveDirection = Quaternion.LookRotation(cameraForwardDirection, upAxis) * inputDirection.normalized;//轉(zhuǎn)換輸入向量方向
        return moveDirection;
    }

    //檢測目前是否著地
    bool GroundProcess(ref RaycastHit raycastHit, ref Vector3 moveDirection, out Vector3 groundNormal, Vector3 upAxis)
    {
        const float GROUND_RAYCAST_LENGTH = 0.2f;//地面檢測射線長度
        var result = false;
        for (int i = 0; i < groundPoints.Length; i++)
        {
            var tempRaycastHit = default(RaycastHit);
            if (Physics.Raycast(new Ray(groundPoints[i].position, -upAxis), out tempRaycastHit, GROUND_RAYCAST_LENGTH, groundLayerMask))//投射地面射線
            {
                if (raycastHit.transform == null || Vector3.Distance(transform.position, tempRaycastHit.point) < Vector3.Distance(transform.position, raycastHit.point))
                    raycastHit = tempRaycastHit;//選取最近的地面點
                result = true;
                break;
            }
        }
        groundNormal = raycastHit.normal;//返回地面法線
        var upQuat = Quaternion.FromToRotation(upAxis, groundNormal);
        moveDirection = upQuat * moveDirection;//根據(jù)地面法線修正移動位置
        return result;
    }

    //檢測是否墻壁
    bool WallProcess(ref RaycastHit raycastHit, ref Vector3 moveDirection, Vector3 groundNormal, Vector3 upAxis)
    {
        const float HEIGHT = 1.2f;//玩家高度估算值
        const float OBLIQUE_P0 = 0.3f, OBLIQUE_P1 = 0.6f;//斜方向射線偏移
        const float RAYCAST_LEN = 0.37f;//射線長度
        const float DOT_RANGE = 0.86f;//點乘范圍約束
        const float ANGLE_STEP = 30f;//檢測角度間距
        var result = false;
        var ray = new Ray(transform.position + upAxis * HEIGHT, moveDirection);
        for (float angle = -90f; angle <= 90f; angle += ANGLE_STEP)//180度內(nèi)每隔一定角度進(jìn)行射線檢測
        {
            var quat = Quaternion.AngleAxis(angle, upAxis);//得到當(dāng)前角度
            ray = new Ray(transform.position, quat * moveDirection);
            var p0 = ray.origin + ray.direction * OBLIQUE_P0;
            var p1 = ray.origin + upAxis * HEIGHT + ray.direction * OBLIQUE_P1;
            if (Physics.Linecast(p0, p1, out raycastHit, wallLayerMask))//是否碰到墻壁
            {
                var newRay = new Ray(Vector3.Project(raycastHit.point, upAxis) + Vector3.ProjectOnPlane(ray.origin, upAxis), ray.direction);
                if (Physics.Raycast(newRay, out raycastHit, RAYCAST_LEN, wallLayerMask))//重新得到射線位置并投射
                {
                    if (Vector3.Dot(moveDirection, -raycastHit.normal) < DOT_RANGE)//點乘約束
                    {
                        var cross = Vector3.Cross(raycastHit.normal, upAxis).normalized;
                        var cross2 = -cross;
                        if (Vector3.Dot(cross, moveDirection) > Vector3.Dot(cross2, moveDirection))//獲得最接近方向
                            moveDirection = cross;
                        else
                            moveDirection = cross2;
                        break;//若已確定修正方向則跳出循環(huán)
                    }
                }
                result = true;//確定碰到了墻壁
            }
        }
        return result;
    }

    //檢測是否是懸崖
    bool CliffProcess(ref RaycastHit raycastHit, ref Vector3 moveDirection, Vector3 groundNormal, Vector3 upAxis)
    {
        const float GROUND_RAYCAST_LENGTH = 0.4f;//地面檢測射線長度
        var result = false;
        for (int i = 0; i < groundPoints.Length; i++)//遍歷地面檢測點
        {
            var relative = groundPoints[i].position - transform.position;//取相對位置
            var quat = Quaternion.FromToRotation(upAxis, groundNormal);//映射到地面法線方向四元數(shù)
            var newPoint = transform.position + moveDirection + quat * relative;
            var ray = new Ray(newPoint, -upAxis);//取移動后的位置投射射線
            if (!Physics.Raycast(ray, out raycastHit, GROUND_RAYCAST_LENGTH, groundLayerMask))//只要有一個未檢測到地面則為懸崖
            {
                result = true;//返回true
                break;
            }
        }
        return result;
    }

    void RotateProcess(Vector3 moveDirection, Vector3 upAxis)
    {
        moveDirection = Vector3.ProjectOnPlane(moveDirection, upAxis);//投影到up平面上
        var playerLookAtQuat = Quaternion.LookRotation(moveDirection, upAxis);//得到移動方向代表的旋轉(zhuǎn)
        transform.rotation = Quaternion.Lerp(transform.rotation, playerLookAtQuat, rotSpeed * Time.deltaTime);//更新插值
    }

    void UpdateGroundDetectPoints()
    {
        var groupPointsIndex_n = Random.Range(0, groundPoints.Length);//隨機一個索引
        var temp = groundPoints[groupPointsIndex_n];
        groundPoints[groupPointsIndex_n] = groundPoints[groundPoints.Length - 1];
        groundPoints[groundPoints.Length - 1] = temp;//交換地面檢測點,防止每次順序都一樣。
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容