【问题标题】:What should I change in my script to get smoother movement/camera movement?我应该在脚本中进行哪些更改以获得更流畅的移动/相机移动?
【发布时间】:2021-12-15 18:17:45
【问题描述】:

最近我观看了精彩的视频教程:Recreating Mirrors Edge Parkour Movement in Unity https://www.youtube.com/watch?v=ZxWfkOhl6bQ&t=586s

我制作了相同的脚本和播放器函数,但我注意到与 Unity 的 PlayerController 组件相比,播放器相机感觉不那么流畅。我试图自己修复它,但我失败了。 把Update()改成FixedUpdate(),把FixedUpdate()改成Update()后,突然转动/相机感觉更流畅了,但其他功能都不起作用了。

我必须在此脚本中进行哪些更改才能使相机/转向更顺畅?

脚本:

public class PlayerMovement : MonoBehaviour
{


    public enum PlayerStates
    {
        Grounded,//on the ground
        InAir, //in the air
        OnWalls, //running on the walls
        LedgeGrab, //pulling up a ledge
    }

    private PlayerCollision Coli;
    private Rigidbody Rigid;
    private CapsuleCollider Cap;
    private Animator Anim;

    [Header("Physics")]
    public float MaxSpeed; //how fast we run forward
    public float BackwardsSpeed; //how fast we run backwards
    public float InAirControl; //how much control you have over your movement direction when in air

    private float ActSpeed; //how much speed is applied to the rigidbody
    public float Acceleration; //how fast we build speed
    public float Decceleration; //how fast we slow down
    public float DirectionControl = 8; //how much control we have over changing direction
    public PlayerStates CurrentState; //the current state the player is in
    private float InAirTimer; //how long we are in the air for (this is for use when wall running or falling off the ground
    private float OnGroundTimer;
    private float AdjustmentAmt; //the amount added to our player acceleration, this is used for adjusting to new speeds such as when we slide


    [Header("Turning")]
    public float TurnSpeed; //how fast we turn when on the ground
    public float TurnSpeedInAir; //how fast we turn when in air
    public float TurnSpeedOnWalls; //how fast we turn when on the walls
    public float LookUpSpeed; //how fast we look up and down
    public Camera Head; //what will function as our players head to tilt up and down (this is a pivot point in our model that the cameras are children of
    private float YTurn; //how much we have turned left and right
    private float XTurn; //how much we have turned Up or Down
    public float MaxLookAngle = 65; //how much we can look up
    public float MinLookAngle = -30; //how much we can look down

    [Header("Jumping")]
    public float JumpHeight; //how high we jump

    [Header("Wall Runs")]
    public float WallRunTime = 2f; //how long we can run on walls
    private float ActWallRunTime = 0; //how long we are actually on a wall
    public float TimeBeforeWallRun = 0.2f; //how long we have to be in the air before we can wallrun
    public float WallRunUpwardsMovement = 2f; //how much we move up a wall when running on it (make this 0 to just slightly move down a wall we run on
    public float WallRunSpeedAcceleration = 2f; //how quickly we build speed to run up walls

    [Header("Crouching")]
    public float CrouchSpeed = 10; //how fast we move when crouching
    public float CrouchHeight = 1.5f; //how tall our capsule will be when crouched
    private float StandingHeight = 2f; //this is how tall our capsule is
    private bool Crouch;

    [Header("Sliding")]
    public float SlideAmt; //how far we slide when pressing crouch
    public float SlideSpeedLimit; //how fast we have to be traveling before a crouch will trigger a slide
    public float SlideControl; //how much we adjust to our slide speed and regain player control

    [Header("WallGrabbing")]
    public float PullUpTime; //the time it takes to pull onto a ledge
    private float ActPullTm; //the actual time it takes to pull up a ledge
    private Vector3 OrigPos; //the original Position before grabbing a ledge
    private Vector3 LedgePos; //the ledge position to move to

    [Header("FOV")]
    public float MaxFov;
    private float MinFov;
    public float FOVSpeed; //how fast we must go before we reach max fov

    // Start is called before the first frame update
    void Start()
    {
        Coli = GetComponent<PlayerCollision>();      
        Rigid = GetComponent<Rigidbody>();
        Anim = GetComponentInChildren<Animator>();
        MinFov = Head.fieldOfView;
        Cap = GetComponent<CapsuleCollider>();
        StandingHeight = Cap.height;

        AdjustmentAmt = 1;
    }

    // Update is called once per frame
    void Update()
    {
        float XMove = Input.GetAxis("Horizontal");
        float YMove = Input.GetAxis("Vertical");

        if (CurrentState == PlayerStates.Grounded)
        {
            //if we press jump
            if (Input.GetButtonDown("Jump"))
            {
                //jump upwards
                JumpUp();
            }
        }
        else if (CurrentState == PlayerStates.InAir)
        {
            //check for ledge grabs
            if (Input.GetButton("Grab"))
            {
                Vector3 LedgePos = Coli.CheckLedges();
                if (LedgePos != Vector3.zero)
                {
                    LedgeGrab(LedgePos);
                }
            }

            //Check if there is a wall to run on
            bool Wall = CheckWalls(XMove, YMove);

            //we are on the wall
            if (Wall)
            {
                if (InAirTimer > TimeBeforeWallRun)
                {
                    SetOnWall();
                    return;
                }
            }

            //check for the ground 
            bool Grounded = Coli.CheckFloor(-transform.up);

            //we are on the ground (and have been in the air for a short time, to prevent multiple jump glitched
            if (Grounded && InAirTimer > 0.25f)
            {
                SetOnGround();
            }
        }
        else if (CurrentState == PlayerStates.OnWalls)
        {
            //check for ledge grabs
            if (Input.GetButton("Grab"))
            {
                Vector3 LedgePos = Coli.CheckLedges();

                if (LedgePos != Vector3.zero)
                {
                    LedgeGrab(LedgePos);
                }
            }

            //Check if there is a wall to run on
            bool Wall = CheckWalls(XMove, YMove);

            //we are no longer on the wall, fall off it
            if (!Wall)
            {
                SetInAir();
                return;
            }

            //check for the ground 
            bool Grounded = Coli.CheckFloor(-transform.up);

            //we are on the ground
            if (Grounded)
            {
                SetOnGround();
            }
        }
        else if(CurrentState == PlayerStates.LedgeGrab)
        {
            //clamp our rigid velocity to nothing
            Rigid.velocity = Vector3.zero;
        }

        AnimCtrl();
    }

    void AnimCtrl()
    {
        int State = 0;
        if (CurrentState == PlayerStates.InAir)
            State = 1;
        else if (CurrentState == PlayerStates.OnWalls)
            State = 2;
        else if (CurrentState == PlayerStates.LedgeGrab)
            State = 3;

        Anim.SetInteger("State", State);
        Anim.SetBool("Crouching", Crouch);

        Vector3 Vel = transform.InverseTransformDirection(Rigid.velocity);
        Anim.SetFloat("XVelocity", Vel.x);
        Anim.SetFloat("ZVelocity", Vel.z);
        Anim.SetFloat("YVelocity", Rigid.velocity.y);
        Anim.SetFloat("XInput", Input.GetAxis("Horizontal"));
    }

    private void FixedUpdate()
    {
        float Del = Time.deltaTime;

        //get our players rotation amount for turning
        float CamX = Input.GetAxis("MouseX");
        float CamY = Input.GetAxis("MouseY");

        //have our player look up and down
        LookUpDown(CamY, Del);

        //handle our fov
        HandleFov(Del);

        //get inputs
        float horInput = Input.GetAxis("Horizontal");
        float verInput = Input.GetAxis("Vertical");

        if (CurrentState == PlayerStates.Grounded)
        {
            //tick our ground timer
            if (OnGroundTimer < 10)
                OnGroundTimer += Del;


            //get magnituded of our inputs
            float InputMagnitude = new Vector2(horInput, verInput).normalized.magnitude;
            //get the amount of speed, based on if we press forwards or backwards
            float TargetSpd = Mathf.Lerp(BackwardsSpeed, MaxSpeed, verInput); //using the vertical input as a lerp from if forward is being pressed
            //if we are crouching our target speed is our crouch speed
            if (Crouch)
                TargetSpd = CrouchSpeed;

            LerpSpeed(InputMagnitude, Del, TargetSpd);

            MovePlayer(horInput, verInput, Del);
            TurnPlayer(CamX, Del, TurnSpeed);

            //check for crouching 
            if(Input.GetButton("Crouching"))
            { 
                //start crouching
                if(!Crouch)
                {
                    StartCrouch();
                }
            }
            else
            {
                //stand up
                bool check = Coli.CheckRoof(transform.up);
                if (!check)
                {
                    StopCrouching();
                }
            }

            //add to our player adjustment
            if (AdjustmentAmt < 1)
                AdjustmentAmt += Del * SlideControl;
            else
                AdjustmentAmt = 1;

            //check for the ground 
            bool Grounded = Coli.CheckFloor(-transform.up);

            //we are in the air
            if (!Grounded)
            {
                if (InAirTimer < 0.2f)
                    InAirTimer += Del;
                else
                {
                    SetInAir();
                    return;
                }
            }
            else
            {
                //we are on the ground to remove any increase in the air timer
                InAirTimer = 0;
            }
        }
        else if(CurrentState == PlayerStates.InAir)
        {
            //tick our Air timer
            if (InAirTimer < 10)
                InAirTimer += Del;

            MoveInAir(horInput, verInput ,Del);

            //turn our player with the in air modifier
            TurnPlayer(CamX, Del, TurnSpeedInAir);
        }
        else if (CurrentState == PlayerStates.OnWalls)
        {
            //tick our wall run timer
            ActWallRunTime += Del;
            Debug.Log(ActWallRunTime);
            //turn our player with the in air modifier
            TurnPlayer(CamX, Del, TurnSpeedOnWalls);

            //move our player when on a wall
            WallMove(verInput, Del);
        }
        else if(CurrentState == PlayerStates.LedgeGrab)
        {
            //tick ledge grab time 
            ActPullTm += Del;

            //pull up the ledge
            float PullUpLerp = ActPullTm / PullUpTime;

            if (PullUpLerp < 0.5)
            {              
                //lerp our player upwards to the leges y position
                float LAmt = PullUpLerp * 2;
                transform.position = Vector3.Lerp(OrigPos, new Vector3(OrigPos.x, LedgePos.y, OrigPos.z), LAmt);               
            }
            else if(PullUpLerp <= 1)
            {
                //set new pull up position
                if (OrigPos.y != LedgePos.y)
                    OrigPos = new Vector3(transform.position.x, LedgePos.y, transform.position.z);


                //move to the ledge position
                float LAmt = (PullUpLerp - 0.5f) * 2;
                transform.position = Vector3.Lerp(OrigPos, LedgePos, PullUpLerp);
            }
            else
            {
                //we have finished pulling up!
                SetOnGround();
            }
        }
    }


    //lerp our current speed to our set max speed, by how much we are pressing the horizontal and vertical input
    void LerpSpeed(float InputMag, float D, float TargetSpeed)
    {
        //multiply our speed by our input amount
        float LerpAmt = TargetSpeed * InputMag;
        //get our acceleration (if we should speed up or slow down
        float Accel = Acceleration;
        if (InputMag == 0)
            Accel = Decceleration;
        //lerp by a factor of our acceleration
        ActSpeed = Mathf.Lerp(ActSpeed, LerpAmt, D * Accel);
    }

    //when in the air or on a wall, we set our action speed to the velocity magnitude, this is so that when we reach the ground again, our speed will carry over our momentum
    void SetSpeedToVelocity()
    {
        float Mag = new Vector2(Rigid.velocity.x, Rigid.velocity.z).magnitude;
        ActSpeed = Mag;
    }

    bool CheckWalls(float X, float Y)
    {
        if (X == 0 && Y == 0) //if no direction input we are not wall running
            return false;

        if (ActWallRunTime >= WallRunTime) //if our wall run timer is more than the amount we can run on walls for, we cannot wall run
            return false;

        //check the collision direction for any walls
        float ClampedY = Mathf.Clamp(Y, 0, 1);
        Vector3 Dir = transform.forward * ClampedY + transform.right * X;

        bool WallCol = Coli.CheckWall(Dir);

        return WallCol;
    }

    void SetInAir()
    {
        StopCrouching(); //cannot crouch in air

        OnGroundTimer = 0; //remove the on ground timer
        CurrentState = PlayerStates.InAir;
    }

    void SetOnGround()
    {
        //set our current speed to our velocity
        SetSpeedToVelocity();

        ActWallRunTime = 0; //we are on the ground again, our wall run timer is reset
        InAirTimer = 0; //remove the in air timer
        CurrentState = PlayerStates.Grounded;
    }

    void SetOnWall()
    {
        OnGroundTimer = 0; //remove the on ground timer
        InAirTimer = 0; //remove the in air timer
        CurrentState = PlayerStates.OnWalls;
    }

    void LedgeGrab(Vector3 Ledge)
    {
        //set our ledge position
        LedgePos = Ledge;
        OrigPos = transform.position;
        //reset ledge grab time
        ActPullTm = 0;
        //remove speed and velocity
        Rigid.velocity = Vector3.zero;
        ActSpeed = 0;
        //start ledge grabs
        CurrentState = PlayerStates.LedgeGrab;
    }

    void StartCrouch()
    {
        Crouch = true;
        Cap.height = CrouchHeight;

        if (ActSpeed > SlideSpeedLimit)
            SlideSelf();
    }

    void StopCrouching()
    {
        Crouch = false;
        Cap.height = StandingHeight;
    }

    void TurnPlayer(float Hor, float D, float turn)
    {
        //add our inputs to our turn value
        YTurn += (Hor * D) * turn; 
        //turn our character
        transform.rotation = Quaternion.Euler(0, YTurn, 0);
    }

    void LookUpDown(float Ver, float D)
    {
        //add our inputs to our look angle
        XTurn -= (Ver * D) * LookUpSpeed;
        XTurn = Mathf.Clamp(XTurn, MinLookAngle, MaxLookAngle);
        //look up and down
        Head.transform.localRotation = Quaternion.Euler(XTurn, 0, 0);
    }

    void MovePlayer(float Hor, float Ver, float D)
    {
        //find the direction to move in, based on the direction inputs
        Vector3 MovementDirection = (transform.forward * Ver) + (transform.right * Hor);
        MovementDirection = MovementDirection.normalized;
        //if we are no longer pressing and input, carryon moving in the last direction we were set to move in
        if (Hor == 0 && Ver == 0)
            MovementDirection = Rigid.velocity.normalized;

        MovementDirection = MovementDirection * ActSpeed;

        //apply Gravity and Y velocity to the movement direction 
        MovementDirection.y = Rigid.velocity.y;

        //apply adjustment to acceleration
        float Acel = DirectionControl * AdjustmentAmt;
        Vector3 LerpVelocity = Vector3.Lerp(Rigid.velocity, MovementDirection, Acel * D);
        Rigid.velocity = LerpVelocity;          
    }

    void MoveInAir(float Hor, float Ver, float D)
    {
        //find the direction to move in, based on the direction inputs
        Vector3 MovementDirection = (transform.forward * Ver) + (transform.right * Hor);
        MovementDirection = MovementDirection.normalized;
        //if we are no longer pressing and input, carryon moving in the last direction we were set to move in
        if (Hor == 0 && Ver == 0)
            MovementDirection = Rigid.velocity.normalized;

        MovementDirection = MovementDirection * ActSpeed;

        //apply Gravity and Y velocity to the movement direction 
        MovementDirection.y = Rigid.velocity.y;

        //lerp to our movement direction based on how much airal control we have
        Vector3 LerpVelocity = Vector3.Lerp(Rigid.velocity, MovementDirection, InAirControl * D);
        Rigid.velocity = LerpVelocity;

    }

    void WallMove(float Ver, float D)
    {
        //get the direction to run up this wall if we press forward (keep in mind this only works if the wall is infront or to the side of the player as we run along on, on walls to our immediate right or left we slide down
        Vector3 MovementDirection = transform.up * Ver;
        MovementDirection = MovementDirection * WallRunUpwardsMovement;

        //our x z velocity are our momentum applied to our forward direction
        MovementDirection += transform.forward * ActSpeed;

        Vector3 LerpVelocity = Vector3.Lerp(Rigid.velocity, MovementDirection, WallRunSpeedAcceleration * D);
        Rigid.velocity = LerpVelocity;
    }

    void JumpUp()
    {
        //only jump if we are still on the ground
        if (CurrentState == PlayerStates.Grounded)
        {
            //reduce our velocity on the y axis so our jump force can be added
            Vector3 VelAmt = Rigid.velocity;
            VelAmt.y = 0;
            Rigid.velocity = VelAmt;
            //add our jump force
            Rigid.AddForce(transform.up * JumpHeight, ForceMode.Impulse);
            //we are now in the air
            SetInAir();
        }
    }

    //increase our fov at high speed and reduce it at low speed
    void HandleFov(float D)
    {
        //get our velocity magniture
        float mag = new Vector2(Rigid.velocity.x, Rigid.velocity.z).magnitude;
        //get appropritate fov 
        float LerpAmt = mag / FOVSpeed;
        float FieldView = Mathf.Lerp(MinFov, MaxFov, LerpAmt);
        //ease into this fov
        Head.fieldOfView = Mathf.Lerp(Head.fieldOfView, FieldView, 4 * D);
    }

    //slide our character forwards
    void SlideSelf()
    {
        //reduce our speed
        ActSpeed = SlideSpeedLimit;

        //remove any control from player 
        AdjustmentAmt = 0;

        //find direction
        Vector3 Dir = Rigid.velocity.normalized;
        Dir.y = 0;

        //slide in direction
        Rigid.AddForce(transform.forward * SlideAmt, ForceMode.Impulse);
    }
}

【问题讨论】:

  • 查看该问题的视频片段会很有帮助。仅从代码中很难获得清晰的图像,尤其是当它如此庞大时。在这种情况下(例如,当使用 FixedUpdate() 时,一个好的经验法则是测试刚体插值的各种设置。
  • 是的,但我相信你不会通过视频来区分。我的相机运动几乎没有抖动/滞后。 fps也会影响我的灵敏度,这也很奇怪。
  • FixedUpdate() 用于物理过程(即与刚体相关的所有内容),Update() 用于(几乎)其他所有内容。你不应该把它们翻过来。反过来说。您应该在Update() 中查询输入(GetAxis("Horizontal"/"Vertical")),并且您绝对应该在Update() 中移动相机。在FixedUpdate() 中,进行速度计算。发布您更改的代码,如果问题仍然存在,请以 60 FPS 的速度拍摄,将其作为未列出的视频上传到 YouTube,并在此处链接。注意在视频中清楚地显示问题。
  • 好吧,我试试。
  • 但是在这个脚本中 update() 和 fixedupdate() 没有翻转。

标签: c# unity3d game-development


【解决方案1】:

我的错误:我旋转了RigidBody,而不是相机本身。

解决方案:不要在不同的功能(更新和固定更新)中旋转和移动RigidBody

为相机制作一个单独的脚本,它将读取鼠标输入并在更新中移动相机。使用相机的方向进行方向计算(transform.right --> camOrientation.right)。

还对RigidBody 使用插值。

【讨论】:

    猜你喜欢
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    • 2020-01-22
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    相关资源
    最近更新 更多