Search code examples
c#unity-game-engine

I'm making a Unity game and if coded this player controller but when it jumps it removes my velocity


When I jump the player loses its previous velocity and creates the feeling your hitting an invisible wall.

I've tried changing the drag, the slope controls and how the jump works to no avail. I've also tried using chatgpt but it didn't do anything which helped. I would also appreciate if there are any more efficient ways of moving the player to the way which I've done it.

using UnityEngine;
using System.Collections;

public class PlayerMovement : MonoBehaviour
{
    [Header("Movement Settings")]
    public float walkSpeed = 5f;
    public float sprintSpeed = 10f;
    public float crouchSpeed = 2f;
    public Transform orientation;
    public float groundDrag = 5f;
    public float airDrag = 0f;
    public float moveSmoothing = 0.1f;
    public float maxSpeed = 10f;

    [Header("Ground Check")]
    public float groundCheckRadius = 0.5f;
    public LayerMask groundLayer;
    private bool grounded;
    private bool wasGrounded;

    [Header("Slope Handling")]
    public float maxSlopeAngle = 45f;
    private bool exitingSlope;
    private bool onSlope;
    private RaycastHit slopeHit;
    private Vector3 slopeNormal;
    private float slopeRayLength = 2f;  // Longer ray length for better accuracy
    private float groundRayOffset = 0.5f;  // Height offset for ray origin
    private int groundRayCount = 3;  // Number of ground check rays

    [Header("Player Jumping")]
    public float jumpForce = 10f;
    public float jumpCooldown = 1f;
    public float airMultiplier = 0.25f;
    private Vector3 storedVelocity;

    [Header("Crouching")]
    public float crouchYScale = 0.5f;
    private float startYScale;

    [Header("Keybinds")]
    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode sprintKey = KeyCode.LeftShift;
    public KeyCode crouchKey = KeyCode.LeftControl;

    private Rigidbody rb;
    private float moveSpeed;
    private float horizontalInput;
    private float verticalInput;
    private bool readyToJump = true;

    private Vector3 moveDirection;

    public MovementState state;
    public enum MovementState
    {
        walking,
        sprinting,
        crouching,
        air
    }

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.freezeRotation = true;
        rb.constraints = RigidbodyConstraints.FreezeRotationX |
                         RigidbodyConstraints.FreezeRotationY |
                         RigidbodyConstraints.FreezeRotationZ;

        startYScale = transform.localScale.y;
        rb.drag = groundDrag;
    }

    private bool IsGrounded()
    {
        // Ground check using multiple raycasts to ensure accuracy
        bool isGrounded = false;
        float rayLength = slopeRayLength;
        Vector3 rayOrigin = transform.position + Vector3.up * groundRayOffset;

        for (int i = -1; i <= 1; i++)
        {
            Vector3 offset = Vector3.right * i * groundCheckRadius;
            Vector3 rayPosition = rayOrigin + offset;

            if (Physics.Raycast(rayPosition, Vector3.down, out RaycastHit hit, rayLength, groundLayer))
            {
                isGrounded = true;
                slopeNormal = hit.normal;
                float angle = Vector3.Angle(Vector3.up, slopeNormal);
                onSlope = angle > 0 && angle <= maxSlopeAngle;  // Within allowed slope range
            }
        }

        return isGrounded;
    }

    private void Update()
    {
        wasGrounded = grounded;
        grounded = IsGrounded();


        MyInput();
        StateHandler();

        if (grounded && !wasGrounded)
        {
            OnLanding();
        }

        if (Input.GetKey(crouchKey))
        {
            moveSpeed = crouchSpeed;
        }
        else if (Input.GetKey(sprintKey))
        {
            moveSpeed = sprintSpeed;
        }
        else
        {
            moveSpeed = walkSpeed;
        }
    }

    private void MyInput()
    {
        horizontalInput = Input.GetAxisRaw("Horizontal");
        verticalInput = Input.GetAxisRaw("Vertical");

        if (Input.GetKeyDown(jumpKey) && readyToJump && grounded)
        {
            readyToJump = false;
            Jump();
            StartCoroutine(JumpCooldown());
        }

        if (Input.GetKey(crouchKey))
        {
            transform.localScale = new Vector3(transform.localScale.x, crouchYScale, transform.localScale.z);
        }

        if (Input.GetKeyUp(crouchKey))
        {
            transform.localScale = new Vector3(transform.localScale.x, startYScale, transform.localScale.z);
        }
    }

    private void FixedUpdate()
    {
        MovePlayer();
        CapSpeed();
    }

        private void StateHandler()
    {
        // Mode - Crouching
        if (Input.GetKey(crouchKey))
        {
            state = MovementState.crouching;
            moveSpeed = crouchSpeed;
        }

        // Mode - Sprinting
        else if(grounded && Input.GetKey(sprintKey))
        {
            state = MovementState.sprinting;
            moveSpeed = sprintSpeed;
        }

        // Mode - Walking
        else if (grounded)
        {
            state = MovementState.walking;
            moveSpeed = walkSpeed;
        }

        // Mode - Air
        else
        {
            state = MovementState.air;
        }
    }

    private void MovePlayer()
    {
        moveDirection = (orientation.forward * verticalInput + orientation.right * horizontalInput).normalized;

        if (onSlope && !exitingSlope)  // No parentheses because 'onSlope' is a boolean, not a method
        {
           rb.AddForce(GetSlopeMoveDirection() * moveSpeed * 20f, ForceMode.Force);

            if (rb.velocity.y > 0)
                rb.AddForce(Vector3.down * 80f, ForceMode.Force);
        }
        else if (grounded)
        {
            Vector3 targetPosition = transform.position + moveDirection * moveSpeed * Time.fixedDeltaTime;
            rb.MovePosition(Vector3.Lerp(transform.position, targetPosition, moveSmoothing));
        }
        else
        {
            rb.AddForce(moveDirection * moveSpeed * airMultiplier, ForceMode.Force);  // Airborne movement
        }
    }


    private void CapSpeed()
    {
        float currentSpeed = rb.velocity.magnitude;

        if (currentSpeed > maxSpeed)
        {
            rb.velocity = rb.velocity.normalized * maxSpeed;  // Limit speed to avoid excessive acceleration
        }
    }

    private void Jump() {
        // Keep horizontal velocity, reset only vertical component to 0 before jump
        Vector3 horizontalVelocity = new Vector3(rb.velocity.x, rb.velocity.z); // Remove Y component
    
        // Set the new velocity with preserved horizontal components
        rb.velocity = new Vector3(horizontalVelocity.x, 0, horizontalVelocity.y);

        // Apply jump force
        rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        Debug.Log("jump");
    }

    private IEnumerator JumpCooldown()
    {
        yield return new WaitForSeconds(jumpCooldown);
        readyToJump = true;
    }

private IEnumerator SmoothDragChange(float newDrag, float duration)
{
    float startDrag = rb.drag;
    float time = 0f;

    while (time < duration)
    {
        rb.drag = Mathf.Lerp(startDrag, newDrag, time / duration);
        time += Time.fixedDeltaTime;
        yield return new WaitForSeconds(Time.fixedDeltaTime);
    }

    rb.drag = newDrag;
} 

    private void OnLanding()
    {
        storedVelocity = rb.velocity;
        Invoke("RestoreVelocity", 0.1f);

        rb.drag = groundDrag;  // Restore drag upon landing
        Invoke(nameof(ResetGroundDrag), 0.1f);  // Slight delay to smooth landing
    }

    private void RestoreVelocity()
    {
        rb.velocity = storedVelocity;
    }  


    private void ResetGroundDrag()
    {
        rb.drag = groundDrag;
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Vector3 rayOrigin = transform.position + Vector3.up * groundRayOffset;
        float rayLength = slopeRayLength;

        for (int i = -1; i <= 1; i++)
        {
            Vector3 offset = Vector3.right * i * groundCheckRadius;
            Gizmos.DrawRay(rayOrigin + offset, Vector3.down * rayLength);
        }
    }
    private Vector3 GetSlopeMoveDirection()
    {
        return Vector3.ProjectOnPlane(moveDirection, slopeHit.normal).normalized;
    }
}


Solution

  • There's a lot of code to understand, so I'm not 100% sure why your problem happens. But I suspect this is because you're moving rigidbody differently depending on the state. When grounded, you manipulate position directly, and when in air, you add force:

    else if (grounded)
    {
        Vector3 targetPosition = transform.position + moveDirection * moveSpeed * Time.fixedDeltaTime;
        rb.MovePosition(Vector3.Lerp(transform.position, targetPosition, moveSmoothing));
    }
    else
    {
        rb.AddForce(moveDirection * moveSpeed * airMultiplier, ForceMode.Force);  // Airborne movement
    }
    

    So when you start jumping, you lose the initial velocity, as AddForce must accelerate your player, and it probably accelerates it slowly. It would be better to stay consistent, and move your player in one way when dealing with one kind of movement. It's fine to use forces for up/down movement, but for horizntal movement either switch to forces, or only use manipulation of position. You can test it by changing horizontal movement in air to manipulation, something along this:

    else if (grounded)
    {
        Vector3 targetPosition = transform.position + moveDirection * moveSpeed * Time.fixedDeltaTime;
        rb.MovePosition(Vector3.Lerp(transform.position, targetPosition, moveSmoothing));
    }
    else
    {
        Vector3 targetPosition = transform.position + moveDirection * moveSpeed * Time.fixedDeltaTime;
        rb.MovePosition(Vector3.Lerp(transform.position, targetPosition, moveSmoothing)); //AirMovement
    }
    

    And you can play with the last else block to adjust it to your needs. If you don't need to adjust, you can just merge it into one block of code, since it's the same operation.

    Your up/down movement shouldn't be affected by this, because you add jump force separately anyway