Jump'n'Run key bounce with awt.event.KeyListener

Dear all,

I have put together some basic classes to make a first Jump’n’Run prototype, however there is one issue, I can’t fix myself. When pressing the “jump” key, my avatar jumps up until max height is reached, then begins to fall. If the jump key is still pressed when the avatar collides with the ground, it starts to jumps up again immediately.
What I rather would like to have is that the avatar can only jump again, when the respective button has been released.
I have tried to achieve this by checking current and previous key state after landing, i.e. if the key state is now not-pressed/released but last state was pressed. However, this did not resolve the issue and I now don’t know if it is my input polling method or the way I record key input.

To handle input, I have implemented KeyListener in a class and attached it to a Panel (AWT). The class has a “boolean[] keys = new boolean[255]” to record key states. In the KeyListener’s keyPressed event I set the value for the respective key to “true” and to “false” in the corresponding keyReleased event.

Can you guys point out to me what I am doing wrong?

Thanks

It would be easier (for me, anyway) to give an accurate answer if I saw your attempt. But maybe the following will help. I have a boolean, keyEngaged that reflects the state of a “piano key” object.

This function is called upon a key press or mouse down, and I had to prevent it from repeatedly executing and calling the note play command.

public void play()
{
	// Called continuously, so switch was added to
	// prevent executing play() repeatedly.
	if (!keyEngaged)
	{
		sh.play(this);
		keyEngaged = true;			
	}
}

And this is called on key release:

public void release()
{
	sh.release(this);
	keyEngaged = false;
}

So, the note would not be allowed to play again unless the key or mouse was explicitly released, which was the only way to get keyEngaged back to false.

Another approach would be to use a “Debounce” pattern. I’ve seen this used in JavaScript, where the idea is to make a timestamp when an even occurs and not allow a repeat to execute until a given amount of time has elapsed.

Hi,

It would be easier (for me, anyway) to give an accurate answer if I saw your attempt.

What I have put together might be overkill, but this is it:
The base is an abstract class Game from which I instantiate my application. It contains a AWT.Frame which I pass an instance of KeyListener on initialisation. This base class also contains a variable to hold the key state for keyboard input:

int[] keys = new int[255];
private class ApplicationInputAdapter implements KeyListener {
public void keyPressed(KeyEvent e) { keys[e.getKeyCode()] = AbstractInput.DOWN; }
public void keyReleased(KeyEvent e) { keys[e.getKeyCode()] = AbstractInput.UP; }
}

Then, I have an abstract class AbstractInput to which I will delegate input key (map will assign an input to an awt.Event.KeyEvent constant in a derived class).

    public abstract class AbstractInput {        
        static final int UP = 0;
        static final int DOWN = 1;
        
        private final Game source;
        protected AbstractInput(Game source) { this.source = source; }

        protected int[] buttons;
        public boolean button(int button) {
            try { return (DOWN == source.keys[buttons[button]]); }
            catch (Exception ex) { return false; }
        }

        public final void map(int key, int button) {
            try { buttons[button] = key; }
            catch (Exception ex) { }
        }
    }

To capture input for each game I derive a concrete Input class from AbstractInput:

public class Input extends Game.AbstractInput {
    public static final int BUTTON_LEFT = 0;
    public static final int BUTTON_RIGHT = 1;
    public static final int BUTTON_A = 2;
    
    public Input(Game source) { super(source); buttons = new int[3]; }
}

Input is then polled in my game loop in an update function, by passing in to p1’s input function:

@Override protected void gameUpdate() {
        long dt = getEllapsedTicks();

        p1.input(in, dt);
        p1.update(dt);
}

The variable p1 is an implementation of class derived from an abstract Actor class and the actor’s input function delegates input polling and handling to the player’s current state like this:

public void input(Game.AbstractInput in, long dt) { getState().input(in, dt); }

The function getState() gets the actor’s current state and delegates (again) input to it:

public void input(Game.AbstractInput in, long dt) {
        if (in.button(Input.BUTTON_LEFT)) {
            actor.setState(Player.WALK);
            actor.direction.setDirection(Direction.LEFT);
        } else if (in.button(Input.BUTTON_RIGHT)) {            
            actor.setState(Player.WALK);
            actor.direction.setDirection(Direction.RIGHT);
        } else if (in.button(Input.BUTTON_A)) {
            actor.setState(Player.JUMP);
            actor.speed.setY(0.75f);
        }
    }

States are derived from an abstract State class for each of the actors possible states. So, when the jump key is pressed, the jump state is set as the Actor’s current state, which will handle input in the game’s loop next iteration and update the actor as explained next.
After input is handled, the actors update function is called, which in turn calls the current state’s update function:

public class PlayerStateJump extends State {
    private static final float JUMP_ACC = 0.007f;
    private float jumpSpeed = 0.0f;

    @Override public void update(long dt) {
        //  Keep moving in x-direction with set speed
        if (actor.direction.checkDirection(Direction.LEFT)) {
            actor.position.setX(actor.position.getX() - actor.speed.getX() * dt);
        } else {
            actor.position.setX(actor.position.getX() + actor.speed.getX() * dt);
        }
        
        jumpSpeed = actor.speed.getY();
        jumpSpeed -= JUMP_ACC * dt;
        actor.speed.setY(jumpSpeed);
        actor.position.setY(actor.position.getY() - actor.speed.getY() * dt);

        if (jumpSpeed <= 0.0f) actor.setState(Player.FALL);
    }
}

So, say my character is in state “idle”, that state polls if the jump button is “DOWN”; if so, the actors current state is set to an already declared and instantiated PlayerStateJump state. While in this state, at each iteration of my game loop, the jump state’s update function (see above) is called, which controls the jump height and direction. After that I have a simple collision detection, that takes action according to the player’s state.

        if (p1.getState().checkState(Player.FALL)) {
            if (onGround) {
                p1.setState(Player.IDLE);
                p1.speed.setY(0.0f);
            } else {
                applyGravity(p1, dt);
            }
        } else if (p1.getState().checkState(Player.JUMP)) {
            if (hitCeiling) {
                p1.setState(Player.FALL);
            }
        } else {
            if (!onGround) {
                p1.setState(Player.FALL);
                applyGravity(p1, dt);
            } else {
            }            
        }

I short:

KeyPress/Release->Array stores key state “UP/DOWN”
In the game update function an instance of a class derived from AbstractInput class (in) is passed to the input functon of an instance of a class derived from Actor (p1)
The p1’s input function passes “in” to the input function of an instance of a class derived from PlayerState class (state)
In the input function of state input is finally polled (via AbstractInput.Button()) and handled (e.g. state change of the Actor derived class from “IDLE” to “JUMP” when the jump button is pressed)
We return to the game’s update function, where the actor is updated, collision check is performed and handled

I hope this helps pointing out to me where I need to make the necessary changes.

Here is a possible implementation to consider, using the example I gave earlier.

Create another state variable, call it something like okayToJump. In a keyReleased method for key “A”, include the line of code

okayToJump = true;

In the input method, update to

    } else if (in.button(Input.BUTTON_A)) {
        if (okayToJump) { 
            actor.setState(Player.JUMP);
            actor.speed.setY(0.75f);
            okayToJump = false;
        }
    }

This way, the “A” key has to be released in order to allow the state to be set to JUMP.