0
\$\begingroup\$

I created a tile-based rpg game in java, but it runs really really slow, the reason is the big tile-map that is stored in the buffer. Is there any way to programmatically load pieces of the map when needed and not the entire map at once, so that the buffer will remain small and the game will run fast? Any sort of useful tip would be helpful.

\$\endgroup\$
  • \$\begingroup\$ Without any details about your implementation, some common tips: * Use instancing to load each tile's image only once, on the chance having all the tiles loaded distinctly is causing some of the slowdown * Render only those tiles that are on screen - within (x,y) range of the camera. If you're not already doing this, it will be a big help. * Update only those tiles/actors that are within X number of screens from the camera/player. If you have a lot of things moving around on the map, limiting updates to only those things near your player character will help a lot as well. \$\endgroup\$ – LLL79 Sep 11 '14 at 16:11
  • \$\begingroup\$ Are your images prerendered or are you building them on the fly? If they're prerendered there's some built-in techniques for only loading when needed. \$\endgroup\$ – Eben Sep 11 '14 at 18:18
  • \$\begingroup\$ @LLL79 yes this is what I want to do - draw only the portion of the map that is within the range of the camera. But how should i do it? my map is a big 2d array. Is there a way to break the map into pieces? \$\endgroup\$ – arandomguy Sep 11 '14 at 22:13
1
\$\begingroup\$

Oh man- I've implemented this a time ago...

enter image description here

The map is divided into map chunks - I've created separate files for each map chunk... a map chunk consist of 32x32 tiles in my implementation, you may vary on this

whenever the player (centre of red X) is moving inside a chunk you can detect if the player is moving from one chunk into another...

// Depending on chunk size - change 32 to your desired size

int xChunkBefore = xPlayer/32;    
int yChunkBefore = yPlayer/32;  

// Move player to new position

int xChunkAfter = xPlayer/32;     
int yChunkAfter = yPlayer/32; 

if (xChunkBefore == xChunkAfter && yChunkBefore == yChunkAfter)
{
    // No changes detected
} 
else
{
    boolean isTeleport = someBool // check if teleport
    if (isTeleport){
         recreateWholeViewPort(newChunkCenter); //make all new!
    }
    else
    {
        Direction dir = determineDirection();
        updateChunksInView(newChunkCenter);
    }
}

So what to do now? When you have do display your view-port you don't have ALL chunks in memory only a 3x3 MapChunk[x][y]...

And when you change into a new centre you do the following:

if (dir == Direction.NORTH)
{
    // All three chunks
    for (int dx = 0; dx < 3; dx ++) 
    {
        viewPort.mapChunk[dx][2] = viewPort.mapChunk[dx][1];
        viewPort.mapChunk[dx][1] = viewPort.mapChunk[dx][0];
        viewPort.mapChunk[dx][0] = loadFromFile();            
    }
}
// So so for all other directions

If you are using this approach you can theoretically have an unlimited large map but only show 3x3 map chunks...

NOTE - Directional movement is not mentioned here

NOTE - Buffering last movement is not mentioned here

NOTE - Teleport (load ALL) is not mentioned here

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ thanks for formatting - this is not my best discipline ^^ hehe! \$\endgroup\$ – Martin Frank Sep 17 '14 at 9:46
1
\$\begingroup\$

To expand upon my earlier comment: For a 2D tile map, I don't usually see any need to stream the map into memory, or load it by chunks. The easiest gains will come from simply not rendering any tiles that are off screen, and not updating any entities that are more than X screen size units away. If the map is truly massive, to the point that you can't iterate through it to cull your off-screen draw calls without breaking your frame-rate, then and only then will you want to find a way to break the map into chunks.

Let's assume your camera is tracked as being at an (x,y) point, and that unless the player character is at the edge of the map, the camera position matches the player position.

The first thing you'll need to know is the screen size, which is language/library dependent, and also another question.

For the sake of example, you've determined the screen size is 1920x1080. Having access to the tile assets, you know they're 16x16. You can then determine that you need to draw 1920/16 x 1080/16 => 120 tiles horizontally, and 68 (67.5) tiles vertically. Considering that the player/camera may not always be directly centered on a tile, you'll want to add a 1 tile buffer on either side in both dimensions. In terms of tile size, your screen dimensions are 122x70.

You'll also need to know the relative position of that screen area within the tile array. If the player/camera is at (4237, 5642), then your player is on tile (x.pos/tile.width, y.pos/tile.height) => (4237/16, 5642/16) => (264, 352). Taking the screen size in tile units from above (122x70), halving it, and adding to / subtracting from the player tile in each direction, your on-screen tiles would therefore range from (203,317) to (325,387). For later reference, I'll call these (xMinTile, yMinTile) and (xMaxTile, yMaxTile).

In your rendering function, where you determine which tiles to pass off to the video card, instead of sending the entire array of tiles you'll check against the min/max tile positions.

 renderer.beginBatch()
 for(i = 0; i < map.size.x; ++i)
    if(i >= xMinTile && i <= xMaxTile)
       for(j = 0; j < map.size.y; ++j)
          if(j >= yMinTile && j <= yMaxTile)
             renderer.draw(map[i,j])
 renderer.endBatch()

 # please excuse any Java syntax errors, I've been working mostly with Ruby lately

The 'j' for loop is inside the if check for x bounds to somewhat optimize the test. Similar to AABB collision testing, why check the Y dimension if we already know X is out of range? This is important to getting as much mileage out of this approach as possible, as is sending your tiles in a draw batch (assuming your renderer supports that, which... it really should), before you have to start breaking the map into chunks.

You'll have to make changes for determining the on-screen tiles as the player approaches the edge of the map, whether it overflows and wraps around, or just caps to the edge tile so you don't see the neutral zone outside the map boundaries, but this should cover the general idea.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I don't need to cull offscreen draw calls. I just don't don't draw them by figuring out what to iterate. The only limitation is the memory the map takes. \$\endgroup\$ – Madmenyo Dec 19 '15 at 8:37
0
\$\begingroup\$

Yes, of course there is a way...if you implement it.

The technique you seem to be looking is often referred to as "terrain paging". It stems from the fact that you divide your terrain/map into multiple pages (basically chunks / tiles) and only load / render those close to the player.

In your tile-map scenario, you already have the pages (= your tiles or sections of tiles depending on how big they are). Given that you know where the player is you know which tiles need to be rendered.

There are some points to consider when deciding on how many tiles to load / display:

  • View that the users has (top down vs. 1st/3rd person with a horizontal view): Depending on that you might need more tiles to be rendered to create the illusion of a vast landscape.
  • Movement speed of player: The faster the player can move the more pages you will need to prepare upfront to prevent lags when the users leaves old pages and enters new ones.
  • You also need to separate between loading and rendering: It might make sense to already pre-load some of the resources needed for future tiles (models, textures, etc.) but simply not render them. This will cut down the time needed to prepare a new page, since you already have everything in the memory, but simply did not render it so far.
  • To prevent lags in the rendering process, it might make sense to load the data for the next pages in the background to keep your main thread humming along nicely.
  • Another technique that might help, is LoD (Level of Detail). In games you can often get away with rendering lower quality objects in the far distance (meaning less polygons and low resolution textures). This can also improve your game's performance.
| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

You can start by doing this ...

I can load new tiles but how do I load new chunks?

Once you have that in place you can then choose what chunks to render by using some basic positional logic like "get me all the chunks with tiles between pos1 and pos2"

pos1 and pos2 can be simply determined like this ...

pos1 = char.Position - renderDistance;
pos2 = char.Position + renderDistance;
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.