Latest News
Mar 27, 2017, 7:34 AM

Introduction

In this tutorial, we’ll explore the principles of screen management, then begin writing a basic screen management system in XNA.

So what makes my Screen Management Tutorial better than the others? Nothing. Every screen management system has it’s strengths and weaknesses, and that’s true for tutorials as well. This particular screen management system is designed to make each screen operate on its own, and have the same structure as a new XNA project. This tutorial is designed to be easy to follow and straight forward. I do make two assumptions about the reader to make things easier for me. First, I’m assuming you know C# programming. Second, I’m assuming you are familiar with XNA.

This tutorial will teach you how to build a basic screen management system. It will not provide you a game engine. It will not teach you how to fade between screens. It will not teach you how to build a menu system. In my opinion, anything that I’ve left out of this tutorial should be in a separate tutorial.

Most game players aren’t interested in knowing how a game works. If you mentioned a game screen to the average game player, they would probably think you were referring to their television set. As game developers, we have an entirely different definition of what a game screen is. In the short definition I can think of, a game screen is a single screen within a game. The main menu might be one screen, the game world is another, the hud displayed over the game world might be yet another. A game screen might cover the entire viewing area of the video display, or it might only cover a small portion overlaid over other game screens.

We’ll only be building a basic screen management system. It is left up to the reader to expand upon what is provided to fit the needs of your game. To prepare for this tutorial, you’ll need to create a new XNA project, then delete all the cs files. You’ll be creating your own cs files from the example code.

Screen Management Explained

Most games use a lot of assets (images, sounds, music, etc.), and maintaining them can be a virtual nightmare. By using screen management, we make this task a little easier. In this section, we are going to take a moment to look at the concept of screen management and how it should work. You may skip this section if you want, but this conceptual knowledge may help you to better understand how the system works later.

The first concept in screen management is the definition and separation of screens. To define a screen, we’ll need to define something that all screens can inherit that declares all the public methods and parameters that every screen will have. When designing a screen management system, the largest decision here is whether to use an Interface, or an inheritable class to define a screen.

The separation of screens is where we decide what parts of our games can stand alone as individual screens. In general, only one screen gets input at a time, but that is not a rule set in stone. It’s entirely possible to build the screen management system to allow more than one screen to accept input at the same time, in this case, it’s up to the developer to decide which screen gets the input when more than one screen accepts the same input, or if both screens should react at once.

The second major concept of screen management is the screen manager itself. The screen manager is the heart of screen management, it tracks what screens have been added, which ones are visible, and which ones should get input. It’s the memory management class that initializes new screens and tells existing screens to perform updates and draw themselves. It’s also in the memory management class where screens that are no longer needed are closed, unloaded, and destroyed.

One main thing to remember is that screens are never directly interacted with in the code except through the screen manager class. It’s also important to understand that very little game code will exist outside of screens. As we proceed forward, these concepts should make more sense the closer we get to having a completed screen management system.

Screen Definition

In defining a screen, we can choose to use either an Interface or a class that every screen must inherit. We must then decide what properties and attributes a screen must inherit. The second decision is conveniently answered for us by the default XNA project. To answer the first, I suggest using a class to be inherited as that allows you to provide basic code common to all screens, for instance, this base class is where an asset management system would be built, allowing each screen to completely manage its own assets. Using a class also allows you to declare default values for the variables declared in it. To start with, however, we’re going to build an empty skeleton class as that will be enough for now.

In your project, you shouldn’t have any cs files. In the solution explorer, right-click on the project name, and select add, new folder. Name this folder “Screens”. Now add a class to that folder named GameScreen.cs. Replace the entire contents of that file with the code below, rename the namespace accordingly.

using Microsoft.Xna.Framework;

namespace XNATutorials.Screens
{
public class GameScreen
{
public bool IsActive = true;
public bool IsPopup = false;
public Color BackgroundColor = Color.CornflowerBlue;

public virtual void LoadAssets() { }
public virtual void Update(GameTime gameTime) { }
public virtual void Draw(GameTime gameTime) { }
public virtual void UnloadAssets() { }
}
}

The declaration of this class is fairly self-explanatory, but our intended usage of the variables may need a little clarification. The screen will only receive player input if IsActive is set to true. BackgroundColor is only used if the screen covers the entire visual display, signified by IsPopup being false.

The Screen Manager Class

Finally, we come to the meat and potatoes of screen management, the screen manager itself. At this point, our project should contain only one folder containing GameScreen.cs. Let’s start by adding the ScreenManager class. Right-click on your project’s name in the Solution Explorer and select add – class. Name this file ScreenManager.cs and delete its entire contents.

This file’s going to be a little large, so we’re going to step through the file from top to bottom one section/method at a time. We won’t be jumping back and forth like so many other tutorials do, I find that to be just too confusing to follow.

As each piece of code is provided, past it below the last code you pasted in your source file.

Let’s start by declaring what assemblies we will be using in this file. Past the following into your source file:

using System;
using System.Collections.Generic;
using XNATutorials.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace XNATutorials
{
public class ScreenManager : Game
{
public static GraphicsDeviceManager GraphicsDeviceMgr;
public static SpriteBatch Sprites;
public static Dictionary<string, Texture2D> Textures2D;
public static Dictionary<string, Texture3D> Textures3D;
public static Dictionary<string, SpriteFont> Fonts;
public static Dictionary<string, Model> Models;
public static List<GameScreen> ScreenList;
public static ContentManager ContentMgr;

public static void Main()
{
using(ScreenManager manager = newScreenManager())
{
manager.Run();
}
}

So far this is all fairly basic. We’ve declared what assemblies we are going to use, started the class, and defined a few public variables. The first two are fairly basic to XNA. The dictionaries might have you wondering though. By having these dictionaries in the screen manager, we can load assets into the screen manager for multiple screens to use. That is, we can load it once and use it everywhere. ScreenList is a list of screens that have been added to the game stored in an array format. Finally, we created a content manager variable so we can have access to it from within screens themselves.

You may also note that the screen manager class inherits from XNA’s Game class. That means that the screen manager is the starting location for the game itself. When you think about it, that makes sense because the screen manager essentially acts as the brain of the game.

    public ScreenManager()
{
GraphicsDeviceMgr = new GraphicsDeviceManager(this);
GraphicsDeviceMgr.PreferredBackBufferWidth = 800;
GraphicsDeviceMgr.PreferredBackBufferHeight = 600;
GraphicsDeviceMgr.IsFullScreen = true;
Content.RootDirectory = "Content";
}

This first method is the screen managers constructor. Take note that it’s in this method that we’re setting the size of the screen and specifying that it should run in full-screen mode. On the XBox 360 you don’t need to specify that it will run in full screen as all applications run in full screen on the console.

    protected override void Initialize()
{
Textures2D = new Dictionary<string,Texture2D>();
Textures3D = new Dictionary<string,Texture3D>();
Models = new Dictionary<string,Model>();
Fonts = new Dictionary<string,SpriteFont>();
base.Initialize();
}

As it’s name suggests, this method initializes the screen manager and sets it’s variables to new instances to prevent them from being null later on.

    protected override void LoadContent()
{
ContentMgr = Content;
Sprites = new SpriteBatch(GraphicsDevice);
// Load any full game assets here
AddScreen(newTestScreen());
}

It’s in this method that you will want to load any assets that should be accessible to the whole game. It’s also here where we declare what the first screen of our game should be. In this case, our first screen will be a screen named “TestScreen”.

    protected override void UnloadContent()
{
foreach(var screen in ScreenList)
{
screen.UnloadAssets();
}
Textures2D.Clear();
Textures3D.Clear();
Fonts.Clear();
Models.Clear();
ScreenList.Clear();
Content.Unload();
}

When the game is preparing to shutdown, this method will be used to unload any assets or other content from memory.

    protected override void Update(GameTime gameTime)
{
try
{
// TODO Remove temp code
if(Keyboard.GetState().IsKeyDown(Keys.Escape))
{
Exit();
}
var startIndex = ScreenList.Count - 1;
while(ScreenList[startIndex].IsPopup && ScreenList[startIndex].IsActive)
{
startIndex--;
}
for(var i = startIndex; i < ScreenList.Count; i++)
{
ScreenList[i].Update(gameTime);
}
}
catch(Exception ex)
{
// ErrorLog.AddError(ex);
throw ex;
}
finally
{
base.Update(gameTime);
}
}

In XNA, the update method is where all game logic is updated and any necessary calculations are performed. Since this is a screen manager that we’re building, then we’ll cycle through all the top screens and call upon their update methods. Normally we would call upon an input manager before we update the screens, but we don’t have one and aren’t building on in this tutorial. Instead, I’ve added some temporary code to allow you to exit the game by pressing Esc on your keyboard.

    protected override void Draw(GameTime gameTime)
{
var startIndex = ScreenList.Count - 1;
while(ScreenList[startIndex].IsPopup)
{
startIndex--;
}
GraphicsDevice.Clear(ScreenList[startIndex].BackgroundColor);
GraphicsDeviceMgr.GraphicsDevice.Clear(ScreenList[startIndex].BackgroundColor);
for(var i = startIndex; i < ScreenList.Count; i++)
{
ScreenList[i].Draw(gameTime);
}
base.Draw(gameTime);
}

XNA calls upon this method several times a second. Here we cycle through the top screens yet again to find the first visible screen, then call upon each screens draw method after it. This allows the most recent screen to be drawn on top of the previous screens. It’s also here where we set the background color to the background color specified in the first visible screen.

    public static void AddFont(string fontName)
{
if(Fonts == null)
{
Fonts = new Dictionary<string,SpriteFont>();
}
if(!Fonts.ContainsKey(fontName))
{
Fonts.Add(fontName, ContentMgr.Load<SpriteFont>(fontName));
}
}

public static void RemoveFont(string fontName)
{
if(Fonts.ContainsKey(fontName))
{
Fonts.Remove(fontName);
}
}

public static void AddTexture2D(string textureName)
{
if(Textures2D == null)
{
Textures2D = new Dictionary<string,Texture2D>();
}
if(!Textures2D.ContainsKey(textureName))
{
Textures2D.Add(textureName, ContentMgr.Load<Texture2D>(textureName));
}
}

public static void RemoveTexture2D(string textureName)
{
if(Textures2D.ContainsKey(textureName))
{
Textures2D.Remove(textureName);
}
}

public static void AddTexture3D(string textureName)
{
if(Textures3D == null)
{
Textures3D = newDictionary<string,Texture3D>();
}
if(!Textures3D.ContainsKey(textureName))
{
Textures3D.Add(textureName, ContentMgr.Load<Texture3D>(textureName));
}
}

public static void RemoveTexture3D(string textureName)
{
if(Textures3D.ContainsKey(textureName))
{
Textures3D.Remove(textureName);
}
}

public static void AddModel(string modelName)
{
if(Models == null)
{
Models = new Dictionary<string,Model>();
}
if(!Models.ContainsKey(modelName))
{
Models.Add(modelName, ContentMgr.Load<Model>(modelName));
}
}

public static void RemoveModel(string modelName)
{
if(Models.ContainsKey(modelName))
{
Models.Remove(modelName);
}
}

All of these methods allow you to add or remove assets from the screen managers asset dictionaries. To prevent errors, when you add assets we first make sure you supplied an asset, then we make sure the asset doesn’t already exist in the dictionary. Likewise, it makes sure the dictionary actually contains the asset your removing before it attempts to remove it. Please note that the same asset can exist more than once in a dictionary, provided that each copy of the asset is supplied a different identifier string. Whenever possible, this should be avoided, and assets should be reused instead.

    public static void AddScreen(GameScreen gameScreen)
{
gameScreen.LoadAssets();
if(ScreenList == null)
{
ScreenList=new List<GameScreen>();
}
ScreenList.Add(gameScreen);
gameScreen.LoadAssets();
}

public static void RemoveScreen(GameScreen gameScreen)
{
gameScreen.UnloadAssets();
ScreenList.Remove(gameScreen);
if(ScreenList.Count < 1)
AddScreen(newTestScreen());
}

public static void ChangeScreens(GameScreen currentScreen, GameScreen targetScreen)
{
RemoveScreen(currentScreen);
AddScreen(targetScreen);
}
}
}

AddScreen and RemoveScreen do just what they say, they add or remove a screen from the screen managers list of screens. When a screen is added, it is automatically added to the end of the list so that it has the highest index value in the list. However, screens are removed from where ever in the list they may exist. This also checks to make sure that at least one screen is present in the list, and adds a default screen if not.

Finally, ChangeScreens is a shortcut method that can be used to both remove an existing screen and add a new screen at the same time.

Hey look, we’ve finally reached the end of the class! At this point, we have a fully functional screen management system. It’s very basic, but it serves the purpose and leaves plenty of possibilities for future expansion.

A Test Screen

Just showing you the definition of a screen and building the screen manager isn’t enough. This tutorial wouldn’t be complete without adding a test screen to teach you how to build the screens themselves. To start, right-click on the Screens folder in the Solution Explorer and add a new class named TestScreen.cs to it.

Our new file should look like this:

using System;
using System.Collections.Generic;
using System.Linq;usingSystem.Text;

namespace XNATutorials.Screens
{
classTestScreen
{
}
}

This is definitely not a screen yet, so let’s give it its core. Add these to the using statements at the top:

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;

We may not need all of these using statements, but I always start with them to ensure I don’t miss anything. Next, change the class to inherit from GameScreen and make it public.

public class TestScreen : GameScreen { }

Believe it or not, we now have ourselves a screen to display. Ok, so it’s an empty screen, but the entire program will compile cleanly at this point, which means we haven’t left anything. It will also run without errors or crashing, which means the screen works as well, but it can’t stay like this. We have to add something to it, but what?

Since this is just a test screen, I don’t want to have to add any content files (assets), so we’re going to draw some primitives instead. Put the following code inside the TestScreen class:

private readonly Matrix _projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.01f, 100f);
private readonly Matrix _view = Matrix.CreateLookAt(newVector3(0, 0, 3), newVector3(0, 0, 0), newVector3(0, 1, 0));
private readonly Matrix _world = Matrix.CreateTranslation(0, 0, 0);
private BasicEffect _basicEffect;
private VertexBuffer _vertexBuffer;

public override void LoadAssets()
{
_basicEffect = new BasicEffect(ScreenManager.GraphicsDeviceMgr.GraphicsDevice);
var vertices = new VertexPositionColor[3];
vertices[0] = new VertexPositionColor(newVector3(0, 1, 0), Color.Red);
vertices[1] = new VertexPositionColor(newVector3(+0.5f, 0, 0), Color.Green);
vertices[2] = new VertexPositionColor(newVector3(-0.5f, 0, 0), Color.Blue);
_vertexBuffer = new VertexBuffer(ScreenManager.GraphicsDeviceMgr.GraphicsDevice, typeof(VertexPositionColor), 3, BufferUsage.WriteOnly);
_vertexBuffer.SetData(vertices);
}

public override voidDraw(GameTime gameTime)
{
_basicEffect.World = _world;
_basicEffect.View = _view;
_basicEffect.Projection = _projection;
_basicEffect.VertexColorEnabled = true;
ScreenManager.GraphicsDeviceMgr.GraphicsDevice.SetVertexBuffer(_vertexBuffer);
var rasterizerState = newRasterizerState
{
CullMode = CullMode.None
};
ScreenManager.GraphicsDeviceMgr.GraphicsDevice.RasterizerState = rasterizerState;
foreach(var pass in _basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
ScreenManager.GraphicsDeviceMgr.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
}
}

Looking back at the GameScreen class you’ll see that we declared a few virtual methods. Those virtual methods are overridden in the screen to provide functionality to the screen.

If you compile and run the program at this point, you should get a screen with a light blue background and a multi-colored triangle similar to this:

Mar 27, 2017, 7:29 AM

Often when programming, you’ll find that you need to read or write to a standard text file. In this tutorial I’ll show you the basics on text file access, as well as a few more tricks to help smooth things along.

The first thing you’ll need to know is that most of the things you will need are located in System.IO. In this class you’ll find many things that are usefull, a brief list is included below. I’ll touch on some of these, but I won’t go over all of them, as you’ll notice there are a good number of them.

BinaryReader, BinaryWriter, BufferedStream, Directory, DirectoryInfo, DriveInfo, DriveType, File, FileInfo, FileStream, FileSystemInfo, MemoryStream, Path,
Stream, StreamReader, StreamWriter, TextReader, TextWriter

To use this class add this line to the top of your source file:

using System.IO;

If you have MS Visual Studio, go to View – Object Browser, and search System.IO for the full list of all methods and members of that class.
Now, looking at the list above, you’ll notice that there are several different readers/writers available. You’d probably guess that TextReader/TextWriter would be the classes that we will be working with for text files, but you’d be wrong in that guess.

I don’t use the Text(Reader/Writer) classes because to instantiate them you have to use the Stream(Reader/Writer) classes. Plus, the Stream classes offer more options than the Text classes.

But for completion, you would instantiate the Text objects as:

TextReader tReader = newStreamReader(FileName);
TextWriter tWriter = newStreamWriter(FileName);

Conversely, to instantiate Stream objects you do essentially the same thing:

StreamReader sReader = newStreamReader(FileName);
StreamWriter sWriter = newStreamWrtier(FileName);

For the rest of this tutorial we’re going to assume that we are using the stream objects above. (Not the text objects.)

This instantiation does two things at once, first it opens the file, then it provides a reference to the open file via the variable (sReader/sWriter).

So, lets work on reading the file first, then when we’re done with that we’ll move on to writing to the file.

Since we have a reference to the open file for reading (sReader), we now need to know how to read the contents of the file. For this, we have three options; ReadLine, ReadToEnd, and ReadBlock. I personally don’t use ReadBlock, so I won’t be covering that here.

ReadLine does exactly what it sounds like it’s supposed to do. It reads the file, one line at a time and returns the line as a string. Simple enough, right? But how do you know when to stop reading?

StreamReader provides an awesome little Boolean that tells us whether or not we are at the end of the file. To be safe, we should check to make sure we aren’t at the end of the file before each and every we read from it. Sounds tedious, doesn’t it?

using(StreamReader sReader = newStreamReader(Filename))
{
while(!sReader.EndOfStream)
{
string line = sReader.ReadLine();
Console.WriteLine(line);
}
}

A lot simpler than it sounds, right? The above piece of code will read the entire file, one line at a time, and output it to the console screen.

Now what’s with that using statement, I thought those only went at the top of the source page? Nope, the using statement, in this case provides an element of error protection. If for some reason the StreamReader can’t access the file, like if it doesn’t exist, then the using statement will terminate without throwing any errors. It also protects us from infinitely open files, as we humans sometimes forget to close them.

What if we want to receive the error messages? Simple, modify the above code to look like this:

StreamReader sReader = newStreamReader(Filename);
while(!sReader.EndOfStream)
{
string line = sReader.ReadLine();
Console.WriteLine(line);
}
sReader.Close();

Notice this time that we have to manually close the file? That’s because in the above using statement, the system automatically closes the file when the code processing exits the using statement.

Now, onto ReadToEnd method. Just like the ReadLine method it returns a string, the difference is this time the string contains the entire contents of the file. Another major difference is you don’t have to check for the end of the file, because we already know we’ll reach it on the first read. If the file is empty, then it’ll return an empty string. Nifty eh?

StreamReader sReader = newStreamReader(Filename);
string fileContents = sReader.ReadToEnd();
Console.WriteLine(fileContents);

Once again, we’ve read the entire file and printed it to the console screen, but with fewer lines of code. If we can read an entire file at once, why would we want to read a line at a time? There are times when you will want to read just one line at a time, then process that line before reading the next line. While it’s still possible to do this by reading the entire file at once, it’s a little bit trickier. Conversely, if you are writing an application such as Notepad, you’d have no reason to process each line, so reading the entire file at once would be faster.

That covers reading from file, now let’s get onto writing to it. The StreamReader object offers two methods for writing to a file; Write and WriteLine.

Assuming you have a string named OutString that contains data to be written to a text file, the following two chunks of code both do the same thing:

StreamWriter sWriter = newStreamWriter(Filename);
sWriter.WriteLine(OutString);
StreamWriter sWriter = newStreamWriter(Filename);
sWriter.Write(OutString);
sWriter.Write(sWriter.NewLine);

Whoah, what was that last line? WriteLine writes the contents of the string to the file then terminates the end of the line. Write does not terminate the end of the line. If you string contained 10 lines, then WriteLine would write 10 lines then stop at the beginning of the 11th line. Write on the otherhand would stop at the end of the 10th line.

Aside from that, you can enlist a using statement, or a close statement just as you would for a StreamReader.

Now, let me cover some additional tricks. Before you open a file for reading, you should check to make sure the file exists. Attempting to open a nonexistant file will cause an error, and if you don’t use error handling, will crash your application. To check to make sure the file exists, use the File object located in the System.IO class:

if(File.Exists(Filename))
{
// Process your file here
}

If the file doesn’t exist, then File.Exists returns false, exiting the above statement.

Now here’s a few other usefull things located in the System.IO class:

Open a file, append some text, and close the file all in one move

void AppendAllText(string path, string contents)

Copy a file to another, and specify if you want to overwrite the target file

void Copy(string sourceFileName, string destFileName, bool overwrite)

Create a file with no contents and return a FileStream object to it

FileStream Create(string path)

Encrypt/decrypt a file

Files encrypted using these can only be decrypted by the same user account on the same pc on which it was encrypted. In other words, only good for your personal files, don’t use for files you’re sending to someone else.

void Encrypt(string path)
void Decrypt(string path)

Get the date and time a file was created, accessed, or written to

DateTime GetCreationTime(string path)
DateTime GetLastAccessTime(string path)
DateTimeGetLastWriteTime(string path)

Set the date and time a file was created, accessed, or written to

void SetCreationTime(string path, DateTime creationTime)
void SetLastAccessTime(string path, DateTime lastAccessTime)
void SetLastWriteTime(string path,DateTime lastWriteTime)