Efficient way to render tile-based map in Java
- by Lucius
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?