Efficient way to render tile-based map in Java

Posted by Lucius on Game Development See other posts from Game Development or by Lucius
Published on 2012-08-30T20:37:54Z Indexed on 2012/08/30 21:51 UTC
Read the original article Hit count: 223

Filed under:
|
|
|
|

Some time ago I posted here because I was having some memory issues with a game I'm working on. That has been pretty much solved thanks to some suggestions here, so I decided to come back with another problem I'm having.

Basically, I feel that too much of the CPU is being used when rendering the map. I have a Core i5-2500 processor and when running the game, the CPU usage is about 35% - and I can't accept that that's just how it has to be.

This is how I'm going about rendering the map:

  • I have the X and Y coordinates of the player, so I'm not drawing the whole map, just the visible portion of it;
  • The number of visible tiles on screen varies according to the resolution chosen by the player (the CPU usage is 35% here when playing at a resolution of 1440x900);
  • If the tile is "empty", I just skip drawing it (this didn't visibly lower the CPU usage, but reduced the drawing time in about 20ms);
  • The map is composed of 5 layers - for more details;
  • The tiles are 32x32 pixels;

And just to be on the safe side, I'll post the code for drawing the game here, although it's as messy and unreadable as it can be T_T (I'll try to make it a little readable)

private void drawGame(Graphics2D g2d){
    //Width and Height of the visible portion of the map (not of the screen)
    int visionWidht = visibleCols * TILE_SIZE;
    int visionHeight = visibleRows * TILE_SIZE;

    //Since the map can be smaller than the screen, I center it just to be sure
    int xAdjust = (getWidth() - visionWidht) / 2;
    int yAdjust = (getHeight() - visionHeight) / 2;


    //This "deducedX" thing is to move the map a few pixels horizontally, since the player moves by pixels and not full tiles
    int playerDrawX = listOfCharacters.get(0).getX();
    int deducedX = 0;
    if (listOfCharacters.get(0).currentCol() - visibleCols / 2 >= 0) {
        playerDrawX = visibleCols / 2 * TILE_SIZE;
        map_draw_col = listOfCharacters.get(0).currentCol() - visibleCols
                / 2;

        deducedX = listOfCharacters.get(0).getXCol();
    }

    //"deducedY" is the same deal as "deducedX", but vertically
    int playerDrawY = listOfCharacters.get(0).getY();
    int deducedY = 0;
    if (listOfCharacters.get(0).currentRow() - visibleRows / 2 >= 0) {
        playerDrawY = visibleRows / 2 * TILE_SIZE;
        map_draw_row = listOfCharacters.get(0).currentRow() - visibleRows
                / 2;

        deducedY = listOfCharacters.get(0).getYRow();
    }

    int max_cols = visibleCols + map_draw_col;
    if (max_cols >= map.getCols()) {
        max_cols = map.getCols() - 1;
        deducedX = 0;
        map_draw_col = max_cols - visibleCols + 1;
        playerDrawX = listOfCharacters.get(0).getX() - map_draw_col
                * TILE_SIZE;
    }

    int max_rows = visibleRows + map_draw_row;
    if (max_rows >= map.getRows()) {
        max_rows = map.getRows() - 1;
        deducedY = 0;
        map_draw_row = max_rows - visibleRows + 1;
        playerDrawY = listOfCharacters.get(0).getY() - map_draw_row
                * TILE_SIZE;
    }

    //map_draw_row and map_draw_col representes the coordinate of the upper left tile on the screen

    //iterate through all the tiles on screen and draw them - this is what consumes most of the CPU
    for (int col = map_draw_col; col <= max_cols; col++) {
        for (int row = map_draw_row; row <= max_rows; row++) {
            Tile[] tiles = map.getTiles(col, row);
            for(int layer = 0; layer < tiles.length; layer++){
                Tile currentTile = tiles[layer];
                boolean shouldDraw = true;

                //I only draw the tile if it exists and is not empty (id=-1)
                if(currentTile != null && currentTile.getId() >= 0){
                    //The layers above 1 can be draw behing or infront of the player according to where it's standing
                    if(layer > 1 && currentTile.getId() >= 0){
                        if(playerBehind(col, row, layer, listOfCharacters.get(0))){
                                behinds.get(0).add(new int[]{col, row});
                                //the tiles that are infront of the player wont be draw right now
                                shouldDraw = false;
                        }
                    }
                    if(shouldDraw){
                        g2d.drawImage(
                            tiles[layer].getImage(), 
                            (col-map_draw_col)*TILE_SIZE - deducedX + xAdjust,
                            (row-map_draw_row)*TILE_SIZE - deducedY + yAdjust, 
                            null);
                    }
                }                           
            }
        }
    }
}

There's some more code in this method but nothing relevant to this question.

Basically, the biggest problem is that I iterate over around 5000 tiles (in this specific resolution) 60 times each second.

I thought about rendering the visible portion of the map once and storing it into a BufferedImage and when the player moved move the whole image the same amount but to the opposite side and then drawn the tiles that appeared on the screen, but if I do it like that, I wont be able to have animated tiles (at least I think).

That being said, any suggestions?

© Game Development or respective owner

Related posts about java

Related posts about rendering