Skip to content
Snippets Groups Projects
CharacterJump.cs 13.8 KiB
Newer Older
using UnityEngine;
using UnityEngine.InputSystem;

#if UNITY_EDITOR
using UnityEditor;
#endif

//This script handles moving the character on the Y axis, for jumping and gravity
[RequireComponent(typeof(CharacterMovement))]
[RequireComponent(typeof(CharacterGround))]
public class CharacterJump : MonoBehaviour
{
    // [Header("Components")]
    [InfoBox("If you want to see a prediction of the jump when this GameObject is selected, check showJumpLine in the Character_Settings_SO that you're using.", EInfoBoxType.Normal)]
    [XnTools.Hidden] public Rigidbody2D rigid;
    [XnTools.Hidden] public Vector2 velocity;

    private CharacterMovement movement;
    private CharacterGround   ground;
    [Header( "Platformer Character Settings" )]
    private Character_Settings_SO characterSettingsSO = null;


//    [Header("Jumping Stats")]
//    [SerializeField, Range(2f, 5.5f)][Tooltip("Maximum jump height")] public float jumpHeight = 7.3f;


////If you're using your stats from Platformer Toolkit with this CharacterJump, please note that the number on the Jump Duration handle does not match this stat
////It is re-scaled, from 0.2f - 1.25f, to 1 - 10.
////You can transform the number on screen to the stat here, using the function at the bottom of this script
//    [SerializeField, Range(0.2f, 1.25f)][Tooltip("How long it takes to reach that height before coming back down")] public float timeToJumpApex;
//    [SerializeField, Range(0f, 5f)][Tooltip("Gravity multiplier to apply when going up")] public float upwardMovementMultiplier = 1f;
//    [SerializeField, Range(1f, 10f)][Tooltip("Gravity multiplier to apply when coming down")] public float downwardMovementMultiplier = 6.17f;
//    [SerializeField, Range(0, 1)][Tooltip("How many times can you jump in the air?")] public int maxAirJumps = 0;

//    [Header("Options")]
//    [Tooltip("Should the character drop when you let go of jump?")] public bool variableJumpHeight;
//    [SerializeField, Range(1f, 10f)][Tooltip("Gravity multiplier when you let go of jump")] public float jumpCutOff;
//    [SerializeField][Tooltip("The fastest speed the character can fall")] public float speedLimit;
//    [SerializeField, Range(0f, 0.3f)][Tooltip("How long should coyote time last?")] public float coyoteTime = 0.15f;
//    [SerializeField, Range(0f, 0.3f)][Tooltip("How far from ground should we cache your jump?")] public float jumpBuffer = 0.15f;

    [Header("Calculations")]
    public float jumpSpeed;
    private float defaultGravityScale;
    public float gravMultiplier;

    [Header("Current State")]
    public bool canJumpAgain = false;
    private bool desiredJump;
    private float jumpBufferCounter;
    private float coyoteTimeCounter = 0;
    private bool pressingJump;
    public bool onGround;
    private bool currentlyJumping;

    void Awake()
    {
        //Find the character's Rigidbody and ground detection and juice scripts
        movement = GetComponent<CharacterMovement>();
        rigid = GetComponent<Rigidbody2D>();
        ground = GetComponent<CharacterGround>();
        defaultGravityScale = 1f;
        characterSettingsSO = movement.characterSettingsSO;
    }

    public void OnJump(InputAction.CallbackContext context)
    {
        //This function is called when one of the jump buttons (like space or the A button) is pressed.
        //When we press the jump button, tell the script that we desire a jump.
        //Also, use the started and canceled contexts to know if we're currently holding the button
        if (context.started)
        {
            desiredJump = true;
            pressingJump = true;
        }

        if (context.canceled)
        {
            pressingJump = false;
        }
    }

    private void SetPhysics() {
        //Determine the character's gravity scale, using the stats provided. Multiply it by a gravMultiplier, used later
        Vector2 newGravity = new Vector2( 0, ( -2 * characterSettingsSO.jumpHeight ) / ( characterSettingsSO.jumpDuration.up * characterSettingsSO.jumpDuration.up ) );
        rigid.gravityScale = ( newGravity.y / Physics2D.gravity.y ) * gravMultiplier;
    }

    void Update()
    {


        //If we're not on the ground and we're not currently jumping, that means we've stepped off the edge of a platform.
        //So, start the coyote time counter...
        if (!currentlyJumping && !onGround)
        {
            coyoteTimeCounter += Time.deltaTime;
        }
        else
        {
            //Reset it when we touch the ground, or jump
            coyoteTimeCounter = 0;
        }
    }



    private void FixedUpdate()
    {

        //Check if we're on ground, using Kit's Ground script
        onGround = ground.GetOnGround();

        SetPhysics();

        //Jump buffer allows us to queue up a jump, which will play when we next hit the ground
        if ( characterSettingsSO.jumpBuffer > 0 ) {
            //Instead of immediately turning off "desireJump", start counting up...
            //All the while, the DoAJump function will repeatedly be fired off
            if ( desiredJump ) {
                jumpBufferCounter += Time.deltaTime;

                if ( jumpBufferCounter > characterSettingsSO.jumpBuffer ) {
                    //If time exceeds the jump buffer, turn off "desireJump"
                    desiredJump = false;
                    jumpBufferCounter = 0;
                }
            }
        }



        //Get velocity from Kit's Rigidbody 
        velocity = rigid.velocity;

        //Keep trying to do a jump, for as long as desiredJump is true
        if (desiredJump)
        {
            DoAJump();

            //Skip gravity calculations this frame, so currentlyJumping doesn't turn off
            //This makes sure you can't do the coyote time double jump bug
            rigid.velocity = velocity; // Assign velocity to rigid because return will be called next line.
            return;
        }

        CalculateGravity();
        rigid.velocity = velocity;
    }

    private void CalculateGravity()
    {
        // I removed all references to the Rigidbody in this script. - JGB 2022-10-30

        //We change the character's gravity based on her Y direction

        //If Kit is going up...
        if ( velocity.y > 0.01f ) //if (rigid.velocity.y > 0.01f)
        {
            if (onGround)
            {
                //Don't change it if Kit is stood on something (such as a moving platform)
                gravMultiplier = defaultGravityScale;
            }
            else
            {
                //If we're using variable jump height...)
                if ( characterSettingsSO.jumpSettingsVariableHeight.useVariableJumpHeight )
                {
                    //Apply upward multiplier if player is rising and holding jump
                    if (pressingJump && currentlyJumping) {
                        gravMultiplier = characterSettingsSO.jumpGrav.up;// characterSettingsSO.upwardMovementMultiplier;
                    }
                    //But apply a special downward multiplier if the player lets go of jump
                    else {
                        gravMultiplier = characterSettingsSO.jumpGrav.up * characterSettingsSO.jumpSettingsVariableHeight.gravUpMultiplierOnRelease;// characterSettingsSO.jumpCutOff;
                    gravMultiplier = characterSettingsSO.jumpGrav.up;// characterSettingsSO.upwardMovementMultiplier;
                }
            }
        }

        //Else if going down...
        else if ( velocity.y < -0.01f ) //else if (rigid.velocity.y < -0.01f)
        {

            if (onGround)
            //Don't change it if Kit is stood on something (such as a moving platform)
            {
                gravMultiplier = defaultGravityScale;
            }
            else
            {
                //Otherwise, apply the downward gravity multiplier as Kit comes back to Earth
                gravMultiplier = characterSettingsSO.jumpGrav.down; //characterSettingsSO.downwardMovementMultiplier;
            }

        }
        //Else not moving vertically at all
        else
        {
            if (onGround)
            {
                currentlyJumping = false;
            }

            gravMultiplier = defaultGravityScale;
        }

        //Set the character's Rigidbody's velocity
        //But clamp the Y variable within the bounds of the speed limit, for the terminal velocity assist option
        velocity.y = Mathf.Clamp( velocity.y, -characterSettingsSO.speedLimit, 100 );
        //rigid.velocity = new Vector3(velocity.x, Mathf.Clamp(velocity.y, -speedLimit, 100));
    }

    private void DoAJump()
    {
        //Create the jump, provided we are on the ground, in coyote time, or have a double jump available
        if (onGround || (coyoteTimeCounter > 0.03f && coyoteTimeCounter < characterSettingsSO.coyoteTime ) || canJumpAgain) {
            StairMaster.ON_STAIRS = false;
            
            desiredJump = false;
            jumpBufferCounter = 0;
            coyoteTimeCounter = 0;

            // Fix the gravity bug that led to double-height jumps when the timing was perfect
            gravMultiplier = defaultGravityScale;
            SetPhysics();

            //If we have double jump on, allow us to jump again (but only once)
            // canJumpAgain = ( characterSettingsSO.maxAirJumps == 1 && canJumpAgain == false);
            canJumpAgain = ( characterSettingsSO.jumpsBetweenGrounding > 1 && canJumpAgain == false);
            
            //Determine the power of the jump, based on our gravity and stats
            jumpSpeed = Mathf.Sqrt(-2f * Physics2D.gravity.y * rigid.gravityScale * characterSettingsSO.jumpHeight );
            if (jumpSpeed > 100) {
                 Debug.LogError( "Break" );
            }

            //If Kit is moving up or down when she jumps (such as when doing a double jump), change the jumpSpeed;
            //This will ensure the jump is the exact same strength, no matter your velocity.
            if (velocity.y > 0f)
            {
                jumpSpeed = Mathf.Max(jumpSpeed - velocity.y, 0f);
            }
            else if (velocity.y < 0f)
            {
                jumpSpeed += Mathf.Abs(rigid.velocity.y);
            }

            //Apply the new jumpSpeed to the velocity. It will be sent to the Rigidbody in FixedUpdate;
            velocity.y += jumpSpeed;
            currentlyJumping = true;
        }

        if ( characterSettingsSO.jumpBuffer == 0)
        {
            //If we don't have a jump buffer, then turn off desiredJump immediately after hitting jumping
            desiredJump = false;
        }
    }

    public void BounceUp(float bounceAmount)
    {
        //Used by the springy pad
        rigid.AddForce(Vector2.up * bounceAmount, ForceMode2D.Impulse);
    }

/*

timeToApexStat = scale(1, 10, 0.2f, 2.5f, numberFromPlatformerToolkit)


  public float scale(float OldMin, float OldMax, float NewMin, float NewMax, float OldValue)
    {

        float OldRange = (OldMax - OldMin);
        float NewRange = (NewMax - NewMin);
        float NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin;

        return (NewValue);
    }

*/
#if UNITY_EDITOR
    
    [CustomEditor( typeof(CharacterJump) )]
    public class CharacterJump_Editor : Editor {
        private const float dashSize = 4;
        
        private CharacterMovement cMove;
        private CharacterJump     cJump;
    
        private void OnEnable() {
            cJump = (CharacterJump) target;
            cMove = cJump.GetComponent<CharacterMovement>();
        }
    
    
        private void OnSceneGUI() {
            if ( cMove                     == null ) return;
            if ( cMove.characterSettingsSO == null ) return;
            Character_Settings_SO csso = cMove.characterSettingsSO;
            if ( !csso.showJumpLine ) return;
            if ( csso.jumpSettingsType == Character_Settings_SO.eJumpSettingsType.GMTK_GameMakersToolKit ) return;
            if (csso.jumpLinePoints == null) cMove.characterSettingsSO.CalculateJumpLine();

            GUIStyle labelStyle = new GUIStyle( EditorStyles.foldoutHeader );
            labelStyle.imagePosition = ImagePosition.TextOnly; // NOTE: This didn't seem to do anything.
            labelStyle.richText = true;
            Handles.matrix = Matrix4x4.Translate(cJump.transform.position);
            Handles.color = Color.green;
            Handles.DrawAAPolyLine(4, csso.jumpLinePoints);
            Vector3[] jSME = csso.jumpStartMidEndPoints;
            if ( jSME != null && jSME.Length == 3 ) {
                Vector3 offset = Vector3.up * 0.2f;
                Handles.DrawDottedLine( jSME[0] + offset, jSME[2] + offset, dashSize );
                tVec = ( jSME[0] + jSME[2] ) / 2f + offset * 4 + Vector3.left * 0.4f;
                Handles.Label( tVec, $"<b>Dist: {csso.maxJumpDistHeight.x:0.##}</b>", labelStyle );
                tVec = jSME[1];
                tVec.y = 0;
                Handles.DrawDottedLine( tVec, jSME[1], dashSize );
                tVec = ( tVec + jSME[1] ) / 2f + Vector3.left * 0.4f;
                Handles.Label( tVec, $"<b>Height: {csso.maxJumpDistHeight.y:0.##}</b>", labelStyle );

            if ( csso.jumpSettingsVariableHeight.useVariableJumpHeight ) {
                Handles.color = Color.magenta;
                Handles.DrawAAPolyLine( 8, csso.minJumpLinePoints.ToArray() );
                if ( csso.minJumpStartMidEndPoints        != null &&
                     csso.minJumpStartMidEndPoints.Length == 3 ) {
                    tVec = csso.minJumpStartMidEndPoints[0] + Vector3.down * 0.25f;
                    Handles.Label( tVec, $"<b>Min: Ht: {csso.minJumpDistHeight.y:0.##}   Dst: {csso.minJumpDistHeight.x:0.##}" +
                                         $"   tApex: {csso.minTimeApexFull.x:0.##}   tFull: {csso.minTimeApexFull.y:0.##}</b>", labelStyle );
                }
            }