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.

Friday, January 27, 2012

Game Elements 1: 2D Camera

Introduction

For my first tutorial I will explain how to create a 2D camera that follows a character using XNA. Since this is my first tutorial I will start a bit slow and introduce some basic concepts so that I do not have to spend as much time on them in future tutorials.

The reason I am making this and other tutorials is because I have been programming games for a few years and have made many beginner mistakes. In the case of Cameras I would often move keep the character stationary and then move the rest of the tiles, which worked fine initially but then I wanted to find the position of the player to see if they were standing on a button and I had to do a whole bunch of complex calculations. Then I learned this technique and my life became so much easier.

Displaying our Character and Background



Lets start with drawing our scene. I have included the two images I use above, you can also find them by following the link to the download package at the end of the tutorial.

First we need couple of local variables to the top of our Game1.cs file. Two for storing the images and two more for the characters position and speed:

// local variables used for this tutorial
Texture2D character, sandtile;
Vector2 position = new Vector2(100,100);
int speed = 5;


Next we load our images in the load content method as follows:

// load the two images
character = Content.Load("Images/character");
sandtile = Content.Load("Images/sand");

Then we draw our tiles using by repeatedly drawing the sand tiles in a loop, and then the character:

spriteBatch.Begin();

// Draw a 10 by 10 grid of tiles
for (int x = 0; x < 10; x++)
{
 for (int y = 0; y < 10; y++)
 {
  spriteBatch.Draw(sandtile, 
   new Vector2(x*sandtile.Width, y*sandtile.Height), null, Color.White, 0, 
   Vector2.Zero, 1, SpriteEffects.None, 0);
 }
}

// Draw the character using the position variable for its position
spriteBatch.Draw(character, position, null, Color.White, 0,
 new Vector2(character.Width / 2, character.Height / 2), 
 1, SpriteEffects.None, 0);

spriteBatch.End();
Moving the Character
Now that we have our scene lets get the character moving using the keyboard. Simply add this to the update method:
// Use arrow keys to move the player
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
 position.Y -= speed;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
 position.Y += speed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
 position.X -= speed;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
 position.X += speed;
}
If you run the game at this point, you will be able to move the character around. However, if you move to far in one direction you will disappear off the screen.
player can move, but camera does not follow
Creating the Camera
We will create the camera as a new class. It will have three variables for position, zoom and rotation and one function for obtaining the transformation matrix. We will make all of these static because generally in 2D games we will only use one camera. Here is the code for the camera class:
public class Camera
{
 public static Vector2 Position = Vector2.Zero;

 public static float Zoom = 1;

 public static float Rotation = 0;

 public static Matrix GetTransformation(GraphicsDevice graphicsdevice)
 {
  return Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
   Matrix.CreateRotationZ(Rotation) *
   Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) *
   Matrix.CreateTranslation(new Vector3(
    graphicsdevice.Viewport.Width * 0.5f,
    graphicsdevice.Viewport.Height * 0.5f, 0));
 }
}
Once that is done we can apply the transformation to the spritebatch begin function in our games draw method:
// Notice that the last argument is the camera's transformation matrix.
// The other arguments are just the default values.
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, 
 SamplerState.LinearClamp, DepthStencilState.Default, 
 RasterizerState.CullCounterClockwise,
 null, Camera.GetTransformation(GraphicsDevice));
Lastly, add this to the end of the update method so that the camera position will be updated ever time the character position changes:
// when the player position changes, so should the camera
Camera.Position = position;
Now when you run the program you will notice that the character always stays at the center of the screen and the camera follows.
player stays at center of the screen and camera follows
Downloads
Source code and images can be found in the download package for this tutorial:
Extensions
I have covered the basics but there are a few more things you can do with this camera that you can try on your own. For example, try setting up keys for zooming and rotating the camera. You can also try to extend this so that the camera only moves when the character gets closer to the edge of the screen. Let me know in the comments below if you making something interesting or have any questions about this tutorial.

Thursday, January 26, 2012

Introduction

Welcome to my new blog, App Elements. I have been programming games and applications all my life. I created this blog to share what I have learned and to help prevent others from making the same rookie mistakes I did when I first started.

I plan on posting regularly (2 times a week), at least until the summer, and will be posting individual quick tutorials, high level design concepts and a series or two that covers couple more complicated projects. So check back regularly for new tutorials.