How do I efficiently render the tiles of a tile map?

I've been working on a game, and I've been using Python with Pyglet to create it. I had an issue on how I could do effective tile based rendering. I tried cocos2d, however the API is very efficient and doesn't support maps which are larger than 1000x1000 tiles. I did some further research, and found you could make it so when you triggered an update, only sprites on the edge of the screen would update, however it is still buggy (it's not fully working).

Here is my code, and here's some relevant rendering code:

self.camera[0]-=amount
for a in range(self.window.height//self.block_size):
self.sprites[(self.camera[0]//self.block_size)-1][a+(self.camera[1]//self.block_size)].batch = render
self.sprites[self.window.width//self.block_size+(self.camera[0]//self.block_size)+1][a+(self.camera[1]//self.block_size)].batch = None


What approach should I use to render maps larger than 1000x1000 tiles?

To support drawing tile-based map of arbitrarily large bounds, you want to take the approach of not rendering every tile all the time. You only need to attempt to draw what the player could see, any other draw attempts are a waste of processing time and resources. This is an API-agnostic approach and it doesn't matter whether you are using Pygame, cocos2d, or any other API.

To accomplish this feat, you need to know some information that is basically an extension of that you'd need for handling scrolling in a side-scrolling platformer.

You need to know

• the position in within the global tile map that the player is looking at,
• the size (in tiles) of the visible around centered around that look position

Every update you simply compute the bounding rectangle that describes which tiles are visible based on those two pieces of information and submit only those tiles for rendering. To re-use the code snippet from my answer in the above-linked question, you compute

var viewportLeft = focusPoint.X - (viewportSize.Width / 2);
var viewportRight = focusPoint.X + (viewportSize.Width / 2);
var viewportTop = focusPoint.Y + (viewportHeight / 2);
var viewportBottom = focusPoint.Y - (viewportHeight / 2);


and draw only the tiles bounded by the above four variables.

If your game support smooth scrolling of the tile map, rather than tile-by-tile scrolling, you may want to expand the viewport width and height slightly to account for the potential overlap.

To efficiently render the set of tiles you know about, you want to minimize state changes and draw calls. Ideally, your actual tile sprites are stored in a texture image, a specific type of texture atlas where all the images in the atlas fit on grid. This allows you to have only that one texture and related shader bound, minimizing those state changes.

To reduce draw calls, you want to have all the geometry for all the tiles you are going to render in as few vertex buffers as possible. If you are using the 2D sprite rendering functionality of your higher-level API, most of those will support the batching of geometry (and state changes, for that matter) in their "sprite batch" style classes if they have begin and end type members. If not you may need to do it manually, usually by making use of a dynamic vertex buffer which you refill with the tile vertices, texture coordinates, and so for all the appropriate tiles every frame.

• Thanks for the detailed response on rendering @JoshPetrie. However, rendering all the tiles on the screen proves quite slow epically if there are up to thousands at once needing to move on and off. I used a method which rendered only the sides when they needed to be added to the "render list" (I render via batches). This method is gone into detail here – user39991 Dec 16 '13 at 20:46
• Rendering only the on-screen tiles is acceptably fast depending on your implementation thereof; it would be worth looking into what you did there to see if there is a simple bug. That said, rendering into a cache as tiles become visible is an option as well although it has it's own collection I negatives. – Josh Dec 16 '13 at 21:04
• That said, you should document your chosen approach as an answer to your own question. – Josh Dec 16 '13 at 21:07
• Ok, thanks for the feedback I've been searching for. I did make a write up, however when I drew up my response, it was too long, and reducing its length would mean it wouldn't go into depth. I will use the information provided by the internet via various sources to make a choice, and keep on working on it. – user39991 Dec 16 '13 at 21:38