CharacterController速度闪烁

时间:2017-06-13 05:22:47

标签: c# unity3d

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine.Unity;

public class PlayerController : MonoBehaviour
{
    #region components
    private CharacterController controller;
    private Animator animator;
    private LedgeGrabber grabber;
    private SkeletonAnimator skelAnim;
    private GameObject root;
    #endregion

    #region movement
    //[HideInInspector]
    public Vector3 velocity = Vector3.zero;
    private float gravity = -100f;
    [HideInInspector]
    public float walkSpeed = 20;
    [HideInInspector]
    public float runSpeed = 40;
    [HideInInspector]
    public float curSpd;
    [Range(0, 1)]
    public float airControlPercent;
    private float speedSmoothTime = 0.1f;
    private float speedSmoothVelocity;
    public bool onGround = false;
    #endregion

    #region jumps
    private float jumpHeight = 10f;
    public bool canJump = false;
    private float timer = .5f;
    public bool landing = false;
    public bool canLeap = false;
    #endregion

    #region animation
    private float animSpdPer;
    private AnimatorStateInfo info;
    #endregion

    #region slope and wall
    private float slopeAngle;
    public bool maxSlope = false;
    public bool normSlope = false;
    private Vector3 slopeNormal;
    public bool wallSliding = false;
    private float wallSlideSpdMax = 16f;
    private int dirX;
    [HideInInspector]
    public int wallDir;
    public Vector2 jumpOff = Vector2.zero;
    public Vector2 wallLeap = Vector2.zero;
    #endregion

    #region platform
    [HideInInspector]
    public GameObject platform;
    #endregion
    [Header("Ledge data intake")]
    #region Ledge data
    public GameObject ledge;
    public BoxCollider targetLedge;
    [HideInInspector]
    public Vector3 ledgePos = Vector3.zero;
    [HideInInspector]
    public bool stallMovement = false;
    [HideInInspector]
    public bool climbingNow = false;
    #endregion
    [Space(50)]

    public float _timer = 0;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
        grabber = GetComponentInChildren<LedgeGrabber>();
        skelAnim = GetComponent<SkeletonAnimator>();
        root = GameObject.Find("root");
    }

    void Update()
    {
        ApplyAnimatorParam();
        ResetAnimatorParam();
        GrabberListener();

        Move();
        JumpGate();
        ResetTimer();
    }

    void Move()
    {
        if (!stallMovement)
        {
            if (controller.isGrounded)
            {
                if (!maxSlope && !normSlope)
                {
                    velocity = GroundVelocity(ref velocity);
                    onGround = true;

                    if (Input.GetKey(KeyCode.Space))
                    {
                        Jump();
                        onGround = false;
                    }
                }
                if (maxSlope)
                {
                    velocity = MaxSlopeVelocity(ref velocity);
                    onGround = true;
                }
                if (normSlope)
                {
                    velocity = NormSlopeVelocity(ref velocity);
                    onGround = true;
                    if (Input.GetKey(KeyCode.Space))
                    {
                        Jump();
                        canJump = false;
                        onGround = false;
                    }
                }
            }
            if (wallSliding)
            {
                WallSlideVelocity(ref velocity);
                onGround = false;
            }

            ApplyGravity();

            controller.Move(velocity * Time.deltaTime);
        }
        FaceDir();
    }

    void TimeIt()
    {
        _timer += 1 * Time.deltaTime;

        if (_timer >= 1.2f)
        {
            canLeap = true;
        }
        else
            canLeap = false;
    }

    void ResetTimer()
    {
        if (_timer != 0)
        {
            if (Input.GetKeyUp(KeyCode.Space))
            {
                _timer = 0;
            }
        }
    } 

    Vector3 GroundVelocity(ref Vector3 vel)
    {
        bool running = Input.GetKey(KeyCode.LeftShift) && controller.isGrounded;
        Vector2 input = new Vector2(Input.GetAxis("Horizontal"), 0);
        Vector2 inputDir = input.normalized;
        float targetSpeed = ((running) ? runSpeed : walkSpeed) * inputDir.magnitude;
        curSpd = Mathf.SmoothDamp(curSpd, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime * 3));
        animSpdPer = Mathf.Lerp(0, 1, curSpd);
        vel = new Vector3(curSpd, 0, 0);

        if (Input.GetKey(KeyCode.D))
        {
            return vel;
        }
        if (Input.GetKey(KeyCode.A))
        {
            return -vel;
        }

        return Vector3.zero;
    }

    Vector3 MaxSlopeVelocity(ref Vector3 vel)
    {
        float velY = velocity.y + slopeNormal.y * gravity * Time.deltaTime;
        float velX = Mathf.Sign(slopeNormal.x) * (Mathf.Abs(velY / 2) / Mathf.Tan(slopeAngle * Mathf.Deg2Rad));

        if (dirX == 1)
        {
            #region leap ready
            if (Input.GetKey(KeyCode.Space))
            {
                if (_timer <= 1.2f)
                {
                    TimeIt();
                }
                velY = Mathf.SmoothDamp(velY, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
                velX = Mathf.SmoothDamp(velX, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
                animator.SetBool("leapRdy", true);
                root.transform.localEulerAngles = new Vector3(0, 0, 0);
            }
            #endregion

            #region leap
            if (Input.GetKeyUp(KeyCode.Space))
            {
                if (canLeap)
                {
                    LeapOnUp(ref velocity);
                    animator.SetBool("leapRdy", false);
                    animator.SetBool("leap", true);
                }
            } 
            #endregion
            else
            vel =  new Vector3(velX, velY, 0);
        }
        if (dirX == -1)
        {
            #region leap ready
            if (Input.GetKey(KeyCode.Space))
            {
                if (_timer <= 1.2f)
                {
                    TimeIt();
                }
                velY = Mathf.SmoothDamp(velY, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
                velX = Mathf.SmoothDamp(velX, 0f, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
                animator.SetBool("leapRdy", true);
                root.transform.localEulerAngles = new Vector3(0, 0, 0);
            }
            #endregion

            #region leap
            if (Input.GetKeyUp(KeyCode.Space))
            {
                if (canLeap)
                {
                    LeapOnUp(ref velocity);
                    animator.SetBool("leapRdy", false);
                    animator.SetBool("leap", true);
                }
            } 
            #endregion
            else
            vel =  new Vector3(-velX, velY, 0);
        }
        return vel;
    }

    Vector3 NormSlopeVelocity(ref Vector3 vel)
    {
        bool running = Input.GetKey(KeyCode.LeftShift) && controller.isGrounded;
        Vector2 input = new Vector2(Input.GetAxis("Horizontal"), 0);
        Vector2 inputDir = input.normalized;
        float targetSpeed = ((running) ? runSpeed : walkSpeed) * inputDir.magnitude;
        curSpd = Mathf.SmoothDamp(curSpd, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
        float moveDist = Mathf.Abs(curSpd);

        float velX = Mathf.Cos(slopeAngle * Mathf.Deg2Rad) * moveDist * Mathf.Sign(curSpd);
        float velY = Mathf.Sin(slopeAngle * Mathf.Deg2Rad) * moveDist;

        vel = new Vector3(velX, velY, 0);

        if (Input.GetKey(KeyCode.D))
        {
            return vel;
        }
        if (Input.GetKey(KeyCode.A))
        {
            return -vel;
        }

        return Vector3.zero;
    }

    Vector3 WallSlideVelocity(ref Vector3 vel)
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            WallJumpOff(ref velocity);
            animator.SetBool("jumped", true);
            animator.SetBool("wallSlide", false);
        }
        if (Input.GetKey(KeyCode.Space))
        {
            if (_timer <= 1.2f)
            {
                TimeIt(); 
            }

            wallSlideSpdMax -= _timer * 10;
            animator.SetBool("leapRdy", true);
        }
        if (Input.GetKeyUp(KeyCode.Space))
        {
            if (canLeap)
            {
                LeapOnUp(ref velocity);
                animator.SetBool("leap", true); 
            }
        }
        else
        {
            vel.y = -wallSlideSpdMax;
            vel.x = 0;
            wallSlideSpdMax = 16f;
        }

        return vel;
    }

    Vector3 LeapOnUp(ref Vector3 vel)
    {
        if (canLeap)
        {
            #region on wall
            if (wallSliding)
            {
                wallSliding = false;
                maxSlope = false;
                if (wallDir == 1)
                {
                    vel.y = Mathf.Abs(wallLeap.y);
                    vel.x = -wallDir * wallLeap.x;
                }
                if (wallDir == -1)
                {
                    vel.y = Mathf.Abs(wallLeap.y);
                    vel.x = -wallDir * wallLeap.x;
                }
                wallSlideSpdMax = 16f;
                animator.SetBool("leapRdy", false);
                animator.SetBool("wallSlide", false);
            }
            #endregion

            #region on max slope
            if (maxSlope)
            {
                maxSlope = false;
                if (dirX == 1)
                {
                    vel.y = Mathf.Abs(wallLeap.y);
                    vel.x = dirX * wallLeap.x;
                }
                if (dirX == -1)
                {
                    vel.y = Mathf.Abs(wallLeap.y);
                    vel.x = dirX * wallLeap.x;
                }
                animator.SetBool("leapRdy", false);
                canLeap = false;
            }
            #endregion 
        }

        return vel;
    }

    float GetModifiedSmoothTime(float smoothTime)
    {
        if (controller.isGrounded)
        {
            return smoothTime;
        }

        if (airControlPercent == 0)
        {
            return float.MaxValue;
        }

        return smoothTime / airControlPercent;
    }

    void FaceDir()
    {
        #region on ground, jump and fall
        if (!wallSliding && !maxSlope)
        {
            if (velocity.x > 0)
            {
                transform.localEulerAngles = new Vector3(0, 0, 0);
            }
            if (velocity.x < 0)
            {
                transform.localEulerAngles = new Vector3(0, 180, 0);
            }
        } 
        #endregion

        #region on wall slide
        if (wallSliding)
        {
            if (!controller.isGrounded)
            {
                if (wallDir == -1)
                {
                    transform.localEulerAngles = new Vector3(0, 180, 0);
                }
                if (wallDir == 1)
                {
                    transform.localEulerAngles = new Vector3(0, 0, 0);
                }
            }
        } 
        #endregion

        #region on max slope
        if (maxSlope)
        {
            if (dirX == 1)
            {
                controller.transform.localEulerAngles = new Vector3(0, 180, 0);
                root.transform.rotation = Quaternion.Euler(0, 0, slopeAngle);
            }
            if (dirX == -1)
            {
                controller.transform.localEulerAngles = new Vector3(0, 0, 0);
                root.transform.rotation = Quaternion.Euler(0, 0, -slopeAngle);
            }
            if (animator.GetBool("leapRdy") == true)
            {
                root.transform.localEulerAngles = new Vector3(0, 0, 0);
            }
        } 
        #endregion
    }

    void ApplyGravity()
    {
        if (!controller.isGrounded || maxSlope || !wallSliding)
        {
            velocity.y += gravity * Time.deltaTime;
            if (velocity.y <= -5f)
            {
                onGround = false; // here onGround conflicts and flickers 
            }
        }
        else
            velocity.y = 0;
    }

    void Jump()
    {
        if (canJump)
        {
            float jumpVelocity = Mathf.Sqrt(-2 * gravity * jumpHeight);
            velocity.y = jumpVelocity;
            animator.SetBool("jumped", true);
            slopeAngle = 0;
            maxSlope = false;
            normSlope = false;
            transform.parent = null;
            landing = false;
            animator.SetBool("onGround", false);
        }
    }

    void JumpGate()
    {
        #region check landing
        if (!maxSlope && transform.parent == null)
        {
            if (velocity.y < -2f)
            {
                float rayLength = 7f;
                Ray landingCheck = new Ray(transform.position, Vector3.down);
                RaycastHit hit; // = new RaycastHit();

                Debug.DrawRay(transform.position, -Vector3.up * rayLength);

                if (Physics.Raycast(landingCheck, out hit, rayLength))
                {
                    //if (Mathf.Abs(hit.normal.x) <= Mathf.Abs(slopeNormal.x))
                    if(Mathf.Abs(hit.collider.transform.localEulerAngles.z) <= controller.slopeLimit)
                    {
                        if (animator.GetBool("onGround") == false)
                        {
                            landing = true;
                            animator.SetBool("landing", true);
                            canJump = false;
                            Invoke("ResetJump", .3f);
                            animator.SetBool("onGround", true); 
                        }
                    }
                }
                else
                    animator.SetBool("onGround", false); 
            }
        }
        #endregion

        #region gate time between jumps

        #endregion
    }

    void ResetJump()
    {
        landing = false;
        animator.SetBool("landing", false);
        animator.SetBool("onGround", true);
        canJump = true;
    }

    void WallJumpOff(ref Vector3 vel)
    {
        wallSliding = false;
        if (wallDir == 1)
        {
            vel.y = velocity.y + Mathf.Abs(velocity.y + jumpHeight);
            vel.x = velocity.x + -wallDir * jumpOff.x;
            controller.Move(vel * Time.deltaTime);
        }
        if (wallDir == -1)
        {
            vel.y = velocity.y + Mathf.Abs(velocity.y + jumpHeight);
            vel.x = velocity.x + -wallDir * jumpOff.x;
            controller.Move(vel * Time.deltaTime);
        }
    }

    void GrabberListener()
    {
        // LedgeGrabber.cs should output some data and this will take it to pass to some others
        if (grabber.targetLedge != null && grabber.ledge != null)
        {
            if (grabber.normalLedge ^ grabber.platLedge)
            {
                ledge = grabber.ledge;
                targetLedge = grabber.targetLedge;
                ledgePos = grabber.ledgePos;
                GetModY();
                //animator.SetTrigger("onLedge"); // this is only place where sets onLedge parameter to true 
            }
        }
        if (grabber.targetLedge == null)
        {
            ledge = null;
            targetLedge = null;
        }
    }

    private void climbStart(int value = 1)
    {
        animator.SetBool("climbing", true);
        climbingNow = true;

        if (grabber.normalLedge)
        {
            stallMovement = true;
        }
        if (grabber.platLedge)
        {
            stallMovement = true;
            transform.parent = ledge.transform;
        }
    }

    private void climbEnd(int value = 1)
    {
        float modX = 3f;

        #region climb normal ledge
        if (grabber.normalLedge)
        {
            Vector3 climb = new Vector3(ledgePos.x - (-modX * 1.5f), transform.position.y + GetModY(), transform.position.z);

            GetComponent<Transform>().position = climb;
        }
        #endregion

        #region climb platform ledge
        if (grabber.platLedge)
        {
            float oldX = transform.position.x;
            transform.parent = ledge.transform;

            Vector3 climbR = new Vector3(oldX - (-modX), transform.position.y + GetModY(), transform.position.z);
            Vector3 climbL = new Vector3(oldX - (modX), transform.position.y + GetModY(), transform.position.z);

            if (ledgePos.x > transform.position.x)
            {
                GetComponent<Transform>().position = climbR;
            }
            else
            {
                GetComponent<Transform>().position = climbL;
            }
        }
        #endregion

        stallMovement = false;
        animator.SetBool("climbing", false);
        climbingNow = false;
        animator.SetBool("onGround", true);
        canJump = true;
    }

    //private void StallPlayer()
    //{
    //    // this will stall Player's movement or parent it if it's a mobile platform
    //    if (grabber.normalLedge)
    //    {
    //        stallMovement = true;
    //    }
    //    if (grabber.platLedge)
    //    {
    //        stallMovement = true;
    //        transform.parent = ledge.transform;
    //    }
    //}

    //private void MovePlayerOnLedge()
    //{
    //    // this will actually move player
    //    float modX = 3f;

    //    #region climb normal ledge
    //    if (grabber.normalLedge)
    //    {
    //        Vector3 climb = new Vector3(ledgePos.x - (-modX * 1.5f), transform.position.y + GetModY(), transform.position.z);

    //        GetComponent<Transform>().position = climb;
    //    } 
    //    #endregion

    //    #region climb platform ledge
    //    if (grabber.platLedge)
    //    {
    //        float oldX = transform.position.x;
    //        transform.parent = ledge.transform;

    //        Vector3 climbR = new Vector3(oldX - (-modX), transform.position.y + GetModY(), transform.position.z);
    //        Vector3 climbL = new Vector3(oldX - (modX), transform.position.y + GetModY(), transform.position.z);

    //        if (ledgePos.x > transform.position.x)
    //        {
    //            GetComponent<Transform>().position = climbR;
    //        }
    //        else
    //        {
    //            GetComponent<Transform>().position = climbL;
    //        }
    //    } 
    //    #endregion

    //    animator.SetBool("onLedge", false);
    //    stallMovement = false;
    //}

    private float GetModY()
    {
        float grabberTop = Mathf.Abs(controller.bounds.min.y);
        float ledgeTop = Mathf.Abs(targetLedge.bounds.max.y);
        return Mathf.Abs(ledgeTop - grabberTop);
    }

    void ApplyAnimatorParam()
    {
        animator.SetFloat("moveVel", Mathf.Abs(velocity.x));

        if (wallSliding)
        {
            animator.SetBool("wallSlide", true);
        }
        if (maxSlope)
        {
            animator.SetBool("maxSlope", true);
        }
        else
            root.transform.localEulerAngles = new Vector3(0, 0, 0);
    }

    void ResetAnimatorParam()
    {
        if (animator.GetCurrentAnimatorStateInfo(0).IsName("jump"))
        {
            animator.SetBool("jumped", false);
        }
        if (animator.GetCurrentAnimatorStateInfo(0).IsName("fall"))
        {
            animator.SetBool("leap", false);
        }
        if (animator.GetCurrentAnimatorStateInfo(0).IsName("trans_jumpToFall"))
        {
            animator.SetBool("jumped", false);
        }
        if (animator.GetCurrentAnimatorStateInfo(0).IsName("wallSlide"))
        {
            animator.SetBool("jumped", false);
            animator.SetBool("onGround", false);
        }
        if (animator.GetBool("wallSlide") == true)
        {
            animator.SetBool("onGround", false);
        }
        if (animator.GetCurrentAnimatorStateInfo(0).IsName("groundMovement"))
        {
            animator.SetBool("onGround", true);
        }
        if (controller.isGrounded)
        {
            animator.SetBool("jumped", false);
            animator.SetBool("wallSlide", false);
        }
        if (!maxSlope)
        {
            animator.SetBool("maxSlope", false);
        }
        if (!canLeap)
        {
            animator.SetBool("leapRdy", false);
        }
        //if (animator.GetCurrentAnimatorStateInfo(0).IsName("climbLedge"))
        //{
        //    if (grabber.ledge == null && grabber.targetLedge == null && !grabber.normalLedge)
        //    {
        //        Debug.LogWarning("climb anim is fired, but ledge is null");
        //        Debug.Break();
        //    }
        //    if (grabber.ledge == null && grabber.targetLedge == null && !grabber.platLedge)
        //    {
        //        Debug.LogWarning("climb anim is fired, but ledge is null");
        //        Debug.Break();
        //    }
        //}
    }

    private void OnControllerColliderHit(ControllerColliderHit hit)
    {
        float targetAngle = hit.collider.transform.localEulerAngles.z;
        float angleOutput = (float)(System.Math.Round(targetAngle, 3));

        if (angleOutput > 180)
        {
            angleOutput -= 360f; // this is necessary since some anlges are not correctly passed and be over what it should be
        }

        if (hit.normal == new Vector3(0, 1, 0))
        {
            maxSlope = false;
        }

        #region normal slope
        if (angleOutput != 0)
        {
            if (Mathf.Abs(angleOutput) < controller.slopeLimit)
            {
                slopeAngle = angleOutput;
                normSlope = true;
            }
        }
        else
            normSlope = false;
        #endregion

        #region max slope detection
        if (controller.slopeLimit <= Mathf.Abs(angleOutput))
        {
            slopeAngle = angleOutput;
            slopeNormal = hit.normal;
            maxSlope = true;
        }
        #endregion

        #region wall detection
        if (controller.collisionFlags == CollisionFlags.Sides)
        {
            if (hit.normal == new Vector3(-1, 0, 0) ^ hit.normal == new Vector3(1, 0, 0))
            {
                if (hit.collider.bounds.max.y >= controller.bounds.max.y - 5f)
                {
                    if (hit.collider.bounds.size.y >= controller.bounds.size.y + 3f)
                    {
                        if (hit.gameObject.tag != "Platform")
                        {
                            Vector3 wallR = new Vector3(1, 0, 0);
                            Vector3 wallL = new Vector3(-1, 0, 0);
                            if (hit.normal == wallR || hit.normal == wallL)
                            {
                                wallSliding = true;
                                wallDir = (hit.normal == wallR) ? 1 : -1;
                            }
                        } 
                    }
                }
            }
        }
        else
        {
            wallSliding = false;
            wallDir = 0;
        }
        #endregion

        #region slope direction detection
        if (angleOutput != 0)
        {
            //if (hit.point.x > root.transform.position.x)
            //{
            //    dirX = 1;
            //}
            //else
            //{
            //    dirX = -1;
            //}
            if (angleOutput > 0)
            {
                dirX = 1;
            }
            else
                dirX = -1;
        }
        #endregion

        #region platform detection
        if (hit.gameObject.GetComponent<PlatformComponent>() != null)
        {
            if (hit.gameObject.tag == "Platform")
            {
                platform = hit.gameObject;
                transform.parent = platform.transform;
                if (true)
                {

                }
            }
        }
        else
        {
            transform.parent = null;
        }
        #endregion
    }
}

编辑)嗯,这是PC移动的整个脚本。提及GrabberListener等等,因为它的另一个类与当前的问题无关。运动。 在坡度角度小于坡度限制PC的速度的地面上,y在0到更小之间闪烁。我需要它留在它停留的位置,以便跟踪另一个功能的速度。 CharacterController可以没有100%可靠的地面检查吗?甚至CollisionFlag也会在空中发射,而不是附近有任何可碰撞的物体,所以我并不太依赖它...

1 个答案:

答案 0 :(得分:0)

我无法理智地回答,因为您没有显示MoveApplyGravity方法的执行顺序。

请注意,controller.collisionFlags会在最后一次CharacterController.Move回调后存储碰撞掩码状态,因此如果您在ApplyGravity Move之前执行Update },你正在检查前一帧是否有碰撞。

这也可能是由浮点近似误差引起的问题,尤其是帧速率非常高(Time.deltaTime非常小),除非您启用了Vsync。

你可以在这里阅读一些有用的帖子:https://forum.unity3d.com/threads/controller-isgrounded-doesnt-work-reliably.91436/

顺便说一下,顺便提一下,为什么你到处都在使用

velocity = Method(ref velocity);

?当您要将返回值存储到完全相同的变量时,将参数作为引用传递没有任何意义。