1
\$\begingroup\$

Which method do you expect would have better performance when stressed? It's a 2D tile map where the camera has view of a small portion of the map. As I pan up/down/left/right, should I be adding and removing tiles from the stage as needed? or have them loaded, offstage, and just change their x/y coords as needed?

I'm using AS3 to code this and I have it working fine. And I know it's not wise to try to optimize early, but since I'm very new at this, I thought maybe someone already knows if one is usually better.

\$\endgroup\$
7
\$\begingroup\$

In a case like this, you are going to want to Pool your world tiles. The idea is to pre-load a fixed amount of tiles that you will be re-using during the rendering of your world.

When setting a tile into the world, you would typically set certain properties such as its position and texture. The rest of the object is already ready to go in memory.

The idea is to try to retain some tiles on the screen and only return them into your Pool when they are far enough. This way, if you revisit an area that was already set, you probably do not have to process as many tiles.

enter image description here

Some sample code that was used to achieve the effect in the gif above:

List<Tile> LoadedTiles = new List<Tile>();

public void Refresh()
{       
    // Determine your start/end X and Y coordinates (viewable area)
    // Make sure to add some padding to ensure you don't see any flickering on the sides
    int startx = View.x < 0 ? (int)(View.x + (-View.x % 1)) : (int)(View.x - (View.x % 1));
    int starty = View.y < 0 ? (int)(View.y + (-View.y % 1)) : (int)(View.y - (View.y % 1));
    startx -= 1;
    starty += 1; 
    int endx = startx + (int)(View.width) + 2;
    int endy = starty - (int)(View.height) - 2;

    // Keep track of visible tiles in this refresh
    HashSet<Tile> inCurrentView = new HashSet<Tile>();

     // Loop through all the tile indices inside of our current view port
    for (int i = startx; i <= endx; i += 1)
    {
        for (int j = starty; j >= endy; j -= 1)
        {
            int x = i;
            int y = j;

            // Make sure your index values are not negative.
            // In this case we are wrapping the world around on the X-axis.  If you are not wrapping, you need
            // to figure out coordinate system that will not result in negative values.
            x = MathHelper.Mod (x, Generator.Width);
            y = MathHelper.Mod (y, Generator.Height);

            // Fetch the tile data at this index - Store in our hashset
            Tile tile = Generator.GetTile (x, y);
            inCurrentView.Add(tile);

            // If the tile being processed is not loaded, then get a Tile from our Pool and load it into view.
            if (!LoadedTiles.Contains(tile))
            {
                if (tile.PoolableGameObject.Item == null)
                {
                    if (TilePool.AvailableCount > 0)
                    {
                        // Get a tile GameObject from our Tile Pool
                        Pool<PoolableGameObject>.Node node = TilePool.Get();
                        node.Item.Set(tile, new Vector2(i, j));
                        LoadedTiles.Add(tile);
                    }
                }                                             
            }
            else {
                // tile already loaded, refresh it
                tile.PoolableGameObject.Item.Set(tile, new Vector2(i, j));
            }
        }
    }


    // Get list of Tiles that are currently outside our view
    // This will remove every single tile that is not in view. 
    // You can refine this to only return tiles when a lower threshold of tiles remaining is met.
    List<Tile> toRemove = new List<Tile>();
    for (int i = 0; i < LoadedTiles.Count; i++) {
        Tile tile = LoadedTiles[i];
        if (!inCurrentView.Contains(tile))
            toRemove.Add(tile);
    }

    // Return tiles that are no longer in view back into our Pool to be re-used
    for (int i = 0; i < toRemove.Count; i++) {
        Tile tile = toRemove[i];
        LoadedTiles.Remove(tile);
        TilePool.Return(tile.PoolableGameObject);        
    }

}
\$\endgroup\$
  • \$\begingroup\$ Would love to see some psuedo code of achieving what that gif is doing! :) \$\endgroup\$ – lozzajp Sep 13 '16 at 15:57
  • \$\begingroup\$ I posted some sample code in my post above as you have requested. I actually started writing up an article on how to accomplish this style of rendering in Unity, for large maps. It was a continuation of my World Generation tutorials. Not sure when that article will be posted, but if you have questions about it let me know. \$\endgroup\$ – jgallant Sep 13 '16 at 16:39
2
\$\begingroup\$

I personally store my map data in a 2D array.

map[y][x]

It doesn't take up that much memory space. I mean, considering the amount of RAM current machines have, a few MBs (10-20 usually, but it depends on the size of the map, of course) is not a big deal.

However I had concerns regarding the CPU usage. When rendering the map, I simply set the code to only render the tiles which are visible. Some pseudo code:

for(int i=currentPosY-someAmount;i<=currentPosY+someAmount;i++){
    for(int y=currentPosX-someAmount;y<=currentPosX+someAmount;y++){
       render(map[y][i]);
    }
}

With this the CPU usage is around 2-3% from 4-6% (if I remember correctly the original amount).

\$\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.