Friday, February 24, 2012

Game Elements 6: Multi-Character Camera


 In my last couple of tutorials I talked about shaders. Today I am going to take a quick diversion from that and go back to talking about the camera. I will be starting with the code I created from game elements tutorial 1, so make sure you check that out so that you can follow along.

I have already explained how to create a camera that follows the players character. However, sometimes we want the camera to follow multiple characters, like if the game is multiplayer. So what I am going to explain today is how to create a camera automatically adjusts so that all of the characters are always in view. If you still do not understand what I mean, check out the video example at the top of the tutorial.

A multiplayer camera can be divided into two separate parts. First we want the camera to move to follow both characters and secondly we want the camera to zoom in and out so that the even if the characters move far apart, they will both always be in view.

To get the camera to move, we first need to know the locations of the objects that we want to follow. Since this is a simple example I have just added a list of positions to the camera class:

public static List<Vector2> ViewTargets = new List<Vector2>();

Now to follow the characters we want to define a rectangle that contains all of the positions and then focus the camera on the center point of this rectangle. Since we want to update the rectangle every time one of the objects moves, we are going to need to recalculate the rectangle each time. So what I have done was create this update function:

public static void Update(GraphicsDevice graphicsdevice)
{
    if (ViewTargets.Count > 0)
    {
        Vector2 min = ViewTargets[0];
        Vector2 max = ViewTargets[0];
        for (int i = 1; i < ViewTargets.Count; i++)
        {
            if (ViewTargets[i].X < min.X) min.X = ViewTargets[i].X;
            else if (ViewTargets[i].X > max.X) max.X = ViewTargets[i].X;
            if (ViewTargets[i].Y < min.Y) min.Y = ViewTargets[i].Y;
            else if (ViewTargets[i].Y > max.Y) max.Y = ViewTargets[i].Y;
        }
        Rectangle rect = new Rectangle((int)min.X, (int)min.Y,
            (int)(max.X - min.X), (int)(max.Y - min.Y));

        Position = new Vector2(rect.Center.X, rect.Center.Y);
    }
}

As you can see, I define the rectangle by finding the min and max values of the positions. Then I get the center point of the rectangle and use that as the focus of the target.

To get this all working, in the main class's update method where we had put in a line to update the camera's position to make it match the position of the object, we now want the position to be calculated in the camera's update method and we also want to pass the camera the positions of the objects. So replace that line with the following, where "position2" is the position of a second object that you can add yourself:

Camera.ViewTargets.Clear();
Camera.ViewTargets.Add(position);
Camera.ViewTargets.Add(position2);
Camera.Update(GraphicsDevice);

Now when you run the game the camera will move so that it will focus on the point halfway between the two objects. However, if you move too far apart the objects will move outside of the view of the camera. So what we want to do is to zoom in and out so that the objects are always on screen.

The steps to figure out how much to zoom are pretty simple. First we need to calculate the difference between the height and width of the screen size and that rectangle that just barely contains all of the objects. Then once we have that we set the zoom value to be equal to the smallest difference. So add this code to the end of the camera's update function:

float widthdiff = ((float)graphicsdevice.Viewport.Width) / ((float)rect.Width);
float heightdiff = ((float)graphicsdevice.Viewport.Height) / ((float)rect.Height);
Zoom = Math.Min(widthdiff, heightdiff);

It you run your program now, you should notice that this works but that one problem remains. Although the characters are always visible on screen, the are always right at the edges of the screen, perhaps even a bit cut off. We would rather have a space between the characters and the edge of the screen. Luckly this is easily to add by simply increase the size of the rectangle. XNA even has a function to do this, just add this line to right after where you first define the rectangle in the camera's update function:

rect.Inflate(200, 200);

You should now have a multi-character camera that automatically moves and zooms to follow the objects you want to be in focus. There are a few obvious extensions to this algorithm, such as putting min and max zoom constraints on the camera or making the camera more gradually move and zoom to the ideal position. However I will leave these as exercises for your to try on your own. Also if you have any questions or create something interesting that you want to share please leave a message in the comments.

No comments:

Post a Comment