Monday, January 30, 2012

Game Elements 2: Efficient Tile Map

Introduction

In my last tutorial we created a character that moves around on top of a map made of several images pieced together in a 10 by 10 grid. Well everything seems to work fine for a 10 by 10 grid, but if you try to make the grid larger than 100 by 100 your game might start to get choppy. In this tutorial I will show how to only draw the tiles that are visible, something that will help us make larger more complicated tile maps in future tutorials.

Determining the Camera's View

If we want our tile map to be efficient we should only be drawing tiles that the camera is looking at, and not draw the ones that are off of the screen.

To do this we first go to our camera class and add two new methods. First, to get the size of the camera's view. GraphicsDevice contains a viewport object that has the size of the screen, however the camera may be zoomed in or out so we need to divide the viewport size by the zoom amount. Then once we have the view size we need calculate the view rectangle.

public static Vector2 GetViewSize()
{
 Viewport viewport = Game.GraphicsDevice.Viewport;
 return new Vector2(viewport.Width / Zoom, viewport.Height / Zoom);
}

public static Rectangle GetViewRectangle()
{
 Vector2 size = GetViewSize();
 Vector2 topleft = Position - size / 2;
 return new Rectangle((int)topleft.X, (int)topleft.Y, (int)size.X, (int)size.Y);
}

Drawing Tiles

Now we need to calculate which tile index will be in the upper right corner and which will be in the lower left corner. To do this we first need a function that can take a point in our scene and return the index of the tile at that point:

public Vector2 PointToTileIndex(Vector2 pos)
{
 return new Vector2(pos.X / sandtile.Width, pos.Y / sandtile.Height);
}

Then add this to the beginning of our draw method to call these functions and to prevent the results from being smaller than 0,0 or greater than the map size:

Rectangle viewrect = Camera.GetViewRectangle(GraphicsDevice);
Vector2 tile1 = PointToTileIndex(new Vector2(viewrect.X, viewrect.Y));
Vector2 tile2 = PointToTileIndex(new Vector2(viewrect.X + viewrect.Width + 150, viewrect.Y + viewrect.Height + 150));
int topx = Math.Max(0, (int)tile1.X);
int topy = Math.Max(0, (int)tile1.Y);
int bottomx = Math.Min((int)mapSize.X, (int)tile2.X);
int bottomy = Math.Min((int)mapSize.Y, (int)tile2.Y);

Lastly, in our loop instead of drawing all of the tiles we start at the top left of the camera's view and end at the bottom right:

for (int x = topx; x < bottomx; x++)
{
 for (int y = topy; y < bottomy; y++)
 {
  spriteBatch.Draw(sandtile, 
   new Vector2(x*sandtile.Width, y*sandtile.Height), null, Color.White, 0, 
   Vector2.Zero, 1, SpriteEffects.None, 0);
 }
}
Now try increasing the size of your tile map, something like 10000 by 10000 and notice that it runs as smoothly as the 10 by 10 did initially. If you are still not sure if any thing is happening, adjust the size of the camera's view rectangle and you should see tiles appearing and disappearing around the edges of the screen as you move.  

Download Package
Source code can be found in the download package for this tutorial:
Download Source  

Extensions
I have covered the basics but there are a few more things you can do with this example. For example, this code does not handle camera rotations. Let me know in the comments below if you making something interesting with this or have any questions about this tutorial.

No comments:

Post a Comment