FirstPersonController

 avatar
unknown
plain_text
6 months ago
14 kB
9
Indexable
using UnityEngine;
using System.Collections;
using UnityEngine.InputSystem;
using UnityEngine.Rendering;

public class FirstPersonController : MonoBehaviour
{
    [Header("Movement Settings")]
    [SerializeField] private float walkSpeed = 40f;
    [SerializeField] private float airControlMultiplier = 0.25f;
    [SerializeField] private float knockbackControlMultiplier = 0.05f;
    [SerializeField] private float acceleration = 1f;   //Mess with later
    [SerializeField] private float jumpForce = 5f;
    [SerializeField] private float gravityMultiplier = 1.0f;
    [SerializeField] private float mouseSensitivity = 0.1f;
    [SerializeField] public float controllerSensitivityMultiplier = 8f;
    [SerializeField] private float groundDrag = 5f; //Higher Values slows the Rigidbody faster.
    [SerializeField] private float airDrag = 0.5f;
    //[SerializeField] private float timeFrozen = 1f; //How long you can't move after using bouncepad

    [Header("References")]
    [SerializeField] public Camera mainCamera;
    [SerializeField] private PlayerController pController;
    [SerializeField] public PlayerInput playerInput;    

    private Rigidbody rb;
    private float verticalRotation;
    private bool isGrounded;
    private bool allowMovement = true;
    private bool beingExploded = false;

    [Header("Ground Check")]
    [SerializeField] private float groundCheckDistance = 1.6f;  //Useless I think
    [SerializeField] private float footRadius = 0.45f;
    [SerializeField] private float footOffset = 0.9f;
    [SerializeField] private LayerMask groundMask;
    public bool groundedCheck = true;

    public bool cantThrowNades = false;

    public bool BounceNadeAvailable = true;
    public bool C4Available = true;
    public bool BouncePadAvailable = true;
    public bool SuckStarAvailable = true;

    public float BounceNadeCooldown = 7f;
    public float C4Cooldown = 8f;
    public float BPadCooldown = 8f;
    public float SuckStarCooldown = 10f;

    public bool bouncePadArmed = false;
    public bool C4InstantThrowCooldown = false;

    public int lives = 3;

    private void Awake()
    {
    
    }


    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.freezeRotation = true; // prevent physics tilt
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
        //Vector3 spawnPos = NadeScript.NadeSpawnPos(mainCamera);
    }

    // Update is called once per frame
    void Update()
    {
        HandleRotation();   
    }

    private void FixedUpdate() // Because this is consistent across frame rates, use this for phsycis!
    {
        HandleMovement();
        ApplyGravityMultiplier();
        CheckGround();
        HandleJump();
        Throw1();
        Throw2();
        Throw3();
        Throw4();
        rb.linearDamping = isGrounded ? groundDrag : airDrag; //This gives a natural stop when you release movement. Drag doesn’t affect vertical motion. 
    }
    

    private void CheckGround()
    {
                      //V3
        Vector3 footPos = transform.position + Vector3.down * footOffset;
        isGrounded = Physics.CheckSphere(footPos, footRadius, groundMask);
        //Todo To check ground: use slope angle. if angle < 30: ground = true
    }

    private void HandleMovement()
    {
        if (!allowMovement) return;

        Vector3 inputDirection = new Vector3(pController.MovementInput.x, 0f, pController.MovementInput.y); //Where you want to go
        Vector3 currentHorizontalVelocity = CurrentHorizantalVelocity();
        Vector3 desiredVelocity = DesiredVelocity();

        if (isGrounded && inputDirection.magnitude > 0.1f)  //If you're grounded, trying to move (If no input detected, no physics applied)
        {
            Vector3 velocityChange = desiredVelocity - currentHorizontalVelocity;
            rb.AddForce(velocityChange * acceleration, ForceMode.Acceleration);
            //Lets say you're moving at 5, and desire to move at 10. 10 - 5 = 5 movement added to your speed
        }
        if (isGrounded==false && beingExploded==false)  //if you're airborn because you jumped
        {
            // Limited control in air
            Vector3 velocityChange = desiredVelocity - currentHorizontalVelocity;
            rb.AddForce(velocityChange * acceleration * airControlMultiplier, ForceMode.Acceleration);
            //Lets say you're moving at 15, and desire to move at 10. 15 - 10 = 5 * 0.5 = 2.5 slowdown
        }
        if (isGrounded==false && beingExploded==true)   //if you're airborn because you just were hit by a knockback or explosion
        {
            // SUPER Limited control in air
            Vector3 velocityChange = desiredVelocity - currentHorizontalVelocity;
            rb.AddForce(velocityChange * acceleration * knockbackControlMultiplier, ForceMode.Acceleration);
            //Lets say you're moving at 15, and desire to move at 10. 15 - 10 = 5 * 0.5 = 2.5 slowdown
        }
    }

    public Vector3 CurrentHorizantalVelocity()
    {
        Vector3 horizontalVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);
        return horizontalVelocity;
    }


    public Vector3 DesiredVelocity()
    {
        Vector3 inputDirection = new Vector3(pController.MovementInput.x, 0f, pController.MovementInput.y); //Where you want to go
        Vector3 moveDirection = transform.TransformDirection(inputDirection.normalized);    //converts local direction (Relative to player) into world space.

        if (isGrounded && inputDirection.magnitude > 0.1f) //If on Slope:
        {
            if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, 1.5f))
            {
                moveDirection = Vector3.ProjectOnPlane(moveDirection, hit.normal).normalized; //hit.normal = the slope’s perpendicular (up) vector.
            }//ProjectOnPlane removes the component of your movement that goes into the slope. Normalizing ensures you don’t get faster speed on steep slopes.
        }
        //How does this function work? Imagine walking up a hill.
        //Without projection → your movement vector points straight forward, but part of it points into the hill, so you move slower.
        //With projection → the vector is “flattened” onto the slope plane, so you move along the slope consistently.
        //todo causes a little jump when leaving a ramp. ChatGPT says it's cause of the IsGrounded function not liking the quick change.

        Vector3 desiredVelocity = moveDirection * walkSpeed;
        return desiredVelocity;
    }


    public void ApplyUpwardBoop()
    {
        //rb.AddExplosionForce(100f, Vector3.up, 1f, 0, ForceMode.Impulse); 
        rb.linearVelocity = new Vector3(0, 7.5f, 0);
        //Debug.Log("Boopup");
    }

    public void ApplyFixedPush(Vector3 pushVector)
    {
        rb.linearVelocity = new Vector3(0, 0, 0);
        Debug.Log("start");
        //StartCoroutine(RestoreControlAfter(3f));
        //StartCoroutine(noGroundedCheck(0.5f));
        StartCoroutine(wasJustExploded(3f));
        Debug.Log($"Push Vector: " + pushVector);
        rb.AddForce(pushVector, ForceMode.Impulse);
        //Player is given no control over their airborn direction for 1 second
    }

//Todo add a 1 second delay on being able to throw suck stars, after being launched by knockback. Prevents player from instantly sucking themselves to ground.

    private IEnumerator wasJustExploded(float tFrozen)
    {
        beingExploded = true;
        yield return new WaitForSeconds(tFrozen);
        beingExploded = false;
    }

    private void HandleJump()
    {
        if (pController.JumpTriggered && isGrounded)
        {
            rb.linearVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z); // reset Y
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }

    private void ApplyGravityMultiplier()
    {
        if (!isGrounded)
        {
            rb.AddForce(Physics.gravity * gravityMultiplier, ForceMode.Acceleration);
        }
    }


    private void HandleRotation()
    {
       float sensitivity = (playerInput.currentControlScheme == "Gamepad") ? mouseSensitivity * controllerSensitivityMultiplier : mouseSensitivity;
        //“If Gamepad is the current controlscheme, use mouseSensitivity * controllerSensitivityMultiplier. Otherwise, just use mouseSensivity"

        float XRotation = pController.LookInput.x * sensitivity;
        float YRotation = pController.LookInput.y * sensitivity;


        transform.Rotate(0, XRotation, 0);

        verticalRotation = Mathf.Clamp(verticalRotation - YRotation, -89f, 89f);    //Max Distance you're allowed to look up or down
        mainCamera.transform.localRotation = Quaternion.Euler(verticalRotation, 0, 0);
    }


    public void Death()
    {
        lives -= 1;
    }

    private void Throw1()
    {
        if (pController.Throw1Triggered && cantThrowNades == false && BounceNadeAvailable) //when throw1 is pressed, if nades aren't on cooldown,
        { 
            NadeScript nadeScript = GetComponent<NadeScript>();

            // Pass the camera reference to NadeScript for NadeSpawnPos() and Vector3 nadeAngleThrown
            nadeScript.SetCamera(mainCamera);

            nadeScript.BounceNadeThrown();
            
            StartCoroutine(BounceNadeCooldownTimer(BounceNadeCooldown)); 
            StartCoroutine(NadeSpamPreventer(1));
        }
    }
    
    private void Throw2()
    {
        NadeScript nadeScript = GetComponent<NadeScript>();
        if (pController.Throw2Triggered && cantThrowNades==false && nadeScript.C4Exists==false && C4InstantThrowCooldown==false && C4Available)
            {
                nadeScript.SetCamera(mainCamera);
                nadeScript.C4Thrown();
                StartCoroutine(C4CooldownTimer(C4Cooldown)); 
                StartCoroutine(NadeSpamPreventer(1));
            }
        
        if (pController.Throw2Triggered && nadeScript.C4Armed && nadeScript.C4Exists)
            {
                nadeScript.C4Thrown();
                StartCoroutine(SpecialC4Cooldown(1));
            }
    }
    
    private void Throw3()
    {
        if (pController.Throw3Triggered && cantThrowNades == false && BouncePadAvailable) //when throw1 is pressed, if nades aren't on cooldown,
        {
            NadeScript nadeScript = GetComponent<NadeScript>();

            // Pass the camera reference to NadeScript for NadeSpawnPos() and Vector3 nadeAngleThrown
            nadeScript.SetCamera(mainCamera);

            nadeScript.BPadThrown();
            
            StartCoroutine(BPadCooldownTimer(BPadCooldown)); 
            StartCoroutine(NadeSpamPreventer(1));

        }
    }
    private void Throw4()
    {
        if (pController.Throw4Triggered && cantThrowNades == false && SuckStarAvailable) //when throw1 is pressed, if nades aren't on cooldown,
        { 
            NadeScript nadeScript = GetComponent<NadeScript>();

            // Pass the camera reference to NadeScript for NadeSpawnPos() and Vector3 nadeAngleThrown
            nadeScript.SetCamera(mainCamera);

            nadeScript.SuckStarThrown();
            
            StartCoroutine(SuckStarCooldownTimer(SuckStarCooldown)); 
            StartCoroutine(NadeSpamPreventer(1));
        }
    }

    public IEnumerator BounceNadeCooldownTimer(float cooldown)  //Time between BounceNade throws
        {
            BounceNadeAvailable = false;
            yield return new WaitForSeconds(cooldown);
            BounceNadeAvailable = true;
        }
    public IEnumerator C4CooldownTimer(float cooldown)  //Time between C4 throws. Starts when C4 is thrown, not explodes.
        {
            C4Available = false;
            yield return new WaitForSeconds(cooldown);
            C4Available = true;
        }
    public IEnumerator BPadCooldownTimer(float cooldown)  //Time between C4 throws. Starts when C4 is thrown, not explodes.
        {
            
            BouncePadAvailable = false;
            yield return new WaitForSeconds(cooldown);
            BouncePadAvailable = true;
        }
    public IEnumerator SuckStarCooldownTimer(float cooldown)  //Time between BounceNade throws
        {
            SuckStarAvailable = false;
            yield return new WaitForSeconds(cooldown);
            SuckStarAvailable = true;
        }


    public IEnumerator BouncePadArmTime(float cooldown)    //Time it takes for a thrown C4 to be allowed to explode (No BO2 insta-popping c4s).
    {
        bouncePadArmed = false;
        yield return new WaitForSeconds(cooldown);
        bouncePadArmed = true;
    }

    public IEnumerator NadeSpamPreventer(float cooldown)    //A 1 second delay on throwing any grenade after a grenade is thrown.
        {
            cantThrowNades = true;
            yield return new WaitForSeconds(cooldown);
            cantThrowNades = false;
        }
    public IEnumerator SpecialC4Cooldown(float cooldown)    //A 1 second delay on throwing a C4 after a C4 EXPLODES. (exploding doesn't trigger NadeSpamPreventer.)
        {
            C4InstantThrowCooldown = true;
            yield return new WaitForSeconds(cooldown);
            C4InstantThrowCooldown = false;
        }

    private void OnDrawGizmosSelected() //No idea what this does
    {
        // Visualize ground check ray in editor
        Gizmos.color = Color.yellow;
        Gizmos.DrawLine(transform.position, transform.position + Vector3.down * groundCheckDistance);
    }
}
Editor is loading...
Leave a Comment