Jump to content
  • Advertisement
Sign in to follow this  
Seer

Libgdx Input System Help

This topic is 763 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all,

 

I would like some help implementing an input system using Libgdx, that allows me to control different objects at different times, each responding in a unique manner to the input received. For a real world example of the functionality I'm looking for, have you ever played or seen the game, Dragonball Z: The Legacy of Goku II? In this game you can control different characters by swapping between them. Each character has their own unique attacks that respond differently to given input. Some attacks, such as the Kamehameha wave only work while a key is held and others, such as the Big Bang attack or Masenko-Ha have increased power or range, respectively, the longer the key is held before being released.

 

As far as I am aware, I cannot use polling to achieve these effects as there is no way to tell if a key has been released using polling in Libgdx. As such, I must use the event driven InputProcessor interface. A class that implements this interface and sets itself as the InputProcessor has its implemented methods called every frame before the Libgdx render() method. 

 

It would look like this:

 

public class InputHandler implements InputProcessor
{
    public void InputHandler()
    {

        Gdx.input.setInputProcessor(this);

    }

    @Override
    public boolean keyDown(int keycode)
    {
        return false;
    }

    @Override
    public boolean keyUp(int keycode)
    {

        return false;
    }

    @Override
    public boolean keyTyped(char character)
    {
        return false;
    }

 

    // Touch and mouse related methods below

}

 

How can I use these methods to communicate to a set of objects - of which only one is controllable at a time - which keys have been pressed and released, so that the objects can perform some action (such as moving, updating its animations, executing attacks) based on the input, without hardcoding the objects into the InputHandler?

 

If you need more information I'll do my best to provide it. Thank you.

Share this post


Link to post
Share on other sites
Advertisement

Make an interface KeyboardControllable that sends the information. Each object that can be controlled implements that interface.

Selecting the object being controlled now is then adding a KeyboardControllable controlled_object; in your input class, and call it :)

Share this post


Link to post
Share on other sites

I tried what you suggested and this is what I have so far:

 

public interface KeyboardControllable
{
    void keyPressed(int keyCode);
    void keyReleased(int keyCode);
}

 

public class InputHandler implements InputProcessor
{
    private KeyboardControllable controllableObject;

 

    // Where would this be called?

    public void setControllableObject(KeyboardControllable controllableObject)
    {
        this.controllableObject = controllableObject;
    }

    @Override
    public boolean keyDown(int keycode)
    {
        controllableObject.keyPressed(keycode);

        return false;
    }

    @Override
    public boolean keyUp(int keycode)
    {
        controllableObject.keyReleased(keycode);

        return false;
    }

    @Override
    public boolean keyTyped(char character)
    {
        return false;
    }

}

 

public class Player extends GameObject implements KeyboardControllable
{
private boolean movingUp;
private boolean movingDown;
private boolean movingLeft;
private boolean movingRight;

public Player()
{
movingUp = false;
movingDown = false;
movingLeft = false;
movingRight = false;
}

@Override
public void update()
{
if(movingUp)
{
position.y += speed * Gdx.graphics.getDeltaTime();
}

if(movingDown)
{
position.y -= speed * Gdx.graphics.getDeltaTime();
}

if(movingLeft)
{
position.x -= speed * Gdx.graphics.getDeltaTime();
}

if(movingRight)
{
position.x += speed * Gdx.graphics.getDeltaTime();
}
}

@Override
public void keyPressed(int keyCode)
{
switch(keyCode)
{
case Input.Keys.UP:
{
movingUp = true;

break;
}

case Input.Keys.DOWN:
{
movingDown = true;

break;
}

case Input.Keys.LEFT:
{
movingLeft = true;

break;
}

case Input.Keys.RIGHT:
{
movingRight = true;

break;
}

 

// Other cases
}
}

@Override
public void keyReleased(int keyCode)
{
switch(keyCode)
{
case Input.Keys.UP:
{
movingUp = false;

break;
}

case Input.Keys.DOWN:
{
movingDown = false;

break;
}

case Input.Keys.LEFT:
{
movingLeft = false;

break;
}

case Input.Keys.RIGHT:
{
movingRight = false;

break;
}

 

// Other cases
}
}
}

 

That's just the relevant code, some bits are left out. Is this something along the lines of the approach you suggested?

 

One problem with my implementation is that the keys are still hardcoded to certain actions. How can I fix this?

 

How would I set the KeyboardControllable object to the InputHandler?

 

Also, is there a way to auto indent the code in the editor? For some reason the pasted code loses its indentation. Thanks.

Share this post


Link to post
Share on other sites
Is this something along the lines of the approach you suggested?

It looks like it, the main question however is whether you consider it useful.

 

 

 

One problem with my implementation is that the keys are still hardcoded to certain actions. How can I fix this?

This is a new requirement :P

 

Think, what do you need for that?

 

 

I would say it needs a key-to-action mapping here? Ie Somewhere you need a table " ';'-key -> kick, '='-key -> skip level" etc.

 

 

Next, where does this table belong? as in, what is the logical place to store this table? (who owns the table?)

 

Next, somewhere, you need a point where you do a translation from key to action (ie you have a key, you look for a matching action).

What is the logical place to do that?

 

 

 

 

How would I set the KeyboardControllable object to the InputHandler?

Isn't it nice, you build one thing and immediately you find new questions. No worries, finding this question is the first step in solving it. :)

 

Well, yeah. I'd think that something that calls it, knows what the keyboardcontrollable objects are, right? (This holds in general, something that makes a decision will need all the relevant information. From this idea, first find what information you need, where this information is in your program, then try to find a place that should make decisions with it.) So is there a place in your program for that? If not (the easy case) you need to make something (obviously). If yes (the difficult case), is that point the right spot for doing calls, or should you still make something new for it?

 

(eg a simple storage of all object would know what keyboardcontrollable objects exist, but it may not be the right spot for adding object selection for keyboard control.)

 

 

 

Also, is there a way to auto indent the code in the editor? For some reason the pasted code loses its indentation.

The editor bar has a key for it, the "<>" (paste code, select it, press "<>")

 

 

You may notice, my design pattern for code is extremely simple, first decide where some data of logic should be added (the  logical place), then add and connect things.

Theory is however simpler than practice, but you can work on that :)

Edited by Alberth

Share this post


Link to post
Share on other sites

I've been busy trying to refactor my code and getting the input system working, but I have a few problems.

 

First of all, is it necessary to have input processed before the main update loop? If not, then I can poll for input in the game objects update() methods and only check for key releases in the InputProcessor. So far, this is the only way I have been able to animate the player semi properly with multiple key presses. I don't like this approach much however, as it means input handling is spread around. I'd prefer it to be centralised.

 

This is the polling approach:

public class Player extends MutableObject implements KeyboardControllable, Renderable
{
    protected ObjectInputProcessor objectInputProcessor;

    public Player()
    {
        stateMachines = new ArrayList<StateMachine>();
        stateMachines.add(new StateMachine());
        stateMachines.add(new StateMachine());
        
        // Set initial states
        stateMachines.get(0).changeState(new FacingDownAnimation(this));
        stateMachines.get(1).changeState(new NotMoving(this));

        objectInputProcessor = new ObjectInputProcessor();
        objectInputProcessor.setControllableObject(this);
    }

    @Override
    public void update()
    {
        // Polling
        if(Gdx.input.isKeyPressed(Input.Keys.UP))
        {
            stateMachines.get(0).changeState(new WalkingUpAnimation(this));
            position.y += speed * Gdx.graphics.getDeltaTime();
        }

        if(Gdx.input.isKeyPressed(Input.Keys.DOWN))
        {
            stateMachines.get(0).changeState(new WalkingDownAnimation(this));
            position.y -= speed * Gdx.graphics.getDeltaTime();
        }

        if(Gdx.input.isKeyPressed(Input.Keys.LEFT))
        {
            stateMachines.get(0).changeState(new WalkingLeftAnimation(this));
            position.x -= speed * Gdx.graphics.getDeltaTime();
        }

        if(Gdx.input.isKeyPressed(Input.Keys.RIGHT))
        {
            stateMachines.get(0).changeState(new WalkingRightAnimation(this));
            position.x += speed * Gdx.graphics.getDeltaTime();
        }

        for(StateMachine stateMachine : stateMachines)
        {
            stateMachine.update();
        }
    }

    @Override
    public void render(SpriteBatch batch)
    {
        for(StateMachine stateMachine : stateMachines)
        {
            stateMachine.render(batch);
        }
    }
    
    @Override
    public void handleKeyPress(int keyCode)
    {
        // Does nothing
    }

    @Override
    public void handleKeyRelease(int keyCode)
    {
        switch(keyCode)
        {
            case Input.Keys.UP:
            {
                stateMachines.get(0).changeState(new FacingUpAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }

            case Input.Keys.DOWN:
            {
                stateMachines.get(0).changeState(new FacingDownAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }

            case Input.Keys.LEFT:
            {
                stateMachines.get(0).changeState(new FacingLeftAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }

            case Input.Keys.RIGHT:
            {
                stateMachines.get(0).changeState(new FacingRightAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }
        }
    }
}

With the event driven approach, input handling is more centralised, but only one key can be processed at a time. This has led to erroneous animating when dealing with multiple key presses and releases. For example, when holding UP, the player moves UP and goes through its WalkUpAnimation. Good. Then, I hold the RIGHT key while still holding UP. Instead of moving UP and RIGHT, the player immediately switches to the WalkRightAnimation and starts moving RIGHT only. Not only that, but when I let go of one of the keys, lets say UP, the player stops moving and sets its animation to FacingUp. Letting go of the RIGHT key at this point would set it back to FacingRight. The system is fine for individual key presses, but not multiple ones simultaneously. The same kind of problem occurs with other key combinations.

 

Looking at the code below, can you see a way to fix this? I've thought of sending two keyCodes to handleKeyPress() and handleKeyRelease(), one with the current value and one with a previous value and using them to determine which actions to execute, but I'm not sure it would work and regardless would probably lead to spaghetti code pretty quickly with all the possible if-else's.

public class Player extends MutableObject implements KeyboardControllable, Renderable
{
    protected ObjectInputProcessor objectInputProcessor;

    public Player()
    {
        stateMachines = new ArrayList<StateMachine>();
        stateMachines.add(new StateMachine());
        stateMachines.add(new StateMachine());

        stateMachines.get(0).changeState(new FacingDownAnimation(this));
        stateMachines.get(1).changeState(new NotMoving(this));

        objectInputProcessor = new ObjectInputProcessor();
        objectInputProcessor.setControllableObject(this);
    }

    @Override
    public void update()
    {
        for(StateMachine stateMachine : stateMachines)
        {
            stateMachine.update();
        }
    }

    @Override
    public void render(SpriteBatch batch)
    {
        for(StateMachine stateMachine : stateMachines)
        {
            stateMachine.render(batch);
        }
    }

    @Override
    public void handleKeyPress(int keyCode)
    {
        switch(keyCode)
        {
            case Input.Keys.UP:
            {
                stateMachines.get(0).changeState(new WalkingUpAnimation(this));
                stateMachines.get(1).changeState(new MovingUp(this));

                break;
            }

            case Input.Keys.DOWN:
            {
                stateMachines.get(0).changeState(new WalkingDownAnimation(this));
                stateMachines.get(1).changeState(new MovingDown(this));

                break;
            }

            case Input.Keys.LEFT:
            {
                stateMachines.get(0).changeState(new WalkingLeftAnimation(this));
                stateMachines.get(1).changeState(new MovingLeft(this));

                break;
            }

            case Input.Keys.RIGHT:
            {
                stateMachines.get(0).changeState(new WalkingRightAnimation(this));
                stateMachines.get(1).changeState(new MovingRight(this));

                break;
            }
        }
    }

    @Override
    public void handleKeyRelease(int keyCode)
    {
        switch(keyCode)
        {
            case Input.Keys.UP:
            {
                stateMachines.get(0).changeState(new FacingUpAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }

            case Input.Keys.DOWN:
            {
                stateMachines.get(0).changeState(new FacingDownAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }

            case Input.Keys.LEFT:
            {
                stateMachines.get(0).changeState(new FacingLeftAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }

            case Input.Keys.RIGHT:
            {
                stateMachines.get(0).changeState(new FacingRightAnimation(this));
                stateMachines.get(1).changeState(new NotMoving(this));

                break;
            }
        }
    }
}

In case you were wondering what ObjectInputProcessor is, it's just InputHandler renamed. Speaking of which, is the Player class a good place to store a reference to the ObjectInputProcessor? At the moment it's the most straightforward way I can think of to register a KeyboardControllable object to it.

Edited by Seer

Share this post


Link to post
Share on other sites

Any help? If my questions aren't clear enough let me know and I'll do my best to clarify.

Share this post


Link to post
Share on other sites
First of all, is it necessary to have input processed before the main update loop?

In programming, the only thing required is that everything works as intended. How you achieve that is entirely your problem (the computer doesn't mind either way, and doesn't care either). In other words, if you manage to get it working while you do input processing "on the fly" during update(), that's a perfectly valid solution.

However, I do agree with you that doing input and update() as separate steps is much easier to understand (which is normally the thing you should aim for!). As such, I would recommend not to try fancy "on-the-fly" things :)


I am quite scared by what you do in "update" with the polling case:

public void update()
{
    if(Gdx.input.isKeyPressed(Input.Keys.UP)) {
        stateMachines.get(0).changeState(new WalkingUpAnimation(this));
        position.y += speed * Gdx.graphics.getDeltaTime();
    }

You're constructing a new "WalkingUpAnimation" object every update (ie every frame??!!) Why?
This is not how you use a state machine, assuming that is your aim.

May I suggest a much simpler idea (which is also a state machine, it just has a different shape).

// Make 5 constants with the animations.
Animation walkUp    = new WalkingUpAnimation(this);
Animation walkDown  = new WalkingDownAnimation(this);
Animation walkLeft  = new WalkingLeftAnimation(this);
Animation walkright = new WalkingrightAnimation(this);
Animation notMoving = new NotMoving(this);

Animation currentWalk = notMoving; // The animation currently shown.

// Your changeState then becomes
currentWalk = walkUp; // Point to 'up walking' as the current animation.
         // or any of the other animations, if user presses the right keys.

// rendering the animation becomes
currentWalk.render(batch);

This is what the usual concept 'state machine' looks like. You have a bunch of states (in your case animation objects), where of them is active (known as the current state, indicated by 'currentWalk' here). Changing state simply means pointing 'current state' to a different state, like 'currentWalk = walkLeft;'. In particular, you normally do not make new states while changing to a new state.

 

 

Your keys have several problems, but let's do one thing at a time.

With polling you say:

So far, this is the only way I have been able to animate the player semi properly with multiple key presses.


With event-handling you say:

... but only one key can be processed at a time. This has led to erroneous animating when dealing with multiple key presses and releases. For example, ...


I disagree with the second conclusion. Both with polling and with event-handling solutions you can handle multi-key pressing/releasing, but if you compare the code, you can see it's not doing the same things at the same time currently. As a result, both versions behave differently (as you discovered).

The point here is however, YOU programmed it. "only one key can be processed" is something you coded in that way. The behavior is not set in stone now. Just change the code, and you'll have different behavior, you are in control over the program, not the other way around!

 

You are however in a very nice situation. You have 2 programs that are supposed to be the same, yet version P that works, and version E doesn't work. Clearly something is wrong here!  :D   You made a mistake somewhere, but didn't realize it. (That's ok, programming is complicated business. This also happens to me, even after 35 years :p )

 

Making a mistake happens. The question is how to go from here.

 

I challenge you to find out why E fails to do what you want it to do.

There are 2 ways to find the bug in E. Either by understanding where E makes the wrong decision (that is, somewhere it should deal with multiple pressed keys, but it doesn't, find the point in the code where it should do that), or by understanding why the (supposedly) equivalent program P works, and comparing that with how E works. (In some way P does handle multiple keys, but how?)

Understanding why and where program P works is much easier than understanding why and where program E does not work. The best approach in my view is thus first find out how P manages to walk UP and LEFT at the same time (or any other two directions). Which statements make it do that? Something must change x and something must change y at mostly the same time, right? Else you could not walk up and left at the same time. So how does that happen in P if you press 2 keys?

Once you understand that, go back to E. Why does it never get in the situation to move in 2 directions at the same time?
 

Share this post


Link to post
Share on other sites
Thanks for getting back to me Alberth, I appreciate your help.

I believe the reason I can process multiple key presses simultaneously while polling is because polling has no practical limit to how many key presses it can handle per frame.

With the InputProcessor interface, it's method keyDown() only registers one key press per frame. If I immediately send this key to the KeyboardControllable object as it is received, then I can only process one key per frame. The only way I can think of to process more than one key at a time that might work, is to store a new key every key press in some List, then send that List to the current KeyboardControllable object to be processed after n keys have been added to it. This however would take n frames, which is not very good for smooth input handling.

Now, I'm sure there is a clean way to process multiple keys at the same time using an event driven approach with the InputProcessor. The Libgdx designers are much smarter than I am and must have taken that into consideration. I'm just not seeing how at the moment.

I apologise if I'm missing something obvious, but I just can't see past the "one frame - one key" problem right now.

Thanks for showing me how to better manage the animations. Your way is at least greatly more memory efficient, but probably actually exponentially more so. I tend to lose sight of obvious optimisations like that when I'm trying to figure out how to get things working.

Share this post


Link to post
Share on other sites
I believe the reason I can process multiple key presses simultaneously while polling is because polling has no practical limit to how many key presses it can handle per frame.

Well, yes, but it's more subtle, and it's right there in your code, let me show you.

 

Suppose we want to go UP and LEFT and press both. In the polling case:

public void update()
{
    // Polling
    if(Gdx.input.isKeyPressed(Input.Keys.UP))
    {
        stateMachines.get(0).changeState(new WalkingUpAnimation(this));
        position.y += speed * Gdx.graphics.getDeltaTime();
    }

    if(Gdx.input.isKeyPressed(Input.Keys.DOWN))
    {
         // Omitted for brevity
    }

    if(Gdx.input.isKeyPressed(Input.Keys.LEFT))
    {
        stateMachines.get(0).changeState(new WalkingLeftAnimation(this));
        position.x -= speed * Gdx.graphics.getDeltaTime();
    }

As you can see in your code, if takes the first "if" and updates y (position.y += ...), and it takes the third "if", and updates x position (position.x -= ...). The crux here however is, you ask "Gdx.input.isKeyPressed", ie "is it pressed NOW?" for both left and up you get "yes" as answer, and you update in both directions.

Now compare that with the event driven solution (I removed some lines to reduce space):

public void handleKeyPress(int keyCode)
{
    switch(keyCode)
    {
        case Input.Keys.UP:
            stateMachines.get(0).changeState(new WalkingUpAnimation(this));
            stateMachines.get(1).changeState(new MovingUp(this));
            break;

        case Input.Keys.DOWN:
            // Omitted for brevity
            break;

        case Input.Keys.LEFT:
            stateMachines.get(0).changeState(new WalkingLeftAnimation(this));
            stateMachines.get(1).changeState(new MovingLeft(this));
            break;

Now this does event processing, ie only when the key is first detected being pressed you get a call. (No "is this key pressed now" query). We need to go through all the steps towards going both UP and LEFT.

So let's see what happens exactly. Suppose we press UP first. It takes the first "case", and sets "stateMachines.get(1).changeState(new MovingUp(this));", which I assume makes that y is being changed every frame. Everything is working as intended.

Next we press LEFT (while still holding UP). We take the third "case", and execute "stateMachines.get(1).changeState(new MovingLeft(this));". See what you're doing here?

 

You overwrite the "moving up" with a "moving left". That makes y stop from being modified, and x gets modified now instead. Et voila, there is why you can't move diagonally. This code never took into consideration that perhaps two keys could be pressed simultaneously.

If you want to move in more than one direction at the same time, don't kill all previous movement each time you take a turn :)


As for solutions, I'll just concentrate on the movement, although what you want for the animation is something to consider too.

One of the simpler solutions for movement is to keep the idea of the polling solution. Have 4 directions of movement that can be combined. The event-driven code should not overwrite movement of other keys as you do now, but instead, give each key its own movement thingie.

Staying close to your state machines, give each key a separate state machine which is either Moving<Direction> or NotMoving, depending on whether or not the key in the <Direction> is pressed. You'll have to consider cases like holding both LEFT and RIGHT, and decide if the behavior in that case is what you want to happen.

Since each direction has 2 possible values (move or not move), you can of course also use a boolean "moving_<direction>" to code that information instead of a state machine. Set it eg to true if moving in the indicated direction, else false. You'll have to derive motion from the 4 direction booleans. This gets you very close to the "is key pressed" solution of the polling solution.
 

Another solution can be to code 8 directions (N, NE, E, SE, S, SW, W, NW) instead of 4 separate directions, and update the direction based on pressing and letting go of keys. This has however "interesting" edge cases when you press "impossible" combinations (although the complexitly is mostly just how complicated you want to make it :) ).

 

 

With the InputProcessor interface, it's method keyDown() only registers one key press per frame. If I immediately send this key to the KeyboardControllable object as it is received, then I can only process one key per frame. The only way I can think of to process more than one key at a time that might work, is to store a new key every key press in some List, then send that List to the current KeyboardControllable object to be processed after n keys have been added to it. This however would take n frames, which is not very good for smooth input handling.

Basically correct, you need to store the information what keys are pressed in some way. The point where you make a wrong turn here is thinking you actually need n key presses before you can process it.

My 4 direction booleans are also n key presses (for n=4), but I allow "this key is not pressed" as a valid value too, avoid waiting.

 

 

 

Now, I'm sure there is a clean way to process multiple keys at the same time using an event driven approach with the InputProcessor. The Libgdx designers are much smarter than I am and must have taken that into consideration. I'm just not seeing how at the moment.

They are indeed likely more experienced and/or smarter. Their goal is however different (not all games do the same way of handling keys, or have 8 movement directions), so they aim for a general solution that works in all cases. That in general means a specific game has to do extra work to cover its case. It seems to me they are exactly doing that, so you are probably not missing anything, there really isn't a better solution.

 

(and even if there is, if you can't see it, it's of no use to you. Just aim for a working solution, any working solution. That's hard enough. Likely if you look at this code in a year, you'll think "what the hell was I doing, this is all wrong, omg how stupid, there are way simpler solutions for this!", but that's when you discover you did learn some new stuff in the past year :)  Until that time, you do want a working solution, even if it's not optimal. )

 

 

 

I apologise if I'm missing something obvious, but I just can't see past the "one frame - one key" problem right now.

Yeah, accepting that a problem is more though than you, is difficult. My record is 10 years, it will take some time before you beat that :D

 

 

 

I tend to lose sight of obvious optimisations like that when I'm trying to figure out how to get things working.

Nah, I am just more paranoid about execution time in functions that get called a lot :)

You can't find every little thing at the first time, although you'll get better at it when you're programming longer.

 

(Actually, execution time of the function is probably fine, memory allocation in Java is unbelievably cheap. You'll pay a huge price when the garbage collector kicks in though.)

Edited by Alberth

Share this post


Link to post
Share on other sites
public class Player extends MutableObject implements KeyboardControllable, Renderable
{
    protected ObjectInputProcessor objectInputProcessor;
    protected Animation walkingUpAnimation;
    protected Animation walkingDownAnimation;
    protected Animation walkingLeftAnimation;
    protected Animation walkingRightAnimation;
    protected Animation facingUpAnimation;
    protected Animation facingDownAnimation;
    protected Animation facingLeftAnimation;
    protected Animation facingRightAnimation;
    protected Animation currentAnimation;
    protected boolean movingUp;
    protected boolean movingDown;
    protected boolean movingLeft;
    protected boolean movingRight;
    protected boolean upPressedFirst;
    protected boolean downPressedFirst;
    protected boolean leftPressedFirst;
    protected boolean rightPressedFirst;

    public Player()
    {
        objectInputProcessor = new ObjectInputProcessor();
        objectInputProcessor.setControllableObject(this);

        movingUp = false;
        movingDown = false;
        movingLeft = false;
        movingRight = false;
        upPressedFirst = false;
        downPressedFirst = false;
        leftPressedFirst = false;
        rightPressedFirst = false;

        spriteSheet = new SpriteSheet(Animations.assets.get("player.png", Texture.class), 4, 3, 32, 32);

        walkingUpAnimation = spriteSheet.getFrameCycle(3, 1.0f / 3.0f);
        walkingDownAnimation = spriteSheet.getFrameCycle(0, 1.0f / 3.0f);
        walkingLeftAnimation = spriteSheet.getFrameCycle(1, 1.0f / 3.0f);
        walkingRightAnimation = spriteSheet.getFrameCycle(2, 1.0f / 3.0f);
        facingUpAnimation = spriteSheet.getFrame(10);
        facingDownAnimation = spriteSheet.getFrame(1);
        facingLeftAnimation = spriteSheet.getFrame(4);
        facingRightAnimation = spriteSheet.getFrame(7);

        currentAnimation = facingDownAnimation;
    }

    @Override
    public void update()
    {
        if(movingUp && !movingDown && !movingLeft && !movingRight) // Moving UP
        {
            position.y += speed * Gdx.graphics.getDeltaTime();
            currentAnimation = walkingUpAnimation;
        }
        else if(!movingUp && movingDown && !movingLeft && !movingRight) // Moving DOWN
        {
            position.y -= speed * Gdx.graphics.getDeltaTime();
            currentAnimation = walkingDownAnimation;
        }
        else if(!movingUp && !movingDown && movingLeft && !movingRight) // Moving LEFT
        {
            position.x -= speed * Gdx.graphics.getDeltaTime();
            currentAnimation = walkingLeftAnimation;
        }
        else if(!movingUp && !movingDown && !movingLeft && movingRight) // Moving RIGHT
        {
            position.x += speed * Gdx.graphics.getDeltaTime();
            currentAnimation = walkingRightAnimation;
        }
        else if(movingUp && !movingDown && movingLeft && !movingRight) // Moving UP and LEFT
        {
            position.y += speed * Gdx.graphics.getDeltaTime();
            position.x -= speed * Gdx.graphics.getDeltaTime();

            if(upPressedFirst)
            {
                upPressedFirst = false;
                currentAnimation = walkingUpAnimation;
            }
            else if(leftPressedFirst)
            {
                leftPressedFirst = false;
                currentAnimation = walkingLeftAnimation;
            }
        }
        else if(movingUp && !movingDown && !movingLeft && movingRight) // Moving UP and RIGHT
        {
            position.y += speed * Gdx.graphics.getDeltaTime();
            position.x += speed * Gdx.graphics.getDeltaTime();

            if(upPressedFirst)
            {
                upPressedFirst = false;
                currentAnimation = walkingUpAnimation;
            }
            else if(rightPressedFirst)
            {
                rightPressedFirst = false;
                currentAnimation = walkingRightAnimation;
            }
        }
        else if(!movingUp && movingDown && movingLeft && !movingRight) // Moving DOWN and LEFT
        {
            position.y -= speed * Gdx.graphics.getDeltaTime();
            position.x -= speed * Gdx.graphics.getDeltaTime();

            if(downPressedFirst)
            {
                downPressedFirst = false;
                currentAnimation = walkingDownAnimation;
            }
            else if(leftPressedFirst)
            {
                leftPressedFirst = false;
                currentAnimation = walkingLeftAnimation;
            }
        }
        else if(!movingUp && movingDown && !movingLeft && movingRight) // Moving DOWN and RIGHT
        {
            position.y -= speed * Gdx.graphics.getDeltaTime();
            position.x += speed * Gdx.graphics.getDeltaTime();

            if(downPressedFirst)
            {
                downPressedFirst = false;
                currentAnimation = walkingDownAnimation;
            }
            else if(rightPressedFirst)
            {
                rightPressedFirst = false;
                currentAnimation = walkingRightAnimation;
            }
        }
    }

    @Override
    public void render(SpriteBatch batch)
    {
        batch.draw(currentAnimation.getKeyFrame(Animations.elapsedTime, true), position.x, position.y);
    }

    @Override
    public void handleKeyPress(int keyCode)
    {
        if(keyCode == Input.Keys.UP)
        {
            movingUp = true;
        }
        else if(keyCode == Input.Keys.DOWN)
        {
            movingDown = true;
        }
        else if(keyCode == Input.Keys.LEFT)
        {
            movingLeft = true;
        }
        else if(keyCode == Input.Keys.RIGHT)
        {
            movingRight = true;
        }

        // Check which key was pressed first
        if(movingUp && !movingDown && !movingLeft && !movingRight)
        {
            upPressedFirst = true;
        }
        else if(!movingUp && movingDown && !movingLeft && !movingRight)
        {
            downPressedFirst = true;
        }
        else if(!movingUp && !movingDown && movingLeft && !movingRight)
        {
            leftPressedFirst = true;
        }
        else if(!movingUp && !movingDown && !movingLeft && movingRight)
        {
            rightPressedFirst = true;
        }
    }

    @Override
    public void handleKeyRelease(int keyCode)
    {
        if(keyCode == Input.Keys.UP)
        {
            currentAnimation = facingUpAnimation;
            movingUp = false;
        }
        else if(keyCode == Input.Keys.DOWN)
        {
            currentAnimation = facingDownAnimation;
            movingDown = false;
        }
        else if(keyCode == Input.Keys.LEFT)
        {
            currentAnimation = facingLeftAnimation;
            movingLeft = false;
        }
        else if(keyCode == Input.Keys.RIGHT)
        {
            currentAnimation = facingRightAnimation;
            movingRight = false;
        }
    }
}

Removing the state machines and state classes, this is what I've come up with so far for eight directional movement. A set of booleans and Animations are used to determine the Players overall state. As you can see, the code is a mess with exactly what I did not want, a monolithic if-else chain that is difficult to maintain and modify. It is closer to the behaviour I'm looking for, but not quite.

 

I will describe the exact behaviour I want first, as I now have a firmer idea of how it should be, then I will describe the behaviour of the system as it currently is.

 

 

How It Should Be

Movement can be in eight directions; North, South, East, West, North East, North West, South East, South West.

 

Movement in a direction is accompanied by the appropriate Animation. For the first four directions, North, South, East and West, the Animation is selected based on the current direction. For example, if moving North the accompanying Animation is walkingUpAnimation. However, if the Player is moving in two directions at once, the Animation that plays is determined by which direction the Player was moving in first. For example, if the Player is moving East then starts moving South, the Animation remains as walkingRightAnimation, it's not overridden by walkingDownAnimation.

 

While moving in a direction, key presses to move the Player contrary to their current direction are ignored. For example, while moving East, if the key to move West is pressed/held, it is ignored. The appropriate bi-directional key presses {North, East}, {North, West}, {South, East}, {South, West}, are unaffected.

 

How It Currently Is

The system currently works as it should according to the first two points, as far as I can tell, but not the third. Contrary key presses are not ignored and cause the Player to act strangely, such as by playing the wrong Animations based on the movement and direction faced.

 

 

I'm not sure how to go about implementing the third requirement. Even if I was, I don't like the way the code is. I wrote it that way just to get something working, but I really would prefer a more refined solution if one exists.

 

How might the third requirement be implemented? If possible, how might the code be refined?

 

As ever, if anything is unclear I'll try to clarify. Thank you.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!