1
\$\begingroup\$

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?

\$\endgroup\$
4
  • \$\begingroup\$ Are you actually running into performance issues or are just worried you might? \$\endgroup\$
    – House
    Aug 30, 2012 at 23:03
  • \$\begingroup\$ @Byte56 I'm worried I might in computers slower than mine \$\endgroup\$
    – vlucki
    Aug 30, 2012 at 23:46
  • \$\begingroup\$ There are plenty of resources on this site about efficient rendering. Given the specifics of this question, it's unlikely anyone would run into this same problem. In my opinion, this question is too localized and a duplicate. \$\endgroup\$
    – House
    Aug 31, 2012 at 14:30
  • \$\begingroup\$ @Byte56 I did some research but wasn't able to find this specific topic you posted amid all the results I got, but there was no clear answer to my specific problem there. Even so, if this is a duplicate, I'm sorry. \$\endgroup\$
    – vlucki
    Sep 1, 2012 at 17:16

2 Answers 2

1
\$\begingroup\$

I'm afraid my answer is to reply with a question: What is slow?

  • Is the rendering itself slow (g2d.drawImage)?
  • Is playerBehind slow?
  • Is iterating over the tiles slow?

Use a profiler like YourKit or (quicker & easier) snip sections of code and re-run.

BTW using "35% of CPU" isn't a useful statistic, especially. Stick to milliseconds so that you can compare optimisations accurately.

\$\endgroup\$
6
  • \$\begingroup\$ It's not really slow - I have a solid 60fps which is all I need - it's just using too much CPU compared to other games I have here. I do know that if I remove the g2d.drawImage the CPU usage here falls from 35% to 15% which is what I would like to have all the time. I haven't had a chance to test this on slower computers - my friend and partner in this project have a computer with the same processor as mine - but I do believe that the frames may drop. I'll look into this YourKit you talk about and see if I can use it - never had too much luck with profiling tools. \$\endgroup\$
    – vlucki
    Aug 31, 2012 at 12:29
  • \$\begingroup\$ You are probably right to flag it as "high" and somewhat of a concern. Ultimately the answer will probably be to use a proper graphics API that will alow you to use sprite sheets/texture atlassing, but if this is still at the prototyping stage I would concentrate on the game itself. Once you've got that fun you can then decide if it is "broke" and needs a "fix". \$\endgroup\$ Aug 31, 2012 at 13:17
  • 1
    \$\begingroup\$ Asking questions goes in the comments, not as an answer. \$\endgroup\$
    – House
    Aug 31, 2012 at 14:26
  • \$\begingroup\$ I can't say this project is near it's end, but it MUST be complete in about 3 months because it's a school project and can't be delayed at all. But I installed YourKit and I can't say how thankfull I am to you. I said that the problem probably was at g2d.drawImage but I wasn't sure exactly why. Turns out that each call of tile.getImage() would go through a huge tileset and cut the part corresponding to the tile. By simply adding a HashMap containing the tiles that were previously draw, the CPU usage now is about 17%. I just hope this wont use too much memory - probably not. Thanks! \$\endgroup\$
    – vlucki
    Aug 31, 2012 at 15:01
  • \$\begingroup\$ @Byte56: I agreed, but how do you do that? There's no "Add Comment" in the question panel. As it happens, my answer was the answer they needed. \$\endgroup\$ Aug 31, 2012 at 15:19
0
\$\begingroup\$

You've got an idea to go on with rendering to a single image. If every tile has to be animated, it may not be the best idea, but with just a few you could have large areas using a single in-memory image.

Another way to get some frames is to only render the top tile (which you may already be doing).

Perhaps you could break up tiles into larger sections or "chunks", and only check the ones in chunks that are visible to the player.

I certainly hope you're already drawing tiles of the same type all at once, there's a huge amount of performance to be gained in sprite batching.

EDIT Only draw changes to the screen, for instance only when the player moves over a tile.

\$\endgroup\$
1
  • \$\begingroup\$ By "only render the top tile" do you mean the top layer? If so, then that's not really an option because some tiles are not fully opaque and show the tiles below. About this "sprite batching" I'm not doing it and it's actually the first time I've ever heard of it. I'm going to look into it and see if I can use it, but I'm not really sure it will work since if I were to draw all the tiles of the same type at once I would have to loop through the same layer a couple of times and not just once. \$\endgroup\$
    – vlucki
    Aug 30, 2012 at 23:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .