Search code examples
javalibgdx

Player Character Movement Error LibGDX RPG


I am making an RPG game, however, I have an error at the moment. The player's character can move in all four cardinal directions, but the player becomes stuck if you move right, up or down.

Also, the error seems to have some logic to it:

if moving down, the character is stuck in a loop moving down

unless the up arrow is pressed, then the player will begin a new infinite loop for up

unless the right arrow is pressed, then the player will begin a new infinite loop for right

So right seems to take precedence over up which takes precedence over down.

Strangely, the leftward movement works perfectly. Even when the character is stuck in an infinite loop, the left arrow key will always cause the player to move left in the correct way.

I don't understand where the error is in my code. I don't know why the leftward movement works while the other three directions do not. I don't understand why there seems to be some sort of priority ordering between the other three directions as well.

I have looked through the program and I don't think I treat leftward movement differently to any other direction. However, I might be wrong, considering this error.

Due to the 30k word limit, I do not have enough space to include all the code here. Therefore, I have included a link to my github, so you can see the code there instead.

https://github.com/davey67/bludbourne

I think these classes are probably the most important classes though:

package com.bludbourne.game.desktop;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.bludbourne.game.BludBourne;

public class DesktopLauncher {
    public static void main (String[] arg) {
        LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();



        config.title="BludBourne";
        config.useGL30=false;
        config.width =480;
        config.height=480;

        Application app = new LwjglApplication(new BludBourne(),config);

        Gdx.app=app;
        Gdx.app.setLogLevel(Application.LOG_DEBUG);


    }
}

/

package com.bludbourne.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;


import com.bludbourne.game.screens.MainGameScreen;
import com.badlogic.gdx.Game;

public class BludBourne extends Game {

    public static final MainGameScreen _mainGameScreen = new MainGameScreen();


    @Override
    public void create () {
        setScreen(_mainGameScreen);
    }

    @Override
    public void dispose () {
        _mainGameScreen.dispose();
    }
}

/

package com.bludbourne.game;

import java.util.HashMap;
import java.util.Map;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.math.Vector3;


public class PlayerController implements InputProcessor
{

    private final static String TAG = PlayerController.class.getSimpleName();

    enum Keys{
        LEFT,RIGHT,UP,DOWN,QUIT
    }

    enum Mouse{

        SELECT,DOACTION
    }

    private static Map<Keys,Boolean> keys=new HashMap<PlayerController.Keys,Boolean>();
    private static Map<Mouse,Boolean> mouseButtons = new HashMap<PlayerController.Mouse,Boolean>();
    private Vector3 lastMouseCoordinates;

    static {
        keys.put(Keys.LEFT,false);
        keys.put(Keys.RIGHT,false);
        keys.put(Keys.UP,false);
        keys.put(Keys.DOWN, false);
        keys.put(Keys.QUIT, false);

    }


    static {

        mouseButtons.put(Mouse.SELECT, false);
        mouseButtons.put(Mouse.DOACTION, false);

    }


    private Entity _player;

    public PlayerController(Entity player) {

        this.lastMouseCoordinates=new Vector3();
        this._player=player;

    }



    @Override
    public boolean keyDown(int keycode)
    {
        if(keycode ==Input.Keys.LEFT||keycode==Input.Keys.A) {
            this.leftPressed();
        }
        if(keycode ==Input.Keys.RIGHT||keycode==Input.Keys.D) {
            this.rightPressed();
        }
        if(keycode ==Input.Keys.UP||keycode==Input.Keys.W) {
            this.upPressed();
        }
        if(keycode ==Input.Keys.DOWN||keycode==Input.Keys.S) {
            this.downPressed();
        }
        if(keycode==Input.Keys.Q) {
            this.quitPressed();
        }
        return true;
    }

    @Override
    public boolean keyUp(int keycode)
    {
        if(keycode ==Input.Keys.LEFT||keycode==Input.Keys.A) {
            this.leftReleased();
        }
        if(keycode ==Input.Keys.RIGHT||keycode==Input.Keys.D) {
            this.rightReleased();
        }
        if(keycode ==Input.Keys.UP||keycode==Input.Keys.W) {
            this.upReleased();
        }
        if(keycode ==Input.Keys.DOWN||keycode==Input.Keys.S) {
            this.downReleased();
        }
        if(keycode==Input.Keys.Q) {
            this.quitReleased();
        }
        return true;
    }

    @Override
    public boolean keyTyped(char character)
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button)
    {
        if(button==Input.Buttons.LEFT||button==Input.Buttons.RIGHT) {
            this.setClickedMouseCoordinates(screenX,screenY);
        }
        if(button==Input.Buttons.LEFT) {
            this.selectMouseButtonPressed(screenX,screenY);

        }

        if(button==Input.Buttons.RIGHT) {
            this.doActionMouseButtonPressed(screenX,screenY);
        }
        return true;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button)
    {
        if(button==Input.Buttons.LEFT) {
            this.selectMouseButtonReleased(screenX,screenY);
        }
        if(button==Input.Buttons.RIGHT) {
            this.doActionMouseButtonReleased(screenX,screenY);
        }
        return true;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer)
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY)
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean scrolled(int amount)
    {
        // TODO Auto-generated method stub
        return false;
    }

    public void dispose() {



    }

    public void leftPressed() {
        keys.put(Keys.LEFT,true);

    }

    public void rightPressed() {
        keys.put(Keys.RIGHT,true);

    }

    public void upPressed() {
        keys.put(Keys.UP,true);

    }

    public void downPressed() {
        keys.put(Keys.DOWN,true);

    }

    public void quitPressed() {

        keys.put(Keys.QUIT, true);
    }


    public void setClickedMouseCoordinates(int x,int y) {

        lastMouseCoordinates.set(x,y,0);

    }

    public void selectMouseButtonPressed(int x,int y) {

        mouseButtons.put(Mouse.SELECT,true);
    }

    public void doActionMouseButtonPressed(int x,int y) {

        mouseButtons.put(Mouse.DOACTION, true);
    }

    public void leftReleased() {
        keys.put(Keys.LEFT,false);

    }

    public void rightReleased() {
        keys.put(Keys.RIGHT,true);

    }

    public void upReleased() {
        keys.put(Keys.UP,true);

    }

    public void downReleased() {
        keys.put(Keys.DOWN,true);

    }

    public void quitReleased() {

        keys.put(Keys.QUIT, true);
    }


    public void selectMouseButtonReleased(int x,int y) {
        mouseButtons.put(Mouse.SELECT, false);
    }

    public void doActionMouseButtonReleased(int x ,int y) {

        mouseButtons.put(Mouse.DOACTION, false);
    }


    public void update(float delta) {

        processInput(delta);
    }

    public static void hide() {

        keys.put(Keys.LEFT, false);
        keys.put(Keys.RIGHT, false);
        keys.put(Keys.UP, false);
        keys.put(Keys.DOWN, false);
        keys.put(Keys.QUIT, false);

    }

    private void processInput(float delta) {

        if(keys.get(Keys.LEFT)) {
            _player.calculateNextPosition(Entity.Direction.LEFT,delta);
            _player.setState(Entity.State.WALKING);
            _player.setDirection(Entity.Direction.LEFT,delta);

        }

        else if(keys.get(Keys.RIGHT)) {
            _player.calculateNextPosition(Entity.Direction.RIGHT,delta);
            _player.setState(Entity.State.WALKING);
            _player.setDirection(Entity.Direction.RIGHT,delta);

        }

        else if(keys.get(Keys.UP)) {
            _player.calculateNextPosition(Entity.Direction.UP,delta);
            _player.setState(Entity.State.WALKING);
            _player.setDirection(Entity.Direction.UP,delta);

        }

        else if(keys.get(Keys.DOWN)) {
            _player.calculateNextPosition(Entity.Direction.DOWN,delta);
            _player.setState(Entity.State.WALKING);
            _player.setDirection(Entity.Direction.DOWN,delta);

        }
        else if(keys.get(Keys.QUIT)) {
            Gdx.app.exit();


        }

        else {

            _player.setState(Entity.State.IDLE);
        }


        if(mouseButtons.get(Mouse.SELECT)) {

            mouseButtons.put(Mouse.SELECT, false);
        }
    }
}

/

package com.bludbourne.game;

import java.util.UUID;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;

public class Entity
{

    private static final String     TAG                 = Entity.class.getSimpleName();
    private static final String _defaultSpritePath = "sprites/characters/Warrior.png";

    private Vector2                 _velocity;
    private String                  _entityID;

    private Direction               _currentDirection   = Direction.LEFT;
    private Direction               _previousDirection  = Direction.UP;

    private Animation               _walkLeftAnimation;
    private Animation               _walkRightAnimation;
    private Animation               _walkUpAnimation;
    private Animation               _walkDownAnimation;

    private Array<TextureRegion>    _walkLeftFrames;
    private Array<TextureRegion>    _walkRightFrames;
    private Array<TextureRegion>    _walkUpFrames;
    private Array<TextureRegion>    _walkDownFrames;

    protected Vector2               _nextPlayerPosition;
    protected Vector2               _currentPlayerPosition;
    protected State                 _state              = State.IDLE;
    protected float                 _frameTime          = 0f;
    protected Sprite                _frameSprite        = null;
    protected TextureRegion         _currentFrame       = null;

    public final int                FRAME_WIDTH         = 16;
    public final int                FRAME_HEIGHT        = 16;
    public static Rectangle         boundingBox;

    public enum State
    {

        IDLE, WALKING
    }

    public enum Direction
    {
        UP, RIGHT, DOWN, LEFT
    }

    public Entity() {

        initEntity();
    }

    public void initEntity()
    {
        this._entityID = UUID.randomUUID().toString();
        this._nextPlayerPosition = new Vector2();
        this._currentPlayerPosition = new Vector2();
        this.boundingBox = new Rectangle();
        this._velocity = new Vector2(2f, 2f);
        Utility.loadTextureAsset(_defaultSpritePath);
        loadDefaultSprite();
        loadAllAnimations();

    }

    public void update(float delta)
    {

        _frameTime = (_frameTime + delta) % 5;

        setBoundingBoxSize(0f, 0.5f);

    }

    public void init(float startX, float startY)
    {

        this._currentPlayerPosition.x = startX;
        this._currentPlayerPosition.y = startY;
        this._nextPlayerPosition.x = startX;
        this._nextPlayerPosition.y = startY;

    }

    public void setBoundingBoxSize(float percentageWidthReduced, float percentageHeightReduced)
    {

        float width;
        float height;

        float widthReductionAmount = 1.0f - percentageWidthReduced;
        float heightReductionAmount = 1.0f - percentageHeightReduced;

        if (widthReductionAmount > 0 && widthReductionAmount < 1)
        {
            width = FRAME_WIDTH * widthReductionAmount;
        }
        else
        {
            width = FRAME_WIDTH;
        }

        if (heightReductionAmount > 0 && heightReductionAmount < 1)
        {
            height = FRAME_HEIGHT * heightReductionAmount;
        }
        else
        {
            height = FRAME_HEIGHT;
        }

        if (width == 0 || height == 0)
        {
            Gdx.app.debug(TAG, "Width and Height are 0!! " + width + ":" + height);
        }

        float minX;
        float minY;

        if (MapManager.UNIT_SCALE > 0)
        {

            minX = _nextPlayerPosition.x / MapManager.UNIT_SCALE;
            minY = _nextPlayerPosition.y / MapManager.UNIT_SCALE;

        }

        else
        {

            minX = _nextPlayerPosition.x;
            minY = _nextPlayerPosition.y;
        }

        boundingBox.set(minX, minY, width, height);

    }

    private void loadDefaultSprite()
    {

        Texture texture = Utility.getTextureAsset(_defaultSpritePath);
        TextureRegion[][] textureFrames = TextureRegion.split(texture, FRAME_WIDTH, FRAME_HEIGHT);
        _frameSprite = new Sprite(textureFrames[0][0].getTexture(), 0, 0, FRAME_WIDTH, FRAME_HEIGHT);
        _currentFrame = textureFrames[0][0];

    }

    public void loadAllAnimations()
    {

        Texture texture = Utility.getTextureAsset(_defaultSpritePath);
        TextureRegion[][] textureFrames = TextureRegion.split(texture, FRAME_WIDTH, FRAME_HEIGHT);

        _walkDownFrames = new Array<TextureRegion>(4);
        _walkLeftFrames = new Array<TextureRegion>(4);
        _walkRightFrames = new Array<TextureRegion>(4);
        _walkUpFrames = new Array<TextureRegion>(4);

        for (int i = 0; i < 4; i++)
        {

            for (int j = 0; j < 4; j++)
            {
                TextureRegion region=textureFrames[i][j];

                if(region==null) {
                    Gdx.app.debug(TAG, "Got null animation frame "+i+","+j);
                }

                switch(i) {


                case 0:
                    _walkDownFrames.insert(j,region);
                    break;
                case 1:
                    _walkLeftFrames.insert(j,region);
                    break;
                case 2:
                    _walkRightFrames.insert(j,region);
                    break;
                case 3:
                    _walkUpFrames.insert(j,region);
                    break;

                }


            }

        }

        _walkDownAnimation = new Animation(0.25f,_walkDownFrames,Animation.PlayMode.LOOP);
        _walkLeftAnimation = new Animation(0.25f,_walkLeftFrames,Animation.PlayMode.LOOP);
        _walkRightAnimation = new Animation(0.25f,_walkRightFrames,Animation.PlayMode.LOOP);
        _walkUpAnimation = new Animation(0.25f,_walkUpFrames,Animation.PlayMode.LOOP);



    }


    public void dispose() {

        Utility.unloadAsset(_defaultSpritePath);

    }

    public void setState(State state) {

        this._state = state;
    }

    public Sprite getFrameSprite() {

        return _frameSprite;
    }

    public TextureRegion getFrame() {

        return _currentFrame;
    }

    public Vector2 getCurrentPosition() {
        return _currentPlayerPosition;
    }

    public void setCurrentPosition(float currentPositionX,float currentPositionY) {

        _frameSprite.setX(currentPositionX);
        _frameSprite.setY(currentPositionY);
        this._currentPlayerPosition.x=currentPositionX;
        this._currentPlayerPosition.y=currentPositionY;
    }

    public void setDirection(Direction direction,float deltaTime) {


        this._previousDirection=this._currentDirection;
        this._currentDirection=direction;

        switch(_currentDirection) {
        //not sure about this
        case DOWN:
            _currentFrame=(TextureRegion) _walkDownAnimation.getKeyFrame(_frameTime);
            break;
        case LEFT:
            _currentFrame=(TextureRegion) _walkLeftAnimation.getKeyFrame(_frameTime);
            break;
        case RIGHT:
            _currentFrame=(TextureRegion) _walkRightAnimation.getKeyFrame(_frameTime);
            break;
        case UP:
            _currentFrame=(TextureRegion) _walkUpAnimation.getKeyFrame(_frameTime);
            break;

        default:
            break;



        }

    }

    public void setNextPositionToCurrent() {

        setCurrentPosition(_nextPlayerPosition.x,_nextPlayerPosition.y);

    }

    public void calculateNextPosition(Direction currentDirection,float deltaTime) {

        float testX=_currentPlayerPosition.x;
        float testY=_currentPlayerPosition.y;

        _velocity.scl(deltaTime);

        switch(currentDirection) {

        case LEFT:
            testX-=_velocity.x;
            break;
        case RIGHT:
            testX+=_velocity.x;
            break;
        case UP:
            testY+=_velocity.y;
            break;
        case DOWN:
            testY-=_velocity.y;
            break;
        default:
            break;


        }

        _nextPlayerPosition.x=testX;
        _nextPlayerPosition.y=testY;

        _velocity.scl(1/deltaTime);


    }
}

I feel that I am probably missing something obvious, however, I still cannot see the error. Help is much appreciated. Thank you.

My github also contains the sprite sheet and the three maps, all of which would be stored in the asset folder of my project.


Solution

  • May I draw your attention to these four methods in PlayerInput

        public void leftReleased() {
            keys.put(Keys.LEFT,false);
    
        }
    
        public void rightReleased() {
            keys.put(Keys.RIGHT,true);
    
        }
    
        public void upReleased() {
            keys.put(Keys.UP,true);
    
        }
    
        public void downReleased() {
            keys.put(Keys.DOWN,true);
    
        }
    

    I believe it should be false, not true when the key is released. I've verified that this prevents the infinite loop.

    The priority order of right, up, then down is due to the order in which the keys are checked in processInput.