Viewports & Split-Screen Games

Introduction

The idea of this tutorial is to go through how to use viewports in XNA. I thought that the best way to do that would be to demonstrate one of the better uses for viewports, which is in making a split-screen game. That's when you have multiple players that each see the game from their own viewpoint, and the screen is divided up and each player gets a part of the screen for themselves. In this tutorial, we will start with a little bit of groundwork, before going on to how viewports work, and then discuss how to make a game that uses a split-screen using viewports.

Preparation

Before we get started, there is a little bit of work that needs to be done. First of all, before you really get going on this tutorial, it would be good to have an understanding of how 3D games work. If you haven't already done that, I would suggest you at least go back and take a look at the tutorial on using 3D models in a game. In this tutorial, I am going to start with the code below, which includes elements from the Simple 3D Animation tutorial, as well as the BasicEffect lighting tutorial. You are free to work this stuff into your own program or to start from the code below.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
 
namespace WorkingWithModels
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private Model model;
        private Matrix world = Matrix.CreateTranslation(new Vector3(0, 0, 0));
        private Matrix view = Matrix.CreateLookAt(new Vector3(0, 0.001f, 4), new Vector3(0, 0, 0), Vector3.UnitZ);
        private Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.1f, 100f);
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
 
        protected override void Initialize()
        {
            base.Initialize();
        }
 
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            model = Content.Load<Model>("Ship");
        }
 
        protected override void UnloadContent()
        {
        }
 
        float angle = 0;
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            angle += 0.01f;
            world = Matrix.CreateRotationZ(angle);
 
            base.Update(gameTime);
        }
 
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
 
            DrawModel(model, world, view, projection);
 
            base.Draw(gameTime);
        }
 
        private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
 
                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                }
 
                mesh.Draw();
            }
        }
    }
}

This code requires the SimpleShip model from the 3D Model Library, which you can download and add to your project. When you run it, you should get something like the image below, with the ship being viewed from the top, with the model spinning in circles.

screenshot1.png

Viewports

XNA uses a class called the Viewport class. This class can be thought of as a window into the game. It is basically made up of just a rectangle on the screen, with a position and size, and also has information about depth, which XNA will use to determine the order of drawing triangles. Viewports are used frequently in XNA. When you tell your game to use a particular viewport, nothing outside of the viewport will get drawn.

In this tutorial, we are going to create four viewports to view our game from four different viewpoints, which would be typical in a four-player game. We will draw the game four times, once in each corner. For this, we will need to create four different view matrices, one for each viewpoint. In the code below, we create these four different view matrices, one from above the model (topView), one from the front of the model (frontView), one from the side of the model (sideView) and one from a random perspective view (perspectiveView). Additionally, we will create four Viewport objects, which will eventually be used for drawing each of the four viewpoints. Add the following four variables as instance variables to your game class:

private Matrix topView = Matrix.CreateLookAt(new Vector3(0, 0, 4), new Vector3(0, 0, 0), new Vector3(0, 0.001f, 1f));
private Matrix frontView = Matrix.CreateLookAt(new Vector3(0, 4, 0), new Vector3(0, 0, 0), Vector3.UnitZ);
private Matrix sideView = Matrix.CreateLookAt(new Vector3(4, 0, 0), new Vector3(0, 0, 0), Vector3.UnitZ);
private Matrix perspectiveView = Matrix.CreateLookAt(new Vector3(4, 4, 4), new Vector3(0, 0, 0), Vector3.UnitZ);
 
private Viewport topViewport;
private Viewport sideViewport;
private Viewport frontViewport;
private Viewport perspectiveViewport;

So now we should have a model matrix, a projection matrix, and four view matrices, as well as our new viewports. The next real step is to set up the viewports. You will want to add the following code somewhere to your game where it won't be called at every update. I put mine in the Initialize() method.

topViewport = new Viewport();
topViewport.X = 0;
topViewport.Y = 0;
topViewport.Width = 400;
topViewport.Height = 240;
topViewport.MinDepth = 0;
topViewport.MaxDepth = 1;
 
sideViewport = new Viewport();
sideViewport.X = 400;
sideViewport.Y = 0;
sideViewport.Width = 400;
sideViewport.Height = 240;
sideViewport.MinDepth = 0;
sideViewport.MaxDepth = 1;
 
frontViewport = new Viewport();
frontViewport.X = 0;
frontViewport.Y = 240;
frontViewport.Width = 400;
frontViewport.Height = 240;
frontViewport.MinDepth = 0;
frontViewport.MaxDepth = 1;
 
perspectiveViewport = new Viewport();
perspectiveViewport.X = 400;
perspectiveViewport.Y = 240;
perspectiveViewport.Width = 400;
perspectiveViewport.Height = 240;
perspectiveViewport.MinDepth = 0;
perspectiveViewport.MaxDepth = 1;

Notice that with this code, we've divided the default screen up into four equal-sized areas, with the topViewport in the upper left corner of the window, the sideViewport in the upper right corner of the window, the frontViewport in the lower-left corner, and the perspectiveViewport in the lower right corner.

Drawing with Split Screens

With our four view matrices prepared, and our viewports set up, all we need to do is handle the drawing of the game with the viewports to make a split screen game. This is fairly simple to do. By default, an XNA game is already using a viewport that covers the entire screen. The first thing we will need to do is store the default viewport to a temporary location, so that when we are done, we can restore it. Then, for each of the views, we will set the correct viewport, clear the viewport, and then draw the scene from the correct viewpoint. This is done with the change below.

Replace the line in the Draw() method that says:

DrawModel(model, world, view, projection);

with this:

Viewport original = graphics.GraphicsDevice.Viewport;
 
graphics.GraphicsDevice.Viewport = topViewport;
DrawModel(model, world, topView, projection);
 
graphics.GraphicsDevice.Viewport = sideViewport;
DrawModel(model, world, sideView, projection);
 
graphics.GraphicsDevice.Viewport = frontViewport;
DrawModel(model, world, frontView, projection);
 
graphics.GraphicsDevice.Viewport = perspectiveViewport;
DrawModel(model, world, perspectiveView, projection);
 
GraphicsDevice.Viewport = original;

With these changes, you should now be able to run your game, and see it from all of the different viewpoints, like in the screenshot below:

screenshot2.png

Click here for the complete project: ViewportsAndSplitScreen.zip

What's Next?

This tutorial covers the basics of viewports, by using them to make a split-screen game for four players. There isn't really another tutorial that is an ideal next step after this one, but you might want to go back and take a look at some of the other advanced XNA tutorials available here.


Troubleshooting.png Having problems with this tutorial? Try the troubleshooting page!