Ammar Tarajia
Ammar Tarajia

Reputation: 189

LibGDX - Map Boundaries

Synopsis

Well, I'm making a little top-down JRPG and today I was like 'Yeah, I'm gonna bust out this whole map collision thing!'. I failed.

Problem

So I went on the internet and looked up 'LibGDX Tiled Map Collision Detection' and found a really neat post about Map Objects so I added in a map object layer and did all that biz and came out with this little method to ensure the player can move freely around the map but at the same time can't exit it but each time I've tried it ends up with a horrible result such as the player moving off the screen. The latest error is that the player gets stuck doing a walk animation and can't move anywhere else!

Code

package com.darkbyte.games.tfa.game.entity.entities;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.objects.RectangleMapObject;
import com.badlogic.gdx.math.Rectangle;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.SpriteSheet;
import com.darkbyte.games.tfa.game.world.map.MapManager;
import com.darkbyte.games.tfa.render.Batch;
import com.darkbyte.games.tfa.render.Camera;

public class Player extends Entity {

    // The constructor for the player class
    public Player(String name, SpriteSheet spriteSheet) {
        super(name, spriteSheet);
        direction = Direction.DOWN;
        collisionBox = new Rectangle(x, y, 64, 64);
    }

    // A flag to see if the player is moving
    private boolean isMoving;
    // The variable that holds the state time
    private float stateTime;

    // The player's walking animations
    private Animation[] walkAnimations = {
            spriteSheet.getAnimation(8, 8, 1 / 16f),
            spriteSheet.getAnimation(9, 8, 1 / 16f),
            spriteSheet.getAnimation(10, 8, 1 / 16f),
            spriteSheet.getAnimation(11, 8, 1 / 16f) };
    // The player's static frames
    private TextureRegion[] staticFrames = {
            spriteSheet.getTexture(8, 0),
            spriteSheet.getTexture(9, 0),
            spriteSheet.getTexture(10, 0),
            spriteSheet.getTexture(11, 0) };

    // The render code for the player
    @Override
    public void render() {
        // Makes the camera follow the player
        Camera.setCameraPosition(x, y);
        Batch.getGameBatch().setProjectionMatrix(Camera.getCamera().combined);

        // Updates the state time
        stateTime += Gdx.graphics.getDeltaTime();

        // Gets the player's direction, if the player's moving, it sets the
        // current frame to the frame that would be played at the current moment
        // based on the state time
        // If the player isn't moving, it sets the current frame to the static
        // frame associated to the direction
        switch (direction) {
        case UP:
            if(isMoving) {
                currentFrame = walkAnimations[0].getKeyFrame(stateTime, true);
            } else
                currentFrame = staticFrames[0];
            break;
        case LEFT:
            if(isMoving) {
                currentFrame = walkAnimations[1].getKeyFrame(stateTime, true);
            } else
                currentFrame = staticFrames[1];
            break;
        case DOWN:
            if(isMoving) {
                currentFrame = walkAnimations[2].getKeyFrame(stateTime, true);
            } else
                currentFrame = staticFrames[2];
            break;
        case RIGHT:
            if(isMoving) {
                currentFrame = walkAnimations[3].getKeyFrame(stateTime, true);
            } else
                currentFrame = staticFrames[3];
            break;
        }
    }

    // The tick code for the player
    @Override
    public void tick() {
        // The object to represent the bounds of the land on the map
        RectangleMapObject land = (RectangleMapObject) MapManager.getCurrentMap().getMap().getLayers().get("collision").getObjects().get("land");

        // Checks if the player is within the bounds of the map
        if(land.getRectangle().contains(collisionBox)) {
            // If the player is moving but the arrow keys aren't pressed, sets isMoving to false
            isMoving = (isMoving && (Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)
                    || Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)
                    || Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)
                    || Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)));

            // Checks to see if the arrow / WASD keys are pressed and moves the
            // player in the correct direction at the speed of 1.5 pixels/tick
            // (45/second)
            // It also sets the players state to moving and corresponds it's
            // direction to the key pressed
            // Doesn't move if opposing keys are pressed
            if(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)) {
                if(!(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN))) {
                    direction = Direction.UP;
                    y += 1.5f;
                    isMoving = true;
                }
            }

            if(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)) {
                if(!(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT))) {
                    direction = Direction.LEFT;
                    isMoving = true;
                    x -= 1.5f;
                }
            }

            if(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)) {
                if(!(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP))) {
                    direction = Direction.DOWN;
                    y -= 1.5f;
                    isMoving = true;
                }
            }

            if(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)) {
                if(!(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT))) {
                    direction = Direction.RIGHT;
                    x += 1.5f;
                    isMoving = true;
                }
            }
        } else {
            if(!isMoving) {
                // If the player's just spawned puts the player to the map's spawn point
                x = MapManager.getCurrentMap().getPlayerSpawnX();
                y = MapManager.getCurrentMap().getPlayerSpawnY();
            } else { // If not, it just moves them back till they're no longer out of the map
                if(x > (land.getRectangle().getX() + land.getRectangle().getWidth())) x -= 1.5;
                if(y > (land.getRectangle().getY() + land.getRectangle().getHeight())) y -= 1.5;
            }
        }

        // Synchronises the collision box with the player's x and y position
        collisionBox.x = x;
        collisionBox.y = y;
    }

    // Returns if the player is moving
    public boolean isMoving() {
        return isMoving;
    }
}

Can you guys make it so that when he reaches the border that he stops but he can still keep moving in other directions instead of staying static!

Thanks for reading!

Upvotes: 0

Views: 1874

Answers (1)

Madmenyo
Madmenyo

Reputation: 8584

At the moment it sounds you just copy/pasted it and you need to familiarize yourself with it first. If you don't know what it does then you should learn or stop the project imho.

Anyway, from what I can tell it's just a player class that handles the animation frames based on which direction it is moving. Nothing to do with collision detection at all. It does update some kind of collisionBox but functionality for this is handled elsewhere, perhaps in the parent class Entity?

My guess is that this is a tile map and units are restricted to the grid. It's pretty easy to detect if A tile exists or not.

private boolean tileExists(int tileX, int tileY, tile[][] map)
{
  return tileX >= 0 && tileY >= 0 &&
    tileX < map.length && tileY < map[0].length;
}

Now whenever a entity requests a move you should check if the destination is within the map bounds.

private void moveRequest(int destinationX, int destinationY, Tile[][] map)
{
  //Just return if the tile is outside of the map
  if (!tileExists(destinationX, destinationY, map) return;
  //Same goes for your other checks...
  //Return if the tile is not walkable
  if (!tileIsWalkable(destinationX, destinationY, map) return;
  //Return if the tile is already occupied
  if (tileIsOccupied(destinationX, destinationY, otherEntities) return;
  //etc..  
  //Now the move is valid and you can set it's state to moving in that direction.
}

Tile maps are not very hard to understand. I will make an attempt to give you some better insight into tile maps. You have a 2D array where you store your tiles in. Tiles have a width and a height and from that you can make your own tile engine:

    //Find out which tiles to draw based on the camera position and viewport size.
    int startX = (int)(camera.position.x - camera.viewportWidth / 2) / tileWidth;
    int startY = (int)(camera.position.y - camera.viewportHeight / 2) / tileHeight;
    int endX = (int)(startX + camera.viewportWidth / tileWidth) + 1;
    int endY = (int)(startY + camera.viewportHeight / tileHeight) + 1;

    //Loop using this data as boundaries 
    for (int y = startY; y < endY; y++)
    {
        for (int x = startX; x < endX; x++)
        {
            //If out of bounds continue to next tile.
            if (!tileExists(x, y, map) continue;
            //Now all we need to draw the on screen tiles properly:
            //x == tile position x in array
            //y == tile position y in array
            //World position of this tile:
            //worldX = x * tileWidth;
            //worldY = y * tileHeight;
            //Let's draw:
            batch.draw(map[x][y].getTexture, worldX, worldY,
             tileWidth, tileHeight)
        }
    }

There really is no magic involved here at all. Drawing only what is on screen like in the above example is very important for larger maps. Other then that you should draw thing in the back first. You have several options to do this, the easiest but least versatile is just a separate the ground from the objects that can obscure things and draw this later.

Characters, creatures or other entities can just use a world position and be easily converted back to tile position.

tileX = worldX / tileWidth;
tileY = worldY / tileHeight;

So if you want to move something with the world position calculate it's tile position first using the aforementioned method. Then lookup if this tile is valid to move to. Then block that tile for other and move to it.

Upvotes: 1

Related Questions