(C#) A better way to limit actions to once per button press?

Hello, I’m working on a simple cannabalt-style platformer where the character jumps (as most platforming characters do). Up until now, I’ve been using a slightly modified version of the basic character controller found on the Unity Scripting Reference:

using UnityEngine;
using System.Collections;
public class movement : MonoBehaviour {
	public float speed = 6.0F;
	public float jumpHeight = 8.0F;
	public float gravity = 20.0F;
	private Vector3 moveDirection = Vector3.zero;
	void Update() {
		CharacterController controller = GetComponent<CharacterController>();
		if (controller.isGrounded) {
			  moveDirection = Vector3.forward;
			  moveDirection = transform.TransformDirection(moveDirection);
			  moveDirection *= speed;			  
			if (Input.GetButton("Jump"))
				moveDirection.y = jumpHeight;
		}       
		moveDirection.y -= gravity * Time.deltaTime;
		controller.Move(moveDirection * Time.deltaTime);
	}
}

For the most part, this worked alright, but my problem with it was that as long as you held down the jump button, the character would keep jumping, but I only want him to jump once per button press. I decided to change the code to this:

using UnityEngine;
using System.Collections;
public class movement : MonoBehaviour {
	public float speed = 6.0F;
	public float jumpHeight = 8.0F;
	public float gravity = 20.0F;
	private bool doJump = true;
	private Vector3 moveDirection = Vector3.zero;
	void Update() {
		CharacterController controller = GetComponent<CharacterController>();
					if (Input.GetButtonUp("Jump"))
			{				
				doJump = true;
			}
		if (controller.isGrounded) {
			  moveDirection = Vector3.forward;
			  moveDirection = transform.TransformDirection(moveDirection);
			  moveDirection *= speed;		
			  

			if (Input.GetButtonDown("Jump") && (doJump = true))
			{
				moveDirection.y = jumpHeight;
				doJump = false;
			}			
	
		}       
		moveDirection.y -= gravity * Time.deltaTime;
		controller.Move(moveDirection * Time.deltaTime);
	}
}

For the most part, adding the boolean check seemed to be working fine. The character only jumped once per pressing of the button, and it looked like things were improved. After further testing though, I found a new problem. What’s happening is that if I hit the jump button right after the character lands, it sometimes fails to register at all and the jump doesn’t activate. I tested several times to make sure that something was wrong with the game and that I wasn’t simply hitting the button before he landed completely, and it indeed fails to activate within the first split second or so of landing. Since this is an endless runner, a single missed jump means death, which will be very frustrating if it’s not the player’s fault. My question is, how could I better go about limiting the game to one jump per button press without causing the controls to become glitchy and sometimes unresponsive?

OK, you’ve got me thinking now…

This is actually kind of tricky to get working as you want. Now, going along with your idea of using a box collider, the way to try this is:

1: create a NEW, empty game object, and give that a box collider.

2: adjust size/position of the box collider to proper position (line it up in the scene with character’s feet)

3: child the object to the player

4: add this script to the new object (make sure it is childed directly to the player object which contains the other script):

using UnityEngine;
using System.Collections;
public class GroundedCheck : MonoBehaviour {

    public movement otherScript; 
    
    void Start(){
    GameObject myParent = transform.parent.gameObject;
    otherScript = myParent.GetComponent<movement>(); 
    }
    
    void OnCollisionEnter(Collision other){
    if(other.gameObject.tag == "ground")
    otherScript.canJump = true;
    }
    
    void OnCollisionExit(Collision other){
    if(other.gameObject.tag == "ground")
    otherScript.canJump = false;
    }
}

5: NOW, we are planning to use this var “canJump” INSTEAD of controller.isGrounded, to decide whether we can jump… So we will update the other script:

    using UnityEngine;
    using System.Collections;
    public class movement : MonoBehaviour {

            public boolean canJump = true;
    	public float speed = 6.0F;
    	public float jumpHeight = 8.0F;
    	public float gravity = 20.0F;
    	private Vector3 moveDirection = Vector3.zero;
    	private CharacterController controller;
    	
    	void Start(){
            controller = GetComponent<CharacterController>();
            }
    	void Update() {		
    			
    		if (controller.isGrounded) { 
    			  moveDirection = Vector3.forward;
    			  moveDirection = transform.TransformDirection(moveDirection);
    			  moveDirection *= speed;	
    	         	}
    			     if (Input.GetButtonDown("Jump"))
                                 {
                                 if(canJump || controller.isGrounded)
                                  {
                                  moveDirection.y = jumpHeight;
                                  canJump = false;          
                                  }
                                 } 			
    	
     
    		moveDirection.y -= gravity * Time.deltaTime;
    		controller.Move(moveDirection * Time.deltaTime);
    	}
    }

6: Add a RigidBody to the ground, check “is kinimatic”, uncheck “use gravity”.

7: finally, you’ll need to tag your ground objects as “ground”.

ok…

This should all work assuming you can only be in contact with one “ground” at a time… but if you have separate “pieces” of ground which can be touched simultaneously, I imagine there could be issues.

This may or may not yield the results you are after…

A much simpler and guaranteed effective solution: previous and current button states.

EDIT: Simplified this a little bit, I realized you don’t need the Start function for this.

private bool prevSpacebarState = false;
private bool currSpacebarState = false;


void Update()
{
     //Neither state has been updated since the last tick, so right now current is the value we want in previous
     //Assign current (of last tick) into previous, then update current
     prevSpacebarState = currSpacebarState;
     currSpacebarState = Input.GetKey(KeyCode.Space);


     if (!prevSpacebarState && currSpacebarState)
           Jump();

}