PlayerPhysics

mail@pastecode.io avatar
unknown
csharp
24 days ago
5.4 kB
8
Indexable
Never
using System;
using System.Collections;
using UnityEngine;

public class PlayerPhysics : MonoBehaviour
{
    // Public Rigidbody component and layer mask for ground detection
    public Rigidbody RB;
    public LayerMask layermask;

    // Horizontal velocity (velocity along the plane perpendicular to the up direction)
    public Vector3 horizontalVelocity => Vector3.ProjectOnPlane(RB.velocity, RB.transform.up);

    // Vertical velocity (velocity in the direction of the up vector)
    public Vector3 verticalVelocity => Vector3.Project(RB.velocity, RB.transform.up);

    // Speed in the vertical direction (dot product of velocity and up vector)
    public float verticalSpeed => Vector3.Dot(RB.velocity, RB.transform.up);

    // Horizontal speed (magnitude of horizontal velocity)
    public float speed => horizontalVelocity.magnitude;

    // Serialized gravity variable to control gravity strength
    [SerializeField] float gravity;

    // Delegate that allows other scripts to hook into the player's physics update
    public Action onPlayerPhysicsUpdate;

    // FixedUpdate is called at a consistent rate for physics calculations
    void FixedUpdate() 
    { 
        // Invoke any functions hooked to the physics update
        onPlayerPhysicsUpdate?.Invoke();

        // If the player is not grounded, apply gravity
        if(!groundInfo.ground)
        {
            Gravity();
        }

        // If grounded and the vertical speed is below the sleep threshold, keep only horizontal velocity
        if(groundInfo.ground && verticalSpeed < RB.sleepThreshold)
            RB.velocity = horizontalVelocity;

        // Start coroutine for LateFixedUpdate
        StartCoroutine(LateFixedUpdateRoutine());

        // Coroutine to ensure LateFixedUpdate happens after physics updates
        IEnumerator LateFixedUpdateRoutine()
        {
            yield return new WaitForFixedUpdate();
            LateFixedUpdate();
        }
    }

    // Apply gravity by modifying the player's velocity
    void Gravity()
    {
        RB.velocity -= Vector3.up * gravity * Time.deltaTime;
    }

    // LateFixedUpdate handles ground detection and snapping to the ground surface
    void LateFixedUpdate()
    {
        Ground();
        Snap();

        // If grounded, set the velocity to only include horizontal movement
        if(groundInfo.ground)
            RB.velocity = horizontalVelocity;
    }

    // Ground detection distance, defined in the Inspector
    [SerializeField] float groundDistance;

    // Struct to store information about ground detection
    public struct GroundInfo
    {
        public Vector3 point;  // Point where the ground is detected
        public bool ground;    // Whether the player is grounded
        public Vector3 normal; // Normal of the ground surface
    }

    // Stores information about the current ground state
    [HideInInspector] public GroundInfo groundInfo;

    // Actions to be invoked when the player enters or exits the ground
    public Action onGroundEnter;
    public Action onGroundExit;

    // Ground detection method using raycasting
    void Ground()
    {
        // Calculate the maximum distance for raycasting to detect the ground
        float maxDistance = Mathf.Max(RB.centerOfMass.y, 0) + (RB.sleepThreshold * Time.fixedDeltaTime);

        // Extend the detection range if the player is grounded and moving slowly
        if(groundInfo.ground && verticalSpeed < RB.sleepThreshold)
            maxDistance += groundDistance;

        // Perform the raycast from the player's center of mass downward
        bool ground = Physics.Raycast(RB.worldCenterOfMass, -RB.transform.up, out RaycastHit hit, maxDistance, layermask, QueryTriggerInteraction.Ignore);

        // If ground is detected, update the hit point and normal, else use current position and default normal
        Vector3 point = ground ? hit.point : RB.transform.position;
        Vector3 normal = ground ? hit.normal : Vector3.up;

        // Trigger ground enter/exit events if the ground state changes
        if (ground != groundInfo.ground)
        {
            if (ground)
                onGroundEnter?.Invoke(); // Ground entered
            else
                onGroundExit?.Invoke();  // Ground exited
        }

        // Update ground information
        groundInfo = new()
        {
            point  = point,
            normal = normal,
            ground  = ground
        };
    }

    // Adjust the player's position and orientation to snap to the ground surface
    void Snap()
    {
        // Align the player's up direction with the ground's normal
        RB.transform.up = groundInfo.normal;

        // Calculate the desired position (the point of ground contact)
        Vector3 goal = groundInfo.point;

        // Calculate the difference between the current position and goal position
        Vector3 difference = goal - RB.transform.position;

        // Perform a sweep test to check for obstructions before snapping to the ground
        if (RB.SweepTest(difference, out _, difference.magnitude, QueryTriggerInteraction.Ignore)) return;

        // Snap the player to the goal position if no obstructions are detected
        RB.transform.position = goal;
    }
}
Leave a Comment