DugganSC

New Glide Controller

Sep 18th, 2025
195
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 21.15 KB | None | 0 0
  1. using FS_ThirdPerson;
  2. using System;
  3. using System.Collections;
  4. using System.Linq;
  5. using Unity.VisualScripting;
  6. using UnityEngine;
  7.  
  8. namespace SD_GlidingSystem
  9. {
  10.     public class GliderController : EquippableSystemBase
  11.     {
  12.         public GliderItem currentGliderData
  13.         {
  14.             get
  15.             {
  16.                 return (itemEquipper?.EquippedItem is GliderItem gliderData)
  17.                     ? gliderData
  18.                     : null;
  19.             }
  20.         }
  21.  
  22.         public GliderObject CurrentGlider => itemEquipper?.EquippedItemObject as GliderObject;
  23.  
  24.         [Tooltip("Enables or disables the glide feature.")]
  25.         public bool enableGlide = false;
  26.  
  27.         [Tooltip("If true, the character will be able to glide without equipping the glider")]
  28.         public bool glideWithoutEquip = false;
  29.  
  30.         [Tooltip("If true, the glider will be unequipped after the gliding action is completed.")]
  31.         public bool unEquipAfterGlide = false;
  32.  
  33.         public bool InAction { get; private set; }
  34.         public override SystemState State { get; } = SystemState.Other;
  35.         public GameObject _floatObject;
  36.  
  37.         EquippableItem availableGlider;
  38.  
  39.         ICharacter player;
  40.         PlayerController playerController;
  41.         LocomotionInputManager locomotionInput;
  42.         CharacterController characterController;
  43.         LocomotionController locomotionController;
  44.         ItemEquipper itemEquipper;
  45.         AnimGraph animGraph;
  46.  
  47.         [Header("Ground Check Settings")]
  48.         [Tooltip("Radius of ground detection sphere")]
  49.         [SerializeField] float groundCheckRadius = 0.7f;
  50.  
  51.         [Tooltip("Distance for ground detection ray")]
  52.         [SerializeField] float groundCheckDistance = 2f;
  53.  
  54.         [Tooltip("Offet between the player's root position and the ground detection sphere")]
  55.         [SerializeField] Vector3 groundCheckOffset = new Vector3(0f, 0.15f, 0.07f);
  56.  
  57.         [Tooltip("All layers that should be considered as ground")]
  58.         public LayerMask groundLayer = 1;
  59.  
  60.         [SerializeField] Vector3 _velocityVector;
  61.         [SerializeField] private float _parachuteDrag = 0.1f;
  62.         [SerializeField] private float _rotationSpeed = 180f;
  63.         [SerializeField] private float _fallSpeed = -0.2f;
  64.         [SerializeField] Animator _animator;
  65.         private Vector3 forwardLandForceMultiplier;
  66.  
  67.         [Header("Gliding Turning Physics")]
  68.         [SerializeField] FlightProfile flightProfile;
  69.  
  70.         [Tooltip("How much forward speed is lost to generate lift when pulling up. >1 is a net loss.")]
  71.         [SerializeField] private float _forwardMomentumCost = 1.2f;
  72.  
  73.         [Tooltip("How quickly the glider's pitch stabilizes with no input.")]
  74.         [SerializeField] private float _pitchDampening = 3f;
  75.  
  76.         [Tooltip("How quickly the glider stabilizes and levels out with no input.")]
  77.         [SerializeField] private float _rollDampening = 3f;
  78.  
  79.  
  80.         [Tooltip("How quickly the glider rolls into a turn. Higher is more responsive.")]
  81.         [SerializeField] private float _turnSpeed = 2.5f;
  82.  
  83.         [Tooltip("The maximum angle in degrees the glider can bank.")]
  84.         [SerializeField] private float _maxRollAngle = 40f;
  85.  
  86.         [Tooltip("How much extra forward speed is gained when diving.")]
  87.         [SerializeField] private float _speedBoostFromPitch = 5f;
  88.  
  89.         [Tooltip("How much lift (reduced fall speed) is generated when pitching up.")]
  90.         [SerializeField] private float _liftFromPitch = 2f;
  91.  
  92.  
  93.  
  94.         // We need a variable to track the current pitch angle
  95.         private float _currentPitch = 0f;
  96.  
  97.         // We need a variable to track the current roll angle
  98.         private float _currentRoll = 0f;
  99.  
  100.         [Header("Gliding Speed")]
  101.         [SerializeField] private float _maxGlideSpeed = 25f;
  102.  
  103.         [Header("Ragdoll Paramters")]
  104.         [Tooltip("The amount of force applied to the legs to make them sway when turning.")]
  105.         public float legSwayFactor = 50f;
  106.  
  107.         [Tooltip("The Rigidbody components of the character's legs (thighs and calves).")]
  108.         public Rigidbody[] legRigidbodies;
  109.  
  110.         private void Start()
  111.         {
  112.             player = GetComponent<ICharacter>();
  113.             playerController = GetComponent<PlayerController>();
  114.             characterController = GetComponent<CharacterController>();
  115.             locomotionController = GetComponent<LocomotionController>();
  116.             itemEquipper = GetComponent<ItemEquipper>();
  117.  
  118.             itemEquipper.OnEquip += EquipItem;
  119.             itemEquipper.OnUnEquip += UnEquipItem;
  120.  
  121.             _animator = GetComponent<Animator>();
  122.  
  123.             locomotionInput = GetComponent<LocomotionInputManager>();
  124.  
  125.             availableGlider = itemEquipper.equippableItems.FirstOrDefault(i => i is GliderItem);
  126.             enableGlide = availableGlider != null;
  127.         }
  128.  
  129.         public void Update()
  130.         {
  131.             HandleGlideInputs();
  132.  
  133.             if (playerController.IsInAir)
  134.             {
  135.                 Debug.Log("In Air");
  136.                 if ((glideWithoutEquip && availableGlider != null) || CurrentGlider != null)
  137.                 {
  138.                     Debug.Log("We have a glider");
  139.                     if (enableGlide && !InAction)
  140.                     {
  141.                         Debug.Log($"High Enough: {HighEnough()} Input Down: {GlideStartInputDown}");
  142.                         if (GlideStartInputDown && HighEnough())
  143.                         {
  144.                             Debug.Log("Should be starting to glide");
  145.                             StartCoroutine(HandleGliding());
  146.                         }
  147.                     }
  148.                 }
  149.             }
  150.  
  151.             //if (enableGlide)
  152.             //{
  153.             //    if (playerController.IsInAir && !InAction && GlideStartInputDown && HighEnough())
  154.             //    {
  155.             //        StartCoroutine(StartGliding());
  156.             //    } else if (InAction && GlideStopInputDown) {
  157.             //        StartCoroutine(HandleLandingMomentum());
  158.             //        StartCoroutine(StopGliding());
  159.             //        return;
  160.             //    }
  161.  
  162.             //    if (InAction && HighEnough())
  163.             //    {
  164.             //        HandleGlidingMovement();
  165.             //    } else if (InAction && !HighEnough())
  166.             //    {
  167.             //        StartCoroutine(HandleLandingMomentum());
  168.             //        StartCoroutine(StopGliding());
  169.             //    }
  170.             //}
  171.         }
  172.  
  173.         // We enter this coroutine when we decide to start the gliding process
  174.         IEnumerator HandleGliding()
  175.         {
  176.             // Check to see if we have an available glider item, even if not deployed. Exit if not.
  177.             // Equip the glider
  178.             // Yield until it is equipped
  179.             bool equippedForAction = false;
  180.  
  181.             if (CurrentGlider == null)
  182.             {
  183.                 if (availableGlider == null) yield break;
  184.  
  185.                 itemEquipper.EquipItem(availableGlider);
  186.                 yield return new WaitUntil(() => !itemEquipper.IsChangingItem);
  187.  
  188.                 equippedForAction = true;
  189.             }
  190.  
  191.             if (InAction || currentGliderData == null) yield break;
  192.            
  193.             // Call StartGliding to start the gliding process and load relevant data
  194.             // Animate starting the glide
  195.             // Animate the glider object?
  196.             // Wait for the time required before gliding begins.
  197.             StartCoroutine(StartGliding());
  198.  
  199.             // While glide exit not pressed and not too close to the ground:
  200.             //  Handle gliding motion
  201.             //  Yield the thread.
  202.             while (HighEnough() && !GlideStopInputDown)
  203.             {
  204.                 HandleGlidingMovement();
  205.                 yield break;
  206.             }
  207.  
  208.             // Call StopGliding to handle winding things down
  209.             StartCoroutine(StopGliding());
  210.  
  211.             // If on the ground, handle that animation
  212.             // Otherwise, we call handleLandingMoime
  213.             if (locomotionController.IsGrounded)
  214.             {
  215.                 // Play a landing animation?
  216.             } else
  217.             {
  218.                 StartCoroutine(HandleLandingMomentum());
  219.             }
  220.                
  221.             yield return null;
  222.         }
  223.  
  224.         private void HandleGlidingMovement()
  225.         {
  226.             // --- BASIC DRAG & INPUT ---
  227.             _velocityVector -= _parachuteDrag * Time.deltaTime * _velocityVector;
  228.             float v = locomotionInput.DirectionInput.y;
  229.             float h = locomotionInput.DirectionInput.x;
  230.  
  231.             // --- PITCH & ROLL CALCULATIONS ---
  232.             // Pitch
  233.             float targetPitch = v > 0 ? v * flightProfile.maxPitchAngle : v * -flightProfile.minPitchAngle;
  234.             _currentPitch = Mathf.Lerp(_currentPitch, targetPitch, flightProfile.pitchSpeed * Time.deltaTime);
  235.             if (Mathf.Approximately(v, 0f))
  236.             {
  237.                 _currentPitch = Mathf.Lerp(_currentPitch, 0f, _pitchDampening * Time.deltaTime);
  238.             }
  239.             // Roll
  240.             float targetRoll = -h * _maxRollAngle;
  241.             _currentRoll = Mathf.Lerp(_currentRoll, targetRoll, _turnSpeed * Time.deltaTime);
  242.             if (Mathf.Approximately(h, 0f))
  243.             {
  244.                 _currentRoll = Mathf.Lerp(_currentRoll, 0f, _rollDampening * Time.deltaTime);
  245.             }
  246.  
  247.             // --- VERTICAL & HORIZONTAL PHYSICS ---
  248.             Vector3 horizontalVelocity = new Vector3(_velocityVector.x, 0, _velocityVector.z);
  249.             float ySpeed = _velocityVector.y + player.Gravity * Time.deltaTime;
  250.  
  251.             // --- STATE 1: PULLING UP (Trade Speed for Lift) ---
  252.             if (_currentPitch < -0.1f)
  253.             {
  254.                 // Lift is proportional to how fast you're already going.
  255.                 float liftFactor = (_currentPitch / flightProfile.minPitchAngle); // A 0-1 value based on how far you're pulled back.
  256.                 float generatedLift = horizontalVelocity.magnitude * liftFactor * _liftFromPitch;
  257.  
  258.                 // Apply the upward lift. This can overcome gravity if you have enough speed.
  259.                 ySpeed += generatedLift * Time.deltaTime;
  260.  
  261.                 // That lift costs forward momentum (induced drag).
  262.                 float liftCost = generatedLift * _forwardMomentumCost;
  263.                 _velocityVector -= transform.forward * liftCost * Time.deltaTime;
  264.             }
  265.             // --- STATE 2: DIVING (Trade Altitude for Speed) ---
  266.             else
  267.             {
  268.                 float difference = 0;
  269.                 // If falling faster than max fall speed, convert the difference to forward thrust.
  270.                 if (ySpeed < _fallSpeed)
  271.                 {
  272.                     difference = _fallSpeed - ySpeed;
  273.                     ySpeed = _fallSpeed;
  274.                 }
  275.  
  276.                 // Add extra boost for how steeply you are diving.
  277.                 float pitchToSpeed = 0; // (_currentPitch > 0) ? (_currentPitch / flightProfile.maxPitchAngle) * _speedBoostFromPitch : 0;
  278.  
  279.                 Vector3 forwardThrust = transform.forward * (difference + pitchToSpeed);
  280.                 _velocityVector += forwardThrust;
  281.             }
  282.  
  283.             _velocityVector.y = ySpeed;
  284.  
  285.             // --- APPLY ROTATION & MOVEMENT ---
  286.             float yawChange = -_currentRoll * flightProfile.yawFromRoll * Time.deltaTime;
  287.             transform.Rotate(0, yawChange, 0, Space.World);
  288.             transform.localRotation = Quaternion.Euler(_currentPitch, transform.localEulerAngles.y, _currentRoll);
  289.  
  290.             // Clamp total speed
  291.             if (_velocityVector.magnitude > _maxGlideSpeed)
  292.             {
  293.                 _velocityVector = _velocityVector.normalized * _maxGlideSpeed;
  294.             }
  295.  
  296.             characterController.Move(_velocityVector * Time.deltaTime);
  297.         }
  298.  
  299.         private IEnumerator StartGliding()
  300.         {
  301.             if (InAction) { yield return null; }
  302.             InAction = true;
  303.  
  304.             // Play animations for deploying the glider and moving into the gliding position
  305.             if (currentGliderData.startGlidingAnimation != null)
  306.             {
  307.                 animGraph.PlayLoopingAnimation(currentGliderData.startGlidingAnimation);
  308.                 yield return new WaitForSeconds(currentGliderData.startGlidingAnimation.length);
  309.             }
  310.  
  311.             // Move into the gliding animation.
  312.  
  313.             _animator.CrossFadeInFixedTime("Hanging Idle", 0.1f);
  314.  
  315.             _animator.SetBool("Gliding", true);
  316.            
  317.             _velocityVector = characterController.velocity;
  318.  
  319.             // player.OnStartSystem(this);
  320.         }
  321.  
  322.         private bool HighEnough()
  323.         {
  324.             Vector3 projectedPosition = transform.position + _velocityVector * Time.deltaTime * 5; // Project 5 frames ahead
  325.             return !CheckGround(projectedPosition);
  326.         }
  327.  
  328.         private void OnDrawGizmosSelected()
  329.         {
  330.             Gizmos.color = new Color(0, 1, 0, 0.5f);
  331.             Gizmos.DrawRay(transform.TransformPoint(groundCheckOffset), Vector3.down * groundCheckRadius);
  332.         }
  333.  
  334.         private IEnumerator LevelOutRotation()
  335.         {
  336.             float time = 0;
  337.             float duration = 0.25f; // A quarter of a second to level out
  338.  
  339.             // Capture the pitch and roll angles when we start landing
  340.             float startingRoll = _currentRoll;
  341.             float startingPitch = _currentPitch;
  342.  
  343.             while (time < duration)
  344.             {
  345.                 // Smoothly interpolate both roll and pitch from their starting angles to zero
  346.                 _currentRoll = Mathf.Lerp(startingRoll, 0f, time / duration);
  347.                 _currentPitch = Mathf.Lerp(startingPitch, 0f, time / duration);
  348.  
  349.                 // Update the character's local rotation to reflect the change
  350.                 transform.localRotation = Quaternion.Euler(_currentPitch, transform.localEulerAngles.y, _currentRoll);
  351.  
  352.                 time += Time.deltaTime;
  353.                 yield return null; // Wait for the next frame
  354.             }
  355.  
  356.             // After the loop, snap to a perfect zero rotation to ensure it's correct
  357.             _currentRoll = 0f;
  358.             _currentPitch = 0f;
  359.             transform.localRotation = Quaternion.Euler(0f, transform.localEulerAngles.y, 0f);
  360.         }
  361.  
  362.         private IEnumerator StopGliding()
  363.         {
  364.             if (!InAction) { yield return null; }
  365.             Debug.Log($"StopGliding");
  366.             InAction = false;
  367.             // _floatObject.SetActive(false);
  368.             Debug.Log("Setting Gliding to false");
  369.             _animator.SetBool("Gliding", false);
  370.             // Debug.Break();
  371.  
  372.             // Call the new coroutine to handle leveling out the character
  373.             StartCoroutine(LevelOutRotation());
  374.  
  375.             player.OnEndSystem(this);
  376.  
  377.         }
  378.  
  379.         private void OnCollisionEnter(Collision collision)
  380.         {
  381.             Debug.Log("Collision");
  382.             // If we have a collision while gliding, we stop gliding.
  383.             if (InAction)
  384.             {
  385.                 StartCoroutine(StopGliding());
  386.             }
  387.         }
  388.  
  389.         #region rootmotion
  390.  
  391.         bool prevRootMotionVal;
  392.         private string glideInputButton;
  393.         private KeyCode glideInput;
  394.  
  395.         public void EnableRootMotion()
  396.         {
  397.             prevRootMotionVal = player.UseRootMotion;
  398.             player.UseRootMotion = true;
  399.         }
  400.         public void ResetRootMotion()
  401.         {
  402.             player.UseRootMotion = prevRootMotionVal;
  403.         }
  404.  
  405.         public override void HandleOnAnimatorMove(Animator animator)
  406.         {
  407.             transform.rotation *= animator.deltaRotation;
  408.             characterController.Move(animator.deltaPosition);
  409.         }
  410.  
  411.         #endregion
  412.  
  413.         #region input manager
  414.  
  415. #if inputsystem
  416.         GlideInputActions input;
  417.  
  418.         private void OnEnable()
  419.         {
  420.             input = new GlideInputActions();
  421.             input.Enable();
  422.         }
  423.  
  424.         private void OnDisable()
  425.         {
  426.             input.Disable();
  427.         }
  428. #endif
  429.  
  430.         [Header("Input Settings")]
  431.         [Tooltip("Key to start gliding.")]
  432.         public KeyCode glideStartInput = KeyCode.Space;
  433.  
  434.         [Tooltip("Key to stop gliding.")]
  435.         public KeyCode glideStopInput = KeyCode.Space;
  436.  
  437.         [Tooltip("The button to start gliding.")]
  438.         public string glideStartButton;
  439.  
  440.         [Tooltip("The button to stop gliding.")]
  441.         public string glideStopButton;
  442.        
  443.         public bool GlideStartInputDown { get; private set; }
  444.         public bool GlideStopInputDown { get; private set; }
  445.  
  446.         void HandleGlideInputs()
  447.         {
  448.  
  449. #if inputsystem
  450.             GlideStartInputDown = input.Glide.GlideStart.WasPerformedThisFrame();
  451.             GlideStopInputDown = input.Glide.GlideEnd.WasPerformedThisFrame();
  452. #else
  453.             GlideStartInputDown = Input.GetKeyDown(glideStartInput) || (!string.IsNullOrEmpty(glideStartButton) && Input.GetButtonDown(glideStartButton));
  454.             GlideStopInputDown = Input.GetKeyDown(glideStopInput) || (!string.IsNullOrEmpty(glideStopButton) && Input.GetButtonDown(glideStopButton));
  455. #endif
  456.         }
  457.  
  458.         #endregion
  459.  
  460.         #region Landing
  461.  
  462.         IEnumerator CrossFadeAsync(string anim, float crossFadeTime = .2f, bool enableRootmotion = false, Action onComplete = null)
  463.         {
  464.             if (enableRootmotion)
  465.                 EnableRootMotion();
  466.             _animator.CrossFadeInFixedTime(anim, crossFadeTime);
  467.             yield return null;
  468.             //while (animator.IsInTransition(0))
  469.             //{
  470.             //    yield return null;
  471.             //}
  472.             var animState = _animator.GetNextAnimatorStateInfo(0);
  473.  
  474.             float timer = 0f;
  475.  
  476.             while (timer <= animState.length)
  477.             {
  478.                 timer += Time.deltaTime * _animator.speed;
  479.                 yield return null;
  480.             }
  481.             if (enableRootmotion)
  482.                 ResetRootMotion();
  483.             onComplete?.Invoke();
  484.         }
  485.  
  486.         private IEnumerator HandleLandingMomentum()
  487.         {
  488.             Debug.Log("Calling HandleLandingMomentum");
  489.             InAction = false;
  490.             _animator.SetBool("Gliding", false);
  491.             StartCoroutine(CrossFadeAsync(AnimationNames.FallTree, .2f, false));
  492.             StartCoroutine(LevelOutRotation());
  493.  
  494.             // Calculate the velocity required for the jump landing.
  495.             float ySpeed = _velocityVector.y;
  496.  
  497.             // While the player is not grounded, apply gravity and move the player towards the landing position.
  498.             while (!locomotionController.IsGrounded)
  499.             {
  500.                 ySpeed += player.Gravity * Time.deltaTime; // Apply gravity.
  501.                 _velocityVector.y = ySpeed;
  502.                 characterController.Move(_velocityVector * Time.deltaTime);
  503.                 yield return null;
  504.             }
  505.  
  506.             // Reset velocity after landing
  507.             _velocityVector = Vector3.zero;
  508.         }
  509.  
  510.         private bool CheckGround(Vector3 position)
  511.         {
  512.             Debug.DrawRay(position + groundCheckOffset, Vector3.down, Color.red);
  513.             return Physics.SphereCast(position + groundCheckOffset, groundCheckRadius, Vector3.down, out _, groundCheckDistance, groundLayer);
  514.         }
  515.  
  516.         // Add this method to the same script to visualize the SphereCast
  517.         private void OnDrawGizmos()
  518.         {
  519.             Debug.Log("Drawing Gizmos...");
  520.             // --- Visualize the SphereCast ---
  521.             Vector3 startPoint = transform.position + groundCheckOffset;
  522.             Vector3 endPoint = startPoint + Vector3.down * groundCheckDistance;
  523.  
  524.             // Check if the cast hits anything to change the gizmo's color
  525.             bool isHit = Physics.SphereCast(startPoint, groundCheckRadius, Vector3.down, out RaycastHit hitInfo, groundCheckDistance, groundLayer);
  526.  
  527.             // Set the color based on hit status
  528.             Gizmos.color = isHit ? Color.green : Color.red;
  529.  
  530.             // Draw the starting and ending spheres of the cast
  531.             Gizmos.DrawWireSphere(startPoint, groundCheckRadius);
  532.             Gizmos.DrawWireSphere(endPoint, groundCheckRadius);
  533.  
  534.             // Draw a line connecting the centers of the two spheres
  535.             Gizmos.DrawLine(startPoint, endPoint);
  536.  
  537.             // If the cast hit something, draw a solid sphere at the exact point of contact
  538.             if (isHit)
  539.             {
  540.                 Vector3 hitCenter = startPoint + Vector3.down * hitInfo.distance;
  541.                 Gizmos.DrawSphere(hitCenter, groundCheckRadius);
  542.             }
  543.         }
  544.  
  545.  
  546.         #endregion
  547.         #region Equip And UnEquip
  548.  
  549.         public void EquipItem(EquippableItem itemdata)
  550.         {
  551.             Debug.Log("Entering EquipItem for " + itemdata.name);
  552.             if (itemdata is GliderItem)
  553.             {
  554.                 enableGlide = true;
  555.                 flightProfile = ((GliderItem)itemdata).flightProfile;
  556.             }
  557.         }
  558.         public void UnEquipItem()
  559.         {
  560.             if (CurrentGlider is GliderObject)
  561.             {
  562.                 enableGlide = false;
  563.                 flightProfile = null;
  564.             }
  565.         }
  566.  
  567.  
  568.         #endregion
  569.  
  570.  
  571.     }
  572. }
Advertisement
Add Comment
Please, Sign In to add comment