How to build a Tiled map in Java for a 2D game?

2019-03-31 05:52发布

问题:

Not sure how to approach this problem.

Basically, I want a Pixel -> Tile representation of a 400x400 window. Each coordinate on the screen, e.g 120x300 should be part of a tile. My smallest sprite is 4 pixels, so we can say that 1 tile = 4 pixels. The player and enemy sprites are all 20 x 20, so each player/bad guy will occupy 5 tiles.

Then I want to use this Map class to:

  • Retrieve the x/y coordinates of a player/monster sprite by suppling the index/id of the tile.

  • Knowing where the boundaries are, so it doesn't move the sprite beyond 400x400, thus hiding it.

  • Collision detection, knowing whether a tile is vacant or not.

How can this be done? Talking specifically about the x,y->tile or tile index->x,y conversion (for drawing the sprites appropriately) here.

回答1:

Firstly, split out the concept of a pixel, which is just concerned with representation, with a tile, which is an actual game object with constraints it places on the game.

I find a good way to disentangle things like this is to start out sketching out the rough API of what you want. Something like:

public class Board {
  public Board (int width, int height){.. }
  public boolean isOccupied(int x, int y){.. }
  public void moveTo(Point from, Point to) { .. 
    (maybe throws an exception for outofbounds )

where all internal units of the board are in tiles, not pixels. Then pixel information can be derived from the board independantly from the tile representation with a bit of internal multiplication-

  public Point getPixelPosition(int xTilePos, int yTilePos, int pixelsPerTile)..

The tiles can be internally represented as a 2d array or a single array, in which case you'd use some kind of internal representation scheme to map your array to the board squares, thus the mod arithmetic.



回答2:

Short answer: Multiplication and Modulo operations.

But if this is stumping you, I'd suggest you do a serious math refresher before trying to write a game.

Also your statement

My smallest sprite is 4 pixels, so we can say that 1 tile = 4 pixels. The player and enemy sprites are all 20 x 20, so each player/bad guy will occupy 5 tiles.

doesn't work out for any reasonable geometry. If by "1 tile = 4 pixels" you mean that the tiles are 2x2, then a player takes 100, not five. If you mean they are 4x4 then players take 25, which still isn't 5.



回答3:

/** size of a tile in pixel (for one dimension)*/
int TILE_SIZE_IN_PIXEL = 4;
/** size of a piece in tiles (for one dimension)*/
int PIECE_SIZE_IN_TILE = 5;


public int tileToPixel(int positionInTiles){
    return TILE_SIZE_IN_PIXEL * positionInTiles;
}

/** returns the tile coordinate to which the given tile coordinate belongs 

Note: tileToPixel(pixelToTile(x)) only returns x if x is the upper or left edge of a tile
*/
public int pixelToTile(int positionInPixel){
    return positionInPixel / TILE_SIZE_IN_PIXEL;
}

You'll probably want methods operating on two arguments (x and y at) as well.

For the ID->piece conversion and vice versa you have various approaches available. Which one to choose depends on the exact requirements (speed, size of game ...). So make sure that you are hiding the implementation details, so you can change them later on.

I'd start with a real easy solution:

public class Piece{
    /** x position measured in tiles */
    private int x;
    /** y position measured in tiles */
    private int y;

    /** I don't think you need this, but you asked for it. I'd pass around Piece instances instead */
    private final  Long id;

    public void getX(){
        return x;
    }
    public void getY(){
        return y;
    }

    public void getID(){
        return id;
    }

}

public class Board(){
    private Set<Long,Piece> pieces = new HashMap<Piece>(pieces);

    public Piece getPieceOnTile(int tileX, int tileY){ 
        for(Piece piece:pieces){
             if (isPieceOnTile(piece, tileX, tileY)) return piece;
        }
    }

    private boolean isPieceOnTile(piece, tileX, tileY){
        if (piece.getX() < tileX) return false;
        if (piece.getX() > tileX + PIECE_SIZE_IN_TILE) return false;

        if (piece.getY() < tileY) return false;
        if (piece.getY() > tileY + PIECE_SIZE_IN_TILE) return false;

        return true;
    }
}

Hope that gets you started. All code is writen without a compiler nearby so it will include typos and of course bugs, which may be distributed under the creative commons license.

The approach of keeping the pieces in a set should work well if there are not to many pieces. It should work better than a 2D array as long as most board area does not contain a piece. The whole thing currently assumes there are no overlapping pieces. If you need those getPieceOnTile must return a Collection of pieces. A set if order does not matter, a List if it does.