2D Tutorial Question #1: Jumping once per button press.

I am still trying to get my little 2D Mario clone off the ground, but I need help! I have decided to preface every post I do with “2D Tutorial Question #X” and if I ever get a level made I will post a full tutorial for all others to follow.

Question #1: I am using the following move script form the script reference, but the problem is that I can simply hold down jump and keep jumping forever. This is not the way most 2D side scrolling games are. How can I force the player to have to press jump each time they want to jump, i.e. holding jump will only result in jumping once until the button is released and pressed again?

Thank you!

/// This script moves the character controller forward
/// and sideways based on the arrow keys.
/// It also jumps when pressing space.
/// Make sure to attach a character controller to the same game object.
var speed = 6.0;
var jumpSpeed = 8.0;
var gravity = 20.0;

private var moveDirection = Vector3.zero;

function FixedUpdate() {
var controller : CharacterController = GetComponent(CharacterController);
if (controller.isGrounded) {
// We are grounded, so recalculate
// move direction directly from axes
moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
Input.GetAxis("Vertical"));
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= speed;

if (Input.GetButton ("Jump")) {
moveDirection.y = jumpSpeed;
}
}

// Apply gravity
moveDirection.y -= gravity * Time.deltaTime;

// Move the controller
controller.Move(moveDirection * Time.deltaTime);
}

Use GetButtonDown instead of GetButton. That way it doesn’t register again after the first time until the user has let up.

–Eric

Aha! I was hoping someone would say that.

I also tried GetButtonDown, but it was not responding to each press. Maybe a weird refresh issue of some kind? It works most of the time, but then it just wouldn’t register. Usually after I had just jumped.

Any thoughts? All I want here is Mario-style responsiveness to the player’s input.

Thank you so very much for an exceptionally quick reply.

You could also put a yes/no 1/0 into a variable that then had to wait a second or two before being allowed to refire (jump again) by testing if he variable still says yes (that wasn’t well explained, was it?). The FPS tutorial has something like that to prevent firing weapons continuiously.

The problem isn’t the firing again issue in terms of the player being able to jump again too soon. It’s the fact that platform games require the jump button to be pressed per each jump. And GetButtonDown does not work every time. I need a smart person to help me figure this out.

Thanks to all.

Pleased to be of service. :wink: But you’re right…I tried it and it’s sporadic. That’s weird. It works much better if you change FixedUpdate to Update, for some reason. However, even then there are still issues…moving and trying to jump repeatedly doesn’t work; apparently GetButtonDown waits for all keys to be released, not just the one in question. Or maybe it’s a keyboard thing and it would work properly with a gamepad, but I didn’t try that.

I guess the thing to do is try implementing GetButtonDown yourself without actually using GetButtonDown. Put a:

var buttonPressed = false;

at the top, and change the GetButton part to:

if (Input.GetButton ("Jump")  !buttonPressed) {
	moveDirection.y = jumpSpeed;
	buttonPressed = true;
}
if (buttonPressed  !Input.GetButton ("Jump") ) {
	buttonPressed = false;
}

I tried using GetButtonUp to do that, but as I suspected it had the same issues as GetButtonDown, so this is the best I can come up with at the moment. Seems to work though.

–Eric

OK! Excellent!

We really are on our way here to a working Mario like controller script. Are you ready for the next problem?

How do I control the movement of the jump in the air?

Currently the script is such that if the character jumps while not pushing left or right, it is forced to simply jump straight vertical without the ability to change direction in mid jump.

I have learned enough this past week to know that the “isGrounded” part of the controller is a prerequisite for the ability to move left and right, but how do I change this so it isn’t the case?

Here is the current script:

/// This script moves the character controller forward
/// and sideways based on the arrow keys.
/// It also jumps when pressing space.
/// Make sure to attach a character controller to the same game object.
var speed = 6.0;
var jumpSpeed = 8.0;
var gravity = 20.0;
var buttonPressed = false;

private var moveDirection = Vector3.zero;

function FixedUpdate() {
var controller : CharacterController = GetComponent(CharacterController);
    if (controller.isGrounded) {
    // We are grounded, so recalculate
    // move direction directly from axes
    moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
    Input.GetAxis("Vertical"));
    moveDirection = transform.TransformDirection(moveDirection);
    moveDirection *= speed;

    
    if (Input.GetButton ("Jump")  !buttonPressed) {
        moveDirection.y = jumpSpeed;
        buttonPressed = true;
    }
    if (buttonPressed  !Input.GetButton ("Jump") ) {
        buttonPressed = false;
    }
}

// Apply gravity
moveDirection.y -= gravity * Time.deltaTime;

// Move the controller
controller.Move(moveDirection * Time.deltaTime);
}

One million thanks!

I’ve never found CharacterController’s isGrounded detection to be very reliable, especially when moving on slopes. In my 2D platform test (which I really should post up here sometime) I ended up writing my own grounded-detection using raycasts.
Platformers, and especially 2D ones, have a very different traditional “feel” than what Unity’s CC provides as far as jumping is concerned, based more on timing than whether a character is actually grounded. That is to say, in many 2d platformers, a character is still considered “grounded” for a fraction of a second after he leaves the ground.

if (controller.isGrounded) { 
    // We are grounded, so recalculate 
    // move direction directly from axes 
    moveDirection = Vector3(Input.GetAxis("Horizontal"), 0, 
    Input.GetAxis("Vertical")); 
    moveDirection = transform.TransformDirection(moveDirection); 
    moveDirection *= speed; 
    
    if (Input.GetButton ("Jump")  !buttonPressed) { 
        moveDirection.y = jumpSpeed; 
        buttonPressed = true; 
    } 
    if (buttonPressed  !Input.GetButton ("Jump") ) { 
        buttonPressed = false; 
    } 
}

After the if block that tells you if the character is grounded, you want to add an else. So you know the character is not grounded. In there you just add some motion to the moveDirection.
eg.

else
{
    moveDirection += Vector3(Input.GetAxis("Horizontal"), 0, 
    Input.GetAxis("Vertical")) * inAirSpeed; 
}

inAirSpeed of course has to be declared in your script.

Thank you so much!!! But someone’s going to kill me because I’m getting a compiling error. Here is the script as it stands:

/// This script moves the character controller forward
/// and sideways based on the arrow keys.
/// It also jumps when pressing space.
/// Make sure to attach a character controller to the same game object.
var speed = 6.0;
var jumpSpeed = 8.0;
var gravity = 20.0;
var buttonPressed = false;
var inAirSpeed = 10;

private var moveDirection = Vector3.zero;

function FixedUpdate() {
var controller : CharacterController = GetComponent(CharacterController);
    if (controller.isGrounded) {
    // We are grounded, so recalculate
    // move direction directly from axes
    moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
    Input.GetAxis("Vertical"));
    moveDirection = transform.TransformDirection(moveDirection);
    moveDirection *= speed;
    else 
    {
    moveDirection += Vector3(Input.GetAxis("Horizontal"), 0,
    Input.GetAxis("Vertical")) * inAirSpeed;
    }
    
    if (Input.GetButton ("Jump")  !buttonPressed) {
        moveDirection.y = jumpSpeed;
        
    }
    if (buttonPressed  !Input.GetButton ("Jump") ) {
        buttonPressed = false;
    }
}

// Apply gravity
moveDirection.y -= gravity * Time.deltaTime;

// Move the controller
controller.Move(moveDirection * Time.deltaTime);
}

The error is

and

Now I’m guessing it has something to do with a missing or extra bracket or something, but I tried my best and couldn’t figure it out.

Any help greatly greatly appreciated.

Thanks.

The first if block needs to be closed before the else on line 22. (There needs to be a }, in other words.)

–Eric

Now I’m getting a

on the last line of the script. I tried deleting it, but it created more errors.

Thanks again for all help. I am such a beggar!

Geez…

You need to remove the close brace, “}”, just before the apply gravity. It is currently closing your FixedUpdate() function.

SWEET! That totally fixed it.

However, dear god I’m amazed I even get replies at this point, there is a NEW problem. When I jump I quite literally shoot through the air in the horizontal directions!

Curses! Surely the members of this forum are not brave enough to help me fix yet another problem?

I am really starting to develop a complex about the one-sided relationship I have going here?

Does somebody want to make some kind of trade for helping me finish a Mario-style controller script once and for all?

One million thanks!

multiply with delta time.

change:
moveDirection += Vector3(Input.GetAxis(“Horizontal”), 0,
Input.GetAxis(“Vertical”)) * inAirSpeed;

to

moveDirection += Vector3(Input.GetAxis(“Horizontal”), 0,
Input.GetAxis(“Vertical”)) * inAirSpeed * Time.deltaTime;

Great Great Great, Super Great. BUT, heh, the new problem is that the character can fly simply by holding down jump! In other words, the character is jumping over and over in the air and flying away!

/// This script moves the character controller forward
/// and sideways based on the arrow keys.
/// It also jumps when pressing space.
/// Make sure to attach a character controller to the same game object.
var speed = 6.0;
var jumpSpeed = 8.0;
var gravity = 20.0;
var buttonPressed = false;
var inAirSpeed = 10;

private var moveDirection = Vector3.zero;

function FixedUpdate() {
var controller : CharacterController = GetComponent(CharacterController);
    if (controller.isGrounded) {
    // We are grounded, so recalculate
    // move direction directly from axes
    moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
    Input.GetAxis("Vertical"));
    moveDirection = transform.TransformDirection(moveDirection);
    moveDirection *= speed;
    }
    else 
    {
    moveDirection += Vector3(Input.GetAxis("Horizontal"), 0,
    Input.GetAxis("Vertical")) * inAirSpeed * Time.deltaTime;
    }
    
    if (Input.GetButton ("Jump")  !buttonPressed) {
        moveDirection.y = jumpSpeed;
        
    }
    if (buttonPressed  !Input.GetButton ("Jump") ) {
        buttonPressed = false;
    }


// Apply gravity
moveDirection.y -= gravity * Time.deltaTime;

// Move the controller
controller.Move(moveDirection * Time.deltaTime);
}

Joachim: you are simply going to kill me at some point!

Signed,
Forum Beggar

Just glancing at your script, but don’t you need to set buttonPressed to true at some point? Otherwise

if (Input.GetButton ("Jump")  !buttonPressed) { 
        moveDirection.y = jumpSpeed; 
        
}

will get performed each FixedUpdate where you have the “Jump” button pressed, so moveDirection.y will be set each time to jumpSpeed and then you will apply that to your object. Perhaps you need to set buttonPressed to true at that point. Since this is outside the “isGrounded” if statement this statement will be evalutated each time FixedUpdate() is called.

I tried to set it to false, but it didn’t seem to do anything… any other ideas?

Thank you very much!

How about going back to the original GetButton code I wrote? You seem to have removed a line for some reason.

–Eric