0
\$\begingroup\$

I'm making a 2D platformer with Unity. I used this video to write the code but with some little changes. I want to support both keyboard and gamepad, so I use the new Input System. I defined three variables called GroundedRememeber, JumpPressedRemember and JumpPressedRememberTime and basically they work like timers and check if the player leaves the ground and if so, then the player can jump when he is near the ground without the need to touch it. These timers were not working, so I added a new method called TryJump and deleted some lines and now, the player is suspended in the air and moving upwards.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour, PlayerInputActions.IPlayerActions
 {
   private PlayerInputActions controls; 
   [SerializeField] LayerMask groundLayers;     
   private Rigidbody2D rb;
   private Animator anim;
   private bool facingRight = true;
   private Vector2 moveInput;
  [SerializeField] private float jumpForce;
    float JumpPressedRemember = 0;
   
   [SerializeField] float JumpPressedRememberTime = 0.2f;
    float GroundedRemember = 0;
     
   [SerializeField]  float GroundedRememberTime = 0.25f;  
   [SerializeField]  float HorizontalAcceleration = 1;
   [SerializeField] [Range(0, 1)] float HorizontalDampingBasic = 0.5f;
   [SerializeField] [Range(0, 1)] float HorizontalDampingWhenStopping = 0.5f;
   [SerializeField] [Range(0, 1)] float HorizontalDampingWhenTurning = 0.5f;
   [SerializeField] [Range(0, 1)] float JumpHeight = 0.5f;

   private void Awake() 
   {
      controls = new PlayerInputActions();

      controls.Player.SetCallbacks(this);
    }
     void Start()
    {
      rb = GetComponent<Rigidbody2D>();
      anim = GetComponent<Animator>();       
}
   void PlayerInputActions.IPlayerActions.OnMove(InputAction.CallbackContext context)
   {
      moveInput = context.ReadValue<Vector2>();
          
   }
     bool TryJump() {
    if (GroundedRemember > 0) {
        Jump();
        return true;
    } else {
        JumpPressedRemember = JumpPressedRememberTime;
        return false;
    }
}
     void PlayerInputActions.IPlayerActions.OnJump(InputAction.CallbackContext context)
   {
       jumpForce = context.ReadValue<float>();
       switch (context.phase) {
            case InputActionPhase.Performed:
               TryJump();
               break;
       }
   }
    void Jump() {
     rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Force);
     GroundedRemember = 0;
     JumpPressedRemember = 0;
   }
 
 
  void FixedUpdate()
  {

   if(facingRight == false && moveInput.x > 0){
   
    Flip();
   
   }else if (facingRight == true && moveInput.x < 0){
    
    Flip();

   }
 }
    void Flip(){
    
    facingRight = !facingRight;
    Vector3 Scaler = transform.localScale;
    Scaler.x *= -1;
    transform.localScale = Scaler;
    
 }  
 void OnEnable()
 {
     controls.Enable();
 }
 
 void OnDisable()
{
    controls.Disable();
}
 void Update()
  {

   Vector2 GroundedBoxCheckPosition = (Vector2)transform.position + new Vector2(0, -0.01f);
   Vector2  GroundedBoxCheckScale = (Vector2)transform.localScale + new Vector2(-0.02f, 0);
   bool Grounded = Physics2D.OverlapBox(GroundedBoxCheckPosition, transform.localScale, 0, groundLayers);
    
    GroundedRemember -= Time.deltaTime;
    if (Grounded)
    {
      GroundedRemember = GroundedRememberTime;
    }

    JumpPressedRemember -= Time.deltaTime; 
  
    if ((JumpPressedRemember > 0)) {
      TryJump();
    }

    rb.velocity = new Vector2(rb.velocity.x, jumpForce);
    
     float HorizontalVelocity = rb.velocity.x;
      HorizontalVelocity += moveInput.x;
          
        if (Mathf.Abs(moveInput.x) < 0.01f)
            HorizontalVelocity *= Mathf.Pow(1f - HorizontalDampingWhenStopping, Time.deltaTime * 10f);
           else if (Mathf.Sign(moveInput.x) != Mathf.Sign(HorizontalVelocity))
           HorizontalVelocity *= Mathf.Pow(1f - HorizontalDampingWhenTurning, Time.deltaTime * 10f);
        else
            HorizontalVelocity *= Mathf.Pow(1f - HorizontalDampingBasic, Time.deltaTime * 10f);

        rb.velocity = new Vector2(HorizontalVelocity, rb.velocity.y);    
  }
}

  
\$\endgroup\$
7
  • \$\begingroup\$ "Without using ground check" does not make sense. To detect whether you're on or have recently touched the ground to avoid jumping in the air, you need to check for the presence of ground. That is a "ground check", no matter what form it takes, even if you use timers. \$\endgroup\$
    – DMGregory
    Nov 8, 2020 at 2:26
  • \$\begingroup\$ Okay, I attached a link to the tutorial that I used for writing this code, I think if you watch it, you will understand what I'm saying. I think there is something that I did wrong. After you see the timers explanation part in the video, please give me a feedback on its possibility and then if you think there is no way to do this, I will do the "Ground Check". \$\endgroup\$
    – Ali
    Nov 8, 2020 at 15:55
  • 1
    \$\begingroup\$ The tutorial used a ground check as well. It just let's you queue the jump for a really short time in case you are not grounded yet or let you still jump a short duration after you are not grounded anymore. Still needs a ground check in the first place \$\endgroup\$
    – Zibelas
    Nov 10, 2020 at 16:27
  • \$\begingroup\$ And your player can jump forever since your inputmanager doesn't care at all. You are calling jump on it, without any check, timer or whatever. If you press jump, jump() gets called. Your jump would work without the whole update block. \$\endgroup\$
    – Zibelas
    Nov 10, 2020 at 21:10
  • \$\begingroup\$ Yes, you're right. The tutorial also used a kind of ground check. With the help of DMGregory, I changed the code a little bit and added a new method, but these things made the physics a little weird, so now, the player is floating in the air and moves upwards. I don't know how much more time I have to waste to have a jumping system that works properly. After solving any problem of jumping, a new and harder problem appears. Maybe using the ground check without timers was a better choice but I'll do my best to solve this one as well. \$\endgroup\$
    – Ali
    Nov 12, 2020 at 6:09

1 Answer 1

1
\$\begingroup\$

As Zibelas points out in the comments, these lines...

void PlayerInputActions.IPlayerActions.OnJump(InputAction.CallbackContext context) {
    jumpForce = context.ReadValue<float>();
    switch (context.phase) {
        case InputActionPhase.Performed:
            this.Jump();
            break;
    }
}

public void Jump() {
    rb.AddForce(Vector2.up * this.jumpForce, ForceMode2D.Force);
}

...allow the input system to 100% skip your ground checks and timers, and just add a force to the player anytime the button is pressed. There's not a single line of code here that knows what ground is. 😉

You can fix this by adding a new method:
(You can make this public if you want to call it from on-screen controls or such)

// Request a jump - but if we can't honour it right away, remember it for later.
bool TryJump() {
    if (GroundedRemember > 0) {
        Jump();
        return true;
    } else {
        JumpPressedRemember = JumpPressedRememberTime;
        return false;
    }
}

Then modify those lines at the top to call this TryJump method instead. You should also clear out the GroundedRemember value in your Jump function so you don't allow double-jumping in a tiny window just after you leave the ground.

void PlayerInputActions.IPlayerActions.OnJump(InputAction.CallbackContext context) {
    jumpForce = context.ReadValue<float>();
    switch (context.phase) {
        case InputActionPhase.Performed:
            TryJump();     // <-- Jump if grounded, otherwise remember the press.
            break;
    }
}

void Jump() {              // <-- Doesn't need to be public
    rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Force);
    GroundedRemember    = 0; // <-- We just jumped. 
    JumpPressedRemember = 0; //     Nothing more to remember.
}

Then down in your update loop you can delete your if (controls.Player.Jump.triggered) clauses, and modify this one:

    /* Delete this. Your interface code will already do this work when the action is performed.
    if (controls.Player.Jump.triggered) {
        JumpPressedRemember = JumpPressedRememberTime;
    }
    if (controls.Player.Jump.triggered) {
        if (rb.velocity.y > 0) {
            rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * JumpHeight);
        }
    }
    */

    if (JumpPressedRemember > 0)) {
        TryJump(); // <-- Don't repeat yourself, keep your jumping logic in one place.
    }

As an aside, I don't recommend jumping with forces. Use velocities or impulses instead. A force represents a gradual push over time, not a sharp change in motion like a platformer bounce. Using the right physics type will make it easier to tune this feature, and avoid inconsistency if your mass or timestep changes.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you very much for the answer. I updated the code so you can see if I wrote anything wrong because now I have a problem. The player is now floating in the air but I think it's not a serious problem and by checking the code again, I might be able to fix it. Also, I wanted to say that the player input component is currently empty and I haven't added any functions yet because they are not appearing for me, could be that a problem? \$\endgroup\$
    – Ali
    Nov 11, 2020 at 20:18
  • \$\begingroup\$ And also about using velocities or impulses that you mentioned, you mean It's better if I change the physics system? Do you think the problem that I have right now, is somehow related to jumping with forces? \$\endgroup\$
    – Ali
    Nov 12, 2020 at 5:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .