6
\$\begingroup\$

I have done mostly tile based games, but never really bothered with optimization. I always just rendered all the tiles that convered the viewport. I am currently working on platformer for a mobile device, which has some performance issues, ecspecially when using multiple layers of tiles.

Most tiles are pretty static without animations. I wouldn't really have to render them each frame. The clear() -> draw() loop is really printed into my brain, I'm having a hard time thinking how the handle rendering without clearing the screen each frame.

I'm currently doing this: - start game, render screen once - Once a tile changes, save this in a "tile_changed" array - Each frame, re-render all the changed tiles and clear the array

This works pretty fine, and there is a notable boos in performance. However, there are a view issues - The PlayN framework offers no way to "erase" drawn items. Whenever I want to erase a single tile I have to completely clear the screen and redraw every tile. (PlayN SurfaceLayer). I'm using a scroll background, so can't just fill an empty tile with the background color. I can imagine low level libraries like opengl having this same issue, do they use some sort of workaround for this?

  • I'm not sure how to recognize when new tiles come into the screen whenever the camera moves. I would normally just render the tiles in view each frame, so this never posed a problem.

  • I have no idea if having such a big drawn rendertarget in memory is a good idea. The player could have walked across a huge map and have thousands of tiles drawn. Should I worry about this?

Any other optimiztions that I'm missing are appreciated.


The awnsers note that most engines draw all tiles every frame. But I know that on some less powerfull platforms this is not the case. I believe flashpunk is using some sort of buffer that only draws new tiles when you make changes in the grid. Unfortunately this is not possible for me because I have no way of clearing a single tile without clearing the entire buffer.

I'm looking into optimazations like this, which enable me to have a very large amount of tiles on the screen.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ As far as I know, most modern 2D engines redraw the scene completely at every Update. Are you sure, this is the cause of your performance issues? How big is your scene? How many objects are on the scene? If you have too many tiles, only run the Update on the ones that are visible (or near the edge of the screen). \$\endgroup\$
    – Marton
    Jul 12, 2012 at 7:50
  • \$\begingroup\$ I'm pretty sure, since all I do right now is draw a tilegrid and a player. Also, just because modern 2d engines redraw everything does not mean that that is the way to go. It personally annoys me that I'm redrawing everything while I feel that shouldn't have to. \$\endgroup\$
    – omgnoseat
    Jul 12, 2012 at 21:11
  • 1
    \$\begingroup\$ @omgnoseat: Actually, that does mean it's the way to go. If games like Terraria, which has lot of lighting effects and dynamic maps, can do it, then yours can too. You need to be more familiar with how things are currently done and why they are before you can start trusting your instincts about whether or not those are the right way to handle things. \$\endgroup\$ Jul 23, 2012 at 18:29

4 Answers 4

6
\$\begingroup\$

Basically its like Marthon said, that most engines simply redraw the whole scene on every render call and not only portions of the visible scene. What you are looking for is a way to limit the objects/tiles, that are actually rendered. If you have a 2d array of tiles, that are all same sized its pretty simple to find the start and the end index of the x and y loop. My render method for a tile layer looks something like this:

public void render(Graphics g, MappedTile[][] tiles, int width, int height, float offsetX, float offsetY, float screenX, float screenY)
{
    int layers = 1;
    int startX = (int) (-offsetX / tileWidth);
    int startY = (int) (-offsetY / tileHeight);
    int endX = (int) ((-offsetX + screenX) / tileWidth + 1);
    int endY = (int) ((-offsetY + screenY) / tileHeight + 1);
    if(startX < 0)
        startX = 0;
    if(startY < 0)
        startY = 0;
    if(endX > width)
        endX = width;
    if(endY > height)
        endY = height;

    for(int y = startY; y < endY; y++)
    {
        for(int x = startX; x < endX; x++)
        {
            //Put render code in here... Select tile, apply offset and render
        }
    }
}

The idea is that you only render those tiles, that are visible on screen and skip those that are outside of the viewport. You don't have to recognize when a new tiles comes into the screen, as you recalculate the variables on every render call. Alternatively you could only update the position, if the viewport has changed since the last render pass.

Note, if you want to add scaling functionality, you would also need to change the calculation of startX, startY, endX and endY, so the scaling is taken into account.

For other objects, for example sprites, that can be positioned freely on a map, a better data structure is needed. You could implement a quad tree and use it to distribute the objects around cells. You then simply select only the visible cells and render the objects from those cells (after you sorted them on the y axis). But thats another topic.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I'm already doing this, but that is indeed a good optimization. \$\endgroup\$
    – omgnoseat
    Jul 23, 2012 at 18:12
  • \$\begingroup\$ Would not work if you need to render a shadow map. \$\endgroup\$ Jan 29, 2017 at 1:27
  • \$\begingroup\$ @MartinKersten Works fine for shadow maps, you just need to expand the range of things that get rendered for the shadow pass by the maximum radius of a shadow from its caster. \$\endgroup\$
    – Dan
    Feb 25, 2021 at 6:19
3
\$\begingroup\$

When designing for current PC GPUs you are not playing after the same performance rules, as, for example, a cellphone or the Amiga.

Modern GPUs are way more powerful than you can imagine and most of the time the bottleneck is not the drawing itself but actually the communication between your code running on the CPU and the GPU.

Take a look at this answer for performance suggestions on modern GPUs:
Drawing lots of tiles with OpenGL, the modern way

\$\endgroup\$
2
\$\begingroup\$

Not clearing the screen can actually degrade performance, because on double buffered framebuffer, a copy has to be performed when you swap buffers, which is apparently more expensive than simply clearing the current framebuffer to some default color value. (see https://stackoverflow.com/questions/2538662/how-does-glclear-improve-performance for example)

The performance hit of having to copy the tiles is probably lower than having to re-render them, but its something to consider.


So youre considering of doing either:

A

  1. Copy entire screen to the current framebuffer from the previous one.
  2. Render updated tiles on top

OR

B

  1. Clear current framebuffer to default color (cheaper than copy?)
  2. Render all tiles on top

And NOT

C

  1. Render updated tiles on top

(this would only be the case if double buffering was not used at all)

If you cant go with option A and dont want option B, you can maybe go with option:

D

  1. Clear current framebuffer to default color (cheaper than copy)
  2. Render updated tiles on top of an offscreen texture
  3. Copy this offscreen texture/framebuffer on top of current framebuffer

This allows you to render the scrolling background before you copy the offscreen texture with the tiles on top. Im assuming you have the ability to render on an offscreen texture/buffer with alpha support though. It would work as in:

  1. Clear current framebuffer
  2. Draw scrolling background to it
  3. Render updated tiles to the offscreen buffer (or clear existing ones by filling with transparent color values)
  4. Blend the offscreen buffer onto the current framebuffer, on top of the background

And of course profile your game with the correct tools to ensure you know exactly where the problem is, and make sure your rendering works as is conventional for the platform (using everything correctly, using the correct features optimally etc).

\$\endgroup\$
0
\$\begingroup\$

I am also working on a tile based game right now for mobile devices. I am working with a canvas, and the way i solved this problem is to make a rect object of the canvas, and then chech if the tile intersects the rect of the canvas using the intersect() method.

\$\endgroup\$

You must log in to answer this question.

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