XNA Tutorial 1 - Merged

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 478

XNA 4.

0 RPG Tutorials
Part 1
Getting Started
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is part one in a series of tutorials I plan to write on creating a role playing game with XNA 4.0.
I've worked on similar tutorials using XNA 3.0 and XNA 3.1. In the past I discovered better ways of
doing things in the process of writing the tutorials and had to go back to fix things. I'm hoping in this
series to not make those same mistakes.
To get started we want to create a new XNA 4.0 Windows game. Open Visual Studio 2010 or Visual C#
2010 Express. For the duration of these tutorials I will use Visual C# to mean either the full version of
Visual Studio or the Express edition.
From the File menu select the New Project item. In the dialog box that shows up select the XNA 4.0
Game Studio option under the Visual C# section in the Installed Templates pane. Select the
Windows Game (4.0) item in the project list. Name this new game EyesOfTheDragon. Visual C# will
create the basic solution for you. In the solution explorer there will be two solutions. The first is your
game, EyesOfTheDragon. The second is for the assets in your game, or content, like images and
sounds, EyesOfTheDragonContent.
For XNA 4.0 there are two graphics profiles your game can use. There is the new HiDef profile that
uses Shader Model 3.0 or greater. The Reach profile uses Shader Model 1.1 or greater. Right click the
EyesOfTheDragon project in the solution explorer and select Properties. That will bring up the
properties for the game. Under the XNA tab select the Reach profile. I will be using the Reach profile
for these tutorials. If you know your graphics card is able to use the new HiDef profile then by all
means use that profile. My version of the project will be using the Reach profile because my lap top
doesn't have a card that is supported under the HiDef profile and I use it frequently when I'm away.
I want to add in two class libraries to this project. The first will be a standard class library that holds
generic classes that can be reused in other projects. The other is an XNA game library. This library will
hold classes that can be shared between XNA projects. Right click your solution this time in the
solution explorer, not the project. Select the Add item and then the New Project entry. From the dialog
that shows up under the XNA Game Studio 4.0 entry select the Windows Game Library (4.0) option
and name the library XRpgLibrary. Right click your solution in the solution explorer again, select
Add and then New Project again. This time make sure the Visual C# entry is selected and add a new
Class Library. Name this new library RpgLibrary. In the two class libraries that were created right
click the Class1.cs entries and select Delete to remove them from the projects as you won't be using
them.

There is one last thing you must do in order to use the libraries. You have to tell your game about them
so Visual C# knows to include them when it builds your game. You do that by adding references of the
libraries to your game. Right click your game project, EyesOfTheDragon, in the solution explorer and
select the Add Reference option. From the dialog box that pops up select the Projects tab. In the
projects tab there will be entries for the two libraries. Hold down the control key and click both of them
to select them both and then click OK.
With that done it is time to actually write some code! In a large game, and role playing games are large
games, it is a good idea to handle some things on a global level. One such item is input in the game. An
XNA game component would be perfect for controlling input in your game. When you create a game
component and add it to the list of components for your game XNA will automatically call its Update
method, and Draw method if it is a drawable game component, for you. Game components have
Enabled properties that you can set to true or false to determine if they should be updated. The
drawable ones also have a Visible property that can be used the same way to determine if they should
be drawn or not.
I'm going to add an XNA game component to the XRpgLibrary to manage all of the input in the game
in one location. For this tutorial I'm only going to handle keyboard input. In future tutorials I will add
in support for a mouse and Xbox 360 game pads. Right click the XRpgLibrary project in the solution
explorer, select Add and then New Item. From the XNA Game Studio 4.0 entry in the templates pane,
select the Game Component entry. Name this new component InputHandler. I'm a firm believer in
showing new code first so you can read it over before explaining things. I'm also breaking the code up
into regions using preprocessor directives. They don't affect the code at all but they help to keep your
code organized better. The code for the InputHandler class follows next.
using
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Audio;
Microsoft.Xna.Framework.Content;
Microsoft.Xna.Framework.GamerServices;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Media;

namespace XRpgLibrary
{
public class InputHandler : Microsoft.Xna.Framework.GameComponent
{
#region Field Region
static KeyboardState keyboardState;
static KeyboardState lastKeyboardState;
#endregion
#region Property Region
public static KeyboardState KeyboardState
{
get { return keyboardState; }
}
public static KeyboardState LastKeyboardState
{
get { return lastKeyboardState; }
}

#endregion
#region Constructor Region
public InputHandler(Game game)
: base(game)
{
keyboardState = Keyboard.GetState();
}
#endregion
#region XNA methods
public override void Initialize()
{
}

base.Initialize();

public override void Update(GameTime gameTime)


{
lastKeyboardState = keyboardState;
keyboardState = Keyboard.GetState();
}

base.Update(gameTime);

#endregion
#region General Method Region
public static void Flush()
{
lastKeyboardState = keyboardState;
}
#endregion
#region Keyboard Region
public static bool KeyReleased(Keys key)
{
return keyboardState.IsKeyUp(key) &&
lastKeyboardState.IsKeyDown(key);
}
public static bool KeyPressed(Keys key)
{
return keyboardState.IsKeyDown(key) &&
lastKeyboardState.IsKeyUp(key);
}
public static bool KeyDown(Keys key)
{
return keyboardState.IsKeyDown(key);
}
#endregion
}

Inside the class there is a region that will hold the fields of the class called Field Region. There are two
fields in the region. One holds the current state of the keyboard, keyboardState, and the other holds
the state of the keyboard in the previous frame of the game, lastKeyboardState. These fields are static,
meaning that they are shared between all instances of the InputHandler class and can be referenced
using the InputHandler class name rather than an instance variable.

Each time the game runs through the Update and Draw methods is considered to be a frame of the
game. When you are looking for a single press of a key you need to compare the state of the keyboard
in the last frame of the game and the current frame of the game. I will get to detecting single presses
shortly.
The next region it the Property Region. This region holds properties that expose the fields of the class.
What I mean by expose is that C# is an object oriented programming language and I'm following the
basic principles of object oriented programming. The principle that I'm using here is encapsulation. The
idea of encapsulation means that other objects are denied access to the inner works of a class and
access the members of a class through a public interface. This public interface "exposes" what you
want to share of the objects of a class. The LastKeyboardState and KeyboardState properties return
the lastKeyboardState and keyboardState fields respectively.
In the Constructor Region is the constructor that XNA Game Studio created when you created your
Game Component. In the constructor I set the keyboardState field using the GetState method of the
Keyboard class.
In the Game Component Method Region are the other two methods that XNA created for you. They
are the Initialize and Update methods. There is no new code in the Initialize method. In the Update
method I set the lastKeyboardState field to be the keyboardState field and get the new state of the
keyboard. What this does is make sure that lastKeyboardState will hold the state of the keyboard in
the last frame of the game and keyboardState will hold the current state of the keyboard.
The last region in this class is the Keyboard Region. In this region I have all of the methods that are
related to the keyboard. There are three methods in this region. Two of them are used for detecting
single presses, KeyPressed, and releases, KeyReleased. The other is for determining if a key is down,
KeyDown.
There is a good reason for having a method for checking for both single presses and releases and not
one or the other. Checking for a release is good for things like menus. You don't want the action to
occur precisely the moment the key is down. If you check for a press instead of a release in this
instance the press may spill over if you change states. There are times when you want instant action,
like firing a bullet. You don't want to wait for the button to be release before firing.
To detect a release you check if a key was down in the last frame and up in the current frame. For a
press it is reversed. You check if a key that was up in the last frame is now down.
I skipped over one region, the General Method region. In this region there is just one method, Flush. I
couldn't think of a better name for the method. The Flush method sets the lastKeyboardState field to
the keyboardState field. What this does is make the KeyPressed and KeyReleased methods return
false. It flushes what I like to call the input buffer. You can use it to make sure, if you are changing state
for example, that there is no spill over into the next state.
Now we can add this game component to the list of components for the game. That will take place in
the Game1 class. This is the main class for you game. The first step is to add a using statement for the
XRpgLibrary name space to bring it into scope. What is meant by bringing a name space into scope is
that you can refer objects with out using their fully qualified name. For example, in the Game1 class
you can reference the InputHandler class using InputHandler instead of its fully qualified name
XRpgLibrary.InputHandler. You will then add an instance of the InputHandler class to the list of

components of the game. Add the following using statement with the other using statements of the
Game1 class and change the constructor to the following.
using XRpgLibrary;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

Components.Add(new InputHandler(this));

A common mistake made when creating a large game is not handling game state properly from the
start. If you try and go back and add it in later it is a major pain and one of the hardest things to fix if
not handled properly from the start. I will be managing game states, or game screens, with a game
component. I will need a basic state, or screen, to start from. Right click the XRpgLibrary project,
select Add and then New Item. Like you did before, select the Game Component option and name
this game component GameState. This is the code for the GameState class.
using
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Audio;
Microsoft.Xna.Framework.Content;
Microsoft.Xna.Framework.GamerServices;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Media;

namespace XRpgLibrary
{
public abstract partial class GameState : DrawableGameComponent
{
#region Fields and Properties
List<GameComponent> childComponents;
public List<GameComponent> Components
{
get { return childComponents; }
}
GameState tag;
public GameState Tag
{
get { return tag; }
}
protected GameStateManager StateManager;
#endregion
#region Constructor Region
public GameState(Game game, GameStateManager manager)
: base(game)
{
StateManager = manager;
childComponents = new List<GameComponent>();
tag = this;
}

#endregion
#region XNA Drawable Game Component Methods
public override void Initialize()
{
base.Initialize();
}
public override void Update(GameTime gameTime)
{
foreach (GameComponent component in childComponents)
{
if (component.Enabled)
component.Update(gameTime);
}
}

base.Update(gameTime);

public override void Draw(GameTime gameTime)


{
DrawableGameComponent drawComponent;
foreach (GameComponent component in childComponents)
{
if (component is DrawableGameComponent)
{
drawComponent = component as DrawableGameComponent;

if (drawComponent.Visible)
drawComponent.Draw(gameTime);

}
}

base.Draw(gameTime);

#endregion
#region GameState Method Region
internal protected virtual void StateChange(object sender, EventArgs e)
{
if (StateManager.CurrentState == Tag)
Show();
else
Hide();
}
protected virtual void Show()
{
Visible = true;
Enabled = true;
foreach (GameComponent component in childComponents)
{
component.Enabled = true;
if (component is DrawableGameComponent)
((DrawableGameComponent)component).Visible = true;
}
}
protected virtual void Hide()
{
Visible = false;
Enabled = false;
foreach (GameComponent component in childComponents)
{
component.Enabled = false;
if (component is DrawableGameComponent)

((DrawableGameComponent)component).Visible = false;
}

#endregion
}

When you create Game Components using the wizard they inherit from GameComponent. Game
states will do drawing so they need to derive from DrawableGameComponent. The three fields in the
class are childComponents, a list of any game components that belong to the screen, tag, a reference
to the game state, and StateManager, the game state manager that I haven't added yet. StateManager
is a protected field and is available to any class that inherits from GameState, either directly or
indirectly. There is a read only property, Components, that returns the collection child components.
The property Tag exposes the tag field and is a read only property as well.
The constructor of the GameState class takes two parameters. The first is a Game parameter that is a
reference to your game object required by game components. The second is a reference to the
GameStateManager so it can be assigned to the StateManager field. The constructor then sets the
StateManager field to the value passed in, initializes the childComponents field, and finally sets the
tag field to be this, the current instance of the class.
The Update method just loops through all of the GameComponents in childComponents and if their
Enabled property is true calls their Update methods. Similarly, the Draw method loops through all of
the GameComponents in childComponents. It then checks if the game component is a
DrawableGameComponent. If it is, it checks to see if it is visible. If it is visible, it then calls the
Draw method of the components.
The other methods in this class are methods that aren't inherited from DrawableGameComponent.
The first method, StateChange, is the event handler code changing game states. All active screens will
subscribe to an event in the game state manager class. All states that are subscribed to the event will
receive a message that they active screen was changed. In the ScreenChange method I call the Show
method if the screen that triggered the event, from the sender parameter, is the Tag property of the
current screen. Otherwise I call the Hide method. The Show method sets a screen enabled and visible.
The Hide method sets a screen disabled and not visible.
In the Show method I set the Visible and Enabled properties of the GameScreen to true. I also loop
through all of the GameComponents in childComponents and set them to enabled. I also check to see
if the component is a DrawableGameComponent and set it to visible. The Hide method works in
reverse.
The next class to add is the GameStateManager class. Right click the XRpgLibrary project, select
Add and then New Item. From the XNA Game Studio 4.0 list select Game Component. Name this
new component GameStateManager. The code for that class follows next.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Audio;
Microsoft.Xna.Framework.Content;
Microsoft.Xna.Framework.GamerServices;
Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace XRpgLibrary
{
public class GameStateManager : GameComponent
{
#region Event Region
public event EventHandler OnStateChange;
#endregion
#region Fields and Properties Region
Stack<GameState> gameStates = new Stack<GameState>();
const int startDrawOrder = 5000;
const int drawOrderInc = 100;
int drawOrder;
public GameState CurrentState
{
get { return gameStates.Peek(); }
}
#endregion
#region Constructor Region
public GameStateManager(Game game)
: base(game)
{
drawOrder = startDrawOrder;
}
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
#endregion
#region Methods Region
public void PopState()
{
if (gameStates.Count > 0)
{
RemoveState();
drawOrder -= drawOrderInc;
if (OnStateChange != null)
OnStateChange(this, null);
}
}
private void RemoveState()
{
GameState State = gameStates.Peek();

OnStateChange -= State.StateChange;
Game.Components.Remove(State);
gameStates.Pop();

public void PushState(GameState newState)


{
drawOrder += drawOrderInc;
newState.DrawOrder = drawOrder;
AddState(newState);
if (OnStateChange != null)
OnStateChange(this, null);
}
private void AddState(GameState newState)
{
gameStates.Push(newState);
Game.Components.Add(newState);
}

OnStateChange += newState.StateChange;

public void ChangeState(GameState newState)


{
while (gameStates.Count > 0)
RemoveState();
newState.DrawOrder = startDrawOrder;
drawOrder = startDrawOrder;
AddState(newState);

if (OnStateChange != null)
OnStateChange(this, null);

#endregion
}

In the Event region is the code for the event that I created that will be triggered when there is a change
in state, or screens. You saw the handler for the event in the GameState class, StateChange. To
manage the states I used a generic Stack<GameState>. A stack, in computer science, is a last-in-firstout data structure. The usual analogy when describing a stack is to think of a stack of plates. To add a
plate you place, or push, it on top of the stack. The plate on top of the stack is removed, or popped, off
the top.
There are three integer fields: startDrawOrder, drawOrderInc, and drawOrder. These fields are
used in determining the order that game screens are drawn. DrawableGameComponent have a
property called DrawOrder. Components will be drawn in ascending order, according to their
DrawOrder property. The component with the lowest draw order will be drawn first. The component
with the highest DrawOrder property will be drawn last. I chose 5000 as a good starting point. When a
screen is added onto the stack, I will set its DrawOrder property higher than the last screen. The
drawOrderInc field is how much to increase or decrease when a screen is added or removed.
drawOrder holds the current value of the screen on top. There is a public property, CurrentScreen,
that returns which screen is on top of the stack.
The constructor of this class just sets the drawOrder field to the startDrawOrder field. By choosing

the magic number, 5000, and 100 for the amount to increase or decrease, there is room for a lot screens.
Far more than you will probably have on the stack at once.
In the Methods region there are three public and two private methods. The public methods are
PopState, PushState, and ChangeState. The private methods are RemoveState and AddState.
PopState is the method to call if you have a screen on top of the stack that you want to remove and go
back to the previous screen. A good example of this is you open an options menu from the main menu.
You want to go back to the main menu from the options menu so you just pop the options menu off the
stack. PushState is the method to call if you want to move to another state and keep the previous state.
From above, you would push the options screen on the stack so where you are done you can return to
the previous state. The last public method, ChangeState, is called when you wish to remove all other
states from the stack.
The PopState method checks to make sure there is a game state to pop off by checking the Count
property of the gameStates stack. It calls the RemoveState method that removes the state that is on the
top of the stack. It then decrements the drawOrder field so that when the next screen is added to the
stack of screens it will be drawn appropriately. It then checks if OnStateChange is not null. What this
means is it checks to see if the OnStateChange event is subscribed to. If the event is not subscribed to
the event handler code should not be executed. It then calls the event handler code passing itself as the
sender and null for the event arguments.
The RemoveState method first gets which state is on top of the stack. It then unsubscribes the state
from the subscribers to the OnStateChange event. It then removes the screen from the components in
the game. It then pops the state off the stack.
The PushState method takes as a parameter the state to be placed on the top of the stack. It first
increases the drawOrder field and set it to the DrawOrder property of the component so it will have
the highest DrawOrder property. It then calls the AddState method to add the screen to the stack. It
then checks to make sure the OnStateChange event is subscribed to before calling the event handler
code.
The AddState method pushes the new screen on the top of the stack. It adds it to the list of components
of the game. Finally it subscribes the new state to the OnStateChange event.
The first thing the ChangeState method does is remove all of the screens from the stack. It then sets
the DrawOrder property of the new screen to the startDrawOrder value and drawOrder to the same
value. It then calls the AddScreen method passing in the screen to be changed to. It then will call the
OnStateChange event handler if it is subscribed to.
Now you can add a GameStateManager to the game. Go to the code for the Game1 class in your
game. You will want to add a field, below the SpriteBatch field, for the GameStateManager. You will
also want to initialize it in the constructor of the game and add it to the list of components. Add this
field and change the constructor to the following.
GameStateManager stateManager;
public Game1()
{
graphics = new GraphicsDeviceManager(this);

Content.RootDirectory = "Content";
Components.Add(new InputHandler(this));

stateManager = new GameStateManager(this);


Components.Add(stateManager);

That is a lot of code but it doesn't really do anything. You can build and run but all you will get is the
blue screen as if you hadn't added any code. Right click the EyesOfTheDragon project in the solution
explorer, select Add and then New Folder. Name this new folder GameScreens. To this folder you are
going to add in two game states. The first state is a base state that will have fields common to all states.
The second is a title screen that will display an image. Special thanks to, Tuckbone, a reader of my
tutorials for creating the graphic. You can download the title image from here.
It is a good idea to organize your content. Right click the EyesOfTheDragonContent project, select
Add and then New Folder. Name this new folder Backgrounds. After you have downloaded and
extracted the image you will want to add it to the Backgrounds folder. Right click the Backgrounds
folder, select Add and then Existing Item. You will want to navigate to where you downloaded and
extracted the image and add the titlescreen.png entry. When you are making game graphics it is a good
idea to use an image format that doesn't have compression. PNG, BMP, TGA, are all good formats for
that. PNG and TGA are good because they support transparency.
Now, going back to the states. Right click the GameScreens folder in the EyesOfTheDragon project,
select Add and then Class. Name this new class BaseGameState. The code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using XRpgLibrary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
namespace EyesOfTheDragon.GameScreens
{
public abstract partial class BaseGameState : GameState
{
#region Fields region
protected Game1 GameRef;
#endregion
#region Properties region
#endregion
#region Constructor Region
public BaseGameState(Game game, GameStateManager manager)
: base(game, manager)
{
GameRef = (Game1)game;
}
#endregion
}

The BaseGameState class is a very basic class. It inherits from the GameState class so it can be used

in the GameStateManager. The lone field in the class is GameRef that is a reference to our game
object. The constructor takes two parameter. The first is the current game object required by game
components and the second is a GameStateManager object required by the GameState class. The
constructor just sets the GameRef field by casting the Game parameter to the Game1 class.
The next screen is going to be a title screen. You are going to get a few errors for the screen because I
made quite a few changes to the Game1 class to get this screen to work. After I've shown you the code
for the screen and explained it I will give you the complete code for the Game1 class. Right click the
GameScreens folder in the EyesOfTheDragon project, select Add and then Class. Name this new
class TitleScreen.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using XRpgLibrary;
namespace EyesOfTheDragon.GameScreens
{
public class TitleScreen : BaseGameState
{
#region Field region
Texture2D backgroundImage;
#endregion
#region Constructor region
public TitleScreen(Game game, GameStateManager manager)
: base(game, manager)
{
}
#endregion
#region XNA Method region
protected override void LoadContent()
{
ContentManager Content = GameRef.Content;
backgroundImage = Content.Load<Texture2D>(@"Backgrounds\titlescreen");
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin();
base.Draw(gameTime);
GameRef.SpriteBatch.Draw(
backgroundImage,

GameRef.ScreenRectangle,
Color.White);
}

GameRef.SpriteBatch.End();

#endregion
}

There are using statements to bring classes from the XNA Framework, XNA Framework Graphics, and
XNA Framework Content name spaces into scope. The Graphics ones are for drawing and the Content
ones are for loading in content. There is also a using statement to bring our XRpgLibrary name space
into scope.
The class inherits from the BaseGameState class. There is just one field in the class at this time, a
Texture2D, for our background image. The constructor takes two parameters. They are the same as any
other game states, the current game object and a reference to the GameStateManager.
In the LoadContent method I get the ContentManager that is associated with our game using the
Content property of GameRef. I then use the Load method passing in the location of the background
of our image.
In the Draw method I draw the image. There is first a call to Begin on SpriteBatch. 2D images, often
called sprites, are drawn between calls to Begin and End of a SpriteBatch object. You will have errors
related to the SpriteBatch because I haven't made that change to the Game1 class yet. After the call to
Begin is the call to base.Draw to draw any child components. I then call the Draw method to draw the
background image passing in the image to be drawn, the destination of the image as a rectangle and the
tint color. You can tint objects using this tint color. Using white means there is no tinting at all. Finally
there is the call to End.
There have been a number of changes to the Game1 class. I will give you the new code for the entire
class and then go over the changes that I made. Here's the code.
using
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Audio;
Microsoft.Xna.Framework.Content;
Microsoft.Xna.Framework.GamerServices;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Media;

using XRpgLibrary;
using EyesOfTheDragon.GameScreens;
namespace EyesOfTheDragon
{
public class Game1 : Microsoft.Xna.Framework.Game
{
#region XNA Field Region
GraphicsDeviceManager graphics;
public SpriteBatch SpriteBatch;
#endregion

#region Game State Region


GameStateManager stateManager;
public TitleScreen TitleScreen;
#endregion
#region Screen Field Region
const int screenWidth = 1024;
const int screenHeight = 768;
public readonly Rectangle ScreenRectangle;
#endregion
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
ScreenRectangle = new Rectangle(
0,
0,
screenWidth,
screenHeight);
Content.RootDirectory = "Content";
Components.Add(new InputHandler(this));
stateManager = new GameStateManager(this);
Components.Add(stateManager);

TitleScreen = new TitleScreen(this, stateManager);


stateManager.ChangeState(TitleScreen);

protected override void Initialize()


{
base.Initialize();
}
protected override void LoadContent()
{
SpriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
}

base.Update(gameTime);

protected override void Draw(GameTime gameTime)


{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
}
}

The first change is that I grouped the fields into regions. The one region, XNA Fields, is for fields that
XNA created. I change the SpriteBatch field to be public and I changed it to start with a capital letter. I
added in another region, Game State Region, that holds fields related to game state. There is the field
for the game state manager and a field for the new TitleScreen we created. The other new region is the
Screen Field Region. This region holds fields related to the screen. There are two integer fields that
hold the width and height of the screen. The third is a rectangle that describes the screen.
The constructor has changed quite a bit. The first change is that I set the PreferredBackBufferWidth
and PreferredBackBufferHeight properties of the GraphicsDeviceManager to be the width and
height of the screen from the Screen Field Region. I then create the instance of ScreenRectangle that
will describe the screen. The constructor continues on as it did before but I create the TitleScreen field
passing in the required parameters and then call the ChangeState method of the game state manager
passing in TitleScreen.
I think this is enough for this tutorial. I'd like to try and keep them to a reasonable length so that you
don't have too much to digest at once. I encourage you to visit the news page of my site, XNA Game
Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 2
More Core Game Components
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is part two in my series of XNA 4.0 Role Playing Game tutorials. I'm going to be adding in a few
more core components in this tutorial that you will be using through out the series. Go a head and load
up your solution from last time.
The first thing I'm going to do is to update the InputHandler class to include Xbox 360 game pad
input. I renamed the field and property regions from the last tutorial to include Keyboard. I will give
you the entire code for the class and explain the game pad specific code.
using
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Audio;
Microsoft.Xna.Framework.Content;
Microsoft.Xna.Framework.GamerServices;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Media;

namespace XRpgLibrary
{
public class InputHandler : Microsoft.Xna.Framework.GameComponent
{
#region Keyboard Field Region
static KeyboardState keyboardState;
static KeyboardState lastKeyboardState;
#endregion
#region Game Pad Field Region
static GamePadState[] gamePadStates;
static GamePadState[] lastGamePadStates;
#endregion
#region Keyboard Property Region
public static KeyboardState KeyboardState
{
get { return keyboardState; }
}
public static KeyboardState LastKeyboardState
{

get { return lastKeyboardState; }


}
#endregion
#region Game Pad Property Region
public static GamePadState[] GamePadStates
{
get { return gamePadStates; }
}
public static GamePadState[] LastGamePadStates
{
get { return lastGamePadStates; }
}
#endregion
#region Constructor Region
public InputHandler(Game game)
: base(game)
{
keyboardState = Keyboard.GetState();
gamePadStates = new GamePadState[Enum.GetValues(typeof(PlayerIndex)).Length];

foreach (PlayerIndex index in Enum.GetValues(typeof(PlayerIndex)))


gamePadStates[(int)index] = GamePad.GetState(index);

#endregion
#region XNA methods
public override void Initialize()
{
}

base.Initialize();

public override void Update(GameTime gameTime)


{
lastKeyboardState = keyboardState;
keyboardState = Keyboard.GetState();
lastGamePadStates = (GamePadState[])gamePadStates.Clone();
foreach (PlayerIndex index in Enum.GetValues(typeof(PlayerIndex)))
gamePadStates[(int)index] = GamePad.GetState(index);
}

base.Update(gameTime);

#endregion
#region General Method Region
public static void Flush()
{
lastKeyboardState = keyboardState;
}
#endregion
#region Keyboard Region
public static bool KeyReleased(Keys key)
{
return keyboardState.IsKeyUp(key) &&

lastKeyboardState.IsKeyDown(key);
}
public static bool KeyPressed(Keys key)
{
return keyboardState.IsKeyDown(key) &&
lastKeyboardState.IsKeyUp(key);
}
public static bool KeyDown(Keys key)
{
return keyboardState.IsKeyDown(key);
}
#endregion
#region Game Pad Region
public static bool ButtonReleased(Buttons button, PlayerIndex index)
{
return gamePadStates[(int)index].IsButtonUp(button) &&
lastGamePadStates[(int)index].IsButtonDown(button);
}
public static bool ButtonPressed(Buttons button, PlayerIndex index)
{
return gamePadStates[(int)index].IsButtonDown(button) &&
lastGamePadStates[(int)index].IsButtonUp(button);
}
public static bool ButtonDown(Buttons button, PlayerIndex index)
{
return gamePadStates[(int)index].IsButtonDown(button);
}
}

#endregion

There can be four game pads connected to the Xbox 360 so I have arrays for the current state of each
game pad and the state of each game pad in the last frame of the game. The gamePadStates array
holds the states of the game pads in the current frame and the lastGamePadStates array holds the
states of the game pads in the last frame of the game. There are read only properties to expose these
fields. The nice thing about using array properties is you can get the entire array or the index you are
interested in. So, if you wanted all of the game pad states you could get them or if you want a specific
game pad you can get that as well.
The constructor creates the gamePadStates array using a little trickery. The Enum class has a method
GetValues that can return all of the values of an enumeration. The PlayerIndex enum has entries for
each game pad, from One to Four. The Length property returns the length of an array. After creating
the array I set the values of the array using a foreach loop and the return of the GetValues array to loop
through each of the values in PlayerIndex. Inside the loop I call the GetState method of GamePad
passing in index, the current PlayerIndex, casting it to an integer.
In the Update method I first assign the gamePadStates to lastGamePadStates using the Clone
method of the array class. The Clone method returns an exact copy of the array, you have to cast it to
be the right type though. After assigning the last states I get the current states using the same process as
I did in the constructor.
The three new methods for game pads work the same way as the methods for the keyboard. The

difference is that they take a Buttons parameter for the button you are interested in and a PlayerIndex
parameter for the game pad. For the release you check if a button is up in this frame and down in the
last. For a press you check if a button that was up in the last frame is down in this frame.
What I'm going to add next are some GUI controls and a class to manage controls on the form. One
thing about working with XNA is that you don't have all of the nice GUI controls that you're used to
working with in Windows. You have to make the controls yourself. It is a good idea to also have a class
that manages all of the controls in a game state, or game screen. The first step is to create a base class
for all controls. Right click the XRpgLibrary project in the solution explorer, select Add and then
New Folder. Name this new folder Controls. Now, right click the Controls folder, select Add and then
Class. Name this new class Control. The code for that class follow next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public abstract class Control
{
#region Field Region
protected
protected
protected
protected
protected
protected
protected
protected
protected
protected
protected
protected

string name;
string text;
Vector2 size;
Vector2 position;
object value;
bool hasFocus;
bool enabled;
bool visible;
bool tabStop;
SpriteFont spriteFont;
Color color;
string type;

#endregion
#region Event Region
public event EventHandler Selected;
#endregion
#region Property Region
public string Name
{
get { return name; }
set { name = value; }
}
public string Text
{
get { return text; }
set { text = value; }
}
public Vector2 Size
{
get { return size; }

set { size = value; }


}
public Vector2 Position
{
get { return position; }
set
{
position = value;
position.Y = (int)position.Y;
}
}
public object Value
{
get { return value; }
set { this.value = value; }
}
public bool HasFocus
{
get { return hasFocus; }
set { hasFocus = value; }
}
public bool Enabled
{
get { return enabled; }
set { enabled = value; }
}
public bool Visible
{
get { return visible; }
set { visible = value; }
}
public bool TabStop
{
get { return tabStop; }
set { tabStop = value; }
}
public SpriteFont SpriteFont
{
get { return spriteFont; }
set { spriteFont = value; }
}
public Color Color
{
get { return color; }
set { color = value; }
}
public string Type
{
get { return type; }
set { type = value; }
}
#endregion
#region Constructor Region
public Control()
{
Color = Color.White;
Enabled = true;
Visible = true;

SpriteFont = ControlManager.SpriteFont;
}
#endregion
#region Abstract Methods
public abstract void Update(GameTime gameTime);
public abstract void Draw(SpriteBatch spriteBatch);
public abstract void HandleInput(PlayerIndex playerIndex);
#endregion
#region Virtual Methods
protected virtual void OnSelected(EventArgs e)
{
if (Selected != null)
{
Selected(this, e);
}
}
}

#endregion

This class has many protected fields that are common to controls. Public properties to expose these
fields, so they can be overriden in inherited classes. There is a protected virtual method, OnSelected,
that is used to fire the event Selected if it is subscribed to. There are also three abstract methods that
any class that inherits from control has to implement. The one, Update, allows the control
to be updated. The second, Draw, allows the control to be drawn. The last, HandleInput, is used to
handle the input for the control. While these methods must be implemented they can be empty.
Controls have many things in common. I've picked a few of the more important ones. Controls have a
name that will identify it. They have text that they may draw. They will have a position on the screen.
They also have a size. The value field is a little more abstract. You can use this field to associate
something with the control. Since the field is of type object you can assign any class to this field. One
property of note is the Position property. I cast the Y component of the position to an integer. One thing
I found with XNA is it doesn't like drawing text when the Y component of the position isn't an integer
value.
Controls can also have focus, be visible or enabled, and be a tab stop. The last one is another peculiar
field. You will be able to move through all of the controls on a screen and skip over ones that you may
not want selected, like a label. The other field that a control will have is a type field that is a string.
Controls also have a SpriteFont associated with them and a Color. For right now there is also an event
associated with controls. This is the Selected event and will be triggered when the player selects the
control.
The constructor of the Control class assigns the color of the control to white. It also sets its visible and
enabled properties to true. It also sets the SpriteFont of the control to a static SpriteFont property of
the ControlManager, a class that I will be designing next.
Right click the Controls folder in the XRpgLibrary project, select Add and then Class. Name this
new class ControlManager. This is the code for the ControlManager class.

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public class ControlManager : List<Control>
{
#region Fields and Properties
int selectedControl = 0;
static SpriteFont spriteFont;
public static SpriteFont SpriteFont
{
get { return spriteFont; }
}
#endregion
#region Constructors
public ControlManager(SpriteFont spriteFont)
: base()
{
ControlManager.spriteFont = spriteFont;
}
public ControlManager(SpriteFont spriteFont, int capacity)
: base(capacity)
{
ControlManager.spriteFont = spriteFont;
}
public ControlManager(SpriteFont spriteFont, IEnumerable<Control> collection) :
base(collection)
{
ControlManager.spriteFont = spriteFont;
}
#endregion
#region Methods
public void Update(GameTime gameTime, PlayerIndex playerIndex)
{
if (Count == 0)
return;
foreach (Control c in this)
{
if (c.Enabled)
c.Update(gameTime);

if (c.HasFocus)
c.HandleInput(playerIndex);

if (InputHandler.ButtonPressed(Buttons.LeftThumbstickUp, playerIndex) ||
InputHandler.ButtonPressed(Buttons.DPadUp, playerIndex) ||
InputHandler.KeyPressed(Keys.Up))
PreviousControl();

if (InputHandler.ButtonPressed(Buttons.LeftThumbstickDown, playerIndex) ||
InputHandler.ButtonPressed(Buttons.DPadDown, playerIndex) ||
InputHandler.KeyPressed(Keys.Down))
NextControl();
}
public void Draw(SpriteBatch spriteBatch)
{
foreach (Control c in this)
{
if (c.Visible)
c.Draw(spriteBatch);
}
}
public void NextControl()
{
if (Count == 0)
return;
int currentControl = selectedControl;
this[selectedControl].HasFocus = false;
do
{
selectedControl++;
if (selectedControl == Count)
selectedControl = 0;
if (this[selectedControl].TabStop && this[selectedControl].Enabled)
break;
} while (currentControl != selectedControl);
}

this[selectedControl].HasFocus = true;

public void PreviousControl()


{
if (Count == 0)
return;
int currentControl = selectedControl;
this[selectedControl].HasFocus = false;
do
{
selectedControl--;
if (selectedControl < 0)
selectedControl = Count - 1;
if (this[selectedControl].TabStop && this[selectedControl].Enabled)
break;
} while (currentControl != selectedControl);
}

this[selectedControl].HasFocus = true;

#endregion
}

There are a few using statements in this class to bring some of the XNA Framework classes into scope.

The class inherits from List<T>. and will have all of the functionality of that class. This makes adding
and removing controls to the manager exceedingly easy.
The List<T> class has three constructors so I have three constructors that will call the constructor of
the List<T> class with the appropriate parameter. The constructors also take a SpriteFont parameter
that all controls can use for their SpriteFont field. You can set the SpriteFont field of a control to use
a different sprite font. This way when a control is first created it is easy to assign it a sprite font as the
spriteFont field is static and can be accessed using a static property. The other field in the class is the
selectedIndex field. This field holds which control is currently selected in the control manager.
There are a few public methods in this class. The Update method is used to update the controls and
handle the input for the currently selected control. The Draw method is used to draw the controls on
the screen. The other methods NextControl and PreviousControl are for moving from control to
control.
The Update method takes as parameters the GameTime object from the game and the PlayerIndex of
the game pad that you want to handle input from. The Update method first checks to see if there are
controls on the screen. If there are none you can exit the method. There is next a foreach loop that loops
through all of the controls on the screen. Inside the loop I check to see if the control is enabled using
the Enabled property and if it is call the Update method of the control. Next there is a check to see if
the control has focus and if it does calls the HandleInput method of the control. To move between
controls you can use the Up and Down keys on the keyboard, the Up and Down directions on the
direction pad, or the Up and Down directions of the left thumb stick. If any of the Up directions
evaluate to true the PreviousControl method is called. Similarly, if any of the Down directions
evaluate to true the NextControl method is called.
The Draw method takes the current SpriteBatch object as a parameter. There is a foreach loop that
loops through all of the controls. Inside the loop there is a check to see if the control is visible. If it is, it
calls the Draw method of the control.
The NextControl and PreviousControl methods aren't as robust as they should be. As I go I will
update them so that they are more robust and to prevent exceptions from being thrown if something
unexpected happens.
The NextControl method checks to make sure there are controls on the screen. If there are no controls
there is nothing to do so it exits. I set a local variable to be the currently selected control. The reason is
that I will loop through the controls and to know when to stop I needed a reference to the current
control that had focus. I use the this keyword to reference the List<Control> part of the class and set
the HasFocus method to false for the selected control.
There is next a do-while loop that loops through the controls until it finds a suitable control or reaches
the starting control. The first step in moving the focus is to increase the selectedControl variable to
move to the next control in the list. I check to see if the selectedControl field is equal to the Count
property. If it is you have reached that last control and I set the selectedControl field back to zero, the
first control. The next if statement checks to see if the control referenced by selectedControl is a
TabStop and is Enabled. If it is I break out of the loop. Finally I set the HasFocus property to true so
the control is selected.
The PreviousControl method has the same format as the NextControl method but instead of

incrementing the selectedControl field it decreases the selectedControl field. You first check to see if
there are controls to work with. Save the value of the selectedControl field and set the HasFocus
property of the control to false. In the do-while loop you first decrease the selectedControl field. If it is
less than zero you set it to the number of controls minus one. If the control is a TabStop control and the
control is Enabled I break out of the loop. Before exiting you set the HasFocus property to true.
With the control manager, it is now time to add in some specific controls. The control that I'm going to
add in first is a simple Label control that can be used to draw text. The advantage of using the control
manager and controls is you can group controls for easy access and you can loop through them using a
foreach loop. Right click the Controls folder in the XRpgLibrary project, select Add and then Class.
Name this new class Label. This is the code for the Label class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.Controls
{
public class Label : Control
{
#region Constructor Region
public Label()
{
tabStop = false;
}
#endregion
#region Abstract Methods
public override void Update(GameTime gameTime)
{
}
public override void Draw(SpriteBatch spriteBatch)
{
spriteBatch.DrawString(SpriteFont, Text, Position, Color);
}
public override void HandleInput(PlayerIndex playerIndex)
{
}
#endregion
}

The class looks simple but coupled with the ControlManager it ends up being quite powerful. There
are using statements to bring a couple of the XNA Framework name spaces into scope. The constructor
of the Label class sets the tabStop field to false by default so Labels can't be selected by default. You
can of course override this behavior if you need to. The Update and HandleInput methods do nothing
at the moment. The Draw method calls the DrawString method of the SpriteBatch class to draw the
text.
Another useful control to add is a LinkLabel. It is like a Label but I allow it to be selected. You could

get away with adding this to the Label class but I like separating them into different controls. Right
click the Controls folder in the XRpgLibrary project, select Add and then Class. Name this new class
LinkLabel. This is the code for the LinkLabel class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public class LinkLabel : Control
{
#region Fields and Properties
Color selectedColor = Color.Red;
public Color SelectedColor
{
get { return selectedColor; }
set { selectedColor = value; }
}
#endregion
#region Constructor Region
public LinkLabel()
{
TabStop = true;
HasFocus = false;
Position = Vector2.Zero;
}
#endregion
#region Abstract Methods
public override void Update(GameTime gameTime)
{
}
public override void Draw(SpriteBatch spriteBatch)
{
if (hasFocus)
spriteBatch.DrawString(SpriteFont, Text, Position, selectedColor);
else
spriteBatch.DrawString(SpriteFont, Text, Position, Color);
}
public override void HandleInput(PlayerIndex playerIndex)
{
if (!HasFocus)
return;

if (InputHandler.KeyReleased(Keys.Enter) ||
InputHandler.ButtonReleased(Buttons.A, playerIndex))
base.OnSelected(null);

#endregion
}

Again, the class is simplistic but combined with the control manager it can be quite powerful. There are
a couple of using statements to bring some of the XNA Framework classes into scope. There is a new
field and property associated with this control, selectedColor and SelectedColor. They are used in
drawing the control in a different color if it is selected.
The constructor sets the TabStop property to true so it can receive focus. It also sets the HasFocus
property to false initially so the control does not have focus and will not be updated or handle input.
The Draw method draws the control in its regular color if it is not select and in the selected color if it
does have focus. The HandleInput method returns if control does not have focus. If it does it checks to
see if the Enter key or the A button on the game pad have been released. If they have, they call the
OnSelected method of the Control class passing null for the EventArgs parameter.
In other tutorials I will be adding in other controls. For now though, there are enough controls to move
between two screens. What I'm going to do is add in a game state, StartMenuScreen. This state will be
a menu that the player will choose items from a menu on how they wish to proceed. To move from the
TitleScreen to the StartMenuScreen there will be a link label on the TitleScreen that if selected will
move to the StartMenuScreen.
To use the ControlManager you need a SpriteFont. Right click the EyesOfTheDragonContent
project, select Add and then New Folder. Name this new folder Fonts. Right click the Fonts folder,
select Add and then New Item. Select the Sprite Font entry and name it ControlFont. Change the
Size element to 20.
I first want to extend the BaseGameState a little. I want to add in a ControlManager to that state.
Change the BaseGameState class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using XRpgLibrary;
using XRpgLibrary.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace EyesOfTheDragon.GameScreens
{
public abstract partial class BaseGameState : GameState
{
#region Fields region
protected Game1 GameRef;
protected ControlManager ControlManager;
protected PlayerIndex playerIndexInControl;
#endregion
#region Properties region
#endregion
#region Constructor Region
public BaseGameState(Game game, GameStateManager manager)
: base(game, manager)

{
GameRef = (Game1)game;
}

playerIndexInControl = PlayerIndex.One;

#endregion
#region XNA Method Region
protected override void LoadContent()
{
ContentManager Content = Game.Content;
SpriteFont menuFont = Content.Load<SpriteFont>(@"Fonts\ControlFont");
ControlManager = new ControlManager(menuFont);
}

base.LoadContent();

public override void Update(GameTime gameTime)


{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
}
#endregion
}

I added in the over rides for the LoadContent, Update, and Draw methods. The Update and Draw
methods just call those methods of the base class. I create a new instance of the ControlManager class
in the LoadContent method. I get the ContentManager from our game using the Content property. I
load in the menu font and create the control manager instance.
I want to add another screen to the game before I show the use of the ControlManager. Right click the
GameScreens, select Add and then Class. Name this new class StartMenuScreen. This is the code for
that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using
using
using
using

Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
XRpgLibrary;

namespace EyesOfTheDragon.GameScreens
{
public class StartMenuScreen : BaseGameState
{
#region Field region
#endregion
#region Property Region
#endregion
#region Constructor Region
public StartMenuScreen(Game game, GameStateManager manager)

: base(game, manager)
{
}
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
if (InputHandler.KeyReleased(Keys.Escape))
{
Game.Exit();
}
base.Draw(gameTime);
}
#endregion
#region Game State Method Region
#endregion
}

This is state a skeleton, for now. In the next tutorial I will add more to it. It is here so I can show how to
move from one game state to another. I did add in a condition that checks to see if the Escape key is
released. If it is, the game exits. This isn't ideal behavior for a game. I will fix that in another tutorial.
Switch back to the code for the Game1 class. Add a field for this screen just below the TitleScreen
field.
public StartMenuScreen StartMenuScreen;

With that done, you will want to create a StartMenuScreen in the Game1 constructor. Change that
constructor to the following.
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
ScreenRectangle = new Rectangle(
0,
0,
screenWidth,
screenHeight);
Content.RootDirectory = "Content";

Components.Add(new InputHandler(this));
stateManager = new GameStateManager(this);
Components.Add(stateManager);
TitleScreen = new TitleScreen(this, stateManager);
StartMenuScreen = new GameScreens.StartMenuScreen(this, stateManager);
}

stateManager.ChangeState(TitleScreen);

All you are doing is creating an instance of the StartMenuScreen and assigning it to the field in the
Game1 class. Flip back to the code for the TitleScreen. The changes I made were extensive so I will
give you the code for the entire class. Change the code for the TitleScreen class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using XRpgLibrary;
using XRpgLibrary.Controls;
namespace EyesOfTheDragon.GameScreens
{
public class TitleScreen : BaseGameState
{
#region Field region
Texture2D backgroundImage;
LinkLabel startLabel;
#endregion
#region Constructor region
public TitleScreen(Game game, GameStateManager manager)
: base(game, manager)
{
}
#endregion
#region XNA Method region
protected override void LoadContent()
{
ContentManager Content = GameRef.Content;
backgroundImage = Content.Load<Texture2D>(@"Backgrounds\titlescreen");
base.LoadContent();
startLabel = new LinkLabel();
startLabel.Position = new Vector2(350, 600);
startLabel.Text = "Press ENTER to begin";
startLabel.Color = Color.White;
startLabel.TabStop = true;
startLabel.HasFocus = true;
startLabel.Selected += new EventHandler(startLabel_Selected);
}

ControlManager.Add(startLabel);

public override void Update(GameTime gameTime)


{
ControlManager.Update(gameTime, PlayerIndex.One);
}

base.Update(gameTime);

public override void Draw(GameTime gameTime)


{
GameRef.SpriteBatch.Begin();
base.Draw(gameTime);
GameRef.SpriteBatch.Draw(
backgroundImage,
GameRef.ScreenRectangle,
Color.White);
ControlManager.Draw(GameRef.SpriteBatch);
}

GameRef.SpriteBatch.End();

#endregion
#region Title Screen Methods
private void startLabel_Selected(object sender, EventArgs e)
{
StateManager.PushState(GameRef.StartMenuScreen);
}
}

#endregion

The first change was the addition of a LinkLabel control to the field section. I will wire an event
handler for the Selected event of the LinkLabel. In the LoadContent method I create the link label
and add it to the control manager. It is important that you construct controls on game screens after the
call to base.LoadContent. The reason is the control manager will not exist until after that. I set the
position of the LinkLabel, I did this by trial and error, the text, color, tab stop, and has focus
properties. I also wire the event handler if the player presses either Enter or A on the controller. The
last step is adding the LinkLabel to the control manager.
In the Update method I call the Update method of the ControlManager. For the parameters I pass in
the GameTime parameter from the Update method and PlayerIndex.One for the player index. For
now I will only be accepting input from the controller and PlayerIndex.One. In the Draw method I
call the Draw method of the ControlManager. If you call this before you draw the background image,
the background image will be drawn over top of the link label. I pass in the SpriteBatch from our
game reference.
The last method is the startLabel_Selected method. This will be called automatically if the player
selects the start label, you do not have to call this method explicitly. This is where events can be very
nice to work with. If you are interested in responding to an event you subscribe, or wire an event
handler to the event. This method just calls the PushState method of the GameStateManager passing
in the StartMenuScreen.
I think this is enough for this tutorial. I'd like to try and keep them to a reasonable length so that you
don't have too much to digest at once. I encourage you to visit the news page of my site, XNA Game

Programming Adventures, for the latest news on my tutorials.


Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 3
Even More Core Game Components
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is part three in the series. What I'm planning on doing in this tutorial is fill out the start menu and
add a new screen for game play. I'm going to add in a new control as well, a picture box control. I'm
also going to add a new event to the ControlManager class that is fired when a control receives focus.
To get started, load up your project from last time. I'm going to start by adding the new event to the
ControlManager. First, you will want to add in the event. I added in a new Event region to the regions
of the control manager class. I changed the NextControl and PreviousControl method to fire the event
if it is subscribed to. Add the following region near the Field region and change the PreviousControl
and NextControl methods to the following.
#region Event Region
public event EventHandler FocusChanged;
#endregion
public void NextControl()
{
if (Count == 0)
return;
int currentControl = selectedControl;
this[selectedControl].HasFocus = false;
do
{
selectedControl++;
if (selectedControl == Count)
selectedControl = 0;
if (this[selectedControl].TabStop && this[selectedControl].Enabled)
{
if (FocusChanged != null)
FocusChanged(this[selectedControl], null);
}

break;

} while (currentControl != selectedControl);


this[selectedControl].HasFocus = true;
}

public void PreviousControl()


{
if (Count == 0)
return;
int currentControl = selectedControl;
this[selectedControl].HasFocus = false;
do
{
selectedControl--;
if (selectedControl < 0)
selectedControl = Count - 1;
if (this[selectedControl].TabStop && this[selectedControl].Enabled)
{
if (FocusChanged != null)
FocusChanged(this[selectedControl], null);
break;
}
} while (currentControl != selectedControl);
}

this[selectedControl].HasFocus = true;

The new event is called FocusChanged and it will be fired when the control that has focus changes. It
will send the control that received focus as the sender. I changed NextControl and PreviousControl to
fire the event if it is subscribed to. I did that in the if statement that breaks out of the do-while loop if a
control has a tab stop and it is enabled.
Before filling out the start screen I'm going to add in a screen for game play to take place. Right click
the GameScreens folder in the solution explorer, select Add and then Class. Name this new class
GamePlayScreen. This is the code for the class.
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;

using XRpgLibrary;
namespace EyesOfTheDragon.GameScreens
{
public class GamePlayScreen : BaseGameState
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public GamePlayScreen(Game game, GameStateManager manager)
: base(game, manager)
{
}
#endregion

#region XNA Method Region


public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
}
#endregion

#region Abstract Method Region


#endregion

Like the StartMenuScreen, this is just a bare bones class that inherits from BaseGameState. There are
using statements to bring a few of the XNA Framework classes into scope and our XRpgLibrary. The
next step is to add in a field for the GamePlayScreen in the Game1 class and create an instance in the
constructor. Add the following field to the Game State region. Also, change the constructor to the
following.
public GamePlayScreen GamePlayScreen;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
ScreenRectangle = new Rectangle(
0,
0,
screenWidth,
screenHeight);
Content.RootDirectory = "Content";
Components.Add(new InputHandler(this));
stateManager = new GameStateManager(this);
Components.Add(stateManager);
TitleScreen = new TitleScreen(this, stateManager);
StartMenuScreen = new StartMenuScreen(this, stateManager);
GamePlayScreen = new GamePlayScreen(this, stateManager);
stateManager.ChangeState(TitleScreen);
}

I want to add in a new control, the picture box control. Right click the Controls folder in the
XRpgLibary project, select Add and then Class. Name this class PictureBox. This is the code for the

PictureBox class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public class PictureBox : Control
{
#region Field Region
Texture2D image;
Rectangle sourceRect;
Rectangle destRect;
#endregion
#region Property Region
public Texture2D Image
{
get { return image; }
set { image = value; }
}
public Rectangle SourceRectangle
{
get { return sourceRect; }
set { sourceRect = value; }
}
public Rectangle DestinationRectangle
{
get { return destRect; }
set { destRect = value; }
}
#endregion
#region Constructors
public PictureBox(Texture2D image, Rectangle destination)
{
Image = image;
DestinationRectangle = destination;
SourceRectangle = new Rectangle(0, 0, image.Width, image.Height);
Color = Color.White;
}
public PictureBox(Texture2D image, Rectangle destination, Rectangle source)
{
Image = image;
DestinationRectangle = destination;
SourceRectangle = source;
Color = Color.White;
}
#endregion
#region Abstract Method Region
public override void Update(GameTime gameTime)
{

}
public override void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(image, destRect, sourceRect, Color);
}
public override void HandleInput(PlayerIndex playerIndex)
{
}
#endregion
#region Picture Box Methods
public void SetPosition(Vector2 newPosition)
{
destRect = new Rectangle(
(int)newPosition.X,
(int)newPosition.Y,
sourceRect.Width,
sourceRect.Height);
}
#endregion
}

There are a few extra using statements to bring the XNA Framework, XNA Framework Input, and
XNA Framework Graphics classes into scope. The class inherits from the base abstract class Control
so it can be added to the ControlManager class.
There are three new fields and properties to expose them. The fields are image, destRect, and
sourceRect. The fields that expose them are Image, DestinationRectangle, and SourceRectangle
respectively. The first one image is of course the image for the picture box and is a Texture2D. The
next one destRect is the destination rectangle where the image is to be drawn. The last, sourceRect, is
the source rectangle in the image. This will be handy down the road as you will see.
There are two constructors in this class. The first takes a Texture2D for the picture box and a
Rectangle for the destination of the image on the screen. That constructor assigns the image field and
destRect field to the values passed in. It also sets the sourceRect field to be the entire image using zero
for the X and Y properties of the rectangle and the Width and Height properties of the Texture2D for
the Width and Height of the rectangle. That means the entire Texture2D will be used as the source
rectangle. The second constructor takes a Texture2D, and two Rectangle parameters. The Texture2D
is the image. The first Rectangle is the destination and the second Rectangle is the source in the
image. It just assigns the fields using the parameters passed in. I also set the Color property to white.
The base class Control has three abstract methods that must be implemented: Draw, Update, and
HandleInput. They don't have to do anything however. The one that I added code to was the Draw
method. The Draw method draws the Texture2D using the image, destRect, sourceRect, and color
fields.
Bixel, a member of my forum, had a nice idea about updating the picture box a little. You can read his
idea in this topic. I added in some of the functionality by adding a SetPosition method that takes a
Vector2 for the new position of the picture box. I create a new destination rectangle by casing the X
and Y properties of the vector passed to integers and use the Width and Height properties of the source
rectangle.

Before I get to the StartMenuScreen I want to add in a couple graphics for GUI controls. Again, I'd
like to thank Tuckbone from my forum for providing the graphics. You can download the graphics from
http://xnagpa.net/xna4/downloads/guigraphics.zip. After you've downloaded the graphics extract them
to a folder. Go back to your game and right click the EyesOfTheDragonContent project, select Add
and then New Folder. Name this new folder GUI. Right click the GUI folder, select Add and then
Existing Item. Navigate to where you extracted the controls and add them all.
Open up the code for the StartMenuScreen. I made a lot of changes to the screen as it was basically a
place holder before. I just noticed that I had the code for checking if the player presses Escape in the
Draw method. That was a mistake on my part. This is the code for the StartMenuScreen.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using
using
using
using

Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Content;

using XRpgLibrary;
using XRpgLibrary.Controls;
namespace EyesOfTheDragon.GameScreens
{
public class StartMenuScreen : BaseGameState
{
#region Field region
PictureBox backgroundImage;
PictureBox arrowImage;
LinkLabel startGame;
LinkLabel loadGame;
LinkLabel exitGame;
float maxItemWidth = 0f;
#endregion
#region Property Region
#endregion
#region Constructor Region
public StartMenuScreen(Game game, GameStateManager manager)
: base(game, manager)
{
}
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
ContentManager Content = Game.Content;

backgroundImage = new PictureBox(


Content.Load<Texture2D>(@"Backgrounds\titlescreen"),
GameRef.ScreenRectangle);
ControlManager.Add(backgroundImage);
Texture2D arrowTexture = Content.Load<Texture2D>(@"GUI\leftarrowUp");
arrowImage = new PictureBox(
arrowTexture,
new Rectangle(
0,
0,
arrowTexture.Width,
arrowTexture.Height));
ControlManager.Add(arrowImage);
startGame = new LinkLabel();
startGame.Text = "The story begins";
startGame.Size = startGame.SpriteFont.MeasureString(startGame.Text);
startGame.Selected +=new EventHandler(menuItem_Selected);
ControlManager.Add(startGame);
loadGame = new LinkLabel();
loadGame.Text = "The story continues";
loadGame.Size = loadGame.SpriteFont.MeasureString(loadGame.Text);
loadGame.Selected += menuItem_Selected;
ControlManager.Add(loadGame);
exitGame = new LinkLabel();
exitGame.Text = "The story ends";
exitGame.Size = exitGame.SpriteFont.MeasureString(exitGame.Text);
exitGame.Selected += menuItem_Selected;
ControlManager.Add(exitGame);
ControlManager.NextControl();
ControlManager.FocusChanged += new EventHandler(ControlManager_FocusChanged);
Vector2 position = new Vector2(350, 500);
foreach (Control c in ControlManager)
{
if (c is LinkLabel)
{
if (c.Size.X > maxItemWidth)
maxItemWidth = c.Size.X;
c.Position = position;
position.Y += c.Size.Y + 5f;
}

ControlManager_FocusChanged(startGame, null);
}
void ControlManager_FocusChanged(object sender, EventArgs e)
{
Control control = sender as Control;
Vector2 position = new Vector2(control.Position.X + maxItemWidth + 10f,
control.Position.Y);
arrowImage.SetPosition(position);
}
private void menuItem_Selected(object sender, EventArgs e)
{
if (sender == startGame)
{
StateManager.PushState(GameRef.GamePlayScreen);

}
if (sender == loadGame)
{
StateManager.PushState(GameRef.GamePlayScreen);
}
if (sender == exitGame)
{
GameRef.Exit();
}
}
public override void Update(GameTime gameTime)
{
ControlManager.Update(gameTime, playerIndexInControl);
}

base.Update(gameTime);

public override void Draw(GameTime gameTime)


{
GameRef.SpriteBatch.Begin();
base.Draw(gameTime);
ControlManager.Draw(GameRef.SpriteBatch);
GameRef.SpriteBatch.End();
}
#endregion
#region Game State Method Region
#endregion
}
}

You may be wondering why I haven't added a menu component to the game when I have a menu on
this screen. You could add a menu component and use one, and there is nothing wrong with that route. I
instead decided to use the controls that I created and the control manager. The way the menu is going to
work is it is going to display the items using link labels. The currently selected link label will have an
arrow to the right of it.
There are some using statements to bring a few of the XNA Framework and our XRpgLibrary name
spaces into scope. The class inherits from BaseGameState so it can be used in our game state manager.
There are six fields in the class. The first two are PictureBox controls. The one is for the background
image and the other is for the arrow. There are also three LinkLabel controls, one for each of the three
menu items: one to start a new game, one to load an old game, and one to exit the game. The last field
is a float and it is used to control where the arrow appears in the menu. It holds the maximum width of
the link labels. The constructor for the class just calls the constructor of the base class with the values
passed in.
The LoadContent method is where I create the controls on the form, wire their event handlers, and
position them on the form. Again, you want to do this after the call to base.LoadContent so that the
ControlManager exists.
The first control I create is the PictureBox for the background image. I load in the same image from

the title screen and set the destination rectangle of the picture box to our ScreenRectangle. I then add it
to the ControlManager.
I then load in the image for the other picture box. I create the picture box for that control passing in the
image I loaded and a rectangle at coordinates (0, 0) with the width and height of the image. Its position
at the moment isn't important as it will be changed at the end of the method.
The next step is to create the startGame LinkLabel. I set the Text property to "The story begins." and
set the Size property to the size of the string using the MeasureString method from the SpriteFont
property. I then wire the event handler for the Selected event but not the default handler. I set it to a
handler that will handle the Selected event of all menu items. I then add it to the ControlManager. I
create the loadGame and exitGame controls the same way. I then call the NextControl method of the
control manager to have startGame as the currently selected control. I then wire the event handler for
the FocusChanged event of the ControlManager.
I create a Vector2 that holds the position of the first LinkLabel on the screen. Then I loop over all of
the controls that were added to the control manage. In the loop I check to see if the control is a
LinkLabel. If it is I compare the X property of its size with maxItemWidth field. If it is greater I set
maxItemWidth to that value. I then set the Position property of the control to the Vector2 I created. I
then increase the Y property of the Vector2 by the Y property of the Size property of the control plus
five pixels.
At the end of the method I call the FocusChanged event handler passing in the startGame control and
null. What this does is call the code that positions the arrow to the right of the sender. In this case, the
arrow will be to the right of the startGame item.
The ControlManager_FocusChanged method sets the sender parameter as a control. I then create a
Vector2, position, using X value of the Position property of the control and the maxItemWidth field
plus 10 pixels for the X value of the Vector2. For the Y value of the vector I use the Y value of the
controls position.
In the menuItem_Selected method I check to see which link label was selected. I compare the sender
parameter with the different link labels. If it is the startGame or loadGame link label I call the push
the GamePlayScreen on top of the stack. If it was the exitGame link label I exit out of the game. It
would probably be a good idea to have a little pop up screen that asks if that is really what you want to
do and I will add that in some time.
In the Update method I call the Update method of the ControlManager class. I pass the gameTime
parameter from the Update method and playerIndexInControl, a field of PlayerIndex that I added to
the BaseGameState screen last tutorial. For the Draw method I call the Begin method of the sprite
batch from our game before the call to base.Draw. This allows any child components to be drawn
before we draw our components. I then call the Draw method of the ControlManager. Finally, the call
to End of the sprite batch.
I think this is enough for this tutorial. I'd like to try and keep them to a reasonable length so that you
don't have too much to digest at once. I encourage you to visit the news page of my site, XNA Game
Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!

Jamie McMahon

XNA 4.0 RPG Tutorials


Part 4
The Tile Engine - Part 1
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
A major part of a role playing game is exploring the map of the world. This is a 2D role playing game
and uses tiling. Tiling is the process of creating a larger image using many smaller images. Each of the
smaller images is called a tile. Drawing the map is done using a tile engine. There are a few different
types of tile engines, I will be using the most basic.
The first thing that you will need is tiles. There are a number of places that you can get tiles from on
the web. I will be using tiles from OpenGameArt.org. This is a site that offers free graphics for open
source games. This game is an Open Source game so I thought I would use the graphics from there.
Now that you have tiles you have a decision to make. You can either create tile sets or use separate
images. I will use a tile set. A tile set is an image that is made up of the individual tiles. You can
download the tile set from this link: http://xnagpa.net/xna4/downloads/tilesets.zip.
After you have downloaded the tile set and unzipped it open up your game in Visual C#. Right click the
EyesOfTheDragonContent project, select Add and then New Folder. You can name this new folder
Tilesets. Next, right click the Tilesets folder select Add and then Existing Item. Navigate to where
you extracted the tile set and add the tileset1.png file.
The next step is to create the tile engine. I will be making a layered tile engine. Your map will be made
up layers. On the different layers you will have different elements of the map. I will also be making an
editor for the maps to make your life easier. Right click your XRpgLibrary project in the solution
explorer, select Add and then New Folder. Call this folder TileEngine. You will want a general class
that holds information about the tiles and the map. Right click the TileEngine folder, select Add and
then Class. Call this class Engine. This is the code for the Engine class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
namespace XRpgLibrary.TileEngine
{
public class Engine
{
#region Field Region
static int tileWidth;

static int tileHeight;


#endregion
#region Property Region
public static int TileWidth
{
get { return tileWidth; }
}
public static int TileHeight
{
get { return tileHeight; }
}
#endregion
#region Constructors
public Engine(int tileWidth, int tileHeight)
{
Engine.tileWidth = tileWidth;
Engine.tileHeight = tileHeight;
}
#endregion
#region Methods
public static Point VectorToCell(Vector2 position)
{
return new Point((int)position.X / tileWidth, (int)position.Y / tileHeight);
}
}

#endregion

The Engine class holds the width and the height of the tiles on the screen. It may be developed more
down the road as the game progresses. The class uses the Point and Vector2 classes of the XNA
Framework so I added a using statement for that. There are static fields in the Engine class and static
read only properties to get their values. The tileWidth and TileWidth field and property are for the
width of the tiles on the screen. The tileHeight and TileHeight field and property are for the height of
the tiles on the screen. The constructor of the class takes as parameters the width and height of the tiles.
The VectorToCell method is used to get the position of a Vector2 in tiles on the map.
Since I decided to go with a tile set over a list of tiles the next thing I added was a class for tile sets.
Right click the TileEngine folder, select Add, and then Class. Name this class Tileset. This is the code
for the Tileset class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.TileEngine
{
public class Tileset
{
#region Fields and Properties

Texture2D image;
int tileWidthInPixels;
int tileHeightInPixels;
int tilesWide;
int tilesHigh;
Rectangle[] sourceRectangles;
#endregion
#region Property Region
public Texture2D Texture
{
get { return image; }
private set { image = value; }
}
public int TileWidth
{
get { return tileWidthInPixels; }
private set { tileWidthInPixels = value; }
}
public int TileHeight
{
get { return tileHeightInPixels; }
private set { tileHeightInPixels = value; }
}
public int TilesWide
{
get { return tilesWide; }
private set { tilesWide = value; }
}
public int TilesHigh
{
get { return tilesHigh; }
private set { tilesHigh = value; }
}
public Rectangle[] SourceRectangles
{
get { return (Rectangle[])sourceRectangles.Clone(); }
}
#endregion
#region Constructor Region
public Tileset(Texture2D image, int tilesWide, int tilesHigh, int tileWidth, int
tileHeight)
{
Texture = image;
TileWidth = tileWidth;
TileHeight = tileHeight;
TilesWide = tilesWide;
TilesHigh = tilesHigh;
int tiles = tilesWide * tilesHigh;
sourceRectangles = new Rectangle[tiles];
int tile = 0;
for (int y = 0; y < tilesHigh; y++)
for (int x = 0; x < tilesWide; x++)
{
sourceRectangles[tile] = new Rectangle(

x * tileWidth,
y * tileHeight,
tileWidth,
tileHeight);
tile++;
}

#endregion

#region Method Region


#endregion

This class just keeps everything that has to do with the tile set in one place. It holds the image, the
width of the tiles in the image, and the source rectangles of the image. The source rectangles describe
the tiles in the image. When I get to drawing the tiles they are drawn using a source rectangle for the
tile set they are being drawn from and a destination that describes where they are draw to on the screen.
The class requires a Texture2D for the tile set and an array of Rectangles for the source rectangles so
there are using statements for the XNA Framework and the XNA Framework graphics classes. The
fields in the class are image, tileWidthInPixels, tileHeightInPixels, tilesWide, tilesHigh, and
sourceRectangles. The first is the Texture2D for the tile set, the second and third are the width and
height of the tiles in pixels, the third and forth are the number of tiles wide and high the image is, and
the last is the source rectangles for the tile set. The properties for the class are Texture, TileWidth,
TileHieght, TilesWide, TilesHigh, and SourceRectangles. They are public get and private set. The
work for the fields list above in the same order. SourceRectangles returns a clone of the source
rectangles. I do that because if you modify one of the rectangles that are returned you modify it inside
the class as well. You are much better to work with a copy of the rectangle.
The constructor for this class takes five parameters. The Texture2D for the tile set, the number of tiles
wide the tile set is, the number of tiles high the tile set is, the width of each tile in pixels, and the height
of each tile in pixels. The constructor then sets the fields with the values passed in. To calculate the
number of source rectangles for the tile set you multiply the number of tiles wide by the number of tiles
high. The next step is to create an array for the source rectangles. The variable tile will be the index of
the source rectangle that is being created. The next step is important. There is a set of nested for loops.
The first loop loops through the tiles high the tile set is. The second loop loops through the tiles wide
the tile set is. What this does is goes through all of the tiles in the image starting at the top left corner,
moving left to right first and then top to bottom. This will be important when you are making your
maps. The first tile will be index 0 in the map, the one just to the right will be index 1, the following
just to the right of that one index 2, and so and so forth. After creating the tile I increment the tile
variable.
The next thing you need to start tiling is the map. I've designed the map so that you can have multiple
layers. I've also designed the map so that you can have multiple tile sets. To allow that instead of just
using an integer to describe a tile, I created a class that will hold the tile information. For now it will
have an index for the tile and an index for the tile set the tile belongs to. Right click the TileEngine
folder, select Add and then Class. Name this class Tile. This is the code for the Tile class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace XRpgLibrary.TileEngine

{
public class Tile
{
#region Field Region
int tileIndex;
int tileset;
#endregion
#region Property Region
public int TileIndex
{
get { return tileIndex; }
private set { tileIndex = value; }
}
public int Tileset
{
get { return tileset; }
private set { tileset = value; }
}
#endregion
#region Constructor Region
public Tile(int tileIndex, int tileset)
{
TileIndex = tileIndex;
Tileset = tileset;
}
#endregion
}

This is a very simple class. It has two fields. One for the index of the tile in the tile set and one for the
index of the tile set. There are properties where the get is public and the set it private. This allows the
value of the tile to be accessed outside of the class and set inside of the class. The constructor just
assigns the fields with the values passed in.
Before I get much farther, I want to add some simple tiling to the game. That will be done in the
GamePlayScreen. I will create a simple map and demonstrate how to tile the map. This is for those
who have not read my other XNA RPG tutorials or my XNA simple tile engine tutorials. Open the code
for the GamePlayScreen.
I won't use the Tile class quite yet. I will, instead, use an array of integers to represent the tiles. If you
look at the image I made with the tiles the first tile is completely transparent. So to show that the map is
being drawn properly I created an array of integers filled with ones. I also created a Tileset object and
an Engine object. First, add a using statement to bring the TileEngine name space of the library into
scope. Also, change the Fields region of the GamePlayScreen to the following.
using XRpgLibrary.TileEngine;
Engine engine = new Engine(32, 32);
Tileset tileset;
int[,] map;

What I've done is add a field engine for the Engine class. The tile engine will require a Tileset to draw
the tiles so there is a field for that as well. The last new field is map which is a 2D array of integers.
When the tile engine is finished this will be a layer of the map and it will be a 2D array of Tile instead
of integers. When I created the instance of the Engine class I passed in 32 for the width and height of
the tiles. This is also the width and height of the tiles in the tile set I made.
You will need to create a Tileset object. For the Tileset object you will need the Texture2D with the
tiles. The best thing to do here is to add in the LoadContent method in the GamePlayScreen. In the
XNA Methods region of the GamePlayScreen class add this LoadContent method.
protected override void LoadContent()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
tileset = new Tileset(tilesetTexture, 8, 8, 32, 32);
map = new int[50, 50];
}

base.LoadContent();

I use the content manager from our Game1 class to load in the texture for the tile set. I then create a
Tileset. I pass in the image of the tiles 8 for the tiles wide and high. For the width and height of the
tiles I pass in 32. The reason for these values is the tile sets are eight tiles wide and high. The tiles are
32 pixels wide and high. I chose these values because it is a good idea to have your texture as a power
of 2. The tile sets are 256 pixels by 256 pixels that will work out to a power of 2. I also create the array
of integer to be 50 by 50.
Now we have everything we need to draw a tile map. Since this involves drawing to the screen that will
happen in the Draw method. I will show you the code and then explain the code. Add this Draw
method to the GamePlayScreen in the XNA Methods region.
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Immediate,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
Matrix.Identity);
for (int y = 0; y < map.GetLength(0); y++)
{
for (int x = 0; x < map.GetLength(1); x++)
{
GameRef.SpriteBatch.Draw(
tileset.Texture,
new Rectangle(
x * Engine.TileWidth,
y * Engine.TileHeight,
Engine.TileWidth,
Engine.TileHeight),
tileset.SourceRectangles[map[y, x]],
Color.White);
}
}
base.Draw(gameTime);
GameRef.SpriteBatch.End();
}

When Microsoft released XNA 4.0 they remodeled the SpriteBatch class entirely to make it much
more robust. Instead of using enumerations for the way sprites are blended they created a class that
controls it. They added in some other classes that give you much more control as well, like being able
to sample textures. When they release the first version of XNA people were complaining about some
issues with sprites. In particular, when you scale sprites in a sprite sheet, like the tile set, you can get
lines around the sprites. This had to do with how the textures for the sprite were loaded and with how
the images were sampled. The way some people got around it was to map meshes to vertex buffers like
you do in 3D. Another was to read in textures in a different manner. The upgrades to SpriteBatch give
you so much more control over the way things are rendered.
So, if you haven't worked much with SpriteBatch in XNA 4.0 the call to Begin, compared to earlier
releases, is quite new. There are five overloads to the call. The one I used takes seven parameters. The
first is the way sprites are sorted. I chose Immediate. This means as soon as you make a call to Draw
the images is sent to the graphics card instead of waiting for the call to End. This mode works well for
tile engines.
The next parameter is a BlendState object. There is a static property of the BlendState class that
returns an alpha blending state. The tiles have transparency, an alpha channel of 0, so you want alpha
blending. There are other states as well.
The third parameter is a SamplerState object. This object helps control the way textures are mapped to
the output. The mode I chose, PointClamp, clamps the sampling to points of the texture. This type of
sampling is what keeps the sprites from "bleeding" when scaled. I'm not going into the next three
parameters that are set to null in this tutorial. The last parameter is why I used this overload over
another. Later to control scrolling, and scaling, I will be using matricies. I passed in the identity matrix
here. You control scrolling, scaling, rotation, etc. by multiplying matricies together. Using the identity
matrix returns the original values with no moving, scaling, or rotation.
In the Draw method there is a nested for loop, much like the nested for loop that I used to create the
source rectangles in the constructor of the Tileset class. This may seem a little backwards to you. In
math, and drawing in 2D and 3D, the coordinates are (x, y) but when it comes to arrays in C# they are
backwards [y, x]. If you tried to do it the other way your map will come out rotated 90 degrees. You can
get the size of each dimension of the array using the GetLength method. Since the Y coordinate comes
first you use 0 as the parameter to get its length. You pass in 1 to get the size of the X coordinate. These
loops work like the loops in the constructor of the Tileset class. The outer loop loops from top to
bottom and the inner loop loops from left to right.
Inside the inner loop is where you actually do the drawing. The overload of the Draw method that I
used takes four parameters. The first is the Texture2D that you want to draw. The second is the
destination rectangle that you want to draw to. The third is the source rectangle in the Texture2D that
you want to draw. The last, and is always the last in the Draw method of the SpriteBatch class is the
tint color. You get the Texture2D and source rectangle from the Tileset object. The destination
rectangle is the interesting part. It has something to do with the x and y values in the map and the width
and height of the tiles. The width and height of the destination rectangle is the width and height of the
tiles defined in the Engine class. To find the X and Y coordinates you do a little math. For the tile at (0,
0) on the screen you would use (0, 0). For the tile (1, 0) you need to add the width of one tile on the
screen to draw it in the right place. Similarly for tile (2, 0) you need to add the width of two tiles to get
it to draw the same way. The same is true as you move down the screen. For tile (0, 1) you need to add
the height of one tile to get it drawn in the same place. Again, for tile (0, 2) you need to add the height

of two tiles. If you think of the tile (1, 1) you need to add the width and height of the tile. The function
for finding the X coordinate is x * tile width. The function for finding the Y coordinate is y * tile
height.
The next step is to create a class to represent a layer of the map. Right click the TileEngine folder,
select Add and then Class. Name this class MapLayer. This is the code for the MapLayer class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace XRpgLibrary.TileEngine
{
public class MapLayer
{
#region Field Region
Tile[,] map;
#endregion
#region Property Region
public int Width
{
get { return map.GetLength(1); }
}
public int Height
{
get { return map.GetLength(0); }
}
#endregion
#region Constructor Region
public MapLayer(Tile[,] map)
{
this.map = (Tile[,])map.Clone();
}
public MapLayer(int width, int height)
{
map = new Tile[height, width];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
map[y, x] = new Tile(0, 0);
}
}
}
#endregion
#region Method Region
public Tile GetTile(int x, int y)
{
return map[y, x];
}
public void SetTile(int x, int y, Tile tile)
{

map[y, x] = tile;
}
public void SetTile(int x, int y, int tileIndex, int tileset)
{
map[y, x] = new Tile(tileIndex, tileset);
}
#endregion
}

The layers won't be responsible for drawing themselves. The map class will do all of the drawing. This
just simplifies things and will make reading in maps easier. There is just the one field in the class, map,
that is a 2D array of type Tile. There are also properties for returning the width and height of the layer.
The use the same method I used above for drawing the map. You can get the width using the
GetLength method passing in 1 and the height using the GetLength method passing in 0.
There are two constructors in this class. The first one takes a 2D array of Tile. This constructor makes a
clone of the array. This is again because if you make a change to the array it will change the original
array. The second constructor takes as parameters the width and height of the map. It then creates a new
array and then initializes the array filled with Tile objects. There are three methods in this class. The
first, GetTile, returns the Tile at the given coordinates in the map. The other two are overloads of the
same method SetTile. The first takes as parameters the X and Y coordinates and a Tile object. The
second takes as parameters the X and Y coordinates of the tile, the index of the tile in the tile set, and
which tile set the tile belongs to.
The next step is to create a class for the map with the layers and tile sets. Right click the TileEngine
folder in your game, select Add and then Class. Name this class TileMap. This is the code for that
class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.TileEngine
{
public class TileMap
{
#region Field Region
List<Tileset> tilesets;
List<MapLayer> mapLayers;
#endregion
#region Property Region
#endregion
#region Constructor Region
public TileMap(List<Tileset> tilesets, List<MapLayer> layers)
{
this.tilesets = tilesets;
this.mapLayers = layers;
}

public TileMap(Tileset tileset, MapLayer layer)


{
tilesets = new List<Tileset>();
tilesets.Add(tileset);
mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
}
#endregion
#region Method Region
public void Draw(SpriteBatch spriteBatch)
{
Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
Tile tile;
foreach (MapLayer layer in mapLayers)
{
for (int y = 0; y < layer.Height; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = 0; x < layer.Width; x++)
{
tile = layer.GetTile(x, y);
destination.X = x * Engine.TileWidth;
spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);
}
}

#endregion
}

This is class is the heart of the tile engine. This is where the tiling is done. There are two fields in this
class. The first, tilesets, is a List<Tileset>. This is a list of tile sets that can grow and shrink as needed.
It is better than using an array because you need to know the size of the array at the start. Using
List<T> is a good idea when you don't know the size you will need. The second field, mapLayers, is a
List<MapLayer> and will hold the layers of the map.
I added in two constructors for this class. One that takes a List<Tileset> and List<MapLayer> as its
parameters. The other takes a Tileset and MapLayer as its parameters. The first constructor just sets
the fields to the parameters passed in. The second creates a new List<Tileset> and adds the Tileset that
is passed in to the list. It then creates a new List<MapLayer> and adds the MapLayer passed in to the
list.
In the Draw method I draw the layers. The Draw method takes a SpriteBatch parameter that is the
active SpriteBatch in between calls to Begin and End. To make it more efficient I have a Rectangle
and Tile object that I will reuse rather than creating and destroying them each time through the loop.
For the Rectangle only the X and Y values will change, the height and width remain constant. As well
I'm get a new tile object each time so it is okay to reuse that as well, as long as it isn't changed.

In a for each loop I loop through all of the layers in the map. Inside of that there are the nested for loops
that you should be familiar with. Inside the outer loop I calculate the Y coordinate. There is no point in
recalculating it each time time through the inner loop as it only changes when you move to the next
row. In the inner loop I get the tile using the GetTile method of the MapLayer class. I then calculate
the X coordinate of the tile.
The complicated part here is deciphering the call do the Draw method. The first parameter is of the
Draw method is the Texture2D of the image. I find that using the Tileset property of the tile and using
that as the index for the List<Tileset> and use the Texture property. The destination rectangle has
already been calculated. The source rectangle is found using the Tileset property of the tile to make
sure we are in the right tile set and then using the TileIndex property to get the proper source rectangle.
The next thing you will want to do is to create a map and test these classes. I did that in the
GamePlayScreen. What I did is replace the array of integers field with a TileMap field. Change the
Field region of the GamePlayScreen to the following.
#region Field Region
Engine engine = new Engine(32, 32);
Tileset tileset;
TileMap map;
#endregion

The next thing that needs to be done is to create the map. I did that in the LoadContent method.
Change the LoadContent to the following.
protected override void LoadContent()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
tileset = new Tileset(tilesetTexture, 8, 8, 32, 32);
MapLayer layer = new MapLayer(40, 40);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

map = new TileMap(tileset, layer);


base.LoadContent();
}

What I did here is first create a MapLayer called Layer using the constructor that takes the width and
height of the layer. There is again the nest for loops. In the inner loop I create a new tile setting the tile
index to 1 and the tile set index to 0. I then use the SetTile method passing in the x and y coordinates
and the Tile object I created. I then create a new map passing in the tileset and layer.
The last thing to do is to draw the map. The Draw method is where I did that. All you need to do is call
the Draw method of the TileMap class. Change the Draw method of the GamePlayScreen class to the

following.
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Immediate,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
Matrix.Identity);
map.Draw(GameRef.SpriteBatch);
base.Draw(gameTime);
}

GameRef.SpriteBatch.End();

Now we have the beginnings of a nice little tile engine to work with. There is still more work to do but
it is a good beginning. So, I think this is enough for this tutorial. I'd like to try and keep them to a
reasonable length so that you don't have too much to digest at once. I encourage you to visit the news
page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 5
The Tile Engine - Part 2
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I will be continuing on with the tile engine. At the moment the map is being drawn but
you need to be able to scroll the map so the player can explore your world. A good way to control
scrolling of the map is to use a 2D camera. The camera shows what the player is looking at in the
world. Right click the TileEngine folder in the XRpgLibrary project, select Add and then Class.
Name this new class Camera. This is the code for the Camera class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.TileEngine
{
public class Camera
{
#region Field Region
Vector2 position;
float speed;
float zoom;
Rectangle viewportRectangle;
#endregion
#region Property Region
public Vector2 Position
{
get { return position; }
private set { position = value; }
}
public float Speed
{
get { return speed; }
set
{
speed = (float)MathHelper.Clamp(speed, 1f, 16f);
}
}
public float Zoom
{

get { return zoom; }


}
#endregion
#region Constructor Region
public Camera(Rectangle viewportRect)
{
speed = 4f;
zoom = 1f;
viewportRectangle = viewportRect;
}
public Camera(Rectangle viewportRect, Vector2 position)
{
speed = 4f;
zoom = 1f;
viewportRectangle = viewportRect;
Position = position;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
if (InputHandler.KeyDown(Keys.Left))
position.X -= speed;
else if (InputHandler.KeyDown(Keys.Right))
position.X += speed;
if (InputHandler.KeyDown(Keys.Up))
position.Y -= speed;
else if (InputHandler.KeyDown(Keys.Down))
position.Y += speed;
}
}

#endregion

This camera is a different from cameras that I've created in my other tutorials. It is going to allow the
player to zoom in and out and it is responsible for determining its position in the world. I'm also
thinking of allowing the player to be able to control the camera to view the world but when they move
their character the camera will snap back to the character. If I do go that route I will also be
implementing a "fog of war" that the player can only see areas they've discovered.
There are using statements to bring the XNA Framework and XNA Framework Graphics classes into
scope. There are four fields in the class. The first, position, is the position of the camera on the map
and is a Vector2. There are two float fields: speed and zoom. The speed field controls the speed at
which the camera moves through the world. The zoom field controls the zoom level of the camera. The
last field is a Rectangle field, viewportRectangle, that describes the view port that the tile engine will
draw to.
There are three properties in the Camera class. The Position property returns the position of the
camera in the world. There is a private set to the property. The Speed property exposes the speed field.
The set part uses the MathHelper.Clamp method to clamp the speed between 1 and 16. The last
property, Zoom, is used to return the zoom level of the camera.

There are two constructors for the Camera class. The first takes a Rectangle that describes the view
port the tile engine will be drawn to. The second takes the same rectangle and Vector2 for the position
of the camera. They both set the speed of the camera to 4 pixels and the zoom to 1. A zoom of 1 is a
map with no zoom at all. Less than one zooms in, showing more of the map. Greater than one zooms
out, showing less of the map. Both set the rectangle for the screen as well. The constructor that takes a
Vector2 for the position sets the position field.
There is also a method Update that takes a GameTime parameter used to update the camera's position
in the world. There are a couple if statements. The first checks to see if the left arrow key is down using
the InputHandler class. If it is it decreases the X property of the camera's position by the camera's
speed. In an else-if I check if the right arrow key is down. If it is I increase the camera's X property.
The reason you decrease to move the camera left is that the values of X increase as you move from left
to right across the screen. There is another if statement that checks to see if the up arrow is down and
decreases the Y property of the camera's position. In the else-if I check to see if the down key is down
and increment the Y property of the camera's position. This is different than the X values because the
values of Y increase as you move down the screen. The reason for this is because of the way memory
for graphics is allocated.
I'm going to add a class to the game for the player. This class will be responsible for updating and
drawing the player. Right click the EyesOfTheDragon project in the solution explorer, select Add and
New Folder. Name this new folder Components. Right click the Components folder, select Add and
then Class. Name this class Player. This is the code for the Player class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XRpgLibrary;
using XRpgLibrary.TileEngine;
namespace EyesOfTheDragon.Components
{
public class Player
{
#region Field Region
Camera camera;
Game1 gameRef;
#endregion
#region Property Region
public Camera Camera
{
get { return camera; }
set { camera = value; }
}
#endregion
#region Constructor Region
public Player(Game game)
{

gameRef = (Game1)game;
camera = new Camera(gameRef.ScreenRectangle);

#endregion
#region Method Region
public void Update(GameTime gameTime)
{
camera.Update(gameTime);
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
}
}

#endregion

This class will be developed more as the game progresses. There are using statements to bring some of
the XNA Framework as well as our XRpgLibrary and XRpgLibrary.TileEngine name spaces. I
added in two fields. The camera is tied to the player so there is a Camera field. There is a property to
expose the camera as well. There is also a Game1 field that will be a reference to the game. The
constructor takes a Game parameter. It casts the parameter to Game1 and assigns it to the gameRef
field. It also creates a camera. The Update method takes a GameTime parameter that is frequently
used by components. It also calls the Update method of the camera passing in the gameTime
parameter. The Draw method a blank method that takes a GameTime parameter but takes a
SpriteBatch parameter as well.
The next step is to add a Player field to the GamePlayScreen class so you will have access to the
camera to scroll the map. You will need to add a using statement for the Components name space of
your game. Also, initialize the player field in the constructor. Add this using statement and change the
Field region and Constructor region to the following.
using EyesOfTheDragon.Components;
#region Field Region
Engine engine = new Engine(32, 32);
Tileset tileset;
TileMap map;
Player player;
#endregion
#region Constructor Region
public GamePlayScreen(Game game, GameStateManager manager)
: base(game, manager)
{
player = new Player(game);
}
#endregion

You also need to call the Update method of the player in the Update method of the GamePlayScreen.
Change the Update method of the GamePlayScreen to the following.
public override void Update(GameTime gameTime)

{
player.Update(gameTime);
}

base.Update(gameTime);

In the first version of tutorial 4 I had neglected to post the code for the TileMap class. I'm including the
full code for that class here now. I did go back and upgrade tutorial 4 if you're interested.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.TileEngine
{
public class TileMap
{
#region Field Region
List<Tileset> tilesets;
List<MapLayer> mapLayers;
#endregion
#region Property Region
#endregion
#region Constructor Region
public TileMap(List<Tileset> tilesets, List<MapLayer> layers)
{
this.tilesets = tilesets;
this.mapLayers = layers;
}
public TileMap(Tileset tileset, MapLayer layer)
{
tilesets = new List<Tileset>();
tilesets.Add(tileset);

mapLayers = new List<MapLayer>();


mapLayers.Add(layer);

#endregion
#region Method Region
public void Draw(SpriteBatch spriteBatch)
{
Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
Tile tile;
foreach (MapLayer layer in mapLayers)
{
for (int y = 0; y < layer.Height; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = 0; x < layer.Width; x++)
{
tile = layer.GetTile(x, y);
destination.X = x * Engine.TileWidth;

}
}

spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);

}
}

#endregion

You still need to update the TileMap class to use the camera. The best option is to pass your camera as
a parameter to the Draw method of the TileMap class. You will also need to use the camera to control
where the tiles are drawn. I'm also going to make a couple changes to make the drawing a little more
efficient. Change the Draw method to the following.
public void Draw(SpriteBatch spriteBatch, Camera camera)
{
Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
Tile tile;
foreach (MapLayer layer in mapLayers)
{
for (int y = 0; y < layer.Height; y++)
{
destination.Y = y * Engine.TileHeight - (int)camera.Position.Y;
for (int x = 0; x < layer.Width; x++)
{
tile = layer.GetTile(x, y);
if (tile.TileIndex == -1 || tile.Tileset == -1)
continue;
destination.X = x * Engine.TileWidth - (int)camera.Position.X;
spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);
}
}

The first change is I added in a Camera parameter after the SpriteBatch parameter. This may be a
little counter intuitive but to move the camera left you add its X value to the X coordinate of the
destination rectangle and to move it right you subtract its X value from the X coordinate. Basically, you
need to subtract the camera's X position from the X coordinate of the destination rectangle. You also
subtract the camera's Y position form the Y coordinate of the destination rectangle.
As you can see, inside the outer for loop I cast the result of subtracting the camera's Y position from the
destination rectangle's Y coordinate. Then in the inner for loop I cast the result of subtracting the
camera's X position form the destination rectangle's X coordinate. In the inner loop I check to make
sure that either the tile index or tile set for the tile is not -1. If it is I move onto the next iteration of the
loop. If you run the game now the map will scroll with the cursor keys being pressed or the left thumb
stick being pressed. Only problem is the map will move off the screen. To fix this you need to lock the

camera so that it will not move off the screen.


You need to add a method to the Camera class to lock the camera and keep it from scrolling off the
edges of the map. The easy parts are the left and top. To keep in from scrolling off the top you keep the
X and Y values of the camera's position from being negative. To keep it from scroll off the right and
bottom you need to know the width and height of the map in pixels and the width and height of the
view port you are drawing to. It would be best to assign two field in the TileMap class the width and
height of the map in tiles. Then you can expose the width and height of the map in pixels using
properties. Change the Field and Property regions to the following.
#region Field Region
List<Tileset> tilesets;
List<MapLayer> mapLayers;
static int mapWidth;
static int mapHeight;
#endregion
#region Property Region
public static int WidthInPixels
{
get { return mapWidth * Engine.TileWidth; }
}
public static int HeightInPixels
{
get { return mapHeight * Engine.TileHeight; }
}
#endregion

You still need to set the mapHeight and mapWidth fields. You will do that in the constructors. I'm not
going to have maps with different size layers. You could do it easily but drawing the layers directly
rather than doing the rendering from the map. What I suggest is in the constructors for the TileMap
class is add a check to make sure that the layers are the same size. Change the Constructor region of
the TileMap class to the following.
#region Constructor Region
public TileMap(List<Tileset> tilesets, List<MapLayer> layers)
{
this.tilesets = tilesets;
this.mapLayers = layers;
mapWidth = mapLayers[0].Width;
mapHeight = mapLayers[0].Height;
for (int i = 1; i < layers.Count; i++)
{
if (mapWidth != mapLayers[i].Width || mapHeight != mapLayers[i].Height)
throw new Exception("Map layer size exception");
}
}
public TileMap(Tileset tileset, MapLayer layer)
{
tilesets = new List<Tileset>();
tilesets.Add(tileset);
mapLayers = new List<MapLayer>();

mapLayers.Add(layer);

mapWidth = mapLayers[0].Width;
mapHeight = mapLayers[0].Height;

#endregion

The final change will be to add a method to the Method region of the Camera class, LockCamera, to
lock the camera. You will also want to add a call to the LockCamera method in the Update method of
the Camera class. Change the Method region of the Camera class to the following.
#region Method Region
public void Update(GameTime gameTime)
{
if (InputHandler.KeyDown(Keys.Left))
position.X -= speed;
else if (InputHandler.KeyDown(Keys.Right))
position.X += speed;
if (InputHandler.KeyDown(Keys.Up))
position.Y -= speed;
else if (InputHandler.KeyDown(Keys.Down))
position.Y += speed;
}

LockCamera();

private void LockCamera()


{
position.X = MathHelper.Clamp(position.X,
0,
TileMap.WidthInPixels - viewportRectangle.Width);
position.Y = MathHelper.Clamp(position.Y,
0,
TileMap.HeightInPixels - viewportRectangle.Height);
}
#endregion

The LockCamera method uses the Clamp method of the MathHelper class to clamp the X value of
the camera's position to the world to the width of the map in pixels minus the width of the view port. If
you don't subtract the width of the view port the camera will keep on moving until it reaches the width
of the map and the background color will show through. You also must make sure that the camera's
position is never negative. You do the same for the keeping the map for scrolling off the top of the
screen by making sure its Y coordinate is never less than zero and never greater than the height of the
map in pixels minus the height of the view port.
So, if you run the game now and move to the game play screen you will see the map scroll but not off
the edges of the screen. One thing you will notice is that if you move the map diagonally it will scroll
faster than vertically or horizontally. This can be explained by the Pythagorean Theorem. If you move
the map 8 pixels down and 8 pixels right in one frame you are moving the map the square root of (8 * 8
+ 8 * 8) which is greater than 8 pixels. Fortunately XNA provides a good way to fix that.
Instead of moving the camera when the player wants to move you find out which direction the player
wants to move. You can find that be creating a Vector2 that will have a Y value of 1 if the player wants
to move down or a value of -1 if the player wants to move up. Similarly, it will have an X value of 1 if
the player wants to move right or a value of -1 if the player wants to move left. You take that value and

normalize it. Normalizing a vector is the process of changing it to a vector of length 1. It will still be in
the same direction though. You can multiply that vector by the speed you want to the camera to move
and the camera will move at the same speed in all eight directions. You must check to make sure the
vector is not the zero vector because it can't be normalized because it has no direction. You can change
the Update method of the Camera class to the following.
public void Update(GameTime gameTime)
{
Vector2 motion = Vector2.Zero;
if (InputHandler.KeyDown(Keys.Left))
motion.X = -speed;
else if (InputHandler.KeyDown(Keys.Right))
motion.X = speed;
if (InputHandler.KeyDown(Keys.Up))
motion.Y = -speed;
else if (InputHandler.KeyDown(Keys.Down))
motion.Y = speed;
if (motion != Vector2.Zero)
motion.Normalize();
position += motion * speed;
}

LockCamera();

I think the next this I will do is demonstrate a tile map with multiple layers. The first thing I will do is
add a new method to the TileMap class called AddLayer. This method will add a new layer to an
existing map. You should make sure that the width and the height of the new layer is the same as width
and height of the map. Add the following method to the TileMap class in the Methods region.
public void AddLayer(MapLayer layer)
{
if (layer.Width != mapWidth && layer.Height != mapHeight)
throw new Exception("Map layer size exception");
}

mapLayers.Add(layer);

The next step is in the LoadContent method of the GamePlayScreen to create a map layer and add it
to the list of layers. If you look at the tile set that I made there are several environmental tiles that are
mostly transparent. I will create a new layer made up mostly of those tiles. The will break up the
monotony of the single tile map that I made. What I will do is create 80 tiles and position them
randomly on the map in a new layer. You can change the LoadConent method to the following.
protected override void LoadContent()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
tileset = new Tileset(tilesetTexture, 8, 8, 32, 32);
MapLayer layer = new MapLayer(40, 40);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

}
map = new TileMap(tileset, layer);
MapLayer splatter = new MapLayer(40, 40);
Random random = new Random();
for (int i = 0; i < 80; i++)
{
int x = random.Next(0, 40);
int y = random.Next(0, 40);
int index = random.Next(2, 14);

Tile tile = new Tile(index, 0);


splatter.SetTile(x, y, tile);

map.AddLayer(splatter);
base.LoadContent();
}

What the new code does is first create a new layer called splatter. I got this name from a friend of mine
who is a game programmer. He calls tiles with a lot of transparency splatter tiles. He says he splatters
them on his maps to break up the boring monotony of fields. You can also use them to give buildings a
weathered looked or burn holes, lots of different things. I then create an instance of the Random class
to generate some random numbers. That is followed by a for loop that loops 80 times to add some of
the splatter tiles to the layer. Inside of the for loop I first create a X coordinate for the tile between 0
and 40 and then a number in the same range for the Y coordinate. The next number is an integer
between 3 and 14, the index of the splatter tiles in the tile set. I then create a new Tile object and call
the SetTile method passing in the coordinates and the Tile object. After creating the layer I add it to the
map using the AddLayer method I just created.
The next step would be to demonstrate using multiple tile sets on the same map. For that you will need
two tile sets. What I decided to do is to split the tile set that I made into two tile sets. The first tile set
will be woodland environmental tiles and the second city type tiles. You can download the two tile sets
from http://xnagpa.net/xna4/downloads/tilesets2.zip.
Unzip the file with the tile sets, then right click the Tilesets folder in the EyesOfTheDragonContent
project, select Add, and then Existing Item. Add the tileset1.png and tileset2.png files. The next step
is to create a map that uses both tile sets. I will do that in the LoadContent method of the
GamePlayScreen. You can replace the LoadContent method with the following. You can also remove
the field tileset from the fields of the GamePlayScreen as well.
protected override void LoadContent()
{
base.LoadContent();
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(40, 40);

for (int y = 0; y < layer.Height; y++)


{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
}

layer.SetTile(x, y, tile);

}
MapLayer splatter = new MapLayer(40, 40);
Random random = new Random();
for (int i = 0; i < 80; i++)
{
int x = random.Next(0, 40);
int y = random.Next(0, 40);
int index = random.Next(2, 14);
Tile tile = new Tile(index, 0);
splatter.SetTile(x, y, tile);
}
splatter.SetTile(1, 0, new Tile(0, 1));
splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
}

map = new TileMap(tilesets, mapLayers);

I moved everything after the call to base.LoadContent so if you want to use controls, or a sprite font,
you will have access to them. What this code does is first load in the texture for the first tile set and
create a Tileset object, tileset1. It then loads in the texture for the second tile set and create another
Tileset object, tileset2. I then created a List<Tileset> and added in the the two Tileset objects. Order is
important here. If you mix up the order the tiles on your map will be mixed up. Then like in the old
LoadContent method I create a MapLayer filled with tile index 0 and tile set 0, which is the grass tile.
I then create a second layer like earlier called splatter. After doing call the SetTile method three times
passing in coordinates (1, 0) to (3, 0) and using tiles from the second tile set. After creating both layers
of the map I create a List<MapLayer> and add the two layers I created to the list. I then call the
constructor of the TileMap class that takes a List<Tileset> and a List<MapLayer> as parameters. If
you build and run now you will get your map with a little building with a door.
You need to update the Draw method of GamePlayScreen. The Draw method of TileMap now needs
a Camera parameter. Update the Draw method of GamePlayScreen to the following.
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Immediate,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
Matrix.Identity);
map.Draw(GameRef.SpriteBatch, player.Camera);

base.Draw(gameTime);
}

GameRef.SpriteBatch.End();

The tile engine is improving but there is still more work to do but it is a good beginning. So, I think this
is enough for this tutorial. I'd like to try and keep them to a reasonable length so that you don't have too
much to digest at once. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 6
Character Generator
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I will be adding in a new game screen to create a character. First I will be adding in a
new control, a left and right selector. When the control is selected pressing the left or right arrow key
will cycle through a collection of strings. Right click the Controls folder in the XRpgLibrary project
in the solution explore. Select Add, and then Class. Name this new class LeftRightSelector. The code
for this class follows.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public class LeftRightSelector : Control
{
#region Event Region
public event EventHandler SelectionChanged;
#endregion
#region Field Region
List<string> items = new List<string>();
Texture2D leftTexture;
Texture2D rightTexture;
Texture2D stopTexture;
Color selectedColor = Color.Red;
int maxItemWidth;
int selectedItem;
#endregion
#region Property Region
public Color SelectedColor
{
get { return selectedColor; }
set { selectedColor = value; }
}

public int SelectedIndex


{
get { return selectedItem; }
set { selectedItem = (int)MathHelper.Clamp(value, 0f, items.Count); }
}
public string SelectedItem
{
get { return Items[selectedItem]; }
}
public List<string> Items
{
get { return items; }
}
#endregion
#region Constructor Region
public LeftRightSelector(Texture2D leftArrow, Texture2D rightArrow, Texture2D stop)
{
leftTexture = leftArrow;
rightTexture = rightArrow;
stopTexture = stop;
TabStop = true;
Color = Color.White;
}
#endregion
#region Method Region
public void SetItems(string[] items, int maxWidth)
{
this.items.Clear();
foreach (string s in items)
this.items.Add(s);
maxItemWidth = maxWidth;
}
protected void OnSelectionChanged()
{
if (SelectionChanged != null)
{
SelectionChanged(this, null);
}
}
#endregion
#region Abstract Method Region
public override void Update(GameTime gameTime)
{
}
public override void Draw(SpriteBatch spriteBatch)
{
Vector2 drawTo = position;
if (selectedItem != 0)
spriteBatch.Draw(leftTexture, drawTo, Color.White);
else
spriteBatch.Draw(stopTexture, drawTo, Color.White);
drawTo.X += leftTexture.Width + 5f;

float itemWidth = spriteFont.MeasureString(items[selectedItem]).X;


float offset = (maxItemWidth - itemWidth) / 2;
drawTo.X += offset;
if (hasFocus)
spriteBatch.DrawString(spriteFont, items[selectedItem], drawTo, selectedColor);
else
spriteBatch.DrawString(spriteFont, items[selectedItem], drawTo, Color);
drawTo.X += -1 * offset + maxItemWidth + 5f;

if (selectedItem != items.Count - 1)
spriteBatch.Draw(rightTexture, drawTo, Color.White);
else
spriteBatch.Draw(stopTexture, drawTo, Color.White);

public override void HandleInput(PlayerIndex playerIndex)


{
if (items.Count == 0)
return;
if (InputHandler.ButtonReleased(Buttons.LeftThumbstickLeft, playerIndex) ||
InputHandler.ButtonReleased(Buttons.DPadLeft, playerIndex) ||
InputHandler.KeyReleased(Keys.Left))
{
selectedItem--;
if (selectedItem < 0)
selectedItem = 0;
OnSelectionChanged();
}

if (InputHandler.ButtonReleased(Buttons.LeftThumbstickRight, playerIndex) ||
InputHandler.ButtonReleased(Buttons.DPadRight, playerIndex) ||
InputHandler.KeyReleased(Keys.Right))
{
selectedItem++;
if (selectedItem >= items.Count)
selectedItem = items.Count - 1;
OnSelectionChanged();
}

#endregion
}

There are a few using statements to bring some of the XNA framework classes into scope. There is an
event associated with this control, SelectionChanged, that if subscribed to will fire if the selection in
the selector is changed.
There are a number of fields in this class. There is a List<string> called items that holds the items for
the selector. There are three Texture2D fields. They hold a stop bar, a left arrow, and a right arrow. I
again want to thank Tuckbone for taking the time to make the graphics. There is a Color field,
selectedColor, that holds the color to draw the control if it is currently selected. There are also two
integer fields, maxItemWidth and selectedIndex. The first is the width of the longest item in the
selector. It is needed because I will be centering items in the selector. The last is the index of the
currently selected item in the selector.
There are a few properties in the class to expose the fields. The Items property exposed the items field.
It is a read only, get, property but you can manipulate the items field with it. You just can't assign to it
directly. The get part of the SelectedIndex property returns the selectedIndex field. The set part sets

the selectedIndex field but uses the Clamp method of MathHelper to make sure the field stays with in
the range of items. The SelectedItem property just returns the item that is currently selected. The
SelectedColor property exposes the selectedColor field.
The constructor for LeftRightSelector takes three parameters. They are an image for displaying the
selector can move left, an image for displaying the selector can move right and a stop bar displaying
the selection can not move in that direction. The constructor sets the fields with the values passed in,
sets the TabStop property to true, and the color of the control to white.
There are two methods that are specific to this control. The first is the SetItems method. It takes an
array of strings for the items and an integer for the maximum width. It loops through the array of
strings passed in and adds them to the items collection. It also assigns the maxItemWidth field with
the value passed in. The OnSelection method will be called if the SelectionChanged if the selection is
changed. It checks to see if the SelectionChanged event is subscribed to. If it is it fires the
SelectionChanged event using itself for the sender and null for the event arguments.
The Update method for this class does nothing. It is there because it is part of the base abstract
class. It would be used if you were doing some sort of animation with the control or other things that
required updating. The HandleInput method will handle the input for the control. It checks to make
sure there are items in the items collection to make sure that there is something to work with. If there
aren't it exits the method. There is next an if statement that checks to see if the left thumb stick has been
released in the left direction, the direction pad has released in the left direction or the left key has been
released. If they have I decrease the selected item by one. If the selected item is less than zero it is set
to zero. It then checks for the releases to the right. I increment the selected item by one. If the selected
item is greater than or equal to the number of items I set the selected item to be the number of items
minus one.
The Draw method draws the control. The first step is to set a local variable to the position the
control is to be drawn at. The reason is that there are several parts to the control. There are the left and
right arrows, the stop bar, and the text. The first step is to see if the left arrow needs to be drawn. It is
only drawn if the selected item is not the first item, the item at index zero. If there is one I draw the left
arrow. If the left arrow does not need to be drawn I draw the stop bar. I then increment the X position of
drawing point by the width of the left arrow plus 5 pixels for padding. I decided to center the text
horizontally in the control. First you need to find the width of the text using the MeasureString
method of the SpriteFont class. I don't need the height of the string so I just used the X for the string.
The offset value is used to determine where to draw the text relative to the drawTo local variable. I
then add the offset value to the X part of the drawTo variable. You want the control drawn in the
selectedColor value if it has focus so there is an if statement to check if the control has focus before
drawing the text. If it selected, it is drawn using the selectedColor and if not the Color value. To figure
out where to draw the right arrow you remove the offset, add the maxItemWidth field and the padding
of five pixels. The last step is to see if the right arrow should be drawn by checking if the selected item
is the last item. It it doesn't need to be drawn then you draw the stop bar.
Now it is time to add in the new screen to the game. Right click the GameScreens folder, select Add
and then Class. Name this new class CharacterGeneratorScreen. The code follows next.
using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XRpgLibrary;
using XRpgLibrary.Controls;
namespace EyesOfTheDragon.GameScreens
{
public class CharacterGeneratorScreen : BaseGameState
{
#region Field Region
LeftRightSelector genderSelector;
LeftRightSelector classSelector;
PictureBox backgroundImage;
string[] genderItems = { "Male", "Female" };
string[] classItems = { "Fighter", "Wizard", "Rogue", "Priest" };
#endregion
#region Property Region
#endregion
#region Constructor Region
public CharacterGeneratorScreen(Game game, GameStateManager stateManager)
: base(game, stateManager)
{
}
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
}

CreateControls();

public override void Update(GameTime gameTime)


{
ControlManager.Update(gameTime, PlayerIndex.One);
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin();
base.Draw(gameTime);
ControlManager.Draw(GameRef.SpriteBatch);
GameRef.SpriteBatch.End();
}
#endregion
#region Method Region

private void CreateControls()


{
Texture2D leftTexture = Game.Content.Load<Texture2D>(@"GUI\leftarrowUp");
Texture2D rightTexture = Game.Content.Load<Texture2D>(@"GUI\rightarrowUp");
Texture2D stopTexture = Game.Content.Load<Texture2D>(@"GUI\StopBar");
backgroundImage = new PictureBox(
Game.Content.Load<Texture2D>(@"Backgrounds\titlescreen"),
GameRef.ScreenRectangle);
ControlManager.Add(backgroundImage);
Label label1 = new Label();
label1.Text = "Who will search for the Eyes of the Dragon?";
label1.Size = label1.SpriteFont.MeasureString(label1.Text);
label1.Position = new Vector2((GameRef.Window.ClientBounds.Width - label1.Size.X) /
2, 150);
ControlManager.Add(label1);
genderSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture);
genderSelector.SetItems(genderItems, 125);
genderSelector.Position = new Vector2(label1.Position.X, 200);
ControlManager.Add(genderSelector);
classSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture);
classSelector.SetItems(classItems, 125);
classSelector.Position = new Vector2(label1.Position.X, 250);
ControlManager.Add(classSelector);
LinkLabel linkLabel1 = new LinkLabel();
linkLabel1.Text = "Accept this character.";
linkLabel1.Position = new Vector2(label1.Position.X, 300);
linkLabel1.Selected += new EventHandler(linkLabel1_Selected);
ControlManager.Add(linkLabel1);
ControlManager.NextControl();
}
void linkLabel1_Selected(object sender, EventArgs e)
{
InputHandler.Flush();
StateManager.PopState();
StateManager.PushState(GameRef.GamePlayScreen);
}
}

#endregion

There are using statements to bring some XNA Framework classes into scope and our XRpgLibrary
project. The class inherits from BaseGameState so it can be used in the state manager and to add some
inherited fields and methods.
There are two LeftRightSelector fields, a PictureBox, and two string arrays. The string arrays hold the
two genders, Male and Female, and the other holds the classes in the game. I'm going with the rather
basic classes for the game, Rogue, Fighter, Priest, and Wizard. I'm hoping to design the class system so
it will be easy to add in different classes. For now they will do. The LeftRightSelectors are to select
the gender and class of the character. The PictureBox is for the background image.

The constructor at the moment does nothing. In the LoadContent method, after the call to to the base
class, I call a method CreateControls that creates the controls on screen. The Update method calls the
Update method of the ControlManager passing in the gameTime parameter of the Update method
and PlayerIndex.One for the first game pad. If you're coding for the Xbox 360 is important for the
player to be able to select what controller they want to use. I will address this in a future tutorial. The
Draw method calls calls the Begin method of the SpriteBatch object from the Game1 class using the
GameRef field. The base.Draw to draw any components on the screen, the Draw method of the
ControlManager, and then End on the SpriteBatch object.
The CreateControls method is where I create the controls on the screen. The first step is to load in the
images for the left arrow, right arrow, and stop bar. Order is important when drawing in 2D. If you don't
get the order right objects will be drawn on top of others and won't be visible. That is why I create the
picture box for the background first and add it to the controls first. Later on down the road I'm going to
update the control manager so you can control how controls will be drawn. After creating the picture
box and adding it to the control manager I create a label with the text: Who will search for the Eyes of
the Dragon?. I find out its size using the MeasureString method of the sprite font then center it
horizontally on the screen. The Y value for its position was completely arbitrary. The label is then
added to the control manager. I then create the LeftRightSelectors for the character's gender and class.
The images for the left arrow, right arrow, and stop bar are passed to the constructor. For the gender
selector the genderItems array is passed in to the SetItems method. Similarly, the classItems array is
passed to the SetItems method of the class selector. For the X coordinate of their position they are
lined up with the X coordinate of the label. I separated the controls 50 pixels a part vertically. I then
create a LinkLabel that the player will select when they are happy with their choices. I set the text to:
Accept this character. I line it up horizontally with the other controls and space it 50 pixels from the
last selector. I also wire an event handler for the Selected event. The control is then added to the
control manager and I call the NextControl method to move the selection to the first control that is a
tab stop.
In the linkLabel1_Selected method is where I handle that the player is happy with their character. I
call the Flush method of the InputHandler to eliminate cascading. I then pop the character generator
off the stack of game states and push the game play screen onto the stack of game states.
The next step is to add this screen to the game. The first step is to add a field to the Game1 class for the
character generator screen and create it in the constructor. Add the following field the Game State
region of the Game1 class. Also, change the constructor to the following as well.
public CharacterGeneratorScreen CharacterGeneratorScreen;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
ScreenRectangle = new Rectangle(
0,
0,
screenWidth,
screenHeight);
Content.RootDirectory = "Content";
Components.Add(new InputHandler(this));

stateManager = new GameStateManager(this);


Components.Add(stateManager);
TitleScreen = new TitleScreen(this, stateManager);
StartMenuScreen = new StartMenuScreen(this, stateManager);
GamePlayScreen = new GamePlayScreen(this, stateManager);
CharacterGeneratorScreen = new CharacterGeneratorScreen(this, stateManager);
}

stateManager.ChangeState(TitleScreen);

The last step to implement the character generator takes place in the StartMenuScreen. In the event
handler for the menu items being selected you want to push the character generator onto the stack
instead of the game play screen. Change the menuItem_Selected method to the following.
private void menuItem_Selected(object sender, EventArgs e)
{
if (sender == startGame)
{
StateManager.PushState(GameRef.CharacterGeneratorScreen);
}
if (sender == loadGame)
{
StateManager.PushState(GameRef.GamePlayScreen);
}
if (sender == exitGame)
{
GameRef.Exit();
}
}

Things are starting to come together but there is still a lot of work to be done. But, I think this is
enough for this tutorial. I'd like to try and keep them to a reasonable length so that you don't have too
much to digest at once. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 7
Animated Sprite
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I will be adding in an animated sprite for the player to control. For that you will of
course need sprites. I will be using the sprites I used from my XNA 3.x RPG series to use for this
tutorial. You can download the sprites from http://xnagpa.net/xna4/downloads/playersprites.zip.
After you have downloaded and decompressed the sprites you will need to add them to the content
project. Right click the EyesOfTheDragonContent project, select Add and then New Folder. Call this
new folder PlayerSprites. Right click the PlayerSprites folder, select Add and then Existing Item.
Navigate to where you decompressed the sprites. Select all eight sprites to add them. The names are of
the format genderclass.png. Where gender is the gender of the character and class is the class of the
character. So, malewizard is the sprite for a male wizard.
It will be best to have the sprite classes part of the XRpgLibrary project. There will be more than just
these animated sprites so I added in a folder for all sprite class. Right click the XRpgLibrary project,
select Add and then New Folder. Name this new folder SpriteClasses.
Animation in computer games is done much like it was done when the first animated cartoons came
out. It is the process of repeatedly drawing one image after another to give the illusion that the image is
changing. When done at an appropriate speed the person seeing the illusion will see the image
changing. If you look at the image below the blue squares show the different images, or frames. There
are three images in each row. The artist created the images so that you go from the first frame to the
second frame to the third and then back to the first. That is the way I will be implementing the
animation of the sprite. I will have a frame rate, the number of frames that will be drawn each second.
I've found that five frames per second is a reasonable rate. I will be creating a class for this type of
animation.

Right click the GameComponents folder in your game, select Add and then Class. Name this new
class Animation. This is the code for the Animation class.

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
namespace XRpgLibrary.SpriteClasses
{
public enum AnimationKey { Down, Left, Right, Up }
public class Animation : ICloneable
{
#region Field Region
Rectangle[] frames;
int framesPerSecond;
TimeSpan frameLength;
TimeSpan frameTimer;
int currentFrame;
int frameWidth;
int frameHeight;
#endregion
#region Property Region
public int FramesPerSecond
{
get { return framesPerSecond; }
set
{
if (value < 1)
framesPerSecond = 1;
else if (value > 60)
framesPerSecond = 60;
else
framesPerSecond = value;
frameLength = TimeSpan.FromSeconds(1 / (double)framesPerSecond);
}
}
public Rectangle CurrentFrameRect
{
get { return frames[currentFrame]; }
}
public int CurrentFrame
{
get { return currentFrame; }
set
{
currentFrame = (int)MathHelper.Clamp(value, 0, frames.Length - 1);
}
}
public int FrameWidth
{
get { return frameWidth; }
}
public int FrameHeight
{
get { return frameHeight; }
}
#endregion
#region Constructor Region

public Animation(int frameCount, int frameWidth, int frameHeight, int xOffset, int
yOffset)

{
frames = new Rectangle[frameCount];
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
for (int i = 0; i < frameCount; i++)
{
frames[i] = new Rectangle(
xOffset + (frameWidth * i),
yOffset,
frameWidth,
frameHeight);
}
FramesPerSecond = 5;
Reset();
}
private Animation(Animation animation)
{
this.frames = animation.frames;
FramesPerSecond = 5;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
frameTimer += gameTime.ElapsedGameTime;

if (frameTimer >= frameLength)


{
frameTimer = TimeSpan.Zero;
currentFrame = (currentFrame + 1) % frames.Length;
}

public void Reset()


{
currentFrame = 0;
frameTimer = TimeSpan.Zero;
}
#endregion
#region Interface Method Region
public object Clone()
{
Animation animationClone = new Animation(this);
animationClone.frameWidth = this.frameWidth;
animationClone.frameHeight = this.frameHeight;
animationClone.Reset();
return animationClone;
}
}

#endregion

There is a using statement for the XNA Framework because this class uses the Rectangle class. There
is an enumeration called AnimationKey. It describes the different types of animations the sprite has. It
can be down, up, left and right. You could also have animations for moving on the diagonals. The sprite

I used doesn't have these animations. This class implements the ICloneable interface. The reason is
you may have multiple sprites in the game that have the same lay out as the player's sprite. It is better
to store the animations in a master list of animations and return copies of them. This is a class so if you
pass just the instance and you make changes it will affect the original.
There are quite a few fields in this class. The first one, frames, is an array of rectangles for the source
rectangles of the animation. If you look back at the image all of the animations for one direction are in
the same row. The reason will become clear when I get to the constructor. The next field is an integer
and holds the number of frames to animate per second. The next two fields are of type TimeSpan.
They hold the length of each frame of the animation and the time since the last animation started. Using
the TimeSpan structure gives you a finer degree of control over the animations than using a double or
floating point value. The next field, currentFrame, is the index of the current animation in the array of
rectangles. The last two fields, frameWidth and frameHeight, hold the height and width of the
frames. The one downfall of this method is that all of the frames must have the same width and height.
There is another method that you could use to animate the sprites but I believe this is the best approach.
There are several properties in this class to expose the fields they represent. The first one is
FramesPerSecond and it is used to get and set the number of frames the sprite will animate in one
second. The get part is trivial, it just returns the value in the framesPerSecond field. The set part is a
little more interesting. It does a little validation on the values passed in. The first check is to make sure
that the value is not less than one. If it was you would get some pretty bizarre results. It also makes sure
that the frame rate is not greater than sixty. This value is actually pretty high. Checking for a value of
ten would probably be a better idea. After validating the values it sets the frameLength field. What is
important here is you want to make sure you cast the framesPerSecond field as a double when you do
the division. If you don't then integer division will be preformed and you will get either one or zero,
depending on if framesPerSecond was one or not. You will often need to know what the current
rectangle of the animation is so there is a property CurrentFrameRect to retrieve that. You will not
only want to be able to get what the current frame is but also set it. You will again have to make sure
that the value passed in is valid. I did that using the MathHelper.Clamp method which makes sure that
the value is with in the minimum and maximum values, inclusive, passed in. There are also properties
that get the width and height of the frames, FrameWidth and FrameHeight respectively.
There are two constructors for this class, a public one and a private one. The public takes as parameters
the number of frames for the animation, the width of each frame, the height of each frame, the X offset
of the frame and the Y offset of the frame. The last two's purpose probably isn't obvious. If you go back
to the image of the sprite sheet above you will see that it is split up into rows and columns. The first
row of animations begins at coordinates (0, 0). The second row of animations begins at coordinates (0,
32). The third and forth at (0, 64) and (0, 96) respectively. As you can see as you move down in rows
the Y value changes. This is the Y offset value. You could also have had the animations in two rows and
two columns. In this case you would also have had an X offset. Later I will get to having more than just
the walking animation and the X offset will be useful.
The constructor then sets the frames, frameWidth, and frameHeight first. Then, in a for loop, it
creates each of the rectangles to describe the frames for the animation. This is where the X and Y
offsets become important. When you create the first animation you will be passing in the values 3, 32,
32, 0, and 0. For the second you will be passing in 3, 32, 32, 0, and 32. In the for loop to find the X
coordinate in the sprite sheet for the rectangle you take the X offset and add the width of the frame
times the frame counter, which is i the loop index. In this case since all of the animations or on the
same row you can just use the Y offset value. The Width and Height values are set using the

frameWidth and frameHeight values passed in. I then use the FramesPerSecond field to set the
number of frames for the sprite to be 5. I then call the Reset method of the class which sets the
animation in its starting position.
The second private constructor is used when I implement the ICloneable interface. This constructor
takes an Animation object as its parameter. The reason is because as I mentioned above about the
frames field being marked readonly. This constructor sets the frames field of the new animation to the
frames field of the old animation. It then sets the FramesPerSecond to 5 like the other constructor.
There are two methods, Update and Reset, that are for controlling the animation of the sprite. The third
method, Clone, implements the ICloneable interface. The Update method takes a GameTime
parameter. This parameter measures how much time has elapsed since the last call to Update in the
Game1 class. This is used to determine when it is time to move to the next frame of the animation.
What I do is add the ElapsedGameTime property of the GameTime parameter to frameTimer. This is
used to determine when to move to the next frame. It is time to move to the next frame when
frameTimer is greater than or equal to frameLength so there is an if statement that checks for that. If
it is you want to reset frameTimer to the zero position and move to the next frame. There is an nice
mathematical formula next to calculate which frame to move to. In our case we have three frames that
are at index 0, 1, and 2. If you add 1 to that you will have the values 1, 2, and 3. You then get the
remainder of dividing those by the number of frames. Those values will be 1, 2, and 0. So if you are on
frame 0 you will move to frame 1, from frame 1 to frame 2, and from frame 2 back to frame. The Reset
method is very simple. It sets the current frame to be 0 and then sets the time back to 0 as well.
The last method in this class is the Clone method. This method is from the ICloneable interface. This
method returns a copy of the current animation. The first step is to create a new Animation object
using the private constructor, passing in the current Animation object. The next step is to set the
frameWidth and frameHeight fields. I call the Reset method to reset the Animation to the first frame.
I then return the new Animation as an object. The Clone method always returns an object as its return
value. When you use the Clone method of the Animation class you will have to cast the return value to
be of type Animation.
The next class I'm going to add is a class for an animated sprite. Right click the SpriteClasses folder,
select Add and then Class. Name this new class AnimatedSprite. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XRpgLibrary.TileEngine;
namespace XRpgLibrary.SpriteClasses
{
public class AnimatedSprite
{
#region Field Region
Dictionary<AnimationKey, Animation> animations;
AnimationKey currentAnimation;
bool isAnimating;
Texture2D texture;

Vector2 position;
Vector2 velocity;
float speed = 2.0f;
#endregion
#region Property Region
public AnimationKey CurrentAnimation
{
get { return currentAnimation; }
set { currentAnimation = value; }
}
public bool IsAnimating
{
get { return isAnimating; }
set { isAnimating = value; }
}
public int Width
{
get { return animations[currentAnimation].FrameWidth; }
}
public int Height
{
get { return animations[currentAnimation].FrameHeight; }
}
public float Speed
{
get { return speed; }
set { speed = MathHelper.Clamp(speed, 1.0f, 16.0f); }
}
public Vector2 Position
{
get { return position; }
set
{
position = value;
}
}
public Vector2 Velocity
{
get { return velocity; }
set
{
velocity = value;
if (velocity != Vector2.Zero)
velocity.Normalize();
}
}
#endregion
#region Constructor Region
public AnimatedSprite(Texture2D sprite, Dictionary<AnimationKey, Animation> animation)
{
texture = sprite;
animations = new Dictionary<AnimationKey, Animation>();
foreach (AnimationKey key in animation.Keys)
animations.Add(key, (Animation)animation[key].Clone());
}

#endregion
#region Method Region
public void Update(GameTime gameTime)
{
if (isAnimating)
animations[currentAnimation].Update(gameTime);
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera)
{
spriteBatch.Draw(
texture,
position - camera.Position,
animations[currentAnimation].CurrentFrameRect,
Color.White);
}
public void LockToMap()
{
position.X = MathHelper.Clamp(position.X, 0, TileMap.WidthInPixels - Width);
position.Y = MathHelper.Clamp(position.Y, 0, TileMap.HeightInPixels - Height);
}
}

#endregion

This is a little different from previous AnimatedSprite classes that I made but the core class is the
same. The difference is in the constructor of the class. There are using statements to bring a few classes
of the XNA Framework into scope as well as our XRpgLibrary.TileEngine name space.
There are a number of fields in this class. The first is a Dictionary<AnimationKey, Animation> to
hold the animations of the sprite. I used a dictionary because you can only have one entry per key and it
is easier than having to remember which animation was added when if you were to use a List<T>. The
currentAnimation field is for the current animation of the sprite. The bool field isAnimating is used
to tell if the sprite is currently animating so the animation should be updated in the Update method.
The texture field is for the sprite sheet. The next two field are Vector2 fields and are for the position
and velocity of the sprite. I will be controlling the speed the sprite moves across the screen like the
scrolling of the map. I will normalize the vector and then multiply it by a constant value, the speed
field that holds the speed the sprite moves.
There are properties to expose information about the sprite. CurrentAnimation is a get and set
property for the currentAnimation field. The IsAnimating property is also a get and set property and
is for the isAnimating field. You will often need to know the height and width of the sprite. The
Height and Width properties return the height and width of the current frame of the animation. The
Speed property is also get and set and exposes the speed field. The set part clamps the speed between 1
and 16 using MathHelper.Clamp. The Position property exposes the position field and is a read/write
property, get/set. The Velocity property exposed the velocity field. It is get/set as well. However, the set
part checks to see if the value passed in is Vector2.Zero because it normalizes the vector and you can't
normalize a vector that has zero length.
The constructor takes two parameters. A Texture2D for the sprite and a Dictionary<AnimationKey,
Animation> for the animations. You don't have to pass a clone of the animations to the constructor as it
will clone the animations passed in. The constructor sets the texture field to the texture passed in and
then creates a new Dictionary<AnimationKey, Animation>. Then in a for each look it loops through

all of the keys in the key collection of the dictionary passed in. Inside the loop it adds the key with a
clone of the animation to the dictionary in the class.
The Update takes the GameTime parameter to be able to update the animation. It checks to see if the
sprite is currently animating. If it is it calls the Update method of the current animation.
The Draw method takes three parameters. The GameTime parameter of our game's Draw method, a
SpriteBatch between calls to Begin and End, a Camera for the map. The reason is the sprite, the map,
and the camera are all related. In drawing the map you subtract the position of the camera. You will
also need to subtract it from any object on the map, including sprites. Things are going to get more than
a little complicated having to subtract the camera here and other things. I've got a good solution though.
To draw the sprite I use the overload that takes the texture for the sprite, a Vector2 for its position, a
source rectangle, and the tint color.
The LockToMap method is used to keep the sprite from going off the map. You need to clamp the X
coordinate between zero and the width of the map in pixels minus the width of the sprite. If you don't
subtract the width of the sprite it will move off the screen to the right. Similarly, you clamp the Y
coordinate between zero and the height of the map in pixels minus the height of the sprite.
For now I'm going to add a sprite to the game play screen. In the next tutorial I will move things around
a bit. The first step for adding the sprite to the GamePlayScreen will be to add a using statement for
the SpriteClasses name space of our XRpgLibrary. and add a field for the sprite in the Field region.
using XRpgLibrary.SpriteClasses;
AnimatedSprite sprite;

In the LoadContent method you load in a sprite sheet and construct the animations and add them to a
dictionary to pass to the constructor of the sprite. This is the new LoadContent method.
protected override void LoadContent()
{
Texture2D spriteSheet = Game.Content.Load<Texture2D>(@"PlayerSprites\malefighter");
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
sprite = new AnimatedSprite(spriteSheet, animations);
base.LoadContent();
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();

tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(40, 40);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
}

layer.SetTile(x, y, tile);

}
MapLayer splatter = new MapLayer(40, 40);
Random random = new Random();
for (int i = 0; i < 80; i++)
{
int x = random.Next(0, 40);
int y = random.Next(0, 40);
int index = random.Next(2, 14);
Tile tile = new Tile(index, 0);
splatter.SetTile(x, y, tile);
}
splatter.SetTile(1, 0, new Tile(0, 1));
splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
}

map = new TileMap(tilesets, mapLayers);

The new code just loads in a texture. It then creates the animations. I chose the male fighter sprite sheet
for the sprite. To create the animations you need to know the offset values for X and Y. The X offsets
are the same as each animation is on a row of its own. The thing that changes is the Y offset. Since the
sprites are 32 pixels high the offsets will be multiples of 32. 0 times 32 for the top row, 1 times 32 for
the second, 2 times 32 for the third and 4 time 32 for the last. After creating the animations and loading
the sprite sheet I create the sprite.
In the Update method you will want to call the Update method of the sprite. In the Draw method you
will want to call the Draw method of the sprite. Change the Update and Draw methods in the
GamePlayScreen to the following.
public override void Update(GameTime gameTime)
{
player.Update(gameTime);
sprite.Update(gameTime);
}

base.Update(gameTime);

public override void Draw(GameTime gameTime)


{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Immediate,
BlendState.AlphaBlend,
SamplerState.PointClamp,

null,
null,
null,
Matrix.Identity);
map.Draw(GameRef.SpriteBatch, player.Camera);
sprite.Draw(gameTime, GameRef.SpriteBatch, player.Camera);
base.Draw(gameTime);
GameRef.SpriteBatch.End();
}

The sprite isn't moving at the moment though. It just sits up in the top corner of the map. We want the
sprite to move not the camera but at the same time we want the player to be able to explore the map
with the camera. The way I'm going to implement it is to have two modes for the camera. In the one
mode the camera will follow the sprite. In the other the camera has free movement.
The first step is to update the Camera class. I ended up making quite a few changes to the Camera
class so I will give you the code for the entire class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XRpgLibrary.SpriteClasses;
namespace XRpgLibrary.TileEngine
{
public enum CameraMode { Free, Follow }
public class Camera
{
#region Field Region
Vector2 position;
float speed;
float zoom;
Rectangle viewportRectangle;
CameraMode mode;
#endregion
#region Property Region
public Vector2 Position
{
get { return position; }
private set { position = value; }
}
public float Speed
{
get { return speed; }
set
{
speed = (float)MathHelper.Clamp(speed, 1f, 16f);
}
}
public float Zoom

{
}

get { return zoom; }

public CameraMode CameraMode


{
get { return mode; }
}
#endregion
#region Constructor Region
public Camera(Rectangle viewportRect)
{
speed = 4f;
zoom = 1f;
viewportRectangle = viewportRect;
mode = CameraMode.Follow;
}
public Camera(Rectangle viewportRect, Vector2 position)
{
speed = 4f;
zoom = 1f;
viewportRectangle = viewportRect;
Position = position;
mode = CameraMode.Follow;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
if (mode == CameraMode.Follow)
return;
Vector2 motion = Vector2.Zero;
if (InputHandler.KeyDown(Keys.Left) ||
InputHandler.ButtonDown(Buttons.RightThumbstickLeft, PlayerIndex.One))
motion.X = -speed;
else if (InputHandler.KeyDown(Keys.Right) ||
InputHandler.ButtonDown(Buttons.RightThumbstickRight, PlayerIndex.One))
motion.X = speed;
if (InputHandler.KeyDown(Keys.Up) ||
InputHandler.ButtonDown(Buttons.RightThumbstickUp, PlayerIndex.One))
motion.Y = -speed;
else if (InputHandler.KeyDown(Keys.Down) ||
InputHandler.ButtonDown(Buttons.RightThumbstickDown, PlayerIndex.One))
motion.Y = speed;
if (motion != Vector2.Zero)
{
motion.Normalize();
position += motion * speed;
LockCamera();
}
}
private void LockCamera()
{
position.X = MathHelper.Clamp(position.X,
0,
TileMap.WidthInPixels - viewportRectangle.Width);
position.Y = MathHelper.Clamp(position.Y,
0,

TileMap.HeightInPixels - viewportRectangle.Height);
}
public void LockToSprite(AnimatedSprite sprite)
{
position.X = sprite.Position.X + sprite.Width / 2
- (viewportRectangle.Width / 2);
position.Y = sprite.Position.Y + sprite.Height / 2
- (viewportRectangle.Height / 2);
LockCamera();
}
public void ToggleCameraMode()
{
if (mode == CameraMode.Follow)
mode = CameraMode.Free;
else if (mode == CameraMode.Free)
mode = CameraMode.Follow;
}
#endregion
}

I add in a using statement to bring the SpriteClasses name space into scope. I also added in a enum
called CameraMode that has two values: Free and Follow. I went this route rather than using a bool as
the bool could get confusing. The Free value allows for free movement and the Follow value has the
camera follow the player's sprite. I add a field, mode, that holds the mode of the camera. There is also a
read only property, CameraMode, that exposes the field's value. The constructors both set the mode
field to Follow initially.
I also made many changes to the Update method. The first is at the start I check to see if the field
mode is set to Follow. If it is the player's sprite is controlling the camera and you don't want free
movement so exit the method. I added in support for the Xbox 360 controller for free movement as
well. Cameras are usually controlled with the right thumb stick so I add in checks for the right thumb
stick in the direction if the arrow keys. Since I use an or in the condition either the keyboard or the
thumb stick will control movement. If both are down the camera behaves the same way as if either are
down. I also moved the code for updating the camera's position and locking the camera inside the if
that checks for motion. I did that because if there is no motion there is no need to update the position
and lock the camera if needed.
I also added in a public method LockToSprite that will snap the camera to the sprite. The method takes
an AnimatedSprite as a parameter. The camera is tied to the position of the sprite. The camera's X
coordinate is found by taking the X position of the sprite, adding half the width of the sprite and
subtracting half the width of the view port. What this does is have the map not scroll horizontally until
the center of the sprite is past the center of the view port. The camera's Y coordinate is found by taking
the Y position of the sprite, adding half the height of the sprite and subtracting half the height of the
view port. It then calls LockCamera to keep the map from scrolling off the view port.
There is one last method, ToggleCameraMode. This method just moves from one camera mode to
another. If the mode of the camera was Free it is set to Follow and if it was Follow it is set to Free.
The last thing is to implement the movement in the GamePlayScreen. I'm also going to add toggling of
the view mode of the camera. The sprite will be controlled using the W, A, S, and D keys or left thumb
stick of the controller. Pressing the C key or left thumb stick will snap the camera to the sprite. Finally,
pressing the F key or right thumb stick toggles the camera mode. Change the Update method of the

GamePlayScreen to the following.


public override void Update(GameTime gameTime)
{
player.Update(gameTime);
sprite.Update(gameTime);
Vector2 motion = new Vector2();
if (InputHandler.KeyDown(Keys.W) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Up;
motion.Y = -1;
}
else if (InputHandler.KeyDown(Keys.S) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Down;
motion.Y = 1;
}
if (InputHandler.KeyDown(Keys.A) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Left;
motion.X = -1;
}
else if (InputHandler.KeyDown(Keys.D) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Right;
motion.X = 1;
}
if (motion != Vector2.Zero)
{
sprite.IsAnimating = true;
motion.Normalize();
sprite.Position += motion * sprite.Speed;
sprite.LockToMap();
if (player.Camera.CameraMode == CameraMode.Follow)
player.Camera.LockToSprite(sprite);
}
else
{
sprite.IsAnimating = false;
}
if (InputHandler.KeyReleased(Keys.F) ||
InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One))
{
player.Camera.ToggleCameraMode();
if (player.Camera.CameraMode == CameraMode.Follow)
player.Camera.LockToSprite(sprite);
}

if (player.Camera.CameraMode != CameraMode.Follow)
{
if (InputHandler.KeyReleased(Keys.C) ||
InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One))
{
player.Camera.LockToSprite(sprite);
}
}
base.Update(gameTime);

You want the sprite to move at a uniform speed so I set up a Vector2 to hold the motion of the sprite so
it can be normalized and multiplied by the constant speed. There is an if-else-if statement that checks if
the W key is down, or the right thumb stick in the up direction. The else if checks for the S key and the
right thumb stick in the down direction. I check for vertical motion before horizontal motion. The
reason is it looks better if the sprite is moving diagonally to use the left and right animations rather then
the up and down animations. If the sprite is moving up the Y component of the motion vector is set to
-1 and the current animation of the sprite to up. Similarly, for the down direction test the Y component
of the motion vector is set to 1 and the current animation is set to the down animation.
I didn't include the if-else-if for horizontal motion with the if-else-if for vertical motion to allow the
sprite to move diagonally. This will make our life a pain down the road but it is what players expect
now, free motion. The if-else-if works the same as before. The one for the A key and the left thumb
stick left sets the X component of the motion vector to -1 and the animation to the left animation. For D
and left thumb stick right, set the X component of the motion vector to 1 and the animation to right.
The next if statement checks to see if there is motion to process. If there is, I set the IsAnimating
property to true so the sprite will animate. I then normalize the vector to have motion uniform and
update the sprite's position using the normalized motion vector and multiplying it by the sprite's speed.
I then call the LockToMap method of the sprite to make sure it doesn't travel off the map. If the
camera is in follow mode you also want to call the LockToSprite method of the camera passing in the
sprite. If there was no motion then IsAnimating is set to false so the sprite will no longer animate.
The next if statement checks to see if the F key or the right thumb stick have been released since the
last frame. If it has I call the ToggleCameraMode method to switch the mode of the camera. If the
mode is now the follow mode you want to lock the camera to the sprite.
The last if checks to see if the mode of the camera is not follow as there is no need to snap to the sprite
as the camera is already locked to the sprite. In that if I check to see if the C key or the left thumb stick
have been been released since the last frame. If they have I call the LockToSprite method of the
camera to snap the camera to the sprite's position.
That seems a lot to digest in one tutorial. Things are coming together but there is still more work to be
done. I definitely think that is more than enough for this tutorial. I'd like to try and keep them to a
reasonable length so that you don't have too much to digest at once. I encourage you to visit the news
page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 8
Updating Character Generator and Tile Engine
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to be updating the character generator to display a picture of the player's
character choice. I will also in the GamePlayScreen class load the appropriate image. I'm going to do a
little work on the tile engine as well, make it so that you don't always have to subtract the position of
the camera and draw only the tiles that need to be drawn.
First thing to change is in the StartMenuScreen. I don't want the game to jump straight to the
GamePlayScreen. if the player decides to load a game. The reason will become apparent shortly. What
I did in the menuItem_Selected method is comment out pushing the GamePlayScreen as the current
state if the loadGame was selected. Change that method to the following.
private void menuItem_Selected(object sender, EventArgs e)
{
if (sender == startGame)
{
StateManager.PushState(GameRef.CharacterGeneratorScreen);
}
if (sender == loadGame)
{
// StateManager.PushState(GameRef.GamePlayScreen);
}

if (sender == exitGame)
{
GameRef.Exit();
}

I'm now going to update the CharacterGeneratorScreen. The first step is to add in a PictureBox field
for the preview of the character. The other is to add in a 2D array of Texture2D for the images. Add
these two fields to the Field region of the CharacterGeneratorScreen.
PictureBox characterImage;
Texture2D[,] characterImages;

You now want to add the PictureBox to the control manager and you want to read in the images. I
added a call in the LoadContent method to a method I wrote to read in the images, LoadImages. I
also wired the handler for the SelectionChanged event of the LeftRightSelectors. Change the
LoadContent method and CreateControls methods to the following. Also, add in the LoadImages
and selectionChanged methods to the Method region.

protected override void LoadContent()


{
base.LoadContent();

LoadImages();
CreateControls();

private void CreateControls()


{
Texture2D leftTexture = Game.Content.Load<Texture2D>(@"GUI\leftarrowUp");
Texture2D rightTexture = Game.Content.Load<Texture2D>(@"GUI\rightarrowUp");
Texture2D stopTexture = Game.Content.Load<Texture2D>(@"GUI\StopBar");
backgroundImage = new PictureBox(
Game.Content.Load<Texture2D>(@"Backgrounds\titlescreen"),
GameRef.ScreenRectangle);
ControlManager.Add(backgroundImage);
Label label1 = new Label();
label1.Text = "Who will search for the Eyes of the Dragon?";
label1.Size = label1.SpriteFont.MeasureString(label1.Text);
label1.Position = new Vector2((GameRef.Window.ClientBounds.Width - label1.Size.X) / 2, 150);
ControlManager.Add(label1);
genderSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture);
genderSelector.SetItems(genderItems, 125);
genderSelector.Position = new Vector2(label1.Position.X, 200);
genderSelector.SelectionChanged += new EventHandler(selectionChanged);
ControlManager.Add(genderSelector);
classSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture);
classSelector.SetItems(classItems, 125);
classSelector.Position = new Vector2(label1.Position.X, 250);
classSelector.SelectionChanged += selectionChanged;
ControlManager.Add(classSelector);
LinkLabel linkLabel1 = new LinkLabel();
linkLabel1.Text = "Accept this character.";
linkLabel1.Position = new Vector2(label1.Position.X, 300);
linkLabel1.Selected += new EventHandler(linkLabel1_Selected);
ControlManager.Add(linkLabel1);
characterImage = new PictureBox(
characterImages[0, 0],
new Rectangle(500, 200, 96, 96),
new Rectangle(0, 0, 32, 32));
ControlManager.Add(characterImage);
ControlManager.NextControl();
}
private void LoadImages()
{
characterImages = new Texture2D[genderItems.Length, classItems.Length];
for (int i = 0; i < genderItems.Length; i++)
{
for (int j = 0; j < classItems.Length; j++)
{
characterImages[i, j] = Game.Content.Load<Texture2D>(@"PlayerSprites\" +
genderItems[i] + classItems[j]);
}
}
}

void selectionChanged(object sender, EventArgs e)


{
characterImage.Image = characterImages[genderSelector.SelectedIndex,
classSelector.SelectedIndex];
}

The LoadContent method calls the LoadImages method before CreateControls. It does this becasue
in the CreateControls method I use the images loaded in for the PictureBox to display the preview of
the player's character. In the CreateControls wires the handler for the SelectionChanged event for the
genderSelector and classSelector to the selectionChanged method. It creates the PictureBox passing
in the texture for a male fighter, the default character. I chose an arbitrary destination rectangle for the
PictureBox and for the source rectangle I used the first frame of the animation. The PictureBox is then
added to the ControlManager.
The LoadImages method is where I load in the sprite sheets for the different types of characters. The
first step is to create an array that holds the images. For the first dimension I use the length of the
genderItems array. For the second the length of the classItems array. The there is a set of nested for
loops. The outer loop is for the gender and the inner loop for the class. To find the name of images I
take the folder where the sprites are located add the gender from the genderItems array and then the
class from the classItems array. This works because I named the sprites gender + class.
The selectionChanged event is where I change the image based on what the choices are in the left and
right selectors. I use the SelectedIndex properties of the selectors for the index of each dimension. The
genderSelector for the first dimension and classSelector for the second.
The next problem is getting the player's selection to the GamePlayScreen. I'm not ready to add in a
class for the world yet. There is much I want to add before I get there. What I elected to do was add in
properties to the CharacterGeneratorScreen that return the SelectedItem property of the gender and
the class selectors. Then, in the GamePlayScreen, I use the properties to retrieve the values. First, to
the Property region of the CharacterGeneratorScreen add in the following properties.
public string SelectedGender
{
get { return genderSelector.SelectedItem; }
}
public string SelectedClass
{
get { return classSelector.SelectedItem; }
}

Now, in the LoadContent method of the GamePlayScreen you use the values of these properties to
load in the appropriate sprite sheet. This is why I removed going straight to the GamePlayScreen from
the StartMenuScreen. If you do that you will get a null reference exception because the LoadContent
method of the CharacterGeneratorScreen has not been called. It won't be called if you don't use the
screen manager to push it on the stack or change to that screen. Change the LoadContent method of
the GamePlayScreen to the following.
protected override void LoadContent()
{
Texture2D spriteSheet = Game.Content.Load<Texture2D>(
@"PlayerSprites\" +
GameRef.CharacterGeneratorScreen.SelectedGender +
GameRef.CharacterGeneratorScreen.SelectedClass);

Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();


Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
sprite = new AnimatedSprite(spriteSheet, animations);
base.LoadContent();
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(40, 40);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
}

layer.SetTile(x, y, tile);

}
MapLayer splatter = new MapLayer(40, 40);
Random random = new Random();
for (int i = 0; i < 80; i++)
{
int x = random.Next(0, 40);
int y = random.Next(0, 40);
int index = random.Next(2, 14);
Tile tile = new Tile(index, 0);
splatter.SetTile(x, y, tile);
}
splatter.SetTile(1, 0, new Tile(0, 1));
splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
}

map = new TileMap(tilesets, mapLayers);

What I want to do next is to do a little work on the tile engine, mostly the Camera class. I'm going to
implement being able to zoom in and zoom out to the Camera class. I'm also going to make it so that

you are not constantly having to subtract the position of the camera in your drawing code. So, open the
code for your Camera class. The first step is to check for keys or buttons to make the camera zoom in
or out and then call methods to have the camera zoom in or out. Change the Update method to the
following and add the methods ZoomIn and ZoomOut.
public void Update(GameTime gameTime)
{
if (InputHandler.KeyReleased(Keys.PageUp) ||
InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One))
ZoomIn();
else if (InputHandler.KeyReleased(Keys.PageDown) ||
InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One))
ZoomOut();
if (mode == CameraMode.Follow)
return;
Vector2 motion = Vector2.Zero;
if (InputHandler.KeyDown(Keys.Left) ||
InputHandler.ButtonDown(Buttons.RightThumbstickLeft, PlayerIndex.One))
motion.X = -speed;
else if (InputHandler.KeyDown(Keys.Right) ||
InputHandler.ButtonDown(Buttons.RightThumbstickRight, PlayerIndex.One))
motion.X = speed;
if (InputHandler.KeyDown(Keys.Up) ||
InputHandler.ButtonDown(Buttons.RightThumbstickUp, PlayerIndex.One))
motion.Y = -speed;
else if (InputHandler.KeyDown(Keys.Down) ||
InputHandler.ButtonDown(Buttons.RightThumbstickDown, PlayerIndex.One))
motion.Y = speed;

if (motion != Vector2.Zero)
{
motion.Normalize();
position += motion * speed;
LockCamera();
}

private void ZoomIn()


{
zoom += .25f;

if (zoom > 2.5f)


zoom = 2.5f;

private void ZoomOut()


{
zoom -= .25f;

if (zoom < .5f)


zoom = .5f;

I check to see if the Page Up or Left Shoulder on the game pad are pressed. If they are I call the
method ZoomIn. I then check to see if the Page Down or Right Shoulder on the game pad are
pressed. If they are I call the method ZoomOut.
Zooming in is the process of making things larger and the ZoomIn method handles that. To do that I
increment the zoom field by .25 or 25 percent. I see the zoom field is greater than 2.5, or 250 percent

larger than normal. If it is I set it to be 2.5 as I think that is more than large enough. Zooming out is the
process of making things smaller and the method ZoomOut handles doing that. To do that I decrement
the zoom field by .25. I see if it is smaller that .5 and if it is set its value to .5 or 50 percent smaller.
Our map still doesn't zoom in or out though. I will get to that in a minute. In the GamePlayScreen
where we do the call to Begin of the SpriteBatch we are using the identity matrix for our
transformation matrix. You want to instead use a transformation matrix related to the camera. You will
want to scale the map according to the zoom field and than you will want to translate, or move, using
the position of the camera. You combine transformations by multiplying the matrix for each together.
The order in which you do this is important. You should following the following rule: Identity, Scale,
Rotation, Orbit, and Translate. So, we want to add in two transformation matricies. The first to scale the
map according to the zoom field and the second to translate the map using the Position of the camera.
The Matrix class has static methods for performing these operations. It will also be helpful to know the
view port the camera so I added in a property to return the viewportRectangle field. Add the following
properties to the camera class in the Property region.
public Matrix Transformation
{
get { return Matrix.CreateScale(zoom) *
Matrix.CreateTranslation(new Vector3(-Position, 0f)); }
}
public Rectangle ViewportRectangle
{
get { return new Rectangle(
viewportRectangle.X,
viewportRectangle.Y,
viewportRectangle.Width,
viewportRectangle.Height); }
}

For the CreateScale method call I just pass in the zoom field. For the CreateTranslation method call I
pass in a Vector3. To create it I use negative position and 0f for the other. I use negative position
because we subtract the camera's position. I use 0f, the Z coordinate because we ignore the Z
coordinate because we are working in two dimensions, not three.
Back in the Draw method of the GamePlayScreen you will want to switch the Matrix.Identity with
the transformation matrix of the Camera class. Update the Draw method of the GamePlayScreen to
the following.
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
player.Camera.Transformation);
map.Draw(GameRef.SpriteBatch, player.Camera);
sprite.Draw(gameTime, GameRef.SpriteBatch, player.Camera);
base.Draw(gameTime);
}

GameRef.SpriteBatch.End();

I also set the SpriteSortMode to Deferred which gives us a bit of a performance boost. You will now
have to go to the TileMap class and remove the code that was subtracting the camera's position when
finding the destination rectangles. Change Draw method of the TileMap class to the following.
public void Draw(SpriteBatch spriteBatch, Camera camera)
{
Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
Tile tile;
foreach (MapLayer layer in mapLayers)
{
for (int y = 0; y < layer.Height; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = 0; x < layer.Width; x++)
{
tile = layer.GetTile(x, y);
if (tile.TileIndex == -1 || tile.Tileset == -1)
continue;
destination.X = x * Engine.TileWidth;
spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);
}
}

You also need to update the Draw method of the AnimatedSprtie class so you are no longer
subtracting the position of the camera. Change that method to the following.
public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera)
{
spriteBatch.Draw(
texture,
position,
animations[currentAnimation].CurrentFrameRect,
Color.White);
}

So now the game builds and runs like before. The zooming of the camera doesn't behave all that nicely
though. For one thing, with the map scaled it is not locking right. If you have a high zoom you can't see
the far edges of the map. Also, with a small zoom you will see the blue background. That is because of
the size of the map. Making a larger map will stop that from happening. Another thing is if you zoom
the camera should move with the zoom. To allow the camera to see more of the map you need to
change the LockCamera method. If the zoom level reduces the size of the map the WidthInPixels and
HeightInPixels properties are smaller. If the zoom level increases the size of the map they increase as
well. To lock the camera properly you can multiply these values by the zoom field. Change the
LockCamera method to the following.
private void LockCamera()
{
position.X = MathHelper.Clamp(position.X,
0,
TileMap.WidthInPixels * zoom - viewportRectangle.Width);
position.Y = MathHelper.Clamp(position.Y,

0,
TileMap.HeightInPixels * zoom - viewportRectangle.Height);

Now, if you zoom in you can see the entire map. Moving the camera's position when you zoom in or
out is a little trickier. What I did is after modifying the zoom field is create a Vector2 multiplying
Position by zoom to scale the position. I then called a method I wrote, SnapToPosition. That will snap
the camera to a Vector2. Change the ZoomIn and ZoomOut method to the following. Also add the
SnapToPosition method.
public void ZoomIn()
{
zoom += .25f;
if (zoom > 2.5f)
zoom = 2.5f;
Vector2 newPosition = Position * zoom;
SnapToPosition(newPosition);
}
public void ZoomOut()
{
zoom -= .25f;
if (zoom < .5f)
zoom = .5f;

Vector2 newPosition = Position * zoom;


SnapToPosition(newPosition);

private void SnapToPosition(Vector2 newPosition)


{
position.X = newPosition.X - viewportRectangle.Width / 2;
position.Y = newPosition.Y - viewportRectangle.Height / 2;
LockCamera();
}

The SnapToPosition method sets the X value of the position to the new position of the camera minus
half the width of the view port. Similarly, the Y value is set to the new position of the camera minus
half the height of the view port. Finally, I call the LockCamera method to keep it from scrolling off the
edges. This has the camera working a little funny if it is in follow mode when zooming in. It is rather
jumpy. The solution I found was moving the code for checking if the player wants to zoom the camera
outside of the camera class in to the GamePlayScreen. This way if the camera is in follow mode you
can call the LockToSprite method. Speaking of which, you need to update that method to have it work
properly as well. What you do is similar to what you did in the LockCamera method. You multiply the
sprite's position plus have the width or height by the zoom field. Change the Update and
LockToSprite methods in the camera class to the following.
public void Update(GameTime gameTime)
{
if (mode == CameraMode.Follow)
return;
Vector2 motion = Vector2.Zero;
if (InputHandler.KeyDown(Keys.Left) ||
InputHandler.ButtonDown(Buttons.RightThumbstickLeft, PlayerIndex.One))
motion.X = -speed;
else if (InputHandler.KeyDown(Keys.Right) ||
InputHandler.ButtonDown(Buttons.RightThumbstickRight, PlayerIndex.One))

motion.X = speed;
if (InputHandler.KeyDown(Keys.Up) ||
InputHandler.ButtonDown(Buttons.RightThumbstickUp, PlayerIndex.One))
motion.Y = -speed;
else if (InputHandler.KeyDown(Keys.Down) ||
InputHandler.ButtonDown(Buttons.RightThumbstickDown, PlayerIndex.One))
motion.Y = speed;
if (motion != Vector2.Zero)
{
motion.Normalize();
position += motion * speed;
LockCamera();
}
}
public void LockToSprite(AnimatedSprite sprite)
{
position.X = (sprite.Position.X + sprite.Width / 2) * zoom
- (viewportRectangle.Width / 2);
position.Y = (sprite.Position.Y + sprite.Height / 2) * zoom
- (viewportRectangle.Height / 2);
LockCamera();
}

Now I'm going to update the Update method of the GamePlayScreen. I moved the code from the
Camera class to control zooming in and out. I also added a check that if the player's camera is in
follow mode to lock the camera to the player's sprite. Change the Update method to the following.
public override void Update(GameTime gameTime)
{
player.Update(gameTime);
sprite.Update(gameTime);
if (InputHandler.KeyReleased(Keys.PageUp) ||
InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One))
{
player.Camera.ZoomIn();
if (player.Camera.CameraMode == CameraMode.Follow)
player.Camera.LockToSprite(sprite);
}
else if (InputHandler.KeyReleased(Keys.PageDown) ||
InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One))
{
player.Camera.ZoomOut();
if (player.Camera.CameraMode == CameraMode.Follow)
player.Camera.LockToSprite(sprite);
}
Vector2 motion = new Vector2();
if (InputHandler.KeyDown(Keys.W) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Up;
motion.Y = -1;
}
else if (InputHandler.KeyDown(Keys.S) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Down;
motion.Y = 1;
}
if (InputHandler.KeyDown(Keys.A) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One))

{
sprite.CurrentAnimation = AnimationKey.Left;
motion.X = -1;
}
else if (InputHandler.KeyDown(Keys.D) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Right;
motion.X = 1;
}
if (motion != Vector2.Zero)
{
sprite.IsAnimating = true;
motion.Normalize();
sprite.Position += motion * sprite.Speed;
sprite.LockToMap();
if (player.Camera.CameraMode == CameraMode.Follow)
player.Camera.LockToSprite(sprite);
}
else
{
sprite.IsAnimating = false;
}
if (InputHandler.KeyReleased(Keys.F) ||
InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One))
{
player.Camera.ToggleCameraMode();
if (player.Camera.CameraMode == CameraMode.Follow)
player.Camera.LockToSprite(sprite);
}
if (player.Camera.CameraMode != CameraMode.Follow)
{
if (InputHandler.KeyReleased(Keys.C) ||
InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One))
{
player.Camera.LockToSprite(sprite);
}
}
base.Update(gameTime);
}

While we have this class open, let's make the map a little bigger. I had used 40 for the width and the
height of the map. I changed my LoadContent method so that the map is now 100 by 100 tiles.
Change the LoadContent method to the following.
protected override void LoadContent()
{
Texture2D spriteSheet = Game.Content.Load<Texture2D>(
@"PlayerSprites\" +
GameRef.CharacterGeneratorScreen.SelectedGender +
GameRef.CharacterGeneratorScreen.SelectedClass);
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);

animation = new Animation(3, 32, 32, 0, 96);


animations.Add(AnimationKey.Up, animation);
sprite = new AnimatedSprite(spriteSheet, animations);
base.LoadContent();
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

MapLayer splatter = new MapLayer(100, 100);


Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);

Tile tile = new Tile(index, 0);


splatter.SetTile(x, y, tile);

splatter.SetTile(1, 0, new Tile(0, 1));


splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
map = new TileMap(tilesets, mapLayers);
}

Things are coming together but there is still more work to be done. I think that is more than enough for
this tutorial. I'd like to try and keep them to a reasonable length so that you don't have too much to
digest at once. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 9
Item Classes
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
I'm going to take a break from XNA in this tutorial and get started with classes related to items in the
game. Load up your solution from last time. Right click the RpgLibrary solution, select Add and then
New Folder. Name this new folder ItemClasses. This folder is going to hold all of the classes related
to items as the name applies. Now, right click the ItemClasses folder, select Add and then Class. Name
this new class BaseItem. The code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public enum Hands { One, Two }
public enum ArmorLocation { Body, Head, Hands, Feet }
public abstract class BaseItem
{
#region Field Region
protected List<Type> allowableClasses = new List<Type>();
string name;
string type;
int price;
float weight;
bool equipped;
#endregion
#region Property Region
public List<Type> AllowableClasses
{
get { return allowableClasses; }
protected set { allowableClasses = value; }
}
public string Type
{
get { return type; }
protected set { type = value; }
}
public string Name
{

get { return name; }


protected set { name = value; }

public int Price


{
get { return price; }
protected set { price = value; }
}
public float Weight
{
get { return weight; }
protected set { weight = value; }
}
public bool IsEquiped
{
get { return equipped; }
protected set { equipped = value; }
}
#endregion
#region Constructor Region
public BaseItem(string name, string type, int price, float weight, params Type[]
allowableClasses)
{
foreach (Type t in allowableClasses)
AllowableClasses.Add(t);
Name = name;
Type = type;
Price = price;
Weight = weight;
IsEquiped = false;
}
#endregion
#region Abstract Method Region
public abstract object Clone();
public virtual bool CanEquip(Type characterType)
{
return allowableClasses.Contains(characterType);
}
public override string ToString()
{
string itemString = "";
itemString
itemString
itemString
itemString
}

+=
+=
+=
+=

Name + ", ";


Type + ", ";
Price.ToString() + ", ";
Weight.ToString();

return itemString;

#endregion
}

There are two enums included in the code for this class, at the name space scope. Having enums at the
name space level makes them accessible with out having to use a class name to reference them. The

Hands enum is for the number of hands it takes to use a weapon. The ArmorLocation enum is for the
location of armor. Instead of having classes for helms, gloves, armor, and boots, I will wrap them all in
one class. The BaseItem class is an abstract class. Other classes will inherit from this class.
There are a number of things common to all items. Only certain classes should be able to use some
items. A fighter shouldn't be able to use a wizard's wand for example. So there is a field List<Type> ,
allowableClasses, that will hold the classes that can use the item. The class Type holds information
about a class. It can be compared to see if two Type fields are equal. This way you can use the typeof
operator to find out what type an object is.
All items in the game will also have a name so there is a field for that. The next field type is a string.
This is a field that can be used to assign a type to an item. An example of a type would be sword.
Sword describes many similar types of items. There are short swords, long swords, bastard swords, etc.
These are all swords and have similar characteristics. Items also have a price and weight so there are
fields for that as well. In previous RPG tutorials I used an integer for weight, this time around I went
for a float so you can have items that don't weigh a lot. For example, a ring won't weight 1 pound and
technically doesn't have a weight of 0 pounds. The last field is to determine if an item is equipped or
not. You can't equip all items though. You don't have to equip a potion to use it for example, you just
use the potion. I will explain how I'm handling that in a bit.
There are properties to expose all of the fields. They are all public properties and the read, or get, parts
are public. The write, or set, parts of the properties are all protected. This means that they can be used
for assigning values in classes that inherit from BaseItem.
Instead of requiring that you create and pass in a List<Type> to the constructor I used the params
keyword. The params keyword allows you to pass in zero or more items of the type specified. The
format is params parameterType[] parameterName. It has to be the last parameter of the method.
The constructor takes as parameters the name of the item, the type of the item, the price of the item, the
weight of the item, and the classes that can use the item. At the start of the constructor in a foreach
loop, I loop through all of the Types that where passed in. Inside the loop I add the Type to the
List<Type> using the AllowableClasses property. It then sets the name, price, and weight fields to the
values passed in. It then sets the equipped field to false. This means that when you retrieve an item it
isn't automatically equipped by a character.
There is an abstract method, Clone, that all classes that inherit from BaseItem must implement. I will
be using a class for managing all items in the game. This method will return a copy of an item. Since
items are represented using classes they are reference types. If you return the original and a change is
made, say equipping the item, all variables that refer to that item will be changed.
The next method is a virtual method that can you can override in inherited classes, CanEquip. This
method returns if the allowableClasses field contains the type passed in. In a class that inherits from
BaseItem that you don't want to be able to be equipped you can just return false. The last method that I
added was an override of the ToString method. It just returns a string with the item's name, type, price
and weight.
With this base class I'm going to create three inherited classes. There are basic items that you find in a
fantasy RPG. They are for weapons, armor, and shields. As the tutorials progress I will be adding in
more and more item types.

I will start with weapons. Right click the ItemClasses folder, select Add and then Class. Name this
new class Weapon. This is the code for the Weapon class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Weapon : BaseItem
{
#region Field Region
Hands hands;
int attackValue;
int attackModifier;
int damageValue;
int damageModifier;
#endregion
#region Property Region
public Hands NumberHands
{
get { return hands; }
protected set { hands = value; }
}
public int AttackValue
{
get { return attackValue; }
protected set { attackValue = value; }
}
public int AttackModifier
{
get { return attackModifier; }
protected set { attackModifier = value; }
}
public int DamageValue
{
get { return damageValue; }
protected set { damageValue = value; }
}
public int DamageModifier
{
get { return damageModifier; }
protected set { damageModifier = value; }
}
#endregion
#region Constructor Region
public Weapon(
string weaponName,
string weaponType,
int price,
float weight,
Hands hands,
int attackValue,
int attackModifier,
int damageValue,
int damageModifier,
params Type[] allowableClasses)

: base(weaponName, weaponType, price, weight, allowableClasses)


{

NumberHands = hands;
AttackValue = attackValue;
AttackModifier = attackModifier;
DamageValue = damageValue;
DamageModifier = damageModifier;

}
#endregion
#region Abstract Method Region
public override object Clone()
{
Type[] allowedClasses = new Type[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Weapon weapon = new Weapon(
Name,
Type,
Price,
Weight,
NumberHands,
AttackValue,
AttackModifier,
DamageValue,
DamageModifier,
allowedClasses);
return weapon;
}
public override string ToString()
{
string weaponString = base.ToString() + ", ";
weaponString += NumberHands.ToString() + ", ";
weaponString += AttackValue.ToString() + ", ";
weaponString += AttackModifier.ToString() + ", ";
weaponString += DamageValue.ToString() + ", ";
weaponString += DamageModifier.ToString();
foreach (Type t in allowableClasses)
weaponString += ", " + t.Name;
}

return base.ToString();

#endregion
}

There are four additional fields and properties in the class. The new fields are attackValue,
attackModifier, damageValue, and damageModifier. They are exposed by the corresponding
properties: AttackValue, AttackModifier, DamageValue, and DamageModifier. The attackValue
and attackModifier fields are for the attack value and bonus of the weapon. The attackModifier is
added to the attackValue or the weapon to get its total attack value. The modifier can be negative if
you have a cursed weapon for example. This value helps to determine if an attack by the weapon is
successful. The damageValue and damageModifier fields are used to determine the damage the
weapon will inflict, reduced by the defense value of the armor worn.
The constructor takes a few parameters in addition to what the BaseItem class requires. There is a
parameter for the number of hands needed to use the weapon. There are also parameters for the attack

and damage fields. The constructor sets the fields added to the Weapon class using the properties that
were created.
The Clone method creates a new Weapon using the fields of the current weapon. It then returns the
new instance. The interesting part was that you want to pass in the allowableClasses field to the
constructor. To handle this, I created an array of Type called allowedClasses the same size as the
Count property of allowableClasses. In a for loop I populate the allowedClasses array with the
allowableClasses array's values. You could probably also have used the Clone method of the array
class to make a clone of the array.
In the override of the ToString method there is a string, armorString, that I assign the value of the
ToString method of the base class, BaseItem. I then append a comma and a space after that. I then add
in the fields for the Weapon class one by one appending a comma and a space after them, except for
the last one. The reason is I don't want there to be a trailing comma if no classes can equip or use the
item. In a foreach loop I loop through allowableClasses and append a comma and Name property of
the type.
The next class is the Armor class. Right click the ItemClasses folder, select Add and then Class.
Name this new class Armor. This is the code for the Armor class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Armor : BaseItem
{
#region Field Region
ArmorLocation location;
int defenseValue;
int defenseModifier;
#endregion
#region Property Region
public ArmorLocation Location
{
get { return location; }
protected set { location = value; }
}
public int DefenseValue
{
get { return defenseValue; }
protected set { defenseValue = value; }
}
public int DefenseModifier
{
get { return defenseModifier; }
protected set { defenseModifier = value; }
}
#endregion
#region Constructor Region

public Armor(
string armorName,
string armorType,
int price,
float weight,
ArmorLocation locaion,
int defenseValue,
int defenseModifier,
params Type[] allowableClasses)
: base(armorName, armorType, price, weight, allowableClasses)
{
Location = location;
DefenseValue = defenseValue;
DefenseModifier = defenseModifier;
}
#endregion
#region Abstract Method Region
public override object Clone()
{
Type[] allowedClasses = new Type[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Armor armor = new Armor(
Name,
Type,
Price,
Weight,
Location,
DefenseValue,
DefenseModifier,
allowedClasses);
return armor;
}
public override string ToString()
{
string armorString = base.ToString() + ", ";
armorString += Location.ToString() + ", ";
armorString += DefenseValue.ToString() + ", ";
armorString += DefenseModifier.ToString();
foreach (Type t in allowableClasses)
armorString += ", " + t.Name;
}

return armorString;

#endregion
}

Like in the Weapon class there are a couple new fields. There are fields for the location of the armor,
the defense value of the armor, and the defense modifier of the armor. There are properties to expose
these fields. The constructor takes parameters for the new fields and sets them. Like the ToString
method of the Weapon class, the ToString method creates a string using the return value of the base
class appending a comma and a space. The location, defense value, and defense modifier are appended
with commas and spaces after the location and defense value. Then the allowed classes are append with
a comma, space, and name of the type.

The last new item class is the shield class. It looks and works the same as the other classes. There are
just two new fields in this class though. There is a defense value and a defense modifier. Since there is
really not much new I won't explain the code. Right click the ItemClasses folder, select Add and then
Class. Name this new class Shield. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Shield : BaseItem
{
#region Field Region
int defenseValue;
int defenseModifier;
#endregion
#region Property Region
public int DefenseValue
{
get { return defenseValue; }
protected set { defenseValue = value; }
}
public int DefenseModifier
{
get { return defenseModifier; }
protected set { defenseModifier = value; }
}
#endregion
#region Constructor Region
public Shield(
string shieldName,
string shieldType,
int price,
float weight,
int defenseValue,
int defenseModifier,
params Type[] allowableClasses)
: base(shieldName, shieldType, price, weight, allowableClasses)
{
DefenseValue = defenseValue;
DefenseModifier = defenseModifier;
}
#endregion
#region Abstract Method Region
public override object Clone()
{
Type[] allowedClasses = new Type[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Shield shield = new Shield(
Name,
Type,
Price,

Weight,
DefenseValue,
DefenseModifier,
allowedClasses);
}

return shield;

public override string ToString()


{
string shieldString = base.ToString() + ", ";
shieldString += DefenseValue.ToString() + ", ";
shieldString += DefenseModifier.ToString();
foreach (Type t in allowableClasses)
shieldString += ", " + t.Name;
}

return shieldString;

#endregion
}

The last class that I'm going to add in this tutorial is a class to manage the items in the game. This is not
a class for inventory. That will be added in further down the line. What this class will be used for is
reading in all of the items in the game, and writing them from an editor. Right click the ItemClasses
folder, select Add and then Class. Name this class ItemManager. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ItemManager
{
#region Fields Region
Dictionary<string, Weapon> weapons = new Dictionary<string, Weapon>();
Dictionary<string, Armor> armors = new Dictionary<string, Armor>();
Dictionary<string, Shield> shields = new Dictionary<string, Shield>();
#endregion
#region Keys Property Region
public Dictionary<string, Weapon>.KeyCollection WeaponKeys
{
get { return weapons.Keys; }
}
public Dictionary<string, Armor>.KeyCollection ArmorKeys
{
get { return armors.Keys; }
}
public Dictionary<string, Shield>.KeyCollection ShieldKeys
{
get { return shields.Keys; }
}
#endregion
#region Constructor Region

public ItemManager()
{
}
#endregion
#region Weapon Methods
public void AddWeapon(Weapon weapon)
{
if (!weapons.ContainsKey(weapon.Name))
{
weapons.Add(weapon.Name, weapon);
}
}
public Weapon GetWeapon(string name)
{
if (weapons.ContainsKey(name))
{
return (Weapon)weapons[name].Clone();
}
}

return null;

public bool ContainsWeapon(string name)


{
return weapons.ContainsKey(name);
}
#endregion
#region Armor Methods
public void AddArmor(Armor armor)
{
if (!armors.ContainsKey(armor.Name))
{
armors.Add(armor.Name, armor);
}
}
public Armor GetArmor(string name)
{
if (armors.ContainsKey(name))
{
return (Armor)armors[name].Clone();
}
return null;
}
public bool ContainsArmor(string name)
{
return armors.ContainsKey(name);
}
#endregion
#region Shield Methods
public void AddShield(Shield shield)
{
if (!shields.ContainsKey(shield.Name))
{
shields.Add(shield.Name, shield);
}
}

public Shield GetShield(string name)


{
if (shields.ContainsKey(name))
{
return (Shield)shields[name].Clone();
}
}

return null;

public bool ContainsShield(string name)


{
return shields.ContainsKey(name);
}
}

#endregion

There are three Dictionary fields. The key for them is a string and the value is the item type. The one
for the Weapon class is called weapons, the one for Armor is armors, and shields for the Shield
class. There is a default constructor here but does nothing at the moment. I added it in because I will be
using it down the road.
There are three get only properties that return the KeyCollection of the corresponding dictionary. The
WeaponKeys property returns the Keys property of the weapons dictionary. ArmorKeys and
ShieldKeys do the same for the armor and shield dictionaries. They were added more for the editor as
there will be times when it will be useful to get the keys related to items.
I added in regions for the methods of the different item types. I can see there being quite a few methods
added for each of the item types so it is best to try to group them. For each of the item types there are
Get and Add methods. The Add methods are used to add new items to the ItemManager. The Get
methods are used to retrieve an item from the ItemManager using the item's name. It doesn't return the
actual item, it casts the return of the Clone method to the appropriate type. You have to do this because
to have the Clone method work for all item types I had to use a type that all other objects inherited
from. I used object because all classes inherit from the object type. The Add methods check to make
sure there isn't an item with that name in the Dictionary already. If you try to add an item with the
same key an exception will be thrown. Similarly, the Get methods check to see if there is an item with
the key in the Dictionary. If you try to retrieve a value that has no key you will also generate an
exception. If there is no appropriate item in a Get method I return null, meaning no item was found.
Things are coming together but there is still more work to be done. I think that is more than enough for
this tutorial. I'd like to try and keep them to a reasonable length so that you don't have too much to
digest at once. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 10
Character Classes
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This tutorial is about adding in classes related to characters in the game. When I say characters I mean
the player character, non-player characters, and monsters. Monster is a rather generic term to mean any
enemy that the player will face. They can be anything for a bandit to a wolf to a dragon. Player
characters and non-player characters will be a little different from monsters. A monster will be a more
generic version than player characters and non-player characters. There are a number of things that all
three will share in common though.
I will be adding items related to characters to the RpgLibrary project. Right click the RpgLibrary
project in the solution explorer, select Add and then New Folder and call it CharacterClasses. I'm
going to add in a class to represent an attribute that has a current and maximum value like health and
mana. Right click the CharacterClasses folder, select Add and then Class. Call this new class
AttributePair. The code for that class follows.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class AttributePair
{
#region Field Region
int currentValue;
int maximumValue;
#endregion
#region Property Region
public int CurrentValue
{
get { return currentValue; }
}
public int MaximumValue
{
get { return maximumValue; }
}
public static AttributePair Zero
{

get { return new AttributePair(); }


}
#endregion
#region Constructor Region
private AttributePair()
{
currentValue = 0;
maximumValue = 0;
}
public AttributePair(int maxValue)
{
currentValue = maxValue;
maximumValue = maxValue;
}
#endregion
#region Method Region
public void Heal(ushort value)
{
currentValue += value;
if (currentValue > maximumValue)
currentValue = maximumValue;
}
public void Damage(ushort value)
{
currentValue -= value;
if (currentValue < 0)
currentValue = 0;
}
public void SetCurrent(int value)
{
currentValue = value;
if (currentValue > maximumValue)
currentValue = maximumValue;
}
public void SetMaximum(int value)
{
maximumValue = value;
if (currentValue > maximumValue)
currentValue = maximumValue;
}
}

#endregion

I had considered making the AttributePair a structure rather than a class. In the end I decided to go
with a class as one of the main reasons for using a structure rather than a class is you will be frequently
creating and destroying objects of that type. That is not something that I will be doing.
There are two fields in this class. The first is currentValue which represents the current value of the
pair. The other is maximumValue and represents the maximum value the pair can have. I want to force
the use of methods that allow for validation to modify the fields so there are read only properties to
expose the fields. CurrentValue exposes the currentValue field and MaximumValue exposes the
maximumValue field. There is a static property, Zero, that just returns an attribute pair with the default
values of zero.

Their are two constructors in the class. The first is a private constructor that sets the fields to 0. It was
called from the static property Zero that returns an attribute pair with the values set to 0. The other
constructor takes an integer parameter for the maximum value of the attribute pair. It sets the current
value and maximum values to that parameter.
I used role playing game terms for two of the methods, Heal and Damage. The Heal method is used to
increase the currentValue field up to the maximum value. The Damage method is used to decrease the
currentValue field. They both take a value parameter that is an unsigned short the value to increase or
decrease. I decided to use unsigned short as passing in negative numbers would reverse the effect. The
Heal method adds the value parameter to the currentValue field. It then checks to make sure that it
doesn't exceed the maximumValue field. If it does then currentValue is set to maximumValue. The
Damage method decreases the currentValue field. If currentValue is less than zero it is set to zero.
The SetCurrent method is used to set the currentValue field to a specific value. It first sets the
currentValue field to the value passed in. It checks to make sure that currentValue is not greater than
maximumValue. If it is it The SetMaximum method is use to set the maximumValue field. It checks
to make sure that the currentValue field is not greater than the maximumValue field. If it is it then
sets currentValue to be maximumValue.
I'm now going to add in a class that can be used to read in information about character classes. This
again includes the player character, non-player character, and monsters in the game. I'm giving the
three a basic name of Entity. Right click the CharacterClasses folder, select Add and then Class.
Name this new class EntityData. This the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class EntityData
{
#region Field Region
public string ClassName;
public
public
public
public
public
public

int
int
int
int
int
int

Strength;
Dexterity;
Cunning;
Willpower;
Magic;
Constitution;

public string HealthFormula;


public string StaminaFormula;
public string MagicFormula;
#endregion
#region Constructor Region
private EntityData()
{
}
#endregion
#region Static Method Region

public static void ToFile(string filename)


{
}
public static EntityData FromFile(string filename)
{
EntityData entity = new EntityData();
return entity;
}
}

#endregion

This is a pretty basic class with a few fields, a private constructor and a static method. There is field for
name of the entity, EntityName. This will be the name of the entity like Fighter or Wolf. All entities in
the game will share some attributes. Strength measures how strong an entity is. Dexterity is a
measure of the entity's agility. Cunning is a measure of the entity's mental reasoning and perception.
Willpower determines an entity's stamina and mana and measures how quickly the entity tires in
combat. Magic is used to determine a magic using entities spell power and how effective healing items
and spells are. Constitution measures how healthy an entity is and is used to determine the entity's
health.
The next three fields will be used to determine the entity's health, mana, and stamina. Different entities
will have different health, mana, and stamina. A dwarf could have higher health than other entities for
example. I will explain how the formulas will work later. I included them as they will be needed.
There is a private constructor in the class that is there to be expanded later as well. I also included two
static methods ToFile and FromFile. Both of them take a string parameter that is the file name. These
will be used to write an entity to a file or read in an entity from a file. The FromFile method creates a
new instance of type EntityData and returns the entity. I will be adding the code to read and write
entities later.
With that I'm going to create an abstract base class for all entities in the game. Right click the
CharacterClasses folder, select Add and then Class. Name this new class Entity. The code for the
Entity class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public enum EntityGender { Male, Female, Unknown }
public abstract class Entity
{
#region Vital Field and Property Region
protected string entityType;
protected EntityGender gender;
public string EntityType
{
get { return entityType; }
}
public EntityGender Gender

{
get { return gender; }
protected set { gender = value; }
}
#endregion
#region Basic Attribute and Property Region
protected
protected
protected
protected
protected
protected

int
int
int
int
int
int

strength;
dexterity;
cunning;
willpower;
magic;
constitution;

protected
protected
protected
protected
protected
protected

int
int
int
int
int
int

strengthModifier;
dexterityModifier;
cunningModifier;
willpowerModifier;
magicModifier;
constitutionModifier;

public int Strength


{
get { return strength + strengthModifier; }
protected set { strength = value; }
}
public int Dexterity
{
get { return dexterity + dexterityModifier; }
protected set { dexterity = value; }
}
public int Cunning
{
get { return cunning + cunningModifier; }
protected set { cunning = value; }
}
public int Willpower
{
get { return willpower + willpowerModifier; }
protected set { willpower = value; }
}
public int Magic
{
get { return magic + magicModifier; }
protected set { magic = value; }
}
public int Constitution
{
get { return constitution + constitutionModifier; }
protected set { constitution = value; }
}
#endregion
#region Calculated Attribute Field and Property Region
protected AttributePair health;
protected AttributePair stamina;
protected AttributePair mana;
public AttributePair Health
{

get { return health; }


}
public AttributePair Stamina
{
get { return stamina; }
}
public AttributePair Mana
{
get { return mana; }
}
protected int attack;
protected int damage;
protected int defense;
#endregion
#region Level Field and Property Region
protected int level;
protected long experience;
public int Level
{
get { return level; }
protected set { level = value; }
}
public long Experience
{
get { return experience; }
protected set { experience = value; }
}
#endregion
#region Constructor Region
private Entity()
{
Strength = 0;
Dexterity = 0;
Cunning = 0;
Willpower = 0;
Magic = 0;
Constitution = 0;

health = new AttributePair(0);


stamina = new AttributePair(0);
mana = new AttributePair(0);

public Entity(EntityData entityData)


{
entityType = entityData.EntityName;
Strength = entityData.Strength;
Dexterity = entityData.Dexterity;
Cunning = entityData.Cunning;
Willpower = entityData.Willpower;
Magic = entityData.Magic;
Constitution = entityData.Constitution;
health = new AttributePair(0);
stamina = new AttributePair(0);
mana = new AttributePair(0);
}
#endregion

}
}

There is an enumeration at the name space level so it will be available with out having to use the class
name to reference it. It is for the gender of the entity. You won't always need to know the gender of an
entity, like a green slime from D&D, so there is an Unknown value as well as Male and Female. By
default an entity will have an Unknown gender that you can override in any class that inherits from
Entity.
There are a number of regions in this class to split it up logically. The first region is the Vital Field and
Property region. This region has fields and properties for the entity's type and the entity's gender.
The next region is the Basic Attribute Field and Property region. This region has fields for the six
basic attributes: Strength, Dexterity, Cunning, Willpower, Magic, and Constitution. There are also
modifier fields for each of the attributes. There are properties for each of the six basic attributes. The
get part is public and returns the attribute plus the modifier. The set part is protected and just sets the
field of the property name starting with a lower case letter.
Then comes the Calculated Attribute Field and Property region. There are fields and properties the
three paired attributes: health, mana, and stamina. The properties that expose them are public and
read only. This allows use of the methods to modify their values but not modify them directly. I also
included fields for the attack, damage, and defense attributes of the entity. I will add properties for
them down the road when they are needed.
There is also a Level Field and Property region that has fields and properties for the level of the entity
and the experience of the entity. Like the basic attributes the properties that expose the fields and public
get and protected set parts to them.
The last region in this class is the Constructor region. It has two constructors. There is a private
constructor and a public constructor. The private constructor just sets values to 0. It will be useful later
on. The public constructor takes an EntityData parameter. It sets the entityType field and the basic
attribute fields. It then creates the calculated fields and sets them to 0 as well. Later you will use the
formula fields to calculate their values.
The next step in the tutorial is to add in the base character classes. Namely classes for fighters, rogues,
wizards and priests. The code is pretty much the same. This is why I had been thinking about the class
system to be dynamic rather than static. I will write tutorials on how to use a dynamic rather than static
system for those who are interested in it.
The first class will be the Fighter class. Right click the CharacterClasses folder, select Add and then
Class. Name this new class Figher. This is the code.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class Fighter : Entity
{
#region Field Region
#endregion

#region Property Region


#endregion
#region Constructor Region
public Fighter(EntityData entityData)
: base(entityData)
{
}
#endregion
#region Method Region
#endregion
}

This class inherits from Entity. There are regions in the class but the only region with code is the
Constructor region. There is a constructor because the public constructor for Entity requires an
EntityData parameter. The other classes have the same code the only difference is they are called
Rogue, Priest, and Wizard. Add classes to the CharacterClasses folder like you did for the Fighter
class but call them Rogue, Priest, and Wizard. The code for each class follows next in the same order.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class Rogue : Entity
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public Rogue(EntityData entityData)
: base(entityData)
{
}
#endregion
#region Method Region
#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class Priest : Entity
{
#region Field Region
#endregion
#region Property Region
#endregion

#region Constructor Region


public Priest(EntityData entityData)
: base(entityData)
{
}
#endregion

#region Method Region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class Wizard : Entity
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public Wizard(EntityData entityData)
: base(entityData)
{
}
#endregion
#region Method Region
#endregion
}

What I'm going to do next is add a class to the game. This class will represent the world the game takes
place in. This class will have some XNA elements in it. It will also require some of the elements from
the RpgLibrary. The solution I chose to go with is add the world class into the XRpgLibrary and add
a reference for the RpgLibrary to the XRpgLibrary.
To start, right click the XRpgLibrary project and select the Add Reference item. In the dialog box that
pops up select the Projects tab. From that tab select the RpgLibrary entry. The RpgLibrary is now
referenced in the XRpgLibrary project so you can use the classes from that project there. Right click
the XRpgLibrary project, select Add and then New Folder. Name this new folder WorldClasses. This
folder will hold classes related to the world the player will explore. Right click the WorldClasses
folder, select Add and then Class. Name this class World. This is the code for the World class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
using XRpgLibrary.TileEngine;
using XRpgLibrary.SpriteClasses;
namespace XRpgLibrary.WorldClasses
{
public class World
{
#region Graphic Field and Property Region
Rectangle screenRect;
public Rectangle ScreenRectangle
{
get { return screenRect; }
}
#endregion
#region Item Field and Property Region
ItemManager itemManager = new ItemManager();
#endregion
#region Level Field and Property Region
#endregion
#region Constructor Region
public World(Rectangle screenRectangle)
{
screenRect = screenRectangle;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
}
#endregion
}

It needs to be fleshed out a bit but it is a good beginning. There are using statements for XNA
Framework name space that will be used. There are also using statements for the two name spaces in
the RpgLibrary project. There are also using statements for the tile engine sprite classes of the
XRpgLibrary.
There are a few field and property regions in this class. The Graphic Field and Property region is for
fields and properties related to graphics like the screenRect field that represents the screen as a
rectangle and the property that exposes it. The Item Field and Property region will hold fields an
properties related to the items in the game. Again, this is items that can be in your game. It is not for
inventory, that will be implemented later on. The Level Field and Property region will be for fields
and properties related to levels in the game.

The constructor takes the area for the screen represented by a rectangle and sets the field. This class
will need to update itself and draw itself so there are method stubs for updating and drawing. Both
methods take GameTime parameters and the Draw method takes an additional SpriteBatch
parameter.
Things are coming together but there is still more work to be done. I think that is enough for this
tutorial. I'd like to try and keep them to a reasonable length so that you don't have too much to digest at
once. I encourage you to visit the news page of my site, XNA Game Programming Adventures, for the
latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 11a
Game Editors
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
I had started a new tutorial that I was adding in editors for the game. I realized when I was about half
done that I needed to revamp a few things. They are minor things but changing them now means that
they won't have to be changed down the road. After changing a few things I will then work on the
editors.
First thing I think is it will be better to be able to read in character classes at run time rather than have
static character classes. The ability is already there, I just need to make a few quick adjustments. First,
right click the Fighter, Thief, Priest, and Wizard classes in the CharacterClasses folder of the
RpgLibrary project and select Delete. You are not going to need them. I am going to add a manager
class to the RpgLibrary to manage the entity types. Right click the RpgLibrary, select Add and then
Class. Name this class EntityDataManager. This is the code for the EntityDataManager class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class EntityDataManager
{
#region Field Region
readonly Dictionary<string, EntityData> entityData = new Dictionary<string,
EntityData>();
#endregion
#region Property Region
public Dictionary<string, EntityData> EntityData
{
get { return entityData; }
}
#endregion
#region Constructor Region
#endregion

#region Method Region


#endregion

A fairly straight forward class. There is one field, entityData, that is marked readonly to hold all of the
EntityData objects in the game. This modifier is like the const modifier. The difference is you can
assign it a value in the class declaration or in the constructor of the class. This doesn't mean that the
field can't be modified though. It just means that there can't be an assignment to the field. You can
access the field using methods and properties, like adding an item to the list using the Add method. The
entityData field is a Dictionary<string, EntityData>. I used a dictionary as EntityData instances
should have unique names, as they are names for character classes. There is a property to expose the
field.
I'm going to make an update to the EntityData class. I'm going to remove the static methods that I
added and add in an override of the ToString method. I'm also going to add in a Clone method to clone
an EntityData class. The way I'm going to do the editors you are not going to need the static methods.
I'm going to do it in such a way that content will be written to an XML file. You can then read in this
XML file using the Content Pipeline and reflection. Change the EntityData class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public class EntityData
{
#region Field Region
public string EntityName;
public
public
public
public
public
public

int
int
int
int
int
int

Strength;
Dexterity;
Cunning;
Willpower;
Magic;
Constitution;

public string HealthFormula;


public string StaminaFormula;
public string MagicFormula;
#endregion
#region Constructor Region
private EntityData()
{
}
public EntityData(
string entityName,
int strength,
int dexterity,
int cunning,
int willpower,
int magic,
int constitution,
string health,
string stamina,
string mana)
{
EntityName = entityName;
Strength = strength;

Dexterity = dexterity;
Cunning = cunning;
Willpower = willpower;
Cunning = cunning;
Willpower = willpower;
Magic = magic;
Constitution = constitution;
HealthFormula = health;
StaminaFormula = stamina;
MagicFormula = mana;

#endregion
#region Method Region
public override string ToString()
{
string toString = "Name = " + EntityName + ", ";
toString += "Strength = " + Strength.ToString() + ", ";
toString += "Dexterity = " + Dexterity.ToString() + ", ";
toString += "Cunning = " + Cunning.ToString() + ", ";
toString += "Willpower = " + Willpower.ToString() + ", ";
toString += "Magic = " + Magic.ToString() + ", ";
toString += "Constitution = " + Constitution.ToString() + ", ";
toString += "Health Formula = " + HealthFormula + ", ";
toString += "Stamina Formula = " + StaminaFormula + ", ";
toString += "Magic Formula = " + MagicFormula;
}

return toString;

public object Clone()


{
EntityData data = new EntityData();
data.EntityName = this.EntityName;
data.Strength = this.Strength;
data.Dexterity = this.Dexterity;
data.Cunning = this.Cunning;
data.Willpower = this.Willpower;
data.Magic = this.Magic;
data.Constitution = this.Constitution;
data.HealthFormula = this.HealthFormula;
data.StaminaFormula = this.StaminaFormula;
data.MagicFormula = this.MagicFormula;
}

return data;

#endregion
}

The ToString and an equals sign, another space and then the field. For all fields except the last I also
add a comma and a space. I didn't implement the ICloneable interface this time. You will also want to
change your Animation class to not use the ICloneable interface. In the .NET Compact version, which
the Xbox 360 uses, you will get a compile time error that ICloneable is not available due to its
protection level. You will still want to have a Clone method though, just don't use the interface to do it.
The Clone method uses the private constructor to create a new instance of EntityData. It then sets all
of the fields of the instance using the current object.
Before I get to the editors I want to update the Entity class. Instead of it being an abstract class I want
it to be a sealed class. A sealed class is a class that can't be inherited from. I also want to add in an
enumeration for different types of entities. Even though there is a lot of code I don't think there is

anything that truly needs to be explained. Things that were protected were made private. A few fields
were added and properties to expose them. The public constructor now takes a string for the name, an
EntityData that defines the type of entity, an EntityGender for the gender of the entity, and an
EntityType for the type of entity. When I talk about types of entities there will be four distinct groups.
There are characters, like the player's characters, NPCs for the player to interact with, monsters and
creatures. I could have lumped all enemies the player will face into one category, monster, but I though
there may be instances where it will be useful to have animals, like giant spiders or wolves. This is the
updated Entity class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.CharacterClasses
{
public enum EntityGender { Male, Female, Unknown }
public enum EntityType { Character, NPC, Monster, Creature }
public sealed class Entity
{
#region Vital Field and Property Region
private
private
private
private

string entityName;
string entityClass;
EntityType entityType;
EntityGender gender;

public string EntityName


{
get { return entityName; }
private set { entityName = value; }
}
public string EntityClass
{
get { return entityClass; }
private set { entityClass = value; }
}
public EntityType EntityType
{
get { return entityType; }
private set { entityType = value; }
}
public EntityGender Gender
{
get { return gender; }
private set { gender = value; }
}
#endregion
#region Basic Attribute and Property Region
private
private
private
private
private
private

int
int
int
int
int
int

strength;
dexterity;
cunning;
willpower;
magic;
constitution;

private int strengthModifier;


private int dexterityModifier;
private int cunningModifier;

private int willpowerModifier;


private int magicModifier;
private int constitutionModifier;
public int Strength
{
get { return strength + strengthModifier; }
private set { strength = value; }
}
public int Dexterity
{
get { return dexterity + dexterityModifier; }
private set { dexterity = value; }
}
public int Cunning
{
get { return cunning + cunningModifier; }
private set { cunning = value; }
}
public int Willpower
{
get { return willpower + willpowerModifier; }
private set { willpower = value; }
}
public int Magic
{
get { return magic + magicModifier; }
private set { magic = value; }
}
public int Constitution
{
get { return constitution + constitutionModifier; }
private set { constitution = value; }
}
#endregion
#region Calculated Attribute Field and Property Region
private AttributePair health;
private AttributePair stamina;
private AttributePair mana;
public AttributePair Health
{
get { return health; }
}
public AttributePair Stamina
{
get { return stamina; }
}
public AttributePair Mana
{
get { return mana; }
}
private int attack;
private int damage;
private int defense;
#endregion

#region Level Field and Property Region


private int level;
private long experience;
public int Level
{
get { return level; }
private set { level = value; }
}
public long Experience
{
get { return experience; }
private set { experience = value; }
}
#endregion
#region Constructor Region
private Entity()
{
Strength = 0;
Dexterity = 0;
Cunning = 0;
Willpower = 0;
Magic = 0;
Constitution = 0;

health = new AttributePair(0);


stamina = new AttributePair(0);
mana = new AttributePair(0);

public Entity(string name, EntityData entityData, EntityGender gender, EntityType


entityType)
{
EntityName = name;
EntityClass = entityData.EntityName;
Gender = gender;
EntityType = entityType;
Strength = entityData.Strength;
Dexterity = entityData.Dexterity;
Cunning = entityData.Cunning;
Willpower = entityData.Willpower;
Magic = entityData.Magic;
Constitution = entityData.Constitution;
health = new AttributePair(0);
stamina = new AttributePair(0);
mana = new AttributePair(0);
}
}

#endregion

The next step is to add in the editor. Make sure that all open files are saved for this step. The next step
is to add a project for the editors. The editors will do the serializing and deserializing of your content.
You can deserialize your objects from the editor so that you don't have to read in your files and parse
them manually. Right click the solution in the solution explorer. Select Add and then New Project.
Select a Windows Forms project from the C# list and name this new project RpgEditor. Now make
sure all open files are saved. Right click the RpgEditor project and select Properties to bring up the

properties dialog for the project. Under the Application tab set the Target framework to .NET
Framework 4 and not .NET Framework 4 Client Profile like below. Right click the RpgEditor and
select Add Reference. Find the Microsoft.Xna.Framework.Content.Pipeline reference and add it.
Also add a reference to the Microsoft.Xna.Framework name space.

Now you need to reference your two libraries. Right click the RpgEditor project and select Add
Reference. From the Projects tab select the RpgLibrary and XRpgLibrary entries. Right click the
Form1 class in the solution explorer and select Delete to rename. Rename it to FormMain and in the
dialog box that pops up choose to rename the code references from Form1 to FormMain. Right click
the RpgEditor again, select Add and then Class. Name this new class XnaSerializer. The code for the
class follows next.
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
System.IO;
System.Xml;

using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
namespace RpgEditor
{
static class XnaSerializer
{
public static void Serialize<T>(string filename, T data)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;

using (XmlWriter writer = XmlWriter.Create(filename, settings))


{
IntermediateSerializer.Serialize<T>(writer, data, null);
}

public static T Deserialize<T>(string filename)


{
T data;
using (FileStream stream = new FileStream(filename, FileMode.Open))
{
using (XmlReader reader = XmlReader.Create(stream))
{
data = IntermediateSerializer.Deserialize<T>(reader, null);
}
}
return data;
}
}

That looks like a whole pile of ugly but once it is explained I don't think you will find it that bad. First
off I added using statements for the System.IO and System.Xml name spaces because I use class from
both of them. There is also a using statement for an XNA name space. That is the reason I added the
reference to the Content Pipeline. I needed access to the IntermediateSerializer class. With this class
you can write out XML content in a way that the Content Pipeline can read it in using reflection as I
mentioned earlier. This class is a static class. You never need to create an instance of this class. You use
the two static methods to serialize and deserialize objects. They are generic methods, like the Load
method of the ContentManager class. You can specify what type you want to use. This will prevent
the need to write methods to serialize and deserialize every class you want to write out and read in. You
can just use the generic method and pass in the type you want to use. You will see it in action in the
editor.
The first method, Serialize<T>, is used to serialize the object into an XML document. It basically
writes out the object in XML format. There maybe times when you don't want a field or property
written. You can flag it with an attribute that will prevent it from being serialized. An example would
be if you had a Texture2D field. You don't want to serialize that, just your other data.
The first step in serializing using the IntermediateSerializer class is to create an XmlWriterSettings
object. You use this object to set the Indent property to true as you want items indented. Unlike using
directives for name spaces a using statement that you find inside code automatically disposes of any
disposable objects with the statement ends. In this case the XmlWriter will be disposed when the
statement ends. Inside the using statement is why I ended up deciding to create generic methods. The
Serialize method of the IntermediateSerializer class requires a type, T, like the Load method of the
ContentManager. I can use the T from the generic method in the call to Serialize. The parameters that
I pass to the Serialize method are an XML stream, the XmlWriter, the object to be serialized, and a
reference path. You can pass in null for this if you don't want to use one.
The Deserialize<T> method returns an object of type T. It first has a local variable of type T that it can
return. To use the XmlReader class to read in an XML file you need a stream for input. In a using
statement I create a stream to open the file passed in as a parameter. Inside that code block there is
another using statement that creates an XmlReader to read in the XML file. I then call the Deserialize
method of the IntermediateSerializer class to deserialize the document. I then return the object that
was deserialized. It would be a good idea to check for exceptions such as the XML file was of the
wrong type or the file didn't exist or the file could not be created. That would work well in the editor
though and I will do that there.
I want to add a class to the RpgLibrary before moving on to the editor. This class will describe your
games. It is used more for the editor than anything. Right click the RpgLibrary project, select Add and
then Class. Name this class RolePlayingGame. This is the code.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary
{
public class RolePlayingGame
{
#region Field Region

string name;
string description;
#endregion
#region Property Region
public string Name
{
get { return name; }
set { name = value; }
}
public string Description
{
get { return description; }
set { description = value; }
}
#endregion
#region Constructor Region
public RolePlayingGame(string name, string description)
{
Name = name;
Description = description;
}
}

#endregion

There are two fields in the class. One for the name of the game and the other for a description of the
game. There are public properties to expose the fields. The constructor of the class takes a name and
description and sets the fields using the properties. This class will be fleshed out more as we go.
Designing forms is never easily done using text. I will show you a picture of what my finished forms
look like in Visual C# and what controls were added. Open the designer view of FormMain by right
clicking it and selecting View Designer. This is what my finished form looked like in the designer.

I first made the form a little bigger. I then I dragged on was a Menu Strip control. In the first entry of
the Menu Strip type &Game. The & before Game means that if the user presses ALT+G it will open
that menu. Under the Game add in four other entries: &New Game, &Open Game, &Save Game, -,
and E&xit Game. Entering the -, minus sign, gives you a separator in your menu. Beside &Game you
want to add &Classes. Set the Enabled property for this item to false. You now want to set a few
property of FormMain. Set the IsMdiContainer property to true, the Text property to XNA Rpg
Editor, and the StartUpPosition to CenterScreen. MDI is a way of having multiple children forms of
a parent form. This way you can have multiple forms open like the form for creating character classes
and a form for creating weapons and switch back and forth between them easily.
The next form I want to create is a form for creating a new game. Right click the RpgEditor project,
select Add and then Windows Form. Name this new form FormNewGame. This is what my
FormNewGame looked like in the designer.

First, make the form a little bigger to house all of the controls. Drag on a Label and set its Text
property to Name:. Dragon on a second Label and set its Text property to Description:. Drag on a
Text Box and position it beside the Name: Label. Set its Name property to tbName. Drag a second
Text Box onto the form. Set its Name property to tbDescription and its Multiline property to true.
Make it a little bigger to give the user more of an area to work with. Drag two buttons onto the form
and position them under tbDescription. Set the Name of the first to btnOK, the Text property to OK
and the DialogResult property to OK. Set the Name of the second to btnCancel, Text to Cancel, and
DialogResult to Cancel. On the form itself set the Text property to New Game, Control Box to false,
and FormBorderStyle to FixedDialog.
While this form is open might as well code the logic for it. Right click FormNewGame in the solution
explorer and select View Code. Change the code for FormNewGame to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary;
namespace RpgEditor
{
public partial class FormNewGame : Form
{
#region Field Region
RolePlayingGame rpg;
#endregion
#region Property Region
public RolePlayingGame RolePlayingGame
{
get { return rpg; }
}
#endregion
#region Constructor Region
public FormNewGame()
{
InitializeComponent();
btnOK.Click += new EventHandler(btnOK_Click);
}
#endregion
#region Event Handler Region
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbName.Text) || string.IsNullOrEmpty(tbDescription.Text))
{
MessageBox.Show("You must enter a name and a description.", "Error");
return;
}
rpg = new RolePlayingGame(tbName.Text, tbDescription.Text);
}

this.Close();

#endregion
}

There is a field of type RolePlayingGame to be constructed using the form and a public property to
expose it as read only, get only. The constructor wires an event handler for the Click event of btnOK.
The event handler checks to see if tbName or tbDescription have text in them. If they don't a message
box is shown and the method exits. Because I set the DialogResult of btnOK to OK the form will
close when btnOK is pressed. If both text boxes had values then I create a new RolePlayingGame
object with their values and then close the form.
I'm going to add two forms for dealing with entity data, or character classes. The one form holds all of
the character classes in the game. The second is use for creating new classes and editing character
classes. Right click the RpgEditor project, select Add and then Windows Form. Name this new form
FormClasses. What my form looked like in the designer is on the next page. To get started drag a
Menu Strip onto the form and making the form a little bigger. You can set the Text property of the
form to Character Classes. For the Menu Strip you can set the first item to &Load and the second

item to &Save. Drag a List Box onto form and size it similarly to mine. You can also drag three
Buttons onto the form positioning them under the List Box.

For the List Box set the following properties. Name is lbClasses, and Anchor property to Top,
Bottom, Left, Right. Setting the Anchor property to that will have the list box change its size as you
change the size of the form. For the first button set its Name property to btnAdd, Anchor property to
Bottom, and its Text property to Add. The second button's Name, Anchor, and Text properties are
btnEdit, Bottom, and Edit respectively. Set those same properties for the last to btnDelete, Bottom,
and Delete.
The last form I'm going to add is the form for creating a new character class or editing an existing one.
Right click the RpgEditor project, select Add and then Windows Form. Name this new form
FormEntityData. What my form looked like is on the next page.
Make the form bigger so there is room for all of the controls. I then dragged 10 Label controls onto the
form. Set the text properties of the labels to Name:, Strength:, Dexterity:, Cunning:, Willpower:,
Magic:, Constitution:, Health Formula:, Stamina Formula:, and Mana Formula:. You will then

drag a Text Box onto the form and position it beside the Name: label. Make it a little bigger and set the
Name property to tbName.

I next dragged a Masked Text Box onto the form. Set the Mask property to 00 which means the text
box will only accept 2 digits. Set the Text property to 10 as well. Instead of having to set those
properties 5 more times you can replicate the control. Select the control and while holding the Ctrl key
drag the Masked Text Box below the other. Repeat the process until there is a masked text box beside
the other attributes. Once you have all six set their Name properties to match the text of the label that
they are across from: mtbStrength, mtbDexterity, mtbCunning, mtbWillpower, mtbMagic, and
mtbConstitution.
You will want to drag a Text Box beside the remaining labels. Name them according to the label they
are beside: tbHealth, tbStamina, and tbMana. The last two controls to drag onto the form are the
buttons. Set the Name of the one to btnOK, and the Text property to OK. For the second button set the
Name to btnCancel, the text to Cancel, and the DialogResult to Cancel. Click on the title bar of the
form to bring up the properties of the form. Set the AcceptButton property to btnOK, the
CancelButton property to btnCancel, FormBorderStyle to FixedDialog, and the Text property to
Character Class.
I'm going to add in the logic for the form as there isn't a lot of code to it. Right click FormEntityData
and select View Code. This is the code for that form.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
RpgLibrary.CharacterClasses;

namespace RpgEditor
{
public partial class FormEntityData : Form
{
#region Field Region
EntityData entityData = null;
#endregion
#region Property Region
public EntityData EntityData
{
get { return entityData; }
set { entityData = value; }
}
#endregion
#region Constructor Region
public FormEntityData()
{
InitializeComponent();
this.Load += new EventHandler(FormEntityData_Load);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormEntityData_Load(object sender, EventArgs e)
{
if (entityData != null)
{
tbName.Text = entityData.EntityName;
mtbStrength.Text = entityData.Strength.ToString();
mtbDexterity.Text = entityData.Dexterity.ToString();
mtbCunning.Text = entityData.Cunning.ToString();
mtbWillpower.Text = entityData.Willpower.ToString();
mtbConstitution.Text = entityData.Constitution.ToString();
tbHealth.Text = entityData.HealthFormula;
tbStamina.Text = entityData.StaminaFormula;
tbMana.Text = entityData.MagicFormula;
}
}
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbName.Text) || string.IsNullOrEmpty(tbHealth.Text) ||
string.IsNullOrEmpty(tbStamina.Text) || string.IsNullOrEmpty(tbMana.Text))
{
MessageBox.Show("Name, Health Formula, Stamina Formula and Mana Formula must have
values.");
return;
}
int
int
int
int
int
int

str
dex
cun
wil
mag
con

=
=
=
=
=
=

0;
0;
0;
0;
0;
0;

if (!int.TryParse(mtbStrength.Text, out str))


{

MessageBox.Show("Strength must be numeric.");


return;

if (!int.TryParse(mtbDexterity.Text, out dex))


{
MessageBox.Show("Dexterity must be numeric.");
return;
}
if (!int.TryParse(mtbCunning.Text, out cun))
{
MessageBox.Show("Cunning must be numeric.");
return;
}
if (!int.TryParse(mtbWillpower.Text, out wil))
{
MessageBox.Show("Willpower must be numeric.");
return;
}
if (!int.TryParse(mtbMagic.Text, out mag))
{
MessageBox.Show("Magic must be numeric.");
return;
}
if (!int.TryParse(mtbConstitution.Text, out con))
{
MessageBox.Show("Constitution must be numeric.");
return;
}
entityData = new EntityData(
tbName.Text,
str,
dex,
cun,
wil,
mag,
con,
tbHealth.Text,
tbStamina.Text,
tbHealth.Text);
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
entityData = null;
this.Close();
}
}

#endregion

I added in a using statement for the CharacterClasses name space of the RpgLibrary. I have a field
entityData of type EntityData for the entity data being entered. There is also a public property to
expose it to other forms.
In the constructor of the form I wire event handlers for the Load event of the form and the Click events
of btnOK and btnCancel. In the Load event of the form I check to see entityData is not null. If it is
not null I fill out the text boxes and masked text boxes with the appropriate fields from entityData.

The click event of btnOK checks to make sure that the Text properties of tbName, tbHealth,
tbStamina, and tbMana have values. If they don't I display a message box and exit the method. There
are then six local integer variables for the stats of EntityData. Next follows a series of if statements
where I use the TryParse method of the int class to parse the Text property of the masked text boxes
for the stats. Even though the mask only allows numeric values they can be empty and if you just use
the Parse method that will generate an exception and your program will crash. If any of the TryParse
calls fail I display a message box and exit the method. I then set the entityData field to a new instance
using the values of the text boxes and local integer variables. Finally I close the form. In the Click
event handler for btnCancel I set the entityData field to null. I then close the form.
I'm going to add in some basic logic for the FormClasses now. Right click FormClasses in the
RpgEditor project and select View Code. Add the following code for FormClasses.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.CharacterClasses;
namespace RpgEditor
{
public partial class FormClasses : Form
{
#region Field Region
EntityDataManager entityDataManager = new EntityDataManager();
#endregion
#region Constructor Region
public FormClasses()
{
InitializeComponent();
loadToolStripMenuItem.Click += new EventHandler(loadToolStripMenuItem_Click);
saveToolStripMenuItem.Click += new EventHandler(saveToolStripMenuItem_Click);
btnAdd.Click += new EventHandler(btnAdd_Click);
btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);
}
#endregion
#region Menu Item Event Handler Region
void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
}
#endregion
#region Button Event Handler Region

void btnAdd_Click(object sender, EventArgs e)


{
using (FormEntityData frmEntityData = new FormEntityData())
{
frmEntityData.ShowDialog();
if (frmEntityData.EntityData != null)
{
lbClasses.Items.Add(frmEntityData.EntityData.ToString());
}
}

void btnEdit_Click(object sender, EventArgs e)


{
}
void btnDelete_Click(object sender, EventArgs e)
{
}
#endregion
}

There is an EntityDataManager field in this class because it will hold the different EntityData
objects. In the constructor I wire the event handlers for the menu items and the buttons. I added some
basic code to the btnAdd_Click event handler. There is a using statement that creates a new instance of
FormEntityData so that when you are done with the form it will be disposed of. Inside I call the
ShowDialog method of the form to display it as a modal form. That required the form to be closed
before you can use the calling form again. I check to see if the EntityData property of the form is not
null. If it is I add the EntityData object to the list box of EntityData objects. I will add in more
functionality in another tutorial.
I'm also going to add a little logic to the FromMain. Right click FormMain and select View Code.
Update the code for FormMain to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary;
using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormMain : Form
{
#region Field Region
RolePlayingGame rolePlayingGame;
FormClasses frmClasses;
#endregion
#region Property Region
#endregion

#region Constructor Region


public FormMain()
{
InitializeComponent();
newGameToolStripMenuItem.Click += new EventHandler(newGameToolStripMenuItem_Click);
openGameToolStripMenuItem.Click += new EventHandler(openGameToolStripMenuItem_Click);
saveGameToolStripMenuItem.Click += new EventHandler(saveGameToolStripMenuItem_Click);
exitToolStripMenuItem.Click += new EventHandler(exitToolStripMenuItem_Click);
classesToolStripMenuItem.Click += new EventHandler(classesToolStripMenuItem_Click);
}
#endregion
#region Menu Item Event Handler Region
void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewGame frmNewGame = new FormNewGame())
{
DialogResult result = frmNewGame.ShowDialog();

if (result == DialogResult.OK && frmNewGame.RolePlayingGame != null)


{
classesToolStripMenuItem.Enabled = true;
rolePlayingGame = frmNewGame.RolePlayingGame;
}

}
void openGameToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void saveGameToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
void classesToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}
frmClasses.Show();
}
#endregion
#region Method Region
#endregion
}

There is a field of type RolePlayingGame to hold the RolePlayingGame object associated with the
editor. There is also a field FormClasses for the form that holds all of the EntityData objects. The
constructor wires the event handlers for all of the menu items.

The handler for the New Game menu item has a using statement that creates an instance of
FormNewGame. Inside the using statement I call the ShowDialog method of the form and capture the
result in the variable result. If result is OK, meaning the form was closed by clicking OK, and the
RolePlayingGame property of the form is not null I set the Classes menu item to Enabled and the
rolePlayingGame field to the RolePlayingGame property of FormNewGame. In the Exit menu item
handler I just call Close on the form to close the form. Later I will add in code to make sure that data is
saved before closing the form.
The handler for the Classes menu item checks to see if frmClasses is null. If it is then it has not been
created yet so I create it. I also set the MdiParent property of the form to be the current instance of
FormMain using this. After creating the form if necessary I call the Show method rather than
ShowDialog. This allows you to flip between child forms of the parent form easily.
I'm going to end this tutorial here as it is rather on the long side. I want to try and keep them to a
reasonable length so that you don't have too much to digest at once. I encourage you to visit the news
page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 11b
Game Editors
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
I'm going to be continuing on with game editors in this tutorial. Since I've moved to a dynamic class
system a few updates need to be made to class in the ItemClasses of the RpgLibrary project. They
were using Type values for allowable classes. Instead I can use string values. The classes need to be
updated to use string instead of Type. The code for the BaseItem, Weapon, Armor, and Shield classes
follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public enum Hands { One, Two }
public enum ArmorLocation { Body, Head, Hands, Feet }
public abstract class BaseItem
{
#region Field Region
protected List<string> allowableClasses = new List<string>();
string name;
string type;
int price;
float weight;
bool equipped;
#endregion
#region Property Region
public List<string> AllowableClasses
{
get { return allowableClasses; }
protected set { allowableClasses = value; }
}
public string Type
{
get { return type; }
protected set { type = value; }
}
public string Name

{
get { return name; }
protected set { name = value; }
}
public int Price
{
get { return price; }
protected set { price = value; }
}
public float Weight
{
get { return weight; }
protected set { weight = value; }
}
public bool IsEquiped
{
get { return equipped; }
protected set { equipped = value; }
}
#endregion
#region Constructor Region
public BaseItem(string name, string type, int price, float weight, params string[]
allowableClasses)
{
foreach (string t in allowableClasses)
AllowableClasses.Add(t);

Name = name;
Type = type;
Price = price;
Weight = weight;
IsEquiped = false;

#endregion
#region Abstract Method Region
public abstract object Clone();
public virtual bool CanEquip(string characterstring)
{
return allowableClasses.Contains(characterstring);
}
public override string ToString()
{
string itemString = "";
itemString
itemString
itemString
itemString

+=
+=
+=
+=

Name + ", ";


type + ", ";
Price.ToString() + ", ";
Weight.ToString();

return itemString;
}
}

#endregion

}
using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
namespace RpgLibrary.ItemClasses
{
public class Armor : BaseItem
{
#region Field Region
ArmorLocation location;
int defenseValue;
int defenseModifier;
#endregion
#region Property Region
public ArmorLocation Location
{
get { return location; }
protected set { location = value; }
}
public int DefenseValue
{
get { return defenseValue; }
protected set { defenseValue = value; }
}
public int DefenseModifier
{
get { return defenseModifier; }
protected set { defenseModifier = value; }
}
#endregion
#region Constructor Region
public Armor(
string armorName,
string armorType,
int price,
float weight,
ArmorLocation locaion,
int defenseValue,
int defenseModifier,
params string[] allowableClasses)
: base(armorName, armorType, price, weight, allowableClasses)
{
Location = location;
DefenseValue = defenseValue;
DefenseModifier = defenseModifier;
}
#endregion
#region Abstract Method Region
public override object Clone()
{
string[] allowedClasses = new string[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Armor armor = new Armor(
Name,
Type,
Price,

Weight,
Location,
DefenseValue,
DefenseModifier,
allowedClasses);
return armor;
}
public override string ToString()
{
string armorString = base.ToString() + ", ";
armorString += Location.ToString() + ", ";
armorString += DefenseValue.ToString() + ", ";
armorString += DefenseModifier.ToString();
foreach (string s in allowableClasses)
armorString += ", " + s;
}

return armorString;

#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Shield : BaseItem
{
#region Field Region
int defenseValue;
int defenseModifier;
#endregion
#region Property Region
public int DefenseValue
{
get { return defenseValue; }
protected set { defenseValue = value; }
}
public int DefenseModifier
{
get { return defenseModifier; }
protected set { defenseModifier = value; }
}
#endregion
#region Constructor Region
public Shield(
string shieldName,
string shieldType,
int price,
float weight,
int defenseValue,
int defenseModifier,
params string[] allowableClasses)
: base(shieldName, shieldType, price, weight, allowableClasses)
{

DefenseValue = defenseValue;
DefenseModifier = defenseModifier;

#endregion
#region Abstract Method Region
public override object Clone()
{
string[] allowedClasses = new string[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Shield shield = new Shield(
Name,
Type,
Price,
Weight,
DefenseValue,
DefenseModifier,
allowedClasses);
return shield;
}
public override string ToString()
{
string shieldString = base.ToString() + ", ";
shieldString += DefenseValue.ToString() + ", ";
shieldString += DefenseModifier.ToString();
foreach (string s in allowableClasses)
shieldString += ", " + s;
return shieldString;
}
}

#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Weapon : BaseItem
{
#region Field Region
Hands hands;
int attackValue;
int attackModifier;
int damageValue;
int damageModifier;
#endregion
#region Property Region
public Hands NumberHands
{
get { return hands; }
protected set { hands = value; }
}

public int AttackValue


{
get { return attackValue; }
protected set { attackValue = value; }
}
public int AttackModifier
{
get { return attackModifier; }
protected set { attackModifier = value; }
}
public int DamageValue
{
get { return damageValue; }
protected set { damageValue = value; }
}
public int DamageModifier
{
get { return damageModifier; }
protected set { damageModifier = value; }
}
#endregion
#region Constructor Region
public Weapon(
string weaponName,
string weaponType,
int price,
float weight,
Hands hands,
int attackValue,
int attackModifier,
int damageValue,
int damageModifier,
params string[] allowableClasses)
: base(weaponName, weaponType, price, weight, allowableClasses)
{
NumberHands = hands;
AttackValue = attackValue;
AttackModifier = attackModifier;
DamageValue = damageValue;
DamageModifier = damageModifier;
}
#endregion
#region Abstract Method Region
public override object Clone()
{
string[] allowedClasses = new string[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Weapon weapon = new Weapon(
Name,
Type,
Price,
Weight,
NumberHands,
AttackValue,
AttackModifier,
DamageValue,
DamageModifier,
allowedClasses);

return weapon;

public override string ToString()


{
string weaponString = base.ToString() + ", ";
weaponString += NumberHands.ToString() + ", ";
weaponString += AttackValue.ToString() + ", ";
weaponString += AttackModifier.ToString() + ", ";
weaponString += DamageValue.ToString() + ", ";
weaponString += DamageModifier.ToString();
foreach (string s in allowableClasses)
weaponString += ", " + s;
return base.ToString();
}
}

#endregion

The next step is to add in items to the editor. First open the design view for FormMain by right
clicking it and selecting View Designer. Beside the Classes menu item add the following entry
&Items. Set the Enabled property to false. Under &Items you want to add &Weapons, &Armor, and
&Shield.
A lot of forms are going to use the same layout as FormClasses. So I'm going to create a master form
called FormDetails. Any form that has that layout can inherit from FormDetails instead of Form.
Right click the RpgEditor project in the solution explorer, select Add and then Windows Form. Name
this form FormDetails. Set the Size property of FormDetails to the size of FormClasses. Go back to
the design view of FormClasses. While holding down the Ctrl key click on the Menu Strip, List Box,
and the three Buttons. The form should look like this.

Now, copy the controls by pressing Ctrl-C. Switch to FormDetails and press Ctrl-V to paste the
controls onto the form. Click on the title bar of the form to deselect the other controls. Rename

lbClasses to lbDetails. Set the Anchor property of lbDetails to Top, Left. Click on the arrow pointing
at FormDetails to expand the entries under it. Bring up the code for FormDetails.Designer.cs by right
clicking it and selecting View Code. Change these fields to protected rather than private.
protected
protected
protected
protected
protected
protected
protected

System.Windows.Forms.Button btnDelete;
System.Windows.Forms.Button btnEdit;
System.Windows.Forms.Button btnAdd;
System.Windows.Forms.ListBox lbDetails;
System.Windows.Forms.MenuStrip menuStrip1;
System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem;
System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem;

There are two more things I want to do before leaving the details form. I want to add a static protected
field for an ItemManager. I also want to move the EntityDataManager from FormClasses to
FormDetails. This way they will be available to any child form that needs to work with items and
entity data. As the game progresses there will be more manager classes that will be needed. Change the
code of FormDetails to the following.
using
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
RpgLibrary.ItemClasses;
RpgLibrary.CharacterClasses;

namespace RpgEditor
{
public partial class FormDetails : Form
{
#region Field Region
protected static ItemManager ItemManager;
protected static EntityDataManager EntityDataManager;
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormDetails()
{
InitializeComponent();
if (FormDetails.ItemManager == null)
ItemManager = new ItemManager();
if (FormDetails.EntityDataManager == null)
EntityDataManager = new EntityDataManager();
}
}

#endregion

The constructor checks to see if the fields are null. If they are null it creates a new instance of the
fields. Since the fields are static you can't reference them using this. You need to reference them using
the class name.

Go to the Design View of FormClasses by right clicking it in the solution explorer and selecting View
Designer. Remove all of the controls that were on the form. Right click FormClasses again and select
View Code to bring up the code for the form. Change the code for FormClasses to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.CharacterClasses;
namespace RpgEditor
{
public partial class FormClasses : FormDetails
{
#region Field Region
#endregion
#region Constructor Region
public FormClasses()
{
InitializeComponent();
loadToolStripMenuItem.Click += new EventHandler(loadToolStripMenuItem_Click);
saveToolStripMenuItem.Click += new EventHandler(saveToolStripMenuItem_Click);

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Menu Item Event Handler Region
void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
}
#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormEntityData frmEntityData = new FormEntityData())
{
frmEntityData.ShowDialog();

if (frmEntityData.EntityData != null)
{
lbDetails.Items.Add(frmEntityData.EntityData.ToString());
}

}
void btnEdit_Click(object sender, EventArgs e)
{
}

void btnDelete_Click(object sender, EventArgs e)


{
}
}

#endregion

The changes are that I now inherit from FormDetails instead of Form, the EntityDataManager field
was removed form the class, and in the Click event handler of btnAdd I use lbDetails instead of
lbClasses. If you go back to the design view of FormClasses all of the controls should be there with
little blue arrows in the top corner. Set the Anchor property of lbDetails to Top, Left, Bottom, Right.
I'm going to add in the forms for weapons, armor, and shields now. Right click the RpgEditor solution,
select Add and then Windows Form. Call this new form FormWeapon. Repeat the process and name
the new forms FormShield and FormArmor. Set the Text property of FormWeapon to Weapons. For
FormShield set the Text property to Shield and for FormArmor set Text to Armor. Make each of the
forms the size of your FormDetails. Set the Anchor property of lbDetails on all of the forms to Top,
Left, Bottom, Right. I added in code skeletons for each of the forms as well. The code follows next in
the following order: FormWeapon, FormShield, and FormArmor.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormWeapon : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormWeapon()
{
InitializeComponent();
loadToolStripMenuItem.Click += new EventHandler(loadToolStripMenuItem_Click);
saveToolStripMenuItem.Click += new EventHandler(saveToolStripMenuItem_Click);

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Menu Item Event Handler Region
void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
}

void saveToolStripMenuItem_Click(object sender, EventArgs e)


{
}
#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
}
void btnEdit_Click(object sender, EventArgs e)
{
}
void btnDelete_Click(object sender, EventArgs e)
{
}
#endregion
}

using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormShield : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormShield()
{
InitializeComponent();
loadToolStripMenuItem.Click += new EventHandler(loadToolStripMenuItem_Click);
saveToolStripMenuItem.Click += new EventHandler(saveToolStripMenuItem_Click);

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Menu Item Event Handler Region
void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
}

#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
}
void btnEdit_Click(object sender, EventArgs e)
{
}
void btnDelete_Click(object sender, EventArgs e)
{
}
#endregion
}

using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormArmor : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormArmor()
{
InitializeComponent();
loadToolStripMenuItem.Click += new EventHandler(loadToolStripMenuItem_Click);
saveToolStripMenuItem.Click += new EventHandler(saveToolStripMenuItem_Click);

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Menu Item Event Handler Region
void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
}
#endregion
#region Button Event Handler Region

void btnAdd_Click(object sender, EventArgs e)


{
}
void btnEdit_Click(object sender, EventArgs e)
{
}
void btnDelete_Click(object sender, EventArgs e)
{
}
#endregion
}

I'm going to add some details forms for specific item types. Right click the RpgEditor project, select
Add and then Windows Form. Name this new form FormWeaponDetails. My finished form looked
like below. Set its Text property to Weapon Details and its FormBorderStyle to FixedDialog.

There are a lot of controls on the form. First make your form bigger to hold all of the controls. Drag a
Label on and set its Text property to Name:. Drag a Text Box on to the form and position it beside the
Label. Set the Name property of the Text Box to tbName. Drag a Label onto the form and position it
under the Name: Label. Set the Text property to Type:. Drag a Text Box to the right of that Label. Set
its Name property to tbType. Add another label under the Type: Label and set its Text property to
Price:. Position a Masked Text Box to the right of that label. Resize it to match the other Text Boxes.
Set its Name property to mtbPrice and its Mask property to 00000. Drag another Label under the
Price: label and set its Text property to Weight:. Drag a Numeric Up Down beside that Label. Set its
Name property to nudWeight, the Decimal Places to 2. Resize it so it lines up with the other controls.
Drag a Label on and set the Text property to Hands:. Drag a Combo Box beside that and size it so that
it matches the other controls. Set its Name property to cboHands and the DropDownStyle to Drop
Down List. Drag another Label on and set its Text property to Attack Value: and a Masked Text Box
beside it and set its Name property to mtbAttackValue. Resize the control so that it matches the others
and set its Mask property to 000. While holding down the ctrl key select that Label and Masked Text
Box. While still holding the ctrl key drag the controls down. That will replicated the controls. Set the
Text property of the label to Attack Modifier: and the Name property of the Masked Text Box to
mtbAttackModifier. Repeat the process twice more. For the first Label set its Text property to

Damage Value: and for the second Damage Modifier:. Set the Name property of the first Masked
Text Box to mtbDamageValue and the second to mtbDamageModifier.
Drag a Label and position it to the right of tbName. Set its Text property to Character Classes:. Drag
a List Box and position it under the Character Classes: Label. Make it longer so there is enough room
for a Button below it and set its Name property to lbClasses. Drag two buttons to the right of that List
Box. Position them sort of centered vertically. Set the Text property of the first Button to >> and the
Name property to btnMoveAllowed. Set the Text property of the second Button to << and the Name
property to btnRemoveAllowed. Drag a List Box and position it to the right of the two Buttons. Set
its size and position so that it matches lbClasses. Set its Name property to lbAllowedClasses. Drag
two more buttons and position them below the List Boxes. Set the Name property of the first Button to
btnOK and its Text property to OK. Set the Name property of the second to btnCancel and the Text
property to Cancel.
The next form to add is a form for the various types of armor in the game. Right click the RpgEditor
project, select Add and then Windows Form. Name this new form FormArmorDetails. Set the Text
property to Armor Details and the FormBorderStyle to FixedDialog. My form looked like below.

Instead of adding all of the controls again, first make the form a big enough so that it will easily fit all
of the controls. Go back to the design view of FormWeaponDetail. Hold down the ctrl key and click
all of the controls except the Combo Box and the controls related attack. Press ctrl+C to copy the
controls. Go back to FormArmorDetail and press ctrl+V to paste the controls. Move them until they
fit nicely on the form. Drag a Label below the Weight label and set its Text property to Armor
Location:. Drag a Combo Box box beside that Label. Set its Name property to cboArmorLocation
and the DropDownStyle to DropDownList. Drag a Label below Armor Location: and set its Text
property to Defense Value:. Drag a Masked Text Box beside it and set its Mask property to 000 and
its Name property to mtbDefenseValue. While holding down the ctrl key select the Label and
Masked Text Box you just added. Still holding down the ctrl key drag them down to replicate them.
Set the Text property of the Label to Defense Modifier:. For the Masked Text Box set the Name
property to mtbDefenseModifier.
That just leaves the the form for shields. Right click the RpgEditor, select Add and then Windows
Form. Name this new form FormShieldDetail. Set the Text property of the form to Shield Details and

the FormBorderStyle to FixedDialog. My finished form is next.

There is no need to add all of the controls to this form as well. Switch to the design view of
FormArmorDetail. You can either click on all of the controls while holding down the ctrl key or you
can drag a rectangle around all of the controls. Once you have all of the controls selected press ctrl+C
to copy them. Go back to the design view of FormShieldDetail and press ctrl+V to paste the controls.
Click on the Armor Location: Label and the cboArmorLocation Combo Box and delete them.
This tutorial is getting long so I'm just going to add in some basic logic to the forms. I will start with
the code for FormMain. Right click FormMain in the solution explorer and select View Code.
Change the code to the following. This is the new code for that form.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary;
using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormMain : Form
{
#region Field Region
RolePlayingGame rolePlayingGame;
FormClasses frmClasses;
FormArmor frmArmor;
FormShield frmShield;
FormWeapon frmWeapon;
#endregion
#region Property Region
#endregion

#region Constructor Region


public FormMain()
{
InitializeComponent();
newGameToolStripMenuItem.Click += new EventHandler(newGameToolStripMenuItem_Click);
openGameToolStripMenuItem.Click += new EventHandler(openGameToolStripMenuItem_Click);
saveGameToolStripMenuItem.Click += new EventHandler(saveGameToolStripMenuItem_Click);
exitToolStripMenuItem.Click += new EventHandler(exitToolStripMenuItem_Click);

classesToolStripMenuItem.Click += new EventHandler(classesToolStripMenuItem_Click);


armorToolStripMenuItem.Click += new EventHandler(armorToolStripMenuItem_Click);
shieldToolStripMenuItem.Click += new EventHandler(shieldToolStripMenuItem_Click);
weaponToolStripMenuItem.Click += new EventHandler(weaponToolStripMenuItem_Click);

#endregion
#region Menu Item Event Handler Region
void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewGame frmNewGame = new FormNewGame())
{
DialogResult result = frmNewGame.ShowDialog();
if (result == DialogResult.OK && frmNewGame.RolePlayingGame != null)
{
classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
}
}

rolePlayingGame = frmNewGame.RolePlayingGame;

void openGameToolStripMenuItem_Click(object sender, EventArgs e)


{
}
void saveGameToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
void classesToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}
}

frmClasses.Show();

void armorToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmArmor == null)
{
frmArmor = new FormArmor();
frmArmor.MdiParent = this;
}

frmArmor.Show();
}
void shieldToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmShield == null)
{
frmShield = new FormShield();
frmShield.MdiParent = this;
}
}

frmShield.Show();

void weaponToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmWeapon == null)
{
frmWeapon = new FormWeapon();
frmWeapon.MdiParent = this;
}
frmWeapon.Show();
}
#endregion
#region Method Region
#endregion
}

There are three new fields. One for each of the forms that list all of the different item types. In the
constructor I wire event handlers for the armorToolStripMenuItem, shieldToolStripMenuItem, and
weaponToolStripMenuItem Click events. The handler for newGameToolStripMenuItem's Click
event if there was a RolePlayingGame object on the form displayed I set the Enabled property of
itemsToolStripMenuItem to true so it is enabled. In the event handler of the Click events of the menu
items I check to see if the appropriate form is null. If it is null I create it an instance and set the
MdiParent property to this, the current instance. Outside of the if statement I call the Show method of
the form rather than ShowDialog.
I will add some basic code to each of the detail forms. What I did was add a field for the type of item
and a property to expose it. I also handle the click events of the OK and Cancel buttons. Change the
code of FormArmorDetails, FormShieldDetails, and FormWeaponDetails to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormArmorDetails : Form
{
#region Field Region
Armor armor = null;

#endregion
#region Property Region
public Armor Armor
{
get { return armor; }
set { armor = value; }
}
#endregion
#region Constructor Region
public FormArmorDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormArmorDetails_Load);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormArmorDetails_Load(object sender, EventArgs e)
{
}
void btnOK_Click(object sender, EventArgs e)
{
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
#endregion
}

using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormShieldDetails : Form
{
#region Field Region
Shield shield;
#endregion
#region Property Region

public Shield Shield


{
get { return shield; }
set { shield = value; }
}
#endregion
#region Constructor Region
public FormShieldDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormShieldDetails_Load);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormShieldDetails_Load(object sender, EventArgs e)
{
}
void btnOK_Click(object sender, EventArgs e)
{
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
}

#endregion

}
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormWeaponDetail : Form
{
#region Field Region
Weapon weapon = null;
#endregion
#region Property Region
public Weapon Weapon
{
get { return weapon; }
set { weapon = value; }
}

#endregion
#region Constructor Region
public FormWeaponDetail()
{
InitializeComponent();

this.Load += new EventHandler(FormWeaponDetail_Load);


btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);

#endregion
#region Event Handler Region
void FormWeaponDetail_Load(object sender, EventArgs e)
{
}
void btnOK_Click(object sender, EventArgs e)
{
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
#endregion
}

I'm going to end this tutorial here as it is rather on the long side. I want to try and keep them to a
reasonable length so that you don't have too much to digest at once. I encourage you to visit the news
page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 11c
Game Editors
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is the third part of the tutorial on adding a game editor to the project. This tutorial will be more
about coding than designing forms. You will want to make the editor the start up project for the
duration of this tutorial. Right click the RpgEditor project in the solution explorer and select Set As
StartUp Project.
I want to make a quick change to the forms that hold all of a specific class in the game. To start remove
all code from FormWeapon, FormArmor, FormShield, and FormClasses that has to do with menu
items. For example the code for FormWeapon looks like this now.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormWeapon : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormWeapon()
{
InitializeComponent();
btnAdd.Click += new EventHandler(btnAdd_Click);
btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);
}
#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)

{
}
void btnEdit_Click(object sender, EventArgs e)
{
}
void btnDelete_Click(object sender, EventArgs e)
{
}
}

#endregion

Go to the designer view of FormDetails by right clicking it and selecting View Designer. Right click
on the Menu Item Strip and select Delete. Now click on the List Box and move its top border up a
little.
You don't want the forms that inherit from FormDetails to close when the user tries to close the form.
You only want to close them when the parent MDI form is closed. Luckily enough there is an event that
you can wire and stop that from happening. Right click FormDetails in the solution explorer and select
View Code. Change the Constructor region to the following and add this new region.
#region Constructor Region
public FormDetails()
{
InitializeComponent();
if (FormDetails.ItemManager == null)
ItemManager = new ItemManager();
if (FormDetails.EntityDataManager == null)
EntityDataManager = new EntityDataManager();
this.FormClosing += new FormClosingEventHandler(FormDetails_FormClosing);
}
#endregion
#region Event Handler Region
void FormDetails_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
this.Hide();
}

if (e.CloseReason == CloseReason.MdiFormClosing)
{
e.Cancel = false;
this.Close();
}

#endregion

The FormClosing event is an event that can be canceled. The FormClosingEventArgs argument has a
property, CloseReason, that holds the reason why the form is being closed. If the value is UserClosing,
the user tried to close the form, I set the Cancel property to true so the event will be canceled and I call

the Hide method to hide the form. If the CloseReason is MdiFormClosing then the main form is
closing and you want to close the form. I set the Cancel property to false and call the Close method of
the form. Right click the FormDetails form again in the solution explorer and this time select View
Designer. Set the MinimizeBox property to false so the user can't minimize the form.
When a menu item is selected you want to bring that form to the front. Lucky enough there is a method
you can call to do just that, BringToFront. Right click FormMain in the solution explorer and select
View Code to open the code for that form. Change the event handlers for the Menu Item Event
Handler region to the following.
#region Menu Item Event Handler Region
void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewGame frmNewGame = new FormNewGame())
{
DialogResult result = frmNewGame.ShowDialog();
if (result == DialogResult.OK && frmNewGame.RolePlayingGame != null)
{
classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
}
}

rolePlayingGame = frmNewGame.RolePlayingGame;

void openGameToolStripMenuItem_Click(object sender, EventArgs e)


{
}
void saveGameToolStripMenuItem_Click(object sender, EventArgs e)
{
}
void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
void classesToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}
frmClasses.Show();
frmClasses.BringToFront();
}
void armorToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmArmor == null)
{
frmArmor = new FormArmor();
frmArmor.MdiParent = this;
}
frmArmor.Show();
frmArmor.BringToFront();
}
void shieldToolStripMenuItem_Click(object sender, EventArgs e)

{
if (frmShield == null)
{
frmShield = new FormShield();
frmShield.MdiParent = this;
}
frmShield.Show();
frmShield.BringToFront();
}
void weaponToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmWeapon == null)
{
frmWeapon = new FormWeapon();
frmWeapon.MdiParent = this;
}
frmWeapon.Show();
frmWeapon.BringToFront();
}
#endregion

The new code just calls the BringToFront method after the Show method so that form will be on top
of all other forms. What I'm going to do next is handle creating a new game from the main form. I want
to add a couple static fields and get only properties to expose their values. Also add a using statement
for the System.IO name space.
using System.IO;
static string gamePath = "";
static string classPath = "";
static string itemPath = "";

There are a few things I need to do with the RpgLibrary. In order to deserialize the objects you need a
constructor that takes no parameters. Instead of adding constructors that take no parameters to the item
classes I instead created data classes with just public fields that match. For the RolePlayingGame class
I did just add in a constructor that took no parameters. Add the following constructor to the constructor
region of the RolePlayingGame class.
public RolePlayingGame()
{
}

Now, right click the ItemClasses folder in the RpgLibrary project, select Add and then Class. Name
the class ArmorData. Repeat the process twice and name the classes ShieldData and WeaponData.
The code for those three classes follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ArmorData
{
public string Name;
public string Type;
public int Price;
public float Weight;

public
public
public
public
public
}

bool Equipped;
ArmorLocation ArmorLocation;
int DefenseValue;
int DefenseModifier;
string[] AllowableClasses;

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ShieldData
{
public string Name;
public string Type;
public int Price;
public float Weight;
public bool Equipped;
public int DefenseValue;
public int DefenseModifier;
public string[] AllowableClasses;
}
}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class WeaponData
{
public string Name;
public string Type;
public int Price;
public float Weight;
public bool Equipped;
public Hands NumberHands;
public int AttackValue;
public int AttackModifier;
public int DamageValue;
public int DamageModifier;
public string[] AllowableClasses;
}
}

I'm going to add in a class to manage all of these item data classes. Right click the ItemClasses folder
in the RpgLibrary project, select Add and then Class. Name this new class ItemDataManager. This
is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ItemDataManager
{
#region Field Region
readonly Dictionary<string,ArmorData> armorData = new Dictionary<string, ArmorData>();
readonly Dictionary<string,ShieldData> shieldData = new Dictionary<string, ShieldData>();

readonly Dictionary<string,WeaponData> weaponData = new Dictionary<string, WeaponData>();


#endregion
#region Property Region
public Dictionary<string, ArmorData> ArmorData
{
get { return armorData; }
}
public Dictionary<string, ShieldData> ShieldData
{
get { return shieldData; }
}
public Dictionary<string, WeaponData> WeaponData
{
get { return weaponData; }
}
#endregion
#region Constructor Region
#endregion

#region Method Region


#endregion

It works the same way as the EntityDataManager class works except there are three dictionaries
instead of the one. I want to change the ItemManager field in FormDetails to be ItemDataManager
instead. I also want to expose the itemManager and entityDataManager field to other forms. Change
the Field, Property, and Constructor regions in FormDetails to the following.
#region Field Region
protected static ItemDataManager itemManager;
protected static EntityDataManager entityDataManager;
#endregion
#region Property Region
public static ItemDataManager ItemManager
{
get { return itemManager; }
private set { itemManager = value; }
}
public static EntityDataManager EntityDataManager
{
get { return entityDataManager; }
private set { entityDataManager = value; }
}
#endregion
#region Constructor Region
public FormDetails()
{
InitializeComponent();
if (FormDetails.ItemManager == null)
ItemManager = new ItemDataManager();

if (FormDetails.EntityDataManager == null)
EntityDataManager = new EntityDataManager();
this.FormClosing += new FormClosingEventHandler(FormDetails_FormClosing);
}
#endregion

I want to add a method to FormClasses, FormArmor, FormShield, and FormWeapon. This method
will fill the list box of the form with the appropriate data. The code for the method of each form
follows next.
FormClasses
public void FillListBox()
{
lbDetails.Items.Clear();

foreach (string s in FormDetails.EntityDataManager.EntityData.Keys)


lbDetails.Items.Add(FormDetails.EntityDataManager.EntityData[s]);

FormArmor
public void FillListBox()
{
lbDetails.Items.Clear();
foreach (string s in FormDetails.ItemManager.ArmorData.Keys)
lbDetails.Items.Add(FormDetails.ItemManager.ArmorData[s]);
}

FormShield
public void FillListBox()
{
lbDetails.Items.Clear();

foreach (string s in FormDetails.ItemManager.ShieldData.Keys)


lbDetails.Items.Add(FormDetails.ItemManager.ShieldData[s]);

FormWeapon
public void FillListBox()
{
lbDetails.Items.Clear();
foreach (string s in FormDetails.ItemManager.WeaponData.Keys)
lbDetails.Items.Add(FormDetails.ItemManager.WeaponData[s]);
}

In the Click event handler of the New Game menu item is where I will handle creating a new game.
Change the newGameMenuToolStripItem_Click method to the following.
void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewGame frmNewGame = new FormNewGame())
{
DialogResult result = frmNewGame.ShowDialog();
if (result == DialogResult.OK && frmNewGame.RolePlayingGame != null)
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Select folder to create game in.";

folderDialog.SelectedPath = Application.StartupPath;
DialogResult folderResult = folderDialog.ShowDialog();
if (folderResult == DialogResult.OK)
{
try
{
gamePath = Path.Combine(folderDialog.SelectedPath, "Game");
classPath = Path.Combine(gamePath, "Classes");
itemPath = Path.Combine(gamePath, "Items");
if (Directory.Exists(gamePath))
throw new Exception("Selected directory already exists.");
Directory.CreateDirectory(gamePath);
Directory.CreateDirectory(classPath);
Directory.CreateDirectory(itemPath + @"\Armor");
Directory.CreateDirectory(itemPath + @"\Shield");
Directory.CreateDirectory(itemPath + @"\Weapon");
rolePlayingGame = frmNewGame.RolePlayingGame;
XnaSerializer.Serialize<RolePlayingGame>(gamePath + @"\Game.xml",

rolePlayingGame);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return;
}

classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
}
}

The new code is in the if statement that checks to see if the result of the dialog was DialogResult.OK
and that the RolePlayingGame property of the form is not null. If those were both true I create a
FolderBrowserDialog object. I set the SelectedPath property to be the path where the editor started
from. I also set the Description property to let the user know what folder they are browsing for. I
capture the result of showing that dialog. If the result was DialogResult.OK there is a try-catch block
where I attempt to make directories to save the game in. I use the Combine method of the Path class to
create the paths to save to. The path for the game, gamePath, is the selected path from the dialog and
Game. The classPath is the gamePath and Classes. The itemPath is the gamePath and Items. I then
check to see if gamePath exists. If it does I throw an exception saying that a game already exists in
that directory. I only want to have one game per directory. I then call the CreateDirectory method of
the Directory class to create the directories. Each sub-item type will be stored in a directory of its own
under the itemPath directory. The rolePlayingGame field is set to the RolePlayingGame property of
the new game dialog. I then call the Serialize<T> method to serialize the rolePlayingGame field. As
you can see I specify RolePlayingGame for T. For the file name I use the gamePath and \Game.xml.
If there was an exception I catch it and display it in a message box and exit the method. If the game
was created successfully I set the Enabled property of the class and item menu items.
There is a bit of a dependency here. Items require that you have the classes that are allowed to use the
item. So in order to code the item forms you need to code the forms dealing with character classes first.
Right click FormClasses and select View Code. I'm going to update the event handler for the click

event of btnAdd. Change the code for btnAdd_Click to the following. Add the AddEntity method to
the Method Region.
void btnAdd_Click(object sender, EventArgs e)
{
using (FormEntityData frmEntityData = new FormEntityData())
{
frmEntityData.ShowDialog();

if (frmEntityData.EntityData != null)
{
AddEntity(frmEntityData.EntityData);
}

}
private void AddEntity(EntityData entityData)
{
if (FormDetails.EntityDataManager.EntityData.ContainsKey(entityData.EntityName))
{
DialogResult result = MessageBox.Show(
entityData.EntityName + " already exists. Do you want to overwrite it?",
"Existing Character Class",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
FormDetails.EntityDataManager.EntityData[entityData.EntityName] = entityData;
FillListBox();
return;
}
lbDetails.Items.Add(entityData.ToString());

FormDetails.EntityDataManager.EntityData.Add(
entityData.EntityName,
entityData);

The event handler creates a new form inside of a using statement so it will be disposed of when the
code exits the block of code. I call the ShowDialog method of the form to display it. If the EntityData
property of the form is not null I call the AddEntity method passing in the EntityData property of the
form.
The AddEntity data method will add the EntityData adds the new EntityData object to lbDetails and
the the EntityDataManager on FormDetails. If there exists an EntityData object with the same name
as the new one and you try and add it an exception will be thrown. So, there is an if statement that
checks to see if the key is in the dictionary. If it is I display a message box stating that there is already
an entry and if the user wants to overwrite it. If they select No I exit the method. If they want to
overwrite it I assign the new EntityData object to that key value. I call the method FillListBox to refill
the list box with the EntityData objects. I then exit the method. If there wasn't an existing EntityData
object I add it to the list box and add it to the EntityDataManager.
I'm going to code saving games next. I believe that you should be able to write out data before trying to
read it in. The first step is to add code to the saveGameMenuToolStripItem_Click event handler.
Change the code for that method to the following.
void saveGameToolStripMenuItem_Click(object sender, EventArgs e)

if (rolePlayingGame != null)
{
try
{
XnaSerializer.Serialize<RolePlayingGame>(gamePath + @"\Game.xml", rolePlayingGame);
FormDetails.WriteEntityData();
FormDetails.WriteItemData();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error saving game.");
}
}

I check to see if rolePlayingGame is not null. If it is null then there is nothing to save. If it is not then
there is a try-catch block that tries to save the game. I first use the Serialize<T> method to write out
the rolePlayingGame field. I then call static methods that I added to FormDetails for writing out the
values in the EntityDataManager and the ItemManager. If an exception was thrown I display a
message box with the error.
The WriteEntityData and WriteItemData methods write out the data for the appropriate type. Add
the following methods to the Method Region of FormDetails.
public static void WriteEntityData()
{
foreach (string s in EntityDataManager.EntityData.Keys)
{
XnaSerializer.Serialize<EntityData>(
FormMain.ClassPath + @"\" + s + ".xml",
EntityDataManager.EntityData[s]);
}
}
public static void WriteItemData()
{
foreach (string s in ItemManager.ArmorData.Keys)
{
XnaSerializer.Serialize<ArmorData>(
FormMain.ItemPath + @"\Armor\" + s + ".xml",
ItemManager.ArmorData[s]);
}
foreach (string s in ItemManager.ShieldData.Keys)
{
XnaSerializer.Serialize<ShieldData>(
FormMain.ItemPath + @"\Shield\" + s + ".xml",
ItemManager.ShieldData[s]);
}
foreach (string s in ItemManager.WeaponData.Keys)
{
XnaSerializer.Serialize<WeaponData>(
FormMain.ItemPath + @"\Weapon\" + s + ".xml",
ItemManager.WeaponData[s]);
}
}

The first method, WriteEntityData, loops through all of the keys in the EntityData dictionary in the
EntityDataManager class. Inside the loop I call the Serialize<T> method of the XnaSerializer class.
For the file name I use the static ClassPath property of FormMain, add a \ to place it in that directory,
add the name of the EntityData, and an xml extension. The WriteItemData method works basically

the same way. The difference is where they are written. Armor is written to the Armor sub-directory of
the Items folder, shields to Shield, and weapons to Weapon. As more classes are added to the game
you just add in methods to write them out.
Opening a game will require a little more work than writing it out. The way I decided to handle reading
in a game is to display a FolderBrowserDialog to allow the user to browse to the folder that holds
their game. From there I try and open the game. Change the openGameToolMenuStripItem_Click
method to the following and add the methods OpenGame and PrepareForms.
void openGameToolStripMenuItem_Click(object sender, EventArgs e)
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Select Game folder";
folderDialog.SelectedPath = Application.StartupPath;
bool tryAgain = false;
do
{
DialogResult folderResult = folderDialog.ShowDialog();
DialogResult msgBoxResult;
if (folderResult == DialogResult.OK)
{
if (File.Exists(folderDialog.SelectedPath + @"\Game\Game.xml"))
{
try
{
OpenGame(folderDialog.SelectedPath);
tryAgain = false;
}
catch (Exception ex)
{
msgBoxResult = MessageBox.Show(
ex.ToString(),
"Error opening game.",
MessageBoxButtons.RetryCancel);

}
}
else
{

if (msgBoxResult == DialogResult.Cancel)
tryAgain = false;
else if (msgBoxResult == DialogResult.Retry)
tryAgain = true;

msgBoxResult = MessageBox.Show(
"Game not found, try again?",
"Game does not exist",
MessageBoxButtons.RetryCancel);
if (msgBoxResult == DialogResult.Cancel)
tryAgain = false;
else if (msgBoxResult == DialogResult.Retry)
tryAgain = true;

}
}
} while (tryAgain);
}

private void OpenGame(string path)


{
gamePath = Path.Combine(path, "Game");
classPath = Path.Combine(gamePath, "Classes");

itemPath = Path.Combine(gamePath, "Items");


rolePlayingGame = XnaSerializer.Deserialize<RolePlayingGame>(
gamePath + @"\Game.xml");
FormDetails.ReadEntityData();
FormDetails.ReadItemData();
PrepareForms();
}
private void PrepareForms()
{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}
frmClasses.FillListBox();
if (frmArmor == null)
{
frmArmor = new FormArmor();
frmArmor.MdiParent = this;
}
frmArmor.FillListBox();
if (frmShield == null)
{
frmShield = new FormShield();
frmShield.MdiParent = this;
}
frmShield.FillListBox();
if (frmWeapon == null)
{
frmWeapon = new FormWeapon();
frmWeapon.MdiParent = this;
}
frmWeapon.FillListBox();
classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
}

The openGameToolStripMenuItem method first creates a FolderBrowserDialog object and sets the
Description property to Select Game folder. I also set the SelectedPath to the folder the application
started in using the StartUpPath property of the Application class. There is a bool variable that will
determine if the user would like to try again if there is an error opening the game. It is set to false
initially.
I did it in a do-while loop instead of a while loop as you know the loop needs to go through at least
once. There are two DialogResult variables inside of the loop. folderResult holds the result of the
ShowDialog method of the FolderBrowserDialog object. msgResult holds the result of any message
boxes displayed. There is an if statement to check if folderResult is DialogResult.OK. Inside that if
statement there is an if statement that checks to see if Game.xml exists in the Game folder of the
selected folder. Inside that if statement there is a try-catch block where I try to actually open the game.
In the try part I call the OpenGame method and set tryAgain to false because opening the game
worked. In the catch part I capture the result of a message box. I set the buttons for the message box so

they are Retry and Cancel. If the result is Cancel I set tryAgain to false as the user doesn't want to try
again. If it is Retry I set tryAgain to true.
In the else part of the if statement that checked if a game exits I capture the result of a message box
similar to the previous one. I then do the same action. If Cancel is select tryAgain is set to false and
true if Retry was selected.
The OpenGame method is where I actually try and read in the data for the game. I first create the paths
to read data to like I did when I created a new game. I then deserialize the Game.xml file into the
rolePlayingGame field. I call the ReadEntityData and ReadItemData methods of FormDetails to
read in the entity data and item data. I then call PrepareForms to prepare the forms.
In the PrepareForms method I check if each of the forms is null. If it is null I create a new instance
and set the MdiParent property to this, the current form. I then call the FillListBox method on the
form to fill the list box with the data for that form.
You need to add two static methods to FormDetails, ReadEntityData and ReadItemData, to read in
the data. You also need to add a using statement for the System.IO name space.
using System.IO;
public static void ReadEntityData()
{
entityDataManager = new EntityDataManager();
string[] fileNames = Directory.GetFiles(FormMain.ClassPath, "*.xml");
foreach (string s in fileNames)
{
EntityData entityData = XnaSerializer.Deserialize<EntityData>(s);
entityDataManager.EntityData.Add(entityData.EntityName, entityData);
}
}
public static void ReadItemData()
{
itemManager = new ItemDataManager();
string[] fileNames = Directory.GetFiles(
Path.Combine(FormMain.ItemPath, "Armor"),
"*.xml");
foreach (string s in fileNames)
{
ArmorData armorData = XnaSerializer.Deserialize<ArmorData>(s);
itemManager.ArmorData.Add(armorData.Name, armorData);
}
fileNames = Directory.GetFiles(
Path.Combine(FormMain.ItemPath, "Shield"),
"*.xml");
foreach (string s in fileNames)
{
ShieldData shieldData = XnaSerializer.Deserialize<ShieldData>(s);
itemManager.ShieldData.Add(shieldData.Name, shieldData);
}
fileNames = Directory.GetFiles(
Path.Combine(FormMain.ItemPath, "Weapon"),
"*.xml");

foreach (string s in fileNames)


{
WeaponData weaponData = XnaSerializer.Deserialize<WeaponData>(s);
itemManager.WeaponData.Add(weaponData.Name, weaponData);
}
}

To read in data you need a file name of the file you want to read in. I have each type of data in a folder
of its own. I use the GetFiles method of the Directory class to get all of the files in that directory that
returns an array of strings with the file names. In a foreach loop I iterate over all of the items in the
array of tile names. Inside of the foreach loop there is a variable of the type of data I want to read in. I
use the Deserialize<T> method of the XnaSerializer class to deserialize the file into the variable. I
then add the object to the manager class.
I think that this is more than enough for this tutorial. The editors are coming along nicely but I think
you deserve a break from them. In the next tutorial I will move back to the game instead of the editors.
I encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest
news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 12
Updating Game
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This tutorial is going to focus on moving a few things around and update the World class. For example,
I want to move the player's sprite to the Player class. I also want to make a few modifications to the tile
engine. To get started load up your solution from last time. Right click the EyesOfTheDragon project
in the solution explorer and select Set As StartUp Project.
'm also going to add a class to go with the World class called Level. The World class will be made up
of levels. Each level is an area of the game. The Level will have various things associated with it,
including a TileMap. After adding the Level class I will integrate it into the World class. I will then
move to using the World class in the GamePlayScreen instead of a TileMap for drawing the world.
Right click the WorldClassses folder in the XRpgLibrary project, select Add and then Class. Name
this new class Level. This is the code for that class.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
XRpgLibrary.TileEngine;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.WorldClasses
{
public class Level
{
#region Field Region
readonly TileMap map;
#endregion
#region Property Region
public TileMap Map
{
get { return map; }
}
#endregion
#region Constructor Region

public Level(TileMap tileMap)


{
map = tileMap;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
}
public void Draw(SpriteBatch spiteBatch, Camera camera)
{
map.Draw(spiteBatch, camera);
}
#endregion
}

This is a rather simple class but it will be fleshed out more as the game progresses. There is a readonly
TileMap field called map. I set it readonly so it can't accidentally get assigned to. There is a public
property that exposes the map field called Map that is get only. The constructor takes a TileMap as a
parameter and sets the map field using it. There is an Update method to update the Level. There is also
a Draw method to draw the map.
I updated the World class to use the Level class. I also made it a DrawableGameComponent. Change
the World class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
using XRpgLibrary.TileEngine;
using XRpgLibrary.SpriteClasses;
namespace XRpgLibrary.WorldClasses
{
public class World : DrawableGameComponent
{
#region Graphic Field and Property Region
Rectangle screenRect;
public Rectangle ScreenRectangle
{
get { return screenRect; }
}
#endregion
#region Item Field and Property Region
ItemManager itemManager = new ItemManager();
#endregion

#region Level Field and Property Region


readonly List<Level> levels = new List<Level>();
int currentLevel = -1;
public List<Level> Levels
{
get { return levels; }
}
public int CurrentLevel
{
get { return currentLevel; }
set
{
if (value < 0 || value >= levels.Count)
throw new IndexOutOfRangeException();
if (levels[value] == null)
throw new NullReferenceException();
currentLevel = value;
}

#endregion
#region Constructor Region
public World(Game game, Rectangle screenRectangle)
: base(game)
{
screenRect = screenRectangle;
}
#endregion
#region Method Region
public override void Update(GameTime gameTime)
{
}
public override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
}
public void DrawLevel(SpriteBatch spriteBatch, Camera camera)
{
levels[currentLevel].Draw(spriteBatch, camera);
}
#endregion
}

I added a readonly field to the class, levels, is a List<Level>. I also added in a field, currentLevel, that
returns the current level the player is on. There is a get only property to expose the levels, Levels.
There is also a property to expose the currentLevel field, CurrentLevel. The get part just returns the
currentLevel field. The set part throws an IndexOutOfBounds exception if you try and set
currentLevel to an inappropriate value. It will also throw an exception if the level at the index is null.
It then sets the currentLevel field to the value passed in. The DrawLevel method draws the current
level. It has for parameters a SpriteBatch and Camera objects. It just calls the Draw method of the
Level class.

The GamePlayScreen needs to be updated to use the World class. I also moved the code for
controlling the sprite and camera into the Player class. I also updated the constructor to take an
AnimatedSprite parameter for the sprite of the player. I also added the code to draw the sprite to the
Player class. Change the Player class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XRpgLibrary;
using XRpgLibrary.TileEngine;
using XRpgLibrary.SpriteClasses;
namespace EyesOfTheDragon.Components
{
public class Player
{
#region Field Region
Camera camera;
Game1 gameRef;
readonly AnimatedSprite sprite;
#endregion
#region Property Region
public Camera Camera
{
get { return camera; }
set { camera = value; }
}
public AnimatedSprite Sprite
{
get { return sprite; }
}
#endregion
#region Constructor Region
public Player(Game game, AnimatedSprite sprite)
{
gameRef = (Game1)game;
camera = new Camera(gameRef.ScreenRectangle);
this.sprite = sprite;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
camera.Update(gameTime);
sprite.Update(gameTime);
if (InputHandler.KeyReleased(Keys.PageUp) ||
InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One))
{
camera.ZoomIn();
if (camera.CameraMode == CameraMode.Follow)

camera.LockToSprite(sprite);
}
else if (InputHandler.KeyReleased(Keys.PageDown) ||
InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One))
{
camera.ZoomOut();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(sprite);
}
Vector2 motion = new Vector2();
if (InputHandler.KeyDown(Keys.W) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Up;
motion.Y = -1;
}
else if (InputHandler.KeyDown(Keys.S) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Down;
motion.Y = 1;
}
if (InputHandler.KeyDown(Keys.A) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Left;
motion.X = -1;
}
else if (InputHandler.KeyDown(Keys.D) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One))
{
sprite.CurrentAnimation = AnimationKey.Right;
motion.X = 1;
}
if (motion != Vector2.Zero)
{
sprite.IsAnimating = true;
motion.Normalize();
sprite.Position += motion * sprite.Speed;
sprite.LockToMap();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(sprite);

}
else
{
sprite.IsAnimating = false;
}

if (InputHandler.KeyReleased(Keys.F) ||
InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One))
{
camera.ToggleCameraMode();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(sprite);
}
if (camera.CameraMode != CameraMode.Follow)
{
if (InputHandler.KeyReleased(Keys.C) ||
InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One))
{
camera.LockToSprite(sprite);
}
}

}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
sprite.Draw(gameTime, spriteBatch, camera);
}
#endregion
}

You will notice that I have the sprite field set to readonly. That means it can't be assigned to other than
as a class initializer or the constructor. You can still interact with it though, like calling a method or
setting a field. I also added a property to expose the sprite outside of the Player class. The one thing I
will mention about the Update method is that you should update the sprite after you update the camera.
The Draw method just calls the Draw method of the sprite field.
I also changed the GamePlayScreen class to use the World class. Change the GamePlayScreen to the
following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using EyesOfTheDragon.Components;
using
using
using
using

XRpgLibrary;
XRpgLibrary.TileEngine;
XRpgLibrary.SpriteClasses;
XRpgLibrary.WorldClasses;

namespace EyesOfTheDragon.GameScreens
{
public class GamePlayScreen : BaseGameState
{
#region Field Region
Engine engine = new Engine(32, 32);
Player player;
World world;
#endregion
#region Property Region
#endregion
#region Constructor Region
public GamePlayScreen(Game game, GameStateManager manager)
: base(game, manager)
{
world = new World(game, GameRef.ScreenRectangle);
}
#endregion
#region XNA Method Region
public override void Initialize()
{

base.Initialize();
}
protected override void LoadContent()
{
Texture2D spriteSheet = Game.Content.Load<Texture2D>(
@"PlayerSprites\" +
GameRef.CharacterGeneratorScreen.SelectedGender +
GameRef.CharacterGeneratorScreen.SelectedClass);
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey,
Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
AnimatedSprite sprite = new AnimatedSprite(spriteSheet, animations);
player = new Player(GameRef, sprite);
base.LoadContent();
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

MapLayer splatter = new MapLayer(100, 100);


Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);

Tile tile = new Tile(index, 0);


splatter.SetTile(x, y, tile);

splatter.SetTile(1, 0, new Tile(0, 1));


splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));

List<MapLayer> mapLayers = new List<MapLayer>();


mapLayers.Add(layer);
mapLayers.Add(splatter);

TileMap map = new TileMap(tilesets, mapLayers);


Level level = new Level(map);
world.Levels.Add(level);
world.CurrentLevel = 0;

public override void Update(GameTime gameTime)


{
player.Update(gameTime);
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
player.Camera.Transformation);
world.DrawLevel(GameRef.SpriteBatch, player.Camera);
player.Draw(gameTime, GameRef.SpriteBatch);
base.Draw(gameTime);
GameRef.SpriteBatch.End();
}
#endregion
#region Abstract Method Region
#endregion
}

I first remove the sprite field as it is no longer needed. I also added in a World field. You can no longer
create the Player object in the constructor so that was moved to the LoadContent method. There is a
local variable in the LoadContent method to hold the sprite for the player. After creating the sprite I
create the Player object passing in the GameRef field and the sprite I just created. There is now a local
variable of type TileMap. I then create a Level object using the TileMap. I add the level to the list of
levels in the World object. I then set the CurrentLevel property to be 0, the only level there is.
The Update method just calls the Update method of the Player object. The Update method now just
calls the Update method of the Player class. The Draw method now calls the Draw method of the
Player object after calling the DrawLevel method of the World object.
The tile engine isn't all that efficient at the moment. Every tile in the map is being drawn and there are
far more tiles in the map than can fit on the screen. It would be much better to only draw the visible
tiles, plus and minus 1 tile. The reason I say plus and minus 1 is if the sprite is in the middle of a tile
and you don't add 1 tile on the right side the blue background will show through, the same is true for
the bottom. The same is true for the left and top except you subtract 1 instead of adding it. Change the
Draw method of the TileMap class to the following.

public void Draw(SpriteBatch spriteBatch, Camera camera)


{
Point cameraPoint = Engine.VectorToCell(camera.Position * (1 / camera.Zoom));
Point viewPoint = Engine.VectorToCell(
new Vector2(
(camera.Position.X + camera.ViewportRectangle.Width) * (1 / camera.Zoom),
(camera.Position.Y + camera.ViewportRectangle.Height) * (1 / camera.Zoom)));
Point min = new Point();
Point max = new Point();
min.X
min.Y
max.X
max.Y

=
=
=
=

Math.Max(0, cameraPoint.X
Math.Max(0, cameraPoint.Y
Math.Min(viewPoint.X + 1,
Math.Min(viewPoint.Y + 1,

- 1);
- 1);
mapWidth);
mapHeight);

Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);


Tile tile;
foreach (MapLayer layer in mapLayers)
{
for (int y = min.Y; y < max.Y; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = min.X; x < max.X; x++)
{
tile = layer.GetTile(x, y);
if (tile.TileIndex == -1 || tile.Tileset == -1)
continue;
destination.X = x * Engine.TileWidth;
spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);
}
}

The fact that I allowed the camera to zoom in and out makes life a little more difficult for you. If you
are zooming in, zoom > 1, you are showing less of the map. If you are zooming out, zoom < 1, you are
showing more of the map. That affects where to start and stop drawing tiles. You need to use the
inverse, 1 divided by a value, of the zoom value to control where to start and stop drawing tiles.
There are four Point variables in this class. The first, cameraPoint, is the tile the camera is in. The
second, viewPoint, is the tile the camera plus the size of the view port. To find the tile the camera is in
you take the camera's position and multiply it by the inverse of the zoom value of the camera. To find
the tile the camera is in plus the size of the screen you do the same. For the X value you take the X
value of the camera's position and add the width of the view port. You then multiply that value by the
inverse of the zoom value. Similarly, for the Y value you take the Y value of the camera's position and
add the height of the view port. You then multiply that value by the inverse of the zoom value.
The other two points are min and max. They hold the start and ending values of where to start and stop
drawing tiles. I use the Math.Max method to determine where to start drawing tiles from. It returns the
maximum of the two values passed in. I pass in 0 and cameraPoint minus 1 for both X and Y. If the
camera starts out in tile (0, 0) and up just subtract 1 you will generate an IndexOutOfBounds
exception that the game will crash. To determine the values of max I used the Math.Min method. This

method returns the minimum of the two values passed in. I pass in the viewPoint plus 1 and the width
or height of the map. Now in the for loops that I do the drawing from I start the outer loop at min.Y and
end at max.Y. Similarly, for the inner loop I start at min.X and end at max.X.
There has been a request for a list box control on my forum. I was going to add in a list box to the
controls in this tutorial. There is a slight problem though. I've been using the up and down keys to
move between controls on the screen. That means I can't easily use the up and down keys to scroll up
and down in the list box as well. So, I'm going to hold off on that for this tutorial. I plan on adding in a
list box control though.
I'm going to end this tutorial here as I don't want to get into anything new. I want to try and keep the
tutorials to a reasonable length so that you don't have too much to digest at once. I encourage you to
visit the news page of my site, XNA Game Programming Adventures, for the latest news on my
tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 13
List Box Control and Load Game Screen
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In going to be adding a List Box control in this tutorial and . I will be starting with the List Box
control. One problem with adding a List Box is that I'm using the up and down arrow keys or the left
thumb stick or direction pad on the control to navigate between controls. You would expect to navigate
the items in a List Box the same way. The way that I'm going to handle this is having a control that can
be selected activate the List Box. If the user presses the user presses enter or the A button to accept a
selection control leaves, the same with pressing the escape key or B button. You are going to need a
graphic for the List Box. I made a rather simple one, for now. You can download it from my web site
at: http://xnagpa.net/xna4/downloads/listbox.zip.
Open your solution from last time. Unzip the file then right click the GUI folder in the
EyesOfTheDragonContent project and select Add Existing Item. Select the listBoxImage.png
image. Open the code for the Control class. I want to make the HasFocus property to be a virtual
property. Change the HasFocus property to the following.
public virtual bool HasFocus
{
get { return hasFocus; }
set { hasFocus = value; }
}

Now, right click the Controls folder in the XRpgLibrary project, select Add and then Class. Name
this new class ListBox. This is the code for the ListBox class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public class ListBox : Control
{
#region Event Region
public event EventHandler SelectionChanged;
public event EventHandler Enter;
public event EventHandler Leave;

#endregion
#region Field Region
List<string> items = new List<string>();
int startItem;
int lineCount;
Texture2D image;
Texture2D cursor;
Color selectedColor = Color.Red;
int selectedItem;
#endregion
#region Property Region
public Color SelectedColor
{
get { return selectedColor; }
set { selectedColor = value; }
}
public int SelectedIndex
{
get { return selectedItem; }
set { selectedItem = (int)MathHelper.Clamp(value, 0f, items.Count); }
}
public string SelectedItem
{
get { return Items[selectedItem]; }
}
public List<string> Items
{
get { return items; }
}
public override bool HasFocus
{
get { return hasFocus; }
set
{
hasFocus = value;
if (hasFocus)
OnEnter(null);
else
OnLeave(null);
}
}
#endregion
#region Constructor Region
public ListBox(Texture2D background, Texture2D cursor)
: base()
{
hasFocus = false;
tabStop = false;
this.image = background;
this.image = cursor;
this.Size = new Vector2(image.Width, image.Height);

lineCount = image.Height / SpriteFont.LineSpacing;


startItem = 0;
Color = Color.Black;
}
#endregion
#region Abstract Method Region
public override void Update(GameTime gameTime)
{
}
public override void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(image, Position, Color.White);
for (int i = 0; i < lineCount; i++)
{
if (startItem + i >= items.Count)
break;

if (startItem + i == selectedItem)
{
spriteBatch.DrawString(
SpriteFont,
items[startItem + i],
new Vector2(Position.X, Position.Y + i * SpriteFont.LineSpacing),
SelectedColor);
spriteBatch.Draw(
cursor,
new Vector2(
Position.X - (cursor.Width + 2),
Position.Y + i * SpriteFont.LineSpacing + 5),
Color.White);
}
else
spriteBatch.DrawString(
SpriteFont,
items[startItem + i],
new Vector2(Position.X, 2 + Position.Y + i * SpriteFont.LineSpacing),
Color);

}
public override void HandleInput(PlayerIndex playerIndex)
{
if (!HasFocus)
return;
if (InputHandler.KeyReleased(Keys.Down) ||
InputHandler.ButtonReleased(Buttons.LeftThumbstickDown, playerIndex))
{
if (selectedItem < items.Count - 1)
{
selectedItem++;
if (selectedItem >= startItem + lineCount)
startItem = selectedItem - lineCount + 1;
OnSelectionChanged(null);
}
}
else if (InputHandler.KeyReleased(Keys.Up) ||
InputHandler.ButtonReleased(Buttons.LeftThumbstickUp, playerIndex))
{
if (selectedItem > 0)
{
selectedItem--;
if (selectedItem < startItem)
startItem = selectedItem;
OnSelectionChanged(null);

}
}
if (InputHandler.KeyReleased(Keys.Enter) ||
InputHandler.ButtonReleased(Buttons.A, playerIndex))
{
HasFocus = false;
OnSelected(null);
}
if (InputHandler.KeyReleased(Keys.Escape) ||
InputHandler.ButtonReleased(Buttons.B, playerIndex))
{
HasFocus = false;
}
}
#endregion
#region Method Region
protected virtual void OnSelectionChanged(EventArgs e)
{
if (SelectionChanged != null)
SelectionChanged(this, e);
}
protected virtual void OnEnter(EventArgs e)
{
if (Enter != null)
Enter(this, e);
}
protected virtual void OnLeave(EventArgs e)
{
if (Leave != null)
Leave(this, e);
}
}

#endregion

There are using statements for a few of the XNA framework name spaces like the other controls. The
class inherits from Control so it can be added to the ControlManager class. I added three events to the
List Box. The first event, SelectionChanged, will be fired if user changes the selection by scrolling up
or down. The Enter event is fired if the ListBox receives focus and the Leave event is fired if the List
Box loses focus.
There are several new fields in the List Box class. Like the Left Right Selector, there is a List<string>
for the items in the ListBox. There are then two integer fields startItem and lineCount. The
lineCount field holds how many lines to draw in the List Box. The startItem field is where to start
drawing items from, the top item in the List Box. The image field is a Texture2D for the List Box. The
cursor field is also a Texture2D and will be drawn with the selected item. The selectedColor field is
what color to draw the selected item in and the selectedIndex field is the index of the currently selected
item.
There are properties to expose some of the new fields. The SelectedColor property is a read and write
property and allows for changing the selectedColor field. The SelectedIndex property returns the
selectedIndex field for the get part and in the set part clamps the value with in the range of items in the
List Box. The SelectedItem property returns the item from the List Box at the SelectedIndex. The

Items property returns the items field. The HasFocus property is a little more interesting than the
others. The get part returns the hasFocus field of the parent class, Control. It is the set part that is more
interesting. It sets the hasFocus field to the value passed in. Then I check to see what the value of
hasFocus is. If it is true the List Box just received focus and I call the OnEnter method passing in null
for the EventArg. Otherwise the List Box has lost focus and I call the OnLeave method again passing
in null for the EventArg.
The constructor of the List Box class takes a Texture2D for the background image of the List Box and
a Texture2D for the cursor. I set the hasFocus and tabStop fields to false. It is important not to have
the List Box as a tabStop or you will break the ControlManager and the List Box won't work as
expected. I set the image field to the Texture2D passed in as well as the cursor field. I then set the
Size of the List Box to be the width and height of the image. To determine how many items will fit in
the image I take the height of the image and divide that by the LineSpacing property of the SpriteFont
of the Control base class. The startItem is set to 0, the first item, and the color for text to be drawn in
is set to Black.
The Draw method first draws the image for the ListBox. There is then a loop that loops from zero to
lineCount. There is an if statement inside the loop that checks to see if startItem + i is greater than or
equal to the Count property of the items. If it is I break out of the loop. There is another if statement
that compares startItem + i with the selectedItem field. If they are equal the string is drawn in using
the SelectedColor property, otherwise it is drawn using the Color property. To determine where to
draw the string I use the X value of the ListBox's Position property. To find the Y value I take the Y
value of the List Box's Position property and add i * SpriteFont.Linespacing. Also, if the current item
is the selectedItem I draw the cursor image to the left of the List Box. The Y position is the position of
the item plus 5 pixels and the X position is the X position of the item minus the width of the cursor and
2 pixels.
The HandleInput method first checks to make sure the control has focus. If it doesn't I exit out of the
method. I then check to see if the down key or the left thumb stick down has been released. If it is you
want to scroll the selection down. I check to see if selectedItem is not at the end of the items. If it isn't
I increment selectedItem by 1. If selectedItem is greater or equal to the startItem plus the lineCount
field then it is outside the items that fit into the ListBox and you want to move the startItem
accordingly. startItem is set to be selectedItem - LineCount + 1. The selection has changed so I call
OnSelectionChanged passing in null for the EventArgs.
For moving the selected item up I check to see if the up key or the left thumb stick up has been
released. If selectedItem is not zero I decrease selectedItem. If selectedItem is less than startItem the
selection is above the top item so I set startItem to be selectedItem. I then call OnSelectionChanged
passing in null for the EventArgs.
There is then an if statement that checks to see if the enter key or the A button have been released. If
they have I set the HasFocus property to false. It is important to use the property instead of the field.
The property will fire the Leave event, if it is subscribed to. I then call the OnSelected method of the
parent class passing in null for the EventArgs.
The last if statement checks to see if the escape key or the B button has been released. If they have I set
the HasFocus property to false. Again, it is important to use the property rather than the field or the
event won't fire.

There are then three methods: OnSelectionChanged, OnEnter, and OnLeave. These methods check
to see if the event associated with them is subscribed to. If the event is subscribed to the event is fired
passing in the EventArgs passed to the method.
I want to make a quick change to the ControlManager class. I want to add in a property, AcceptInput,
that if set to false the ControlManager won't accept input. What I mean is that if you press the up or
down key the selected control won't change. Add theses field and properties to the ControlManager
class and change the Update method to the following. The Update method exits if the AcceptInput
property is false.
bool acceptInput = true;
public bool AcceptInput
{
get { return acceptInput; }
set { acceptInput = value; }
}
public void Update(GameTime gameTime, PlayerIndex playerIndex)
{
if (Count == 0)
return;
foreach (Control c in this)
{
if (c.Enabled)
c.Update(gameTime);

if (c.HasFocus)
c.HandleInput(playerIndex);

if (!AcceptInput)
return;
if (InputHandler.ButtonPressed(Buttons.LeftThumbstickUp, playerIndex) ||
InputHandler.ButtonPressed(Buttons.DPadUp, playerIndex) ||
InputHandler.KeyPressed(Keys.Up))
PreviousControl();

if (InputHandler.ButtonPressed(Buttons.LeftThumbstickDown, playerIndex) ||
InputHandler.ButtonPressed(Buttons.DPadDown, playerIndex) ||
InputHandler.KeyPressed(Keys.Down))
NextControl();

I've been planning on adding a screen to load games, now is a good time to do that. Before I get to that
screen though I want to change the CharacterGeneratorScreen and the GamePlayScreen. What I
plan to do is to move the code for creating a character and the world from the GamePlayScreen into
the CharacterGeneratorScreen. I'm going to make the player and world fields static and expose
them using properties. Open to code for GamePlayScreen and change the code to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using EyesOfTheDragon.Components;

using
using
using
using

XRpgLibrary;
XRpgLibrary.TileEngine;
XRpgLibrary.SpriteClasses;
XRpgLibrary.WorldClasses;

namespace EyesOfTheDragon.GameScreens
{
public class GamePlayScreen : BaseGameState
{
#region Field Region
Engine engine = new Engine(32, 32);
static Player player;
static World world;
#endregion
#region Property Region
public static World World
{
get { return world; }
set { world = value; }
}
public static Player Player
{
get { return player; }
set { player = value; }
}
#endregion
#region Constructor Region
public GamePlayScreen(Game game, GameStateManager manager)
: base(game, manager)
{
}
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
world.Update(gameTime);
player.Update(gameTime);
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,

null,
null,
player.Camera.Transformation);
base.Draw(gameTime);
world.DrawLevel(GameRef.SpriteBatch, player.Camera);
player.Draw(gameTime, GameRef.SpriteBatch);
}

GameRef.SpriteBatch.End();

#endregion

#region Abstract Method Region


#endregion

As you can see, it is much cleaner. The Update method just calls the Update methods of the world and
player fields. The Draw method just calls the Draw methods of the world and player fields as well.
Now, open the code for the CharacterGeneratorScreen. First, make sure you have all of the using
statements that you will need. Change the using statement area to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using
using
using
using
using

XRpgLibrary;
XRpgLibrary.Controls;
XRpgLibrary.SpriteClasses;
XRpgLibrary.TileEngine;
XRpgLibrary.WorldClasses;

using EyesOfTheDragon.Components;

What I did was in the linkLabel1_Selected method, after changing states, is call two methods. The
first, CreatePlayer, creates a player object and the second, CreateWorld, creates a world object.
Change the linkLabel1_Selected method to the following and add the CreatePlayer and CreateWorld
methods to the Method region.
void linkLabel1_Selected(object sender, EventArgs e)
{
InputHandler.Flush();
StateManager.ChangeState(GameRef.GamePlayScreen);

CreatePlayer();
CreateWorld();

private void CreatePlayer()


{
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);

animation = new Animation(3, 32, 32, 0, 32);


animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
AnimatedSprite sprite = new AnimatedSprite(
characterImages[genderSelector.SelectedIndex, classSelector.SelectedIndex],
animations);
}

GamePlayScreen.Player = new Player(GameRef, sprite);

private void CreateWorld()


{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

MapLayer splatter = new MapLayer(100, 100);


Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);

Tile tile = new Tile(index, 0);


splatter.SetTile(x, y, tile);

splatter.SetTile(1, 0, new Tile(0, 1));


splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
TileMap map = new TileMap(tilesets, mapLayers);
Level level = new Level(map);
World world = new World(GameRef, GameRef.ScreenRectangle);
world.Levels.Add(level);
world.CurrentLevel = 0;
GamePlayScreen.World = world;

The only code that you haven't seen before is where I create the AnimatedSprite object. I was able to
use the characterImages array with the values of the SelectedIndex properties of genderSelector and
classSelected.
Now it is time to add in the screen for loading games. Right click the GameScreens folder in the
EyesOfTheDragon project, select Add and then Class. Name this class LoadGameScreen. This is the
code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using
using
using
using

Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Content;

using
using
using
using
using

XRpgLibrary;
XRpgLibrary.Controls;
XRpgLibrary.SpriteClasses;
XRpgLibrary.TileEngine;
XRpgLibrary.WorldClasses;

using EyesOfTheDragon.Components;
namespace EyesOfTheDragon.GameScreens
{
public class LoadGameScreen : BaseGameState
{
#region Field Region
PictureBox backgroundImage;
ListBox loadListBox;
LinkLabel loadLinkLabel;
LinkLabel exitLinkLabel;
#endregion
#region Property Region
#endregion
#region Constructor Region
public LoadGameScreen(Game game, GameStateManager manager)
: base(game, manager)
{
}
#endregion
#region Method Region
protected override void LoadContent()
{
base.LoadContent();
ContentManager Content = Game.Content;
backgroundImage = new PictureBox(
Content.Load<Texture2D>(@"Backgrounds\titlescreen"),
GameRef.ScreenRectangle);
ControlManager.Add(backgroundImage);

loadLinkLabel = new LinkLabel();


loadLinkLabel.Text = "Select game";
loadLinkLabel.Position = new Vector2(50, 100);
loadLinkLabel.Selected += new EventHandler(loadLinkLabel_Selected);
ControlManager.Add(loadLinkLabel);
exitLinkLabel = new LinkLabel();
exitLinkLabel.Text = "Back";
exitLinkLabel.Position = new Vector2(50, 100 + exitLinkLabel.SpriteFont.LineSpacing);
exitLinkLabel.Selected += new EventHandler(exitLinkLabel_Selected);
ControlManager.Add(exitLinkLabel);
loadListBox = new ListBox(
Content.Load<Texture2D>(@"GUI\listBoxImage"),
Content.Load<Texture2D>(@"GUI\rightarrowUp"));
loadListBox.Position = new Vector2(400, 100);
loadListBox.Selected += new EventHandler(loadListBox_Selected);
loadListBox.Leave += new EventHandler(loadListBox_Leave);
for (int i = 0; i < 20; i++)
loadListBox.Items.Add("Game number: " + i.ToString());
ControlManager.Add(loadListBox);
}

ControlManager.NextControl();

public override void Update(GameTime gameTime)


{
ControlManager.Update(gameTime, PlayerIndex.One);
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin();
base.Draw(gameTime);
ControlManager.Draw(GameRef.SpriteBatch);
GameRef.SpriteBatch.End();
}
#endregion
#region Method Region
void loadListBox_Leave(object sender, EventArgs e)
{
ControlManager.AcceptInput = true;
}
void loadLinkLabel_Selected(object sender, EventArgs e)
{
ControlManager.AcceptInput = false;
loadLinkLabel.HasFocus = false;
loadListBox.HasFocus = true;
}
void loadListBox_Selected(object sender, EventArgs e)
{
loadLinkLabel.HasFocus = true;
loadListBox.HasFocus = false;
ControlManager.AcceptInput = true;
StateManager.ChangeState(GameRef.GamePlayScreen);
CreatePlayer();
CreateWorld();

}
void exitLinkLabel_Selected(object sender, EventArgs e)
{
StateManager.PopState();
}
private void CreatePlayer()
{
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey,
Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
AnimatedSprite sprite = new AnimatedSprite(
GameRef.Content.Load<Texture2D>(@"PlayerSprites\malefighter"),
animations);
GamePlayScreen.Player = new Player(GameRef, sprite);
}
private void CreateWorld()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
}

layer.SetTile(x, y, tile);

}
MapLayer splatter = new MapLayer(100, 100);
Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);
Tile tile = new Tile(index, 0);
splatter.SetTile(x, y, tile);
}
splatter.SetTile(1, 0, new Tile(0, 1));

splatter.SetTile(2, 0, new Tile(2, 1));


splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
TileMap map = new TileMap(tilesets, mapLayers);
Level level = new Level(map);
World world = new World(GameRef, GameRef.ScreenRectangle);
world.Levels.Add(level);
world.CurrentLevel = 0;
GamePlayScreen.World = world;
}
}

#endregion

There are some using statements to bring some of the XNA Framework classes into scope as well as
classes from our XRpgLibrary. There is also a using statement to bring the Components name space
of EyesOfTheDragon into scope.
There is a PictureBox field for a background image, a ListBox field to hold the games that can be
loaded, and two LinkLabel fields. The first, loadLinkLabel, will be used to activate the ListBox that
holds the games. The second, exitLinkLabel, returns back to the start menu.
In the LoadContent method I create the controls on the form. The code for creating the PictureBox
and LinkLabels is nothing new. Creating the ListBox will be new. The list box takes two parameters:
the image for the ListBox and an image for the cursor. I set the position of the ListBox to line up
vertically with the loadLinkLabel. I then wire the Selected and Leave events of the ListBox. In a for
loop I add 20 strings to the ListBox, to demonstrate how it works. I add it to the ControlManager and
then call the NextControl method of the ControlManager class.
The Update method just calls the Update method of the ControlManager class. The Draw method
wraps the base.Draw method call in calls to Begin and End of the SpriteBatch from the game and
calls the Draw method of the ControlManager after the call to base.Draw.
The loadLinkLabel_Selected method is where I handle if the loadLinkLabel has been selected. If it
has I set the AcceptInput property of ControlManager to false. I then set the HasFocus property of
loadLinkLabel to false and the HasFocus property of loadListBox to true. This essentially makes it so
that the player can select items from the List Box with out the selected item in the control manager
changing.
The exitLinkLabel_Selected method it where I handle if the exitLinkLabel has been selected. All that
this method does is pop the current screen of the top of the state manager.
The loadListBox_Selected method is where I handle if the player hit the enter key when loadListBox
was the active control. I set the HasFocus property of loadLinkLabel to true and the AcceptInput
property of ControlManager to true so the selected control can be changed. I then change the state to
the GamePlayScreen and call the CreatePlayer and CreateWorld methods.
The loadListBox_Leave method is where I handle if the player hit the escape key when loadListBox

was the active control. I set the HasFocus method of loadLinkLabel to true and the AcceptInput
property of ControlManager to true.
The CreatePlayer and CreateWorld methods create the player and world. The only difference is
where the AnimatedSprite is created for the player. For the Texture2D of the sprite I use the male
fighter sprite. Eventually when you are able to write out and read in games you will load the sprite
appropriate to the player's character.
I'm going to end this tutorial here as it is getting to be on the long side. I want to try and keep them to a
reasonable length so that you don't have too much to digest at once. I encourage you to visit the news
page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 14A
Back to Editors
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
Before we go much further you will need data to work with. There is only so much you can do with out
data. When we create a character in the game, the player character, non-player character, or monster,
you need information about the character. You are going to want to know what weapon a character is
holding. What kind of skill do they have with that weapon. So, we need to work on the editors.
Open up the project in Visual C# from last time. Right click on the RpgEditor project and select the
Set As StartUp Project option. To make parsing the data in the list boxes a little easier I want to
change the ToString method of the EntityData class. I want to remove all of the parts that contained
an =. This is the new ToString method for the EntityData class.
public override string ToString()
{
string toString = EntityName + ", ";
toString += Strength.ToString() + ", ";
toString += Dexterity.ToString() + ", ";
toString += Cunning.ToString() + ", ";
toString += Willpower.ToString() + ", ";
toString += Magic.ToString() + ", ";
toString += Constitution.ToString() + ", ";
toString += HealthFormula + ", ";
toString += StaminaFormula + ", ";
toString += MagicFormula;
}

return toString;

You also need to add in overrides of the ToString methods to the item data classes: WeaponData,
ArmorData and ShieldData. I also included a constructor with no parameters, just to be sure that there
will be no problem serializing the data. This is the updated code for those three classes.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ArmorData
{
public string Name;
public string Type;
public int Price;

public
public
public
public
public
public

float Weight;
bool Equipped;
ArmorLocation ArmorLocation;
int DefenseValue;
int DefenseModifier;
string[] AllowableClasses;

public ArmorData()
{
}
public override string ToString()
{
string toString = Name + ", ";
toString += Type + ", ";
toString += Price.ToString() + ", ";
toString += Weight.ToString() + ", ";
toString += ArmorLocation.ToString() + ", ";
toString += DefenseValue.ToString() + ", ";
toString += DefenseModifier.ToString();
foreach (string s in AllowableClasses)
toString += ", " + s;
return toString;
}

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ShieldData
{
public string Name;
public string Type;
public int Price;
public float Weight;
public bool Equipped;
public int DefenseValue;
public int DefenseModifier;
public string[] AllowableClasses;
public ShieldData()
{
}
public override string ToString()
{
string toString = Name + ", ";
toString += Type + ", ";
toString += Price.ToString() + ", ";
toString += Weight.ToString() + ", ";
toString += DefenseValue.ToString() + ", ";
toString += DefenseModifier.ToString();
foreach (string s in AllowableClasses)
toString += ", " + s;
}
}

return toString;

using System;
using System.Collections.Generic;

using System.Linq;
using System.Text;
namespace RpgLibrary.ItemClasses
{
public class WeaponData
{
public string Name;
public string Type;
public int Price;
public float Weight;
public bool Equipped;
public Hands NumberHands;
public int AttackValue;
public int AttackModifier;
public int DamageValue;
public int DamageModifier;
public string[] AllowableClasses;
public WeaponData()
{
}
public override string ToString()
{
string toString = Name + ", ";
toString += Type + ", ";
toString += Price.ToString() + ", ";
toString += Weight.ToString() + ", ";
toString += NumberHands.ToString() + ",
toString += AttackValue.ToString() + ",
toString += AttackModifier.ToString() +
toString += DamageValue.ToString() + ",
toString += DamageModifier.ToString();

";
";
", ";
";

foreach (string s in AllowableClasses)


toString += ", " + s;
return toString;
}

You've seen the same code when I added in the overrides to the ToString methods of the other item
classes. Now everything is in place to start adding in logic to the forms.
I'm planning on making life a little easier when it comes to forms being closed. What I'm going to do is
disable the close button on the forms for entering in specific data. Right click FormEntityData in the
solution explorer and select the View Designer option. Click on the title bar of the form and set the
ControlBox property to False. Also, set the StartPosition property to CenterParent. I'm going to add
in a little code to cancel the form being closed if it isn't done by hitting the OK or Cancel buttons. Right
click FormEntityData and select View Code. Change the code for that form to the following.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
RpgLibrary.CharacterClasses;

namespace RpgEditor
{

public partial class FormEntityData : Form


{
#region Field Region
EntityData entityData = null;
#endregion
#region Property Region
public EntityData EntityData
{
get { return entityData; }
set { entityData = value; }
}
#endregion
#region Constructor Region
public FormEntityData()
{
InitializeComponent();
this.Load += new EventHandler(FormEntityData_Load);
this.FormClosing += new FormClosingEventHandler(FormEntityData_FormClosing);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormEntityData_Load(object sender, EventArgs e)
{
if (entityData != null)
{
tbName.Text = entityData.EntityName;
mtbStrength.Text = entityData.Strength.ToString();
mtbDexterity.Text = entityData.Dexterity.ToString();
mtbCunning.Text = entityData.Cunning.ToString();
mtbWillpower.Text = entityData.Willpower.ToString();
mtbConstitution.Text = entityData.Constitution.ToString();
tbHealth.Text = entityData.HealthFormula;
tbStamina.Text = entityData.StaminaFormula;
tbMana.Text = entityData.MagicFormula;
}
}
void FormEntityData_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbName.Text) || string.IsNullOrEmpty(tbHealth.Text) ||
string.IsNullOrEmpty(tbStamina.Text) || string.IsNullOrEmpty(tbMana.Text))
{
MessageBox.Show("Name, Health Formula, Stamina Formula and Mana Formula must have
values.");
return;
}
int str = 0;

int
int
int
int
int

dex
cun
wil
mag
con

=
=
=
=
=

0;
0;
0;
0;
0;

if (!int.TryParse(mtbStrength.Text, out str))


{
MessageBox.Show("Strength must be numeric.");
return;
}
if (!int.TryParse(mtbDexterity.Text, out dex))
{
MessageBox.Show("Dexterity must be numeric.");
return;
}
if (!int.TryParse(mtbCunning.Text, out cun))
{
MessageBox.Show("Cunning must be numeric.");
return;
}
if (!int.TryParse(mtbWillpower.Text, out wil))
{
MessageBox.Show("Willpower must be numeric.");
return;
}
if (!int.TryParse(mtbMagic.Text, out mag))
{
MessageBox.Show("Magic must be numeric.");
return;
}
if (!int.TryParse(mtbConstitution.Text, out con))
{
MessageBox.Show("Constitution must be numeric.");
return;
}
entityData = new EntityData(
tbName.Text,
str,
dex,
cun,
wil,
mag,
con,
tbHealth.Text,
tbStamina.Text,
tbMana.Text);

this.FormClosing -= FormEntityData_FormClosing;
this.Close();

void btnCancel_Click(object sender, EventArgs e)


{
entityData = null;

this.FormClosing -= FormEntityData_FormClosing;
this.Close();

#endregion
}

What the new code is doing is first wiring an event handler for the FormClosing event. In that handler
I check to see if the reason for the form closing is UserClosing. That means the form is being close by
the X, hitting ALT+F4, or code calling the Close method. If it is, I cancel the event. Then, before
calling the Close method at the end of the click event handlers for the buttons I remove the subscription
to the FormClosing event.
Now, I'm going to finish coding FormClasses. Right click FormClasses in the solution explorer and
select View Code. I'm going to first add the logic for the Delete button. Change the btnDelete_Click
method of FormClasses to the following. Also, add the following using statement with the other using
statements at the beginning of the code.
using System.IO;
void btnDelete_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
entityDataManager.EntityData.Remove(entity);

}
}

if (File.Exists(FormMain.ClassPath + @"\" + entity + ".xml"))


File.Delete(FormMain.ClassPath + @"\" + entity + ".xml");

You first want to check to see that an item in the list box is selected by check to be sure it is not null. I
then get the selected item as a string. Strings are separated by a comma so I call the Split method of the
string class passing in a comma. The first string in the array is the name so I call the Trim method on
parts[0] to remove any white space. I display a message box asking to make sure that you want to
delete the entity and capture the result. If the result was yes I remove the entry form the list box, I then
remove it from the entity data manager. I then check to see if a file for the entity exists, if it does I
delete it.
To edit an item you follow somewhat the same process. Change the btnEdit_Click method to the
following.
void btnEdit_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
EntityData data = entityDataManager.EntityData[entity];
EntityData newData = null;
using (FormEntityData frmEntityData = new FormEntityData())

{
frmEntityData.EntityData = data;
frmEntityData.ShowDialog();
if (frmEntityData.EntityData == null)
return;
if (frmEntityData.EntityData.EntityName == entity)
{
entityDataManager.EntityData[entity] = frmEntityData.EntityData;
FillListBox();
return;
}
newData = frmEntityData.EntityData;
}
DialogResult result = MessageBox.Show(
"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (entityDataManager.EntityData.ContainsKey(newData.EntityName))
{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}
lbDetails.Items.Add(newData);
entityDataManager.EntityData.Add(newData.EntityName, newData);
}

You first want to check to see that an item in the list box is selected by check to be sure it is not null. I
then get the selected item as a string. Strings are separated by a comma so I call the Split method of the
string class passing in a comma. The first string in the array is the name so I call the Trim method on
parts[0] to remove any white space. I then get the EntityData for the selected entity and set a local
variable to hold the new data of the entity. That is followed by a using statement that I use to create the
FormEntityData form for editing EntityData objects. I set the EntityData property of the form to be
the currently selected object and then call the ShowDialog method. If the EntityData property is null
after the ShowDialog method was called then the Cancel button was clicked and I exit the method. If
the EntityName proeprty of the EntityData object is the same as the entity variable then the name of
the EntityData object didn't change and it is safe to assign it to the entry at entity in the entity data
manager. I then call the FillListBox method to update the list box. I then return out of the method.
Before leaving the using statement for the form I set the newData variable to be the EntityData
property of the form. I display a message box asking if the user wants to add a new entry for the
EntityData object. If the result is No I exit the method. If there exists an entry in the entity data
manager already I display a message box and exit the method. I finally add the new object to the list
box and add the entry to the entity data manager.
The logic for the other forms that display the list is the same as FormClasses. Some of the logic for the
forms creating and editing individual objects is similar to FormEntityData. The implementation is a
little different but that is because the data on the forms is a little different. Before you can work on the
forms that hold the list of objects you need to do the logic for the forms for creating new objects and
editing existing objects.

Let's get started with FormArmorDetails. First right click it in the solution explorer and select View
Designer. Click on the title bar. Set the ControlBox property to False and the StartPosition property
to CenterParent. Right click it again and select View Code. This is the code for that form.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
using RpgLibrary.CharacterClasses;
namespace RpgEditor
{
public partial class FormArmorDetails : Form
{
#region Field Region
ArmorData armor = null;
#endregion
#region Property Region
public ArmorData Armor
{
get { return armor; }
set { armor = value; }
}
#endregion
#region Constructor Region
public FormArmorDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormArmorDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormArmorDetails_FormClosing);
btnMoveAllowed.Click += new EventHandler(btnMoveAllowed_Click);
btnRemoveAllowed.Click += new EventHandler(btnRemoveAllowed_Click);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormArmorDetails_Load(object sender, EventArgs e)
{
foreach (string s in FormDetails.EntityDataManager.EntityData.Keys)
lbClasses.Items.Add(s);
foreach (ArmorLocation location in Enum.GetValues(typeof(ArmorLocation)))
cboArmorLocation.Items.Add(location);
cboArmorLocation.SelectedIndex = 0;
if (armor != null)
{
tbName.Text = armor.Name;

tbType.Text = armor.Type;
mtbPrice.Text = armor.Price.ToString();
nudWeight.Value = (decimal)armor.Weight;
cboArmorLocation.SelectedIndex = (int)armor.ArmorLocation;
mtbDefenseValue.Text = armor.DefenseValue.ToString();
mtbDefenseModifier.Text = armor.DefenseModifier.ToString();
foreach (string s in armor.AllowableClasses)
{
if (lbClasses.Items.Contains(s))
lbClasses.Items.Remove(s);
lbAllowedClasses.Items.Add(s);
}

}
void FormArmorDetails_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
void btnMoveAllowed_Click(object sender, EventArgs e)
{
if (lbClasses.SelectedItem != null)
{
lbAllowedClasses.Items.Add(lbClasses.SelectedItem);
lbClasses.Items.RemoveAt(lbClasses.SelectedIndex);
}
}
void btnRemoveAllowed_Click(object sender, EventArgs e)
{
if (lbAllowedClasses.SelectedItem != null)
{
lbClasses.Items.Add(lbAllowedClasses.SelectedItem);
lbAllowedClasses.Items.RemoveAt(lbAllowedClasses.SelectedIndex);
}
}
void btnOK_Click(object sender, EventArgs e)
{
int price = 0;
float weight = 0f;
int defVal = 0;
int defMod = 0;
if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must enter a name for the item.");
return;
}
if (!int.TryParse(mtbPrice.Text, out price))
{
MessageBox.Show("Price must be an integer value.");
return;
}
weight = (float)nudWeight.Value;
if (!int.TryParse(mtbDefenseValue.Text, out defVal))
{
MessageBox.Show("Defense valule must be an interger value.");
return;
}

if (!int.TryParse(mtbDefenseModifier.Text, out defMod))


{
MessageBox.Show("Defense valule must be an interger value.");
return;
}
List<string> allowedClasses = new List<string>();
foreach (object o in lbAllowedClasses.Items)
allowedClasses.Add(o.ToString());
armor = new ArmorData();
armor.Name = tbName.Text;
armor.Type = tbType.Text;
armor.Price = price;
armor.Weight = weight;
armor.ArmorLocation = (ArmorLocation)cboArmorLocation.SelectedIndex;
armor.DefenseValue = defVal;
armor.DefenseModifier = defMod;
armor.AllowableClasses = allowedClasses.ToArray();
this.FormClosing -= FormArmorDetails_FormClosing;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
armor = null;
this.FormClosing -= FormArmorDetails_FormClosing;
this.Close();
}
#endregion
}

This code should look a little familiar from FormEntityData. Some of it is new though. There are
using statements to bring classes for our RpgLibrary into scope. There is a field of type ArmorData
to hold the armor created or edited. There is a public property to expose the ArmorData field as well.
The constructor wires a few event handlers. There are handlers for the Load event of the form and the
FormClosing event, like in FormEntityData. There are handlers for the Click events of btnOK and
btnCancel as well, again just like FormEntityData. There are two new handlers though. They are for
btnMoveAllowed and btnRemoveAllowed. When btnMoveAllowed is clicked the currently selected
item in lbClasses will be moved to lbAllowedClasses. The other button, btnRemoveAllowed, works
in reverse. It will move the currently selected item in lbAllowedClasses back to lbClasses.
In the Load event handler for the form I loop through all if the keys in the EntityDataManager. The
keys are then added to the items in lbClasses. I also fill the combo box with the items from the enum
ArmorLocation. I use the GetValues method to get the values. I also set the SelectedIndex of the
combo box to be the first item, at index 0. It then checks to see if the armor field is not null, meaning
the form is being opened to edit an armor. If it is not I set the values of the controls on the form. The
text boxes have their Text properties set to the appropriate field of the ArmorData field. The masked
text boxes have their Text properties set to the appropriate value using the ToString method. For the
nudWeight I set the Value property to the Weight field by casting it to a decimal. For the combo box I
set the SelectedIndex property to the ArmorLocation field, casting it to an integer. The list boxes
work a little differently. I loop through all of the classes in the array AllowableClasses. If the Items
collection of lbClasses contains the value it is removed. If you try and remove a value that isn't in the
collection you will generate an exception. I then add the value to the Items collection of the second list

box, lbAllowedClasses. The other two forms, FormShieldDetails and FormWeaponDetails, have
basically the same code for their Load events, just appropriate to the item they are for.
The event handler for the FormClosing event is a duplicate from FormEntityData. It just cancels
closing the form if the close reason is UserClosing. It was subscribed to in the constructor and will be
unsubscribed from if creating the ArmorData is successful in the Click event of btnOK and in the
Click event of btnCancel.
The code for the Click event handler of btnMoveAllowed handles moving the currently selected item
from lbClasses to lbAllowedClasses. It checks to see if the SelectedItem property of lbClasses is not
null. If it isn't then an item is selected and should be moved. I add the SelectedItem from lbClasses to
lbAllowedClasses. I then use the RemoveAt method of the Items collection of lbClasses to remove
the SelectedIndex of lbClasses.
The code for the Click event handler of btnRemoveAllowed works in reverse. It checks to see if the
SelectedItem property of lbAllowedClasses is not null. If it has a value the SelectedItem is added to
lbClasses and removed from lbAllowedClasses.
The event handler for the Click event of btnOK does a little validation on the form. It checks to make
sure that the Text property tbName has a value. It then uses the TryParse method of the integer class
to make sure that the masked text boxes' Text property have integer values. Creating the array of
allowed classes takes a little work. I have a local variable of List<string> that will hold all of the items
in lbAllowedClasses. Items in a list box's Items collection are stored as objects so there is a foreach
loop looping through all of the objects in Items. I add them to the allowedClasses using the ToString
method. I then create a new ArmorData object and assign the fields. I unsubscribe the FormClosing
event and close the form.
The event handler for btnCancel's Click event works just like on FormEntityData. I set the field to
null, unsubscribe the event and close the form.
The other two details forms, FormShieldDetails and FormWeaponDetails, have the same form as
FormArmorDetails. The difference is they work with shields and weapons respectively. I don't see a
reason to go over the code in depth like this form. Right click FormShieldDetails and select View
Code. This is the code for FormShieldDetails.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormShieldDetails : Form
{
#region Field Region
ShieldData shield;
#endregion

#region Property Region


public ShieldData Shield
{
get { return shield; }
set { shield = value; }
}
#endregion
#region Constructor Region
public FormShieldDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormShieldDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormShieldDetails_FormClosing);

btnMoveAllowed.Click += new EventHandler(btnMoveAllowed_Click);


btnRemoveAllowed.Click += new EventHandler(btnRemoveAllowed_Click);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);

#endregion
#region Event Handler Region
void FormShieldDetails_Load(object sender, EventArgs e)
{
foreach (string s in FormDetails.EntityDataManager.EntityData.Keys)
lbClasses.Items.Add(s);
if (shield != null)
{
tbName.Text = shield.Name;
tbType.Text = shield.Type;
mtbPrice.Text = shield.Price.ToString();
nudWeight.Value = (decimal)shield.Weight;
mtbDefenseValue.Text = shield.DefenseValue.ToString();
mtbDefenseModifier.Text = shield.DefenseModifier.ToString();
foreach (string s in shield.AllowableClasses)
{
if (lbClasses.Items.Contains(s))
lbClasses.Items.Remove(s);
}
}

lbAllowedClasses.Items.Add(s);

void FormShieldDetails_FormClosing(object sender, FormClosingEventArgs e)


{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
void btnMoveAllowed_Click(object sender, EventArgs e)
{
if (lbClasses.SelectedItem != null)
{
lbAllowedClasses.Items.Add(lbClasses.SelectedItem);
lbClasses.Items.RemoveAt(lbClasses.SelectedIndex);
}
}

void btnRemoveAllowed_Click(object sender, EventArgs e)


{
if (lbAllowedClasses.SelectedItem != null)
{
lbClasses.Items.Add(lbAllowedClasses.SelectedItem);
lbAllowedClasses.Items.RemoveAt(lbAllowedClasses.SelectedIndex);
}
}
void btnOK_Click(object sender, EventArgs e)
{
int price = 0;
float weight = 0f;
int defVal = 0;
int defMod = 0;
if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must enter a name for the item.");
return;
}
if (!int.TryParse(mtbPrice.Text, out price))
{
MessageBox.Show("Price must be an integer value.");
return;
}
weight = (float)nudWeight.Value;
if (!int.TryParse(mtbDefenseValue.Text, out defVal))
{
MessageBox.Show("Defense valule must be an interger value.");
return;
}
if (!int.TryParse(mtbDefenseModifier.Text, out defMod))
{
MessageBox.Show("Defense valule must be an interger value.");
return;
}
List<string> allowedClasses = new List<string>();
foreach (object o in lbAllowedClasses.Items)
allowedClasses.Add(o.ToString());
shield = new ShieldData();
shield.Name = tbName.Text;
shield.Type = tbType.Text;
shield.Price = price;
shield.Weight = weight;
shield.DefenseValue = defVal;
shield.DefenseModifier = defMod;
shield.AllowableClasses = allowedClasses.ToArray();
this.FormClosing -= FormShieldDetails_FormClosing;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
shield = null;
this.FormClosing -= FormShieldDetails_FormClosing;
this.Close();
}
#endregion
}

Nothing really new there, if anything it is a little simpler than FormArmorDetails as you don't have to
worry about the location of a shield. FormWeaponDetails is basically the same as well. It just works
with a weapon rather than armor. Right click FormWeaponDetails and select View Code. This is the
code.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormWeaponDetails : Form
{
#region Field Region
WeaponData weapon = null;
#endregion
#region Property Region
public WeaponData Weapon
{
get { return weapon; }
set { weapon = value; }
}
#endregion
#region Constructor Region
public FormWeaponDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormWeaponDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormWeaponDetails_FormClosing);
btnMoveAllowed.Click += new EventHandler(btnMoveAllowed_Click);
btnRemoveAllowed.Click += new EventHandler(btnRemoveAllowed_Click);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormWeaponDetails_Load(object sender, EventArgs e)
{
foreach (string s in FormDetails.EntityDataManager.EntityData.Keys)
lbClasses.Items.Add(s);
foreach (Hands location in Enum.GetValues(typeof(Hands)))
cboHands.Items.Add(location);
cboHands.SelectedIndex = 0;

if (weapon != null)
{
tbName.Text = weapon.Name;
tbType.Text = weapon.Type;
mtbPrice.Text = weapon.Price.ToString();
nudWeight.Value = (decimal)weapon.Weight;
cboHands.SelectedIndex = (int)weapon.NumberHands;
mtbAttackValue.Text = weapon.AttackValue.ToString();
mtbAttackModifier.Text = weapon.AttackModifier.ToString();
mtbDamageValue.Text = weapon.DamageValue.ToString();
mtbDamageModifier.Text = weapon.DamageModifier.ToString();
foreach (string s in weapon.AllowableClasses)
{
if (lbClasses.Items.Contains(s))
lbClasses.Items.Remove(s);
}
}

lbAllowedClasses.Items.Add(s);

void FormWeaponDetails_FormClosing(object sender, FormClosingEventArgs e)


{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
void btnMoveAllowed_Click(object sender, EventArgs e)
{
if (lbClasses.SelectedItem != null)
{
lbAllowedClasses.Items.Add(lbClasses.SelectedItem);
lbClasses.Items.RemoveAt(lbClasses.SelectedIndex);
}
}
void btnRemoveAllowed_Click(object sender, EventArgs e)
{
if (lbAllowedClasses.SelectedItem != null)
{
lbClasses.Items.Add(lbAllowedClasses.SelectedItem);
lbAllowedClasses.Items.RemoveAt(lbAllowedClasses.SelectedIndex);
}
}
void btnOK_Click(object sender, EventArgs e)
{
int price = 0;
float weight = 0f;
int attVal = 0;
int attMod = 0;
int damVal = 0;
int damMod = 0;
if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must enter a name for the item.");
return;
}
if (!int.TryParse(mtbPrice.Text, out price))
{
MessageBox.Show("Price must be an integer value.");
return;
}
weight = (float)nudWeight.Value;

if (!int.TryParse(mtbAttackValue.Text, out attVal))


{
MessageBox.Show("Attack value must be an interger value.");
return;
}
if (!int.TryParse(mtbAttackModifier.Text, out attMod))
{
MessageBox.Show("Attack value must be an interger value.");
return;
}
if (!int.TryParse(mtbDamageValue.Text, out damVal))
{
MessageBox.Show("Damage value must be an interger value.");
return;
}
if (!int.TryParse(mtbDamageModifier.Text, out damMod))
{
MessageBox.Show("Damage value must be an interger value.");
return;
}
List<string> allowedClasses = new List<string>();
foreach (object o in lbAllowedClasses.Items)
allowedClasses.Add(o.ToString());
weapon = new WeaponData();
weapon.Name = tbName.Text;
weapon.Type = tbType.Text;
weapon.Price = price;
weapon.Weight = weight;
weapon.AttackValue = attVal;
weapon.AttackModifier = attMod;
weapon.DamageValue = damVal;
weapon.DamageModifier = damMod;
weapon.AllowableClasses = allowedClasses.ToArray();
this.FormClosing -= FormWeaponDetails_FormClosing;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
weapon = null;
this.FormClosing -= FormWeaponDetails_FormClosing;
this.Close();
}
#endregion
}

I don't think there is anything that needs explaining. The only difference is a few variable names and
instead of using ArmorLocation to fill the combo box use Hands.
I'm going to end this tutorial here and add a B part to it. Instead of posting the A part before finishing
the B part, I'm going to finish the B part and post them both. I encourage you to visit the news page of
my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!

Jamie McMahon

XNA 4.0 RPG Tutorials


Part 14B
Back to Editors
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is the second part of tutorial 14 on adding more to the editors for the game. In this part I'm going
to be concentrating on the forms that hold the list of data items. Right click FormArmor and select
View Code. This is the code for FormArmor.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormArmor : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormArmor()
{
InitializeComponent();

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormArmorDetails frmArmorDetails = new FormArmorDetails())
{
frmArmorDetails.ShowDialog();

if (frmArmorDetails.Armor != null)
{
AddArmor(frmArmorDetails.Armor);
}
}

void btnEdit_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
ArmorData data = itemManager.ArmorData[entity];
ArmorData newData = null;
using (FormArmorDetails frmArmorData = new FormArmorDetails())
{
frmArmorData.Armor = data;
frmArmorData.ShowDialog();
if (frmArmorData.Armor == null)
return;
if (frmArmorData.Armor.Name == entity)
{
itemManager.ArmorData[entity] = frmArmorData.Armor;
FillListBox();
return;
}
newData = frmArmorData.Armor;
}
DialogResult result = MessageBox.Show(
"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (itemManager.ArmorData.ContainsKey(newData.Name))
{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}
lbDetails.Items.Add(newData);
itemManager.ArmorData.Add(newData.Name, newData);
}

void btnDelete_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)

{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.ArmorData.Remove(entity);

}
}

if (File.Exists(FormMain.ItemPath + @"\Armor\" + entity + ".xml"))


File.Delete(FormMain.ItemPath + @"\Armor\" + entity + ".xml");

#endregion
#region Method Region
public void FillListBox()
{
lbDetails.Items.Clear();

foreach (string s in FormDetails.ItemManager.ArmorData.Keys)


lbDetails.Items.Add(FormDetails.ItemManager.ArmorData[s]);

private void AddArmor(ArmorData armorData)


{
if (FormDetails.ItemManager.ArmorData.ContainsKey(armorData.Name))
{
DialogResult result = MessageBox.Show(
armorData.Name + " already exists. Overwrite it?",
"Existing armor",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
itemManager.ArmorData[armorData.Name] = armorData;
FillListBox();
return;
}
itemManager.ArmorData.Add(armorData.Name, armorData);
lbDetails.Items.Add(armorData);
}
}

#endregion

The code should look familiar, it is pretty much the same code as FormClasses. It just works with
armor instead of entity data. There is the using statement for the System.IO name space. Event
handlers for the click event of the buttons are wired in the constructors. The Click event handler of
btnAdd creates a form in a using block. I show the form. If the Armor property of the form is not null
I call the AddArmor method passing in the Armor property. The Click event handler of btnEdit
checks to see if the SelectedItem of lbDetails is not null. It parses the string to get the name of the
armor. It then gets the ArmorData for the selected item and sets newData to null. In a using statement
a form is created. The Armor property of the form is set to the ArmorData of SelectedItem. I call the
ShowDialog method to display the form. If the Armor property of form is null I exit the method. If the
name is the same as before I assign the entry in the item manager to be the new armor, call FillListBox
to update the armor and exit the method. I then set newData to be the Armor property of the form. The
name of the armor changed so I display a message box asking if the new armor should be added. If the
result is no I exit the method. If there is armor with that name already I display a message box and exit.
If there wasn't I add the new armor to the list box and the item manager. The Click event handler for
btnDelete checks to make sure that the SelectedItem of the list box is not null. It parses the selected
item and displays a message box asking if the armor should be deleted. If the result of the message box

is Yes I remove the armor from the list box and I remove if from the item manager as well. I then delete
the file, if it exists.
Right click FormShield now and select View Code. The code for FormShield is almost a carbon copy
of FormArmor. In fact, I copied and pasted the code. I then renamed Armor to Shield and then armor
to shield. This is the code for FormShield.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormShield : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormShield()
{
InitializeComponent();

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormShieldDetails frmShieldDetails = new FormShieldDetails())
{
frmShieldDetails.ShowDialog();

if (frmShieldDetails.Shield != null)
{
AddShield(frmShieldDetails.Shield);
}

}
void btnEdit_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
ShieldData data = itemManager.ShieldData[entity];

ShieldData newData = null;


using (FormShieldDetails frmShieldData = new FormShieldDetails())
{
frmShieldData.Shield = data;
frmShieldData.ShowDialog();
if (frmShieldData.Shield == null)
return;
if (frmShieldData.Shield.Name == entity)
{
itemManager.ShieldData[entity] = frmShieldData.Shield;
FillListBox();
return;
}
}

newData = frmShieldData.Shield;

DialogResult result = MessageBox.Show(


"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (itemManager.ShieldData.ContainsKey(newData.Name))
{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}

lbDetails.Items.Add(newData);
itemManager.ShieldData.Add(newData.Name, newData);

}
void btnDelete_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.ShieldData.Remove(entity);
if (File.Exists(FormMain.ItemPath + @"\Shield\" + entity + ".xml"))
File.Delete(FormMain.ItemPath + @"\Shield\" + entity + ".xml");
}

}
#endregion
#region Method Region
public void FillListBox()
{
lbDetails.Items.Clear();

foreach (string s in FormDetails.ItemManager.ShieldData.Keys)


lbDetails.Items.Add(FormDetails.ItemManager.ShieldData[s]);
}
private void AddShield(ShieldData shieldData)
{
if (FormDetails.ItemManager.ShieldData.ContainsKey(shieldData.Name))
{
DialogResult result = MessageBox.Show(
shieldData.Name + " already exists. Overwrite it?",
"Existing shield",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;

itemManager.ShieldData[shieldData.Name] = shieldData;
FillListBox();
return;

itemManager.ShieldData.Add(shieldData.Name, shieldData);
lbDetails.Items.Add(shieldData);

#endregion
}

As you can see, other than variable and class names, the code is the same. The code for FormWeapon
is the same again. Right click FormWeapon and select View Code. This is the code for that form.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormWeapon : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormWeapon()
{
InitializeComponent();

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormWeaponDetails frmWeaponDetails = new FormWeaponDetails())
{
frmWeaponDetails.ShowDialog();

if (frmWeaponDetails.Weapon != null)
{
AddWeapon(frmWeaponDetails.Weapon);
}

}
void btnEdit_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
WeaponData data = itemManager.WeaponData[entity];
WeaponData newData = null;
using (FormWeaponDetails frmWeaponData = new FormWeaponDetails())
{
frmWeaponData.Weapon = data;
frmWeaponData.ShowDialog();
if (frmWeaponData.Weapon == null)
return;
if (frmWeaponData.Weapon.Name == entity)
{
itemManager.WeaponData[entity] = frmWeaponData.Weapon;
FillListBox();
return;
}
}

newData = frmWeaponData.Weapon;

DialogResult result = MessageBox.Show(


"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (itemManager.WeaponData.ContainsKey(newData.Name))
{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}

lbDetails.Items.Add(newData);
itemManager.WeaponData.Add(newData.Name, newData);

}
void btnDelete_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;

string[] parts = detail.Split(',');


string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.WeaponData.Remove(entity);
if (File.Exists(FormMain.ItemPath + @"\Weapon\" + entity + ".xml"))
File.Delete(FormMain.ItemPath + @"\Weapon\" + entity + ".xml");
}

}
#endregion
#region Method Region
public void FillListBox()
{
lbDetails.Items.Clear();
foreach (string s in FormDetails.ItemManager.WeaponData.Keys)
lbDetails.Items.Add(FormDetails.ItemManager.WeaponData[s]);
}
private void AddWeapon(WeaponData weaponData)
{
if (FormDetails.ItemManager.WeaponData.ContainsKey(weaponData.Name))
{
DialogResult result = MessageBox.Show(
weaponData.Name + " already exists. Overwrite it?",
"Existing weapon",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;

itemManager.WeaponData[weaponData.Name] = weaponData;
FillListBox();
return;

itemManager.WeaponData.Add(weaponData.Name, weaponData);
lbDetails.Items.Add(weaponData);

#endregion
}

It might be a pain in the ass to have to do this every time but I'm going to ask the user if they are sure
they want to exit before closing the editor. If they choose the No option then closing the form will be
cancelled. I will wire a handler for the FormClosing event in the constructor of FormMain and handle
the event. Right click FormMain and select View Code. Change the constructor to the following and
add in the following handler.
public FormMain()
{
InitializeComponent();
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);

newGameToolStripMenuItem.Click += new EventHandler(newGameToolStripMenuItem_Click);


openGameToolStripMenuItem.Click += new EventHandler(openGameToolStripMenuItem_Click);
saveGameToolStripMenuItem.Click += new EventHandler(saveGameToolStripMenuItem_Click);
exitToolStripMenuItem.Click += new EventHandler(exitToolStripMenuItem_Click);

classesToolStripMenuItem.Click += new EventHandler(classesToolStripMenuItem_Click);


armorToolStripMenuItem.Click += new EventHandler(armorToolStripMenuItem_Click);
shieldToolStripMenuItem.Click += new EventHandler(shieldToolStripMenuItem_Click);
weaponToolStripMenuItem.Click += new EventHandler(weaponToolStripMenuItem_Click);

void FormMain_FormClosing(object sender, FormClosingEventArgs e)


{
DialogResult result = MessageBox.Show(
"Unsaved changes will be lost. Are you sure you want to exit?",
"Exit?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);

if (result == DialogResult.No)
e.Cancel = true;

That gets the editors up and going. With these you have different classes for the player character, and
non-player characters, and some basic items. We still don't have any data to work with though and there
is something that I need to explain, the formula fields in the EntityData class. I don't plan on using
complicated formulae for health, mana, or stamina. What I plan to do is have a three part formula, with
each part separated with a |. The first part is the base for the attribute, the second is the basic attribute to
add to the base, and the third is a random amount that will be added with each level. So, if you had 20|
CON|12, the formula would be 20 + CON + 1-12 for first level and 1-12 more points each level gained
after the first. I will be, eventually, be adding in modifiers to health, mana, and stamina. The reason I
say modifiers rather than bonuses is that bonuses, to me, signifies positives and there could be penalties
from injuries or cursed items. If a class can not have the attribute, fighters don't have mana, you use 0|0|
0 as the formula.
I was torn about whether or not to go over adding in how I came up with the data values I used or not.
In the end I decided that it was worth it. I will be using a 100 point system for the basic character
attributes with 10 being an average person. As you've seen I decided to go with Fighters, Rogues,
Priests, and Wizards as character classes for the player character. There will be other character classes
that are for non-player characters. Fighters are strong, hardy characters, and are not gifted magically.
Rogues are fair fighters but very perceptive and dexterous. Priests are not bad fighters and have access
to healing magic and a few offensive spells. Wizards, while not the strongest physically, command
powerful magics that make them dreaded foes. This is the data I used for each of the character classes.

Character Classes
Class Name

STR

DEX

CUN

WIL

MAG

CON

Health
Formula

Stamina
Formula

Magic
Formula

Fighter

14

12

10

12

10

12

20|CON|12

12|WIL|12

0|0|0

Rogue

10

14

14

12

10

10

10|CON|10

10|WIL|10

0|0|0|

Priest

12

10

12

12

12

12

12|CON|12

0|0|0

10|WIL|10

Wizard

10

10

12

14

14

10

10|CON|10

0|0|0|

20|WIL|12

What I suggest is you run the editor and create a game and data, to see the editor in action and we
really need data to work with. Launch the editor and then create a new game. Select a directory other
than the EyesOfTheDragonContent folder. I named my game: Eyes of the Dragon. For the
description I put something like: Tutorial game for creating a RPG with XNA 4.0. Click Classes to
bring up the Classes form. Add each of the classes above. My data is in the project file for this tutorial,
or if you just want the data: http://xnagpa.net/xna4/downloads/gamedata14.zip.
I'm now going to go into creating a few items in this tutorial. I'm going to set up a few tables with the
values I used. If you're not interested in doing them on your own they are all in the file I linked to in the
last paragraph. These are just suggest values.

Armor
Name

Type

Price

Weight

Location

Defense
Value

Defense
Modifier

Allowed Classes

Leather Gloves

Gloves 10

Hands

Fighter
Rogue
Priest

Leather Boots

Boots

10

Feet

Fighter
Rogue
Priest
Wizard

Leather Armor

Armor 20

Body

10

Fighter
Rogue
Priest

Studded Leather Gloves

Gloves 15

Hands

Fighter
Rogue
Priest

Studded Leather Boots

Boots

Feet

Fighter
Rogue
Priest

Studded Leather Armor

Armor 30

10

Body

14

Fighter
Rogue
Priest

Leather Helm

Helm

10

Head

Fighter
Rogue
Priest

Studded Leather Helm

Helm

15

Head

Fighter
Rogue
Priest

Chain Mail Boots

Boots

30

Feet

10

Fighter
Priest

Chain Mail Gloves

Gloves 30

Hands

10

Fighter
Priest

Chain Mail

Armor 80

25

Body

20

Fighter
Priest

Chain Mail Helm

Helm

40

Head

10

Fighter
Priest

Light Robes

Robes 10

Body

Wizard

Medium Robes

Robes 20

Body

Wizard

15

Shields
Name

Type

Price

Weight

Defense
Value

Defense
Modifier

Allowed Classes

Small Wooden Shield

Small

Fighter
Rogue
Priest

Medium Wooden Shield

Medium

10

Fighter
Priest

Large Wooden Shield

Large

20

12

15

Fighter

Small Metal Shield

Small

10

Fighter
Rogue
Priest

Medium Metal Shield

Medium

40

12

12

Fighter
Priest

Large Metal Shield

Large

80

16

20

Fighter

Large Kite Shield

Large

100

18

25

Fighter

Heavy Tower Shield

Large

125

20

30

Fighter

Attack
Value

Attack
Modifier

Weapons
Name

Type

Price

Weight

Hands

Damage
Value

Damage
Modifier

Allowed Classes

Club

Crushing

10

One

Fighter
Rogue
Priest

Mace

Crushing

16

12

One

Fighter
Rogue
Priest

Flail

Crushing

20

14

One

10

Fighter
Priest

Apprentice Staff

Magic

20

Two

Wizard

Acolyte Staff

Magic

40

Two

Wizard

Dagger

Piercing

10

One

Fighter
Rogue

Short Sword

Piercing

20

10

One

Fighter
Rogue

Long Sword

Slashing

40

15

One

10

12

Fighter
Rogue

Broad Sword

Slashing

60

18

One

12

14

Fighter
Rogue

Great Sword

Slashing

80

25

Two

12

16

Fighter

Halberd

Slashing

100

30

Two

16

20

Fighter

War Axe

Slashing

20

15

One

10

10

Fighter
Rogue

Battle Axe

Slashing

50

25

Two

12

16

Fighter

I'm going to end the second part of the tutorial here. I'd like to try and keep the tutorials to a reasonable
length. I encourage you to visit the news page of my site, XNA Game Programming Adventures, for the
latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 15
Skills, Spells, and Talents
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to get started with adding skills, talents, and spells to the game. I will be
adding in place holders that will be filled in as the game progresses. First I want to explain what skills,
spells, and talents are for.
Skills can be learned by any humanoid creature with intelligence. Spells are specific to characters that
have an understanding of magic. Talents are available to characters that are not magic using, basically
special moves like bashing an enemy with a shield, picking a lock, or disarming a trap.
Before I get to the game there is I thought I'd share with you that I use a lot. It is a pain to always be
entering all of the #region and #endregion directives into your code but it is good for organizational
purposes to use them. I made a snippet for adding them and added it to the snippet manager in Visual
Studio. So, I can just right click in the code editor and insert the snippet. Right click your game, select
Add and then New Item. Select the XML File entry and name it regions.snippet. This is the code.
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>
Regions Snippet
</Title>
</Header>
<Snippet>
<Code Language="CSharp">
<![CDATA[#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>

That is what a snippet file looks like for the snippet manager in Visual Studio. The Title tag is the title
of the snippet in the snippet manager, I named it Regions Snippet. I set the Language attribute of the
Code tag to CSharp as it is a C# snippet. In between the inner square brackets of CDATA is where you
place to code to insert. Now, from the File menu select Save As to save it to a different directory.
Navigate to the \Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets\ folder and save
the file there. Now, right click the regions.snippet file in the solution explorer and select Remove.
When you want to use the snippet right click in the editor where you want to insert it, select Insert
Snippet, My Code Snippets, then Regions Snippet. You can make many more snippets. I've just
found this one incredibly useful when it comes to organizing code.
I want to add a static class to the RpgLibrary for handling game mechanics, like generating random
numbers, resolving skill use, etc. Right click the RpgLibrary, select Add and then Class. Name this
new class Mechanics. This is the code for that class.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.CharacterClasses;

namespace RpgLibrary
{
public enum DieType { D4 = 4, D6 = 6, D8 = 8, D10 = 10, D12 = 12, D20 = 20, D100 = 100 }
public static class Mechanics
{
#region Field Region
static Random random = new Random();
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
public static int RollDie(DieType die)
{
return random.Next(0, (int)die) + 1;
}
#endregion

#region Virtual Method Region


#endregion

There is an enumeration, DieType, that has entries for different dice that are found in role playing
games. I have it at the name space level so you don't have to use a class name as well as the enum name
when referencing a DieType variable. I also give them values based on the type of die. For example,
the D8 member has a value of 8. So you don't have to use if or switch statements to get the upper limit
of the die. The value 8 is already associated with D8.

The class itself is static as the fields, properties, and methods are all meant to be static. You reference
the fields, properties and methods using the class name rather than an object of the class type. It is
common to group items that are similar together. A good example is the MathHelper class. There are a
number of useful math functions in the MathHelper class. There are going to be many methods and
properties that will be useful and I'm going to keep them all in the same place.
At the moment there is just one variable and one method. The variable is of type Random and will be
used for generating random numbers when they are needed. The static method, RollDie, will be called
when you want to roll a die. You pass in the type of die that you want to roll. The overload of the Next
method that I use takes two parameters. The first is the inclusive lower bound and the second is the
exclusive upper bound. That means the first number is included in the range of numbers to create and
the second is not. That is why I pass in zero for the first value and the die cast to an integer for the
second and add one to the result. So, for the D8 example the number generated will be between 0 and
7. Adding 1 to that gives the 1 to 8 range we are looking for.
I first want to add classes for skills, spells, and talents. Right click the RpgLibrary, select Add and
then New Folder. Name this new folder SkillClasses. Repeat the process twice and name the folders
SpellClasses and TalentClasses.
Now to each of those folders I want to add in a class. Right click each of the folders, select Add and
then Class. To the SkillClasses folder you want to add a class Skill, to the SpellClasses folder Spell,
and to the TalentClasses folder Talent. Right click each of the folders again, select Add and then
Class. To SkillClasses add a new class SkillData, to SpellClasses add a new class SpellData, and to
TalentClasses add a new class TalentData. One last time, right click each of the folders, select Add
and then Class. To SkillClasses you want to add SkillDataManager, to SpellClasses you want to add
SpellDataManager, and to TalentClasses you want to add TalentDataManager.
To the class and class data classes I added in regions and made the classes public. This is the code that I
added to the Skill and SkillData classes. I included an enum called DifficultyLevel in the Skill class.
This is how hard it is to use the skill. It can be very easy to perform a skill or it can be next to
impossible. The harder a skill is to perform the lower its value.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SkillClasses
{
public enum DifficultyLevel
{
Master = -50,
Expert = -25,
Improved = -10,
Normal = 0,
Easy = 25,
}
public class Skill
{
#region Field Region
#endregion
#region Property Region
#endregion

#region Constructor Region


#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SkillClasses
{
public class SkillData
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

I did that using the snippet I showed you at the start of the tutorial. Alternatively after adding the
regions for the Skill class you could copy and paste the region code. The code for the Spell, SpellData,
Talent and TalentData classes follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SpellClasses
{
public class Spell
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method Region
#endregion
}

using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RpgLibrary.SpellClasses
{
public class SpellData
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TalentClasses
{
public class Talent
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method Region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TalentClasses
{
public class TalentData
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region

#endregion

#region Virtual Method region


#endregion

To the manager classes I added a bit more code. I added in private readonly fields, public properties to
expose those fields, and in the constructor I create the fields. The code for my SkillDataManager,
SpellDataManager, and TalentDataManager classes follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SkillClasses
{
public class SkillDataManager
{
#region Field Region
readonly Dictionary<string, SkillData> skillData;
#endregion
#region Property Region
public Dictionary<string, SkillData> SkillData
{
get { return skillData; }
}
#endregion
#region Constructor Region
public SkillDataManager()
{
skillData = new Dictionary<string, SkillData>();
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SpellClasses
{
public class SpellDataManager
{
#region Field Region
readonly Dictionary<string, SpellData> spellData;
#endregion
#region Property Region

public Dictionary<string, SpellData> SpellData


{
get { return spellData; }
}
#endregion
#region Constructor Region
public SpellDataManager()
{
spellData = new Dictionary<string, SpellData>();
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TalentClasses
{
public class TalentDataManager
{
#region Field Region
readonly Dictionary<string, TalentData> talentData;
#endregion
#region Property Region
public Dictionary<string, TalentData> TalentData
{
get { return talentData; }
}
#endregion
#region Constructor Region
public TalentDataManager()
{
talentData = new Dictionary<string, TalentData>();
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

There will be modifiers to skills, spells, and talents. I say modifiers rather than bonuses because I feel
that bonuses are beneficial and there could be negative modifiers. I'm going to add a class to represent a

modifier. Right click the RpgLibrary project, select Add and then Class. Name the class Modifier.
This is the code.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary
{
public struct Modifier
{
#region Field Region
public int Amount;
public int Duration;
public TimeSpan TimeLeft;
#endregion
#region Constructor Region
public Modifier(int amount)
{
Amount = amount;
Duration = -1;
TimeLeft = TimeSpan.Zero;
}
public Modifier(int amount, int duration)
{
Amount = amount;
Duration = duration;
TimeLeft = TimeSpan.FromSeconds(duration);
}
#endregion
#region Method Region
public void Update(TimeSpan elapsedTime)
{
if (Duration == -1)
return;

TimeLeft -= elapsedTime;
if (TimeLeft.TotalMilliseconds < 0)
{
TimeLeft = TimeSpan.Zero;
Amount = 0;
}

#endregion

#region Virtual Method Region


#endregion

The first thing you will notice is that I made this a structure rather than a class. The reason I did this is
that you will be adding and removing them frequently. That is one reason to use a structure rather than
a class.
There are three fields in the structure and they are all public. The first, Amount, is the amount of the
modifier. Duration is how long the modifier lasts, measured in seconds, and the TimeLeft tracks how

much time is left before the modifier expires. If you set the Duration to -1 the modifier lasts until it is
dispelled some how.
There are two constructors in the structure. The first takes the amount of the modifier as a parameter.
The second takes the amount and the duration. The first constructor sets the Amount field with the
value passed in, Duration to -1, and TimeLeft to TimeSpan.Zero. The second sets the Amount and
Duration fields to the values passed in. It then creates the TimeLeft field using the FromSeconds
method of the TimeSpan class.
The Update method takes as a parameter a TimeSpan object. I can't pass in a GameTime object
because this library has nothing to do with XNA. The GameTime class's ElapsedGameTime property
is a TimeSpan object so that can be passed to the Update method. I first check to see if Duration is -1
and if it is I exit the method as this modifier lasts until it is dispelled in some way. I then reduce the
TimeLeft field by the elapsedTime parameter. If the TotalMilliseconds is less than zero I set
TimeLeft to be TimeSpan.Zero and Amount to 0. If there is a modifier that has an amount of 0 it will
be removed from the list of modifiers.
I will be adding skills that have crafting formulas or recipes. I decided to go with the term recipe for
them in the end. Since they are related to skills I put them into the SkillClasses folder. Right click the
SkillClasses folder, select Add and then Class. Name this new class Recipe. I added in a structure to
this class as well. Add the following code to the Recipe class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SkillClasses
{
public struct Reagents
{
#region Field Region
public string ReagentName;
public ushort AmountRequired;
#endregion
#region Property Region
#endregion
#region Constructor Region
public Reagents(string reagent, ushort number)
{
ReagentName = reagent;
AmountRequired = number;
}
#endregion
#region Method Region
#endregion
}
public class Recipe
{
#region Field Region
public string Name;

public Reagents[] Reagents;


#endregion
#region Property Region
#endregion
#region Constructor Region
private Recipe()
{
}
public Recipe(string name, params Reagents[] reagents)
{
Name = name;
Reagents = new Reagents[reagents.Length];
for (int i = 0; i < reagents.Length; i++)
Reagents[i] = reagents[i];
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

The structure Reagents is for the different reagents in a recipe. I use reagent to mean herbs and other
items that can be used to make potions, poisons, traps, etc. Since it is a structure I made the two fields
public. The first field, ReagentName, is the name of the reagent from the item classes. I will add that
class shortly. The AmountRequired field is the amount of that reagent required to craft the item and is
an unsigned short so its value can never be negative. The constructor takes two parameters. A string for
the name of the reagent and the amount of the reagent that is required.
I decided against creating a data class for recipes. That just seemed unnecessary as there isn't anything
difficult about recipes. The Recipe class has two public fields. The first is a string for the name of the
recipe, Name, and the second is an array of Reagents that holds the reagents required for the recipe.
There are two constructors for the class. The first a private constructor with no parameters to be used to
deserialize a recipe. The second takes a string parameter for the name and a params of Reagents. You
could just as easily use an array of Reagents or a List<Reagents> for it. It might be a good idea as you
could enforce that there be at least one Reagents for a recipe. The second constructor sets the Name
field to the name parameter. I then creates a new array of Reagents the same length as the reagents
passed in. I then copy the values from the reagents parameter to the Reagents field. Structures are
value types so you don't need to worry about unexpected side effects from changing values like you do
in a class that is a reference type.
You will also want a class to manage all of the recipes in the game. Right click the SkillClasses folder,
select Add and then Class. Name this new class RecipeManager. The code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SkillClasses
{
public class RecipeManager
{
#region Field Region
readonly Dictionary<string, Recipe> recipies;
#endregion
#region Property Region
public Dictionary<string, Recipe> Recipies
{
get { return recipies; }
}
#endregion
#region Constructor Region
public RecipeManager()
{
recipies = new Dictionary<string, Recipe>();
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

Nothing really new here. It is like all of the other manager classes I've created so far. There is a private
readonly field for the recipes. It is a Dictionary<string, Recipe>. There is a read only field to expose
the field. The constructor just creates a new instance of the dictionary.
The last thing I'm going to do is add in a classes to the ItemClasses folders for reagents. Right click the
ItemClasses folder, select Add and then Class. Name this new class ReagentData. This is the code for
that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ReagentData
{
public string Name;
public string Type;
public int Price;
public float Weight;
public ReagentData()
{
}
public override string ToString()
{
string toString = Name + ", ";

toString += Type + ", ";


toString += Price.ToString() + ", ";
toString += Weight.ToString();
return toString;
}

There are only four fields of interest to us from the BaseItem class. You want the name of the reagent,
the type of the reagent, the price of the reagent, and the weight of the reagent. You could probably even
get away with out the weight as for the inventory I won't be keeping track of weights. Weight will be of
importance to what a character is actually carrying. I will show you how I'm going to deal with the fact
that BaseItem requires zero or more strings for classes that are allowed to use the item in a moment.
The ToString method returns the a string with the name, type, price, and weight of the reagent.
Right click the ItemClasses folder again, select Add and then Class. Name this new class Reagent.
The code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Reagent : BaseItem
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public Reagent(
string reagentName,
string reagentType,
int price,
float weight)
: base(reagentName, reagentType, price, weight, null)
{
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public override object Clone()
{
Reagent reagent = new Reagent(Name, Type, Price, Weight);
}

return reagent;

#endregion
}
}

There are no new fields or properties in this class and as you can see it doesn't have the params
parameter at the end. This is where the properties params allowing zero or more parameters comes
into play. I can just pass null in to the call to the base constructor. So, what is the purpose of inheriting
from BaseItem? Well, doing that you can still use polymorphism, the ability of a base class to act as an
inherited class at run time. When you get to inventory you can have a backpack of BaseItem instead of
all of the different item types for the items the player/party is carrying. In the Clone method you see
that I don't need to even pass in null as a parameter to the constructor. Just the values that I'm interested
in.
The last thing I'm going to do in this tutorial is update the ItemDataManager class. What I'm going to
do is add in a field for the ReagentData objects and a property to expose it. Change that class to the
following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ItemDataManager
{
#region Field Region
readonly Dictionary<string,
readonly Dictionary<string,
ShieldData>();
readonly Dictionary<string,
WeaponData>();
readonly Dictionary<string,
ReagentData>();
#endregion

ArmorData> armorData = new Dictionary<string, ArmorData>();


ShieldData> shieldData = new Dictionary<string,
WeaponData> weaponData = new Dictionary<string,
ReagentData> reagentData = new Dictionary<string,

#region Property Region


public Dictionary<string, ArmorData> ArmorData
{
get { return armorData; }
}
public Dictionary<string, ShieldData> ShieldData
{
get { return shieldData; }
}
public Dictionary<string, WeaponData> WeaponData
{
get { return weaponData; }
}
public Dictionary<string, ReagentData> ReagentData
{
get { return reagentData; }
}
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
}

I'm going to end this tutorial here. The plan was just to add in some place holder classes that will be
needed shortly. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 16
Quests and Conversations
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to get started with adding quests and conversations to the game. I will be
adding in place holders that will be filled in as the game progresses, like I did for skills, spells, and
talents. I will then fill out the Entity class to use the new classes and add in a couple classes to the
XRpgLibrary as well.
To get started, right click the RpgLibrary project, select Add and then New Folder. Name this new
folder QuestClasses. I'm going to add three classes to this folder. For quests I'm going to implement
quests that have multiple steps. So the first class to add is a class for the steps. Right click the
QuestClasses folder, select Add and then Class. Name this new class QuestStep. For now this is just a
place holder. Later on down the road it will be added to. This is the code for that class so far.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.QuestClasses
{
public class QuestStep
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

As I mentioned, it is a place holder class that will be filled over time. It is just needed for a few things
and I get started with them now. You will want to right click the QuestClasses folder again, select Add
and then Class. Name this new class Quest. The code for the class is the same, just a bare class split up
with regions.

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.QuestClasses
{
public class Quest
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

As will all of the other classes it is a good idea to have a class to manage quests in the game, especially
for the editor. Right click the QuestClasses folder, select Add and then Class. Name this new class
QuestManager. There is actually something to this class. It should look familiar as I've used the same
format several times now.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.QuestClasses
{
public class QuestManager
{
#region Field Region
readonly Dictionary<string, Quest> quests;
#endregion
#region Property Region
public Dictionary<string, Quest> Quests
{
get { return quests; }
}
#endregion
#region Constructor Region
public QuestManager()
{
quests = new Dictionary<string, Quest>();
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

The next set of classes that I'm going to add are for conversations. I like the approach that Jim Perry
took in his book, RPG Programming using XNA Game Studio 3.0, for working with conversations
using nodes. In my XNA 3.0 RPG tutorials I used a system similar to the one that Nick Gravlyn used in
his tile engine video tutorials. I'm trying to reduce the complexity of handling conversations.
I'm going to give the instructions for adding all the classes, then the code. Right click the RpgLibrary,
select Add and then New Folder. Name this new folder ConversationClasses. Right click that folder,
select Add and then Class. Name this new class ConversationNode. Right click the folder, select Add
and then Class. Name the class Conversation. Right click the folder again, select Add and then Class.
Name this new class ConversationManager. The code for all three classes follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ConversationClasses
{
public class ConversationNode
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ConversationClasses
{
public class Conversation
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion

}
}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ConversationClasses
{
public class ConversationManager
{
#region Field Region
readonly Dictionary<string, Conversation> conversations;
#endregion
#region Property Region
public Dictionary<string, Conversation> Conversations
{
get { return conversations; }
}
#endregion
#region Constructor Region
public ConversationManager()
{
conversations = new Dictionary<string, Conversation>();
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

Nothing really new there either. It is all code that you've seen before. Now I'm going to get in to some
new stuff. The first thing I want to do is to update the Entity class. What I want to do is to add fields
and properties for skills, spells, and talents. I added in new regions to the Entity class. I added in a
Skill Field and Property region, a Spell Field and Property region, and a Talent Field and Property
region. To these regions I added a field for each of the items and a field for modifiers of the items. Add
the following regions to the Entity class.
#region Skill Field and Property Region
readonly Dictionary<string, Skill> skills;
readonly List<Modifier> skillModifiers;
public Dictionary<string, Skill> Skills
{
get { return skills; }
}
public List<Modifier> SkillModifiers
{
get { return skillModifiers; }
}

#endregion
#region Spell Field and Property Region
readonly Dictionary<string, Spell> spells;
readonly List<Modifier> spellModifiers;
public Dictionary<string, Spell> Spells
{
get { return spells; }
}
public List<Modifier> SpellModifiers
{
get { return spellModifiers; }
}
#endregion
#region Talent Field and Property Region
readonly Dictionary<string, Talent> talents;
readonly List<Modifier> talentModifiers;
public Dictionary<string, Talent> Talents
{
get { return talents; }
}
public List<Modifier> TalentModifiers
{
get { return talentModifiers; }
}
#endregion

The next step is to create the fields. I did that in the private constructor for the class. I also made a
change to the public constructor to call the private constructor. You can do that using this like you
would use base to call a base constructor. Change the constructor region of the Entity class to the
following.
#region Constructor Region
private Entity()
{
Strength = 10;
Dexterity = 10;
Cunning = 10;
Willpower = 10;
Magic = 10;
Constitution = 10;
health = new AttributePair(0);
stamina = new AttributePair(0);
mana = new AttributePair(0);
skills = new Dictionary<string, Skill>();
spells = new Dictionary<string, Spell>();
talents = new Dictionary<string, Talent>();
skillModifiers = new List<Modifier>();
spellModifiers = new List<Modifier>();
talentModifiers = new List<Modifier>();
}
public Entity(
string name,

EntityData entityData,
EntityGender gender,
EntityType entityType) : this()
{

EntityName = name;
EntityClass = entityData.EntityName;
Gender = gender;
EntityType = entityType;
Strength = entityData.Strength;
Dexterity = entityData.Dexterity;
Cunning = entityData.Cunning;
Willpower = entityData.Willpower;
Magic = entityData.Magic;
Constitution = entityData.Constitution;
health = new AttributePair(0);
stamina = new AttributePair(0);
mana = new AttributePair(0);

#endregion

The private constructor will be called before the public constructor. So the code there will be executed
before the other constructor's code, the one that takes an EntityData parameter.
Another thing that I added to the Entity class was a method to update modifiers, called Update, of
course. Change the Method region of the Entity class to the following. You may have to add it come to
think of it.
#region Method Region
public void Update(TimeSpan elapsedTime)
{
foreach (Modifier modifier in skillModifiers)
modifier.Update(elapsedTime);
foreach (Modifier modifier in spellModifiers)
modifier.Update(elapsedTime);
foreach (Modifier modifier in talentModifiers)
modifier.Update(elapsedTime);
}
#endregion

The Update method takes a TimeSpan parameter that represents the ElapsedGameTime from the
gameTime parameter in the Update method of XNA. In a foreach loop I loop through all of the
modifiers in each of the lists of modifiers. I call their Update methods passing in the elapsedTime
passed to the Update method. You're not going to want a lot of characters at a time as it will bog things
down. Fortunately for the most part modifiers will only be needed by party members and in combat.
Since I moved to using a transformation matrix for the tile engine to control where things are rendered
you no longer need to pass a Camera object to the Draw method of the AnimatedSprite class. Change
the Draw method of the AnimatedSprite class to the following.
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
spriteBatch.Draw(
texture, position,
animations[currentAnimation].CurrentFrameRect,
Color.White);
}

You will also need to update the Draw method of the Player class. You can change that method to the
following.
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
sprite.Draw(gameTime, spriteBatch);
}

I want to add a new folder to the XRpgLibrary project classes that are related to characters. Right
click the XRpgLibrary, select Add and then New Folder. Name this new folder CharacterClasses. To
this folder I'm going to add two classes. Right click the CharacterClasses folder in the XRpgLibrary
select Add, and then Class. Name this new class Character. A Character will be a character in the
game that doesn't have a conversation or quest associated with them. They can also be monsters or shop
keepers. This is the code for that class.
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.CharacterClasses;
XRpgLibrary.SpriteClasses;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.CharacterClasses
{
public class Character
{
#region Field Region
protected Entity entity;
protected AnimatedSprite sprite;
#endregion
#region Property Region
public Entity Entity
{
get { return entity; }
}
public AnimatedSprite Sprite
{
get { return sprite; }
}
#endregion
#region Constructor Region
public Character(Entity entity, AnimatedSprite sprite)
{
this.entity = entity;
this.sprite = sprite;
}
#endregion
#region Method Region
#endregion
#region Virtual Method region

public virtual void Update(GameTime gameTime)


{
entity.Update(gameTime.ElapsedGameTime);
sprite.Update(gameTime);
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
sprite.Draw(gameTime, spriteBatch);
}
#endregion
}

I added a couple using statements to bring some of the XNA framework classes into scope. As well as
the CharacterClasses from the RpgLibrary and the SpriteClasses of the XRpgLibrary. There are
two protected fields, Entity and AnimatedSprite. As you can guess the Entity field is the entity for the
character and the AnimatedSprite is the sprite for the character. There are two public properties to
expose their values that are read only. The constructor for this class takes two parameters. The first is
an Entity object and the second is an AnimatedSprite object. I also added in two virtual methods that
can be overridden in a class that inherits from Character. The Update method takes a GameTime
parameter. It calls the Update method of the entity field passing in the ElapsedGameTime property. It
also calls the Update method of the sprite field. The Draw method takes as parameters a GameTime
parameter and a SpriteBatch parameter. The Draw method calls the Draw method of the sprite field.
The other class I want to add will be called NonPlayerCharacter. It is a bit of a misnomer as the other
class is for NPCs. These are just special NPCs that have conversations and quests associated with them.
Right click the CharacterClasses folder of the XRpgLibrary project, select Add and then Class.
Name this new class NonPlayerCharacter. This is the code for that class so far.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using XRpgLibrary.SpriteClasses;
using RpgLibrary.CharacterClasses;
using RpgLibrary.ConversationClasses;
using RpgLibrary.QuestClasses;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.CharacterClasses
{
public class NonPlayerCharacter : Character
{
#region Field Region
readonly List<Conversation> conversations;
readonly List<Quest> quests;
#endregion
#region Property Region
public List<Conversation> Conversations
{
get { return conversations; }
}

public List<Quest> Quests


{
get { return quests; }
}
public bool HasConversation
{
get { return (conversations.Count > 0); }
}
public bool HasQuest
{
get { return (quests.Count > 0); }
}
#endregion
#region Constructor Region
public NonPlayerCharacter(Entity entity, AnimatedSprite sprite)
: base(entity, sprite)
{
conversations = new List<Conversation>();
quests = new List<Quest>();
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
base.Draw(gameTime, spriteBatch);
}
#endregion
}

There are extra using statements to bring some classes into scope for this class. There are two new
fields in this class. The first is a List<Conversation> called conversations that holds the conversations
related to the NPC. The second is a List<Quest> called quests that holds the quests associated with the
NPC. They are readonly properties so they can be assigned to as an initializer in a class or in the
constructor of the class. That just keeps them from being changed unintentionally. There are get only
properties to expose them to other classes. There are two other properties that are associated with an
NPC. They are HasConversation and HasQuest that return Boolean values. To determine their values
I compare the Count property of the associated list with 0. If it is greater than 0 then the NPC has a
conversation or quest associated with it. The class inherits from Character so the constructor requires
an Entity and an AnimatedSprite for parameter to pass to the base class Character. In the constructor
I initialize the conversations and quests fields. I went a head and added in overrides to the Update and
Draw methods of the base class. All they do right now is call the base Update and Draw methods.
The last thing I want to do is add characters to the Level class. I'm also going to update the Draw
method to take a GameTime parameter as it is needed in the Draw method of the Character class.

Change the Level class to the following.


using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using XRpgLibrary.TileEngine;
using XRpgLibrary.CharacterClasses;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.WorldClasses
{
public class Level
{
#region Field Region
readonly TileMap map;
readonly List<Character> characters;
#endregion
#region Property Region
public TileMap Map
{
get { return map; }
}
public List<Character> Characters
{
get { return characters; }
}
#endregion
#region Constructor Region
public Level(TileMap tileMap)
{
map = tileMap;
characters = new List<Character>();
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
foreach (Character character in characters)
character.Update(gameTime);
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera)
{
map.Draw(spriteBatch, camera);
foreach (Character character in characters)
character.Draw(gameTime, spriteBatch);
}
}

#endregion

The first thing I did was add a using statement to bring the Character class into scope for this class. I

then added a readonly field, characters, that is a List<Character>. It maybe a good idea to use a
Dictionary<string, Character> instead and I may change that. I also added in a get only property to
expose the characters field. The constructor create a new List<Character> for the level. In the
Update method I do something that isn't the most efficient thing in the world and it will be updated
later on down the road. I loop through all of the characters in the characters field and call their Update
methods. This isn't very efficient but for now it is okay. It wouldn't be a bad thing if you don't have a lot
of characters on a level. You will want to balance your levels so that you aren't bogging down your
game with a lot of entities, or game objects is a better term, and updating them needlessly. I'm also
drawing all of the characters in the level. You would more than likely only want to draw characters that
are visible. That is another bridge we can cross when we get to it.
The last thing we need to do is to update the DrawLevel method of the World class and the Draw
method of the GamePlayScreen because I added a GameTime parameter to the Draw method of the
Level class. Change the DrawLevel method of the World class to the following.
public void DrawLevel(GameTime gameTime, SpriteBatch spriteBatch, Camera camera)
{
levels[currentLevel].Draw(gameTime, spriteBatch, camera);
}

Finally, change the Draw method of the GamePlayScreen to the following.


public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
player.Camera.Transformation);
base.Draw(gameTime);
world.DrawLevel(gameTime, GameRef.SpriteBatch, player.Camera);
player.Draw(gameTime, GameRef.SpriteBatch);
GameRef.SpriteBatch.End();
}

I'm going to end this tutorial here. The plan was just to add in some place holder classes that will be
needed shortly. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 17
Finding Loot
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This tutorial is going to cover adding in loot for the player to find and pick up be it in chests, boxes,
barrels, whatever. After I handle the combat engine I will add in mobs, any enemy the player kills,
dropping items.
The first thing we are going to need is a basic sprite class that we can use to draw sprites with. So, I
added a class called BaseSprite to the SpriteClasses folder in the XrprLibrary project. Right click
the SpriteClasses folder, select Add and then Class. Call the class BaseSprite. This is the code.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XRpgLibrary.TileEngine;
namespace XRpgLibrary.SpriteClasses
{
public class BaseSprite
{
#region Field Region
protected Texture2D texture;
protected Rectangle sourceRectangle;
protected Vector2 position;
protected Vector2 velocity;
protected float speed = 2.0f;
#endregion
#region Property Region
public int Width
{
get { return sourceRectangle.Width; }
}
public int Height
{
get { return sourceRectangle.Height; }
}
public Rectangle Rectangle
{

get
{

return new Rectangle(


(int)position.X,
(int)position.Y,
Width,
Height);

public float Speed


{
get { return speed; }
set { speed = MathHelper.Clamp(speed, 1.0f, 16.0f); }
}
public Vector2 Position
{
get { return position; }
set
{
position = value;
}
}
public Vector2 Velocity
{
get { return velocity; }
set
{
velocity = value;
if (velocity != Vector2.Zero)
velocity.Normalize();
}
}
#endregion
#region Constructor Region
public BaseSprite(Texture2D image, Rectangle? sourceRectangle)
{
this.texture = image;
if (sourceRectangle.HasValue)
this.sourceRectangle = sourceRectangle.Value;
else
this.sourceRectangle = new Rectangle(
0,
0,
image.Width,
image.Height);
this.position = Vector2.Zero;
this.velocity = Vector2.Zero;
}
public BaseSprite(Texture2D image, Rectangle? sourceRectangle, Point tile)
: this(image, sourceRectangle)
{
this.position = new Vector2(
tile.X * Engine.TileWidth,
tile.Y * Engine.TileHeight);
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


public virtual void Update(GameTime gameTime)
{
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
spriteBatch.Draw(
texture,
Position,
sourceRectangle,
Color.White);
}
}

#endregion

Compared to some of the classes in the game it is pretty straight forward. The fields came from the
AnimatedSprite class. They are included as down the road they could be useful. I added a few using
statements to bring some of the XNA framework classes into scope, as well as the TileEngine classes.
When you create a sprite you can specify what tile it is in, rather than saying it is at pixel (x,y) in the
map. To do that I need the Engine class.
There are a number of protected fields in the class. They are protected because down the road you
might want a special kind of sprite. The fields are a Texture2D for the sprite. A Rectangle that is a
source rectangle, if you want to use sprite sheets. There are Vector2 fields for the position of the sprite
and the velocity of the sprite. For items sprites will remain in the same place. But, you could have a
sprite that bobs up and down and including a velocity was a good idea. There is also a float field for the
speed of the sprite.
There are a few properties that expose some information about the sprite. The Width and Height
properties return the Width and Height properties of the sourceRectangle field. The Speed, Position,
and Velocity properties were taken from the AnimatedSprite class.
I added in two constructors for this class. The first takes a Texture2D for the sprite and a Rectangle for
the source rectangle of the sprite. The Rectangle parameter is a nullable parameter, indicated by the ?
after Rectangle. So, if you don't want to specify a source rectangle you can pass in null for that
parameter and the class will use the entire image. The second constructor takes a third parameter, a
Point that represents the tile the sprite is in.
The first constructor sets the texture field with the image parameter. It checks to see if the Rectangle
parameter has a value using the HasValue property. If it does I set the sourceRectangle field to be the
sourceRectangle parameter. If it doesn't have a value I create a new Rectangle using the width and
height of the image passed in. I then set the position and velocity fields to be the Zero vector.
The second constructor calls the first constructor using this to reference that constructor with the image
and sourceRectangle parameters passed in. To set the position of the sprite, in tiles, I multiply the X
property of the Point by the TileWidth and the the Y property of the Point by the TileHeight from the
Engine class. What is important here is that you create sprites after creating an instance of the Engine
class. Otherwise the TileWidth and TileHeight will both be 0 and the sprites will all line up in the top
left corner of the map. You are not going to have to worry about it as when the GamePlayScreen is
created an Engine class is constructed. Just be aware that limitation is there.

I'm going to add a class that holds a BaseItem from the RpgLibrary and has a BaseSprite as well.
This will allow you to have icons for your items. This will be helpful when I get to inventory and you
can have icons on the screen in your game, for a chest for example. This is a hybrid class and is a cross
between an item and a sprite. I decided the best course of action with to create an ItemClasses folder in
the XRpgLibrary to represent items like this. Right click the XrpgLibrary, select Add and then New
Folder. Name this new folder ItemClasses. Right click the ItemClasses folder, select Add and then
Class. Name this new class ItemSprite. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RpgLibrary.ItemClasses;
using XRpgLibrary.SpriteClasses;
namespace XRpgLibrary.ItemClasses
{
public class ItemSprite
{
#region Field Region
BaseSprite sprite;
BaseItem item;
#endregion
#region Property Region
public BaseSprite Sprite
{
get { return sprite; }
}
public BaseItem Item
{
get { return item; }
}
#endregion
#region Constructor Region
public ItemSprite(BaseItem item, BaseSprite sprite)
{
this.item = item;
this.sprite = sprite;
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public virtual void Update(GameTime gameTime)
{
sprite.Update(gameTime);
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{

sprite.Draw(gameTime, spriteBatch);
}
}

#endregion

This is another straight forward class. There are using statements to bring a few name space into scope.
There are two fields and two properties to expose the fields. The first field is a BaseSprite and the
second is a BaseItem. The property Sprite exposes the sprite field and the property Item exposes the
item field. The constructor takes two parameters. A BaseItem for the item and a BaseSprite for the
sprite. It just sets the fields to the values passed in. I added Update and Draw methods that are virtual
so you can create classes that inherit from this one and override those properties if needed. They have
the same parameters as the BaseSprite class. They just call the Update and Draw methods of the
sprite field.
Before I get to chests I thought I'd add in a couple place holder classes that will be needed for chests.
What fun is having a chests if you can't trap them! You need to make your thief characters feel useful
after all. To handle that I added in a few place holders for traps. I added them to the RpgLibrary as
they deal more with mechanics than with XNA. Right click the RpgLibrary, select Add and then New
Folder. Name this new folder TrapClasses. To this folder you are going to want to add three empty
classes, I included the regions in mine. Right click the TrapClasses folder three times, selecting Add
and then Class each time. Name the classes Trap, TrapData, and TrapManager. The code for my
classes follows next in that order.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TrapClasses
{
public class Trap
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TrapClasses
{
public class TrapData
{
#region Field Region
#endregion

#region Property Region


#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TrapClasses
{
public class TrapManager
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method region


#endregion

Containers that the player can interact with that hold items, and gold, are all going to be lumped into
one class, a Chest class. The containers can be anything you can imagine though and you're only
limited by the graphics you have. To get started, right click the ItemClasses in the RpgLibrary, select
Add and then Class. Name this class ChestData. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ChestData
{
public string Name;
public string TextureName;
public bool IsTrapped;
public bool IsLocked;
public string TrapName;
public string KeyName;
public int MinGold;
public int MaxGold;
public Dictionary<string, string> ItemCollection;
public ChestData()
{

ItemCollection = new Dictionary<string, string>();


}
public override string ToString()
{
string toString = Name + ", ";
toString += TextureName + ", ";
toString += IsTrapped.ToString() + ", ";
toString += IsLocked.ToString() + ", ";
toString += TrapName + ", ";
toString += KeyName + ", ";
toString += MinGold.ToString() + ", ";
toString += MaxGold.ToString();
foreach (KeyValuePair<string, string> pair in ItemCollection)
{
toString += ", " + pair.Key + "+" + pair.Value;
}
}
}

return toString;

This is a data class that will be used to generate chests on the fly and in the editor. There are a number
of fields. The Name field is the name of the chest. The TextureName field is the name of the texture
for the chest. The Boolean fields, IsTrapped and IsLocked, tell if a chest is trapped or if it locked. The
TrapName and KeyName are used if the chest has a trap and requires a special key to open it. If they
are set to none then there is no trap and a special key isn't needed to open the chest. The MinGold and
MaxGold fields represent the minimum and maximum gold a chest can hold. If both are set to zero
then the chest holds no gold. The ItemCollection field is a Dictionary<string, string> that holds the
items in the chest. The first string is the name of the item and the second string is type of the item.
There is a limitation here though. A chest can't hold more than one of any particular item. It is good
enough for right now though.
Like the other data classes for items there is an override of the ToString method. I append the fields,
plus a comma and a space, using the ToString method if needed to convert a field to a string. The
ItemCollection is the interesting item here though. In a foreach loop I loop through all of the
KeyValuePairs in the dictionary. I append a comma and a space and for the pair I append the Key a +
and the Value. This does mean that your item names can't use a + sign. If that is an issue, say you want
a longsword +5, you can change to + to another symbol that you don't use. It then returns the string that
was created.
I'm also going to add a basic class for chests. It will need to be fleshed out more but it will be enough to
get chests into the game. Right click the ItemClasses folder in the RpgLibrary, select Add and then
Class. Name this new class Chest. This is the code so far.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.TrapClasses;

namespace RpgLibrary.ItemClasses
{
public class Chest : BaseItem
{
#region Field Region
static Random Random = new Random();

ChestData chestData;
#endregion
#region Property Region
public bool IsLocked
{
get { return chestData.IsLocked; }
}
public bool IsTrapped
{
get { return chestData.IsTrapped; }
}
public int Gold
{
get
{
if (chestData.MinGold == 0 && chestData.MaxGold == 0)
return 0;
int gold = Random.Next(chestData.MinGold, chestData.MaxGold);
chestData.MinGold = 0;
chestData.MaxGold = 0;
}

return gold;

}
#endregion
#region Constructor Region
public Chest(ChestData data)
: base(data.Name, "", 0, 0)
{
this.chestData = data;
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public override object Clone()
{
ChestData data = new ChestData();
data.Name = chestData.Name;
data.IsLocked = chestData.IsLocked;
data.IsTrapped = chestData.IsTrapped;
data.TextureName = chestData.TextureName;
data.TrapName = chestData.TrapName;
data.KeyName = chestData.KeyName;
data.MinGold = chestData.MinGold;
data.MaxGold = chestData.MaxGold;
foreach (KeyValuePair<string, string> pair in chestData.ItemCollection)
data.ItemCollection.Add(pair.Key, pair.Value);
Chest chest = new Chest(data);
return chest;
}
}
}

#endregion

There is one using statement that I added for the TrapClasses in the RpgLibrary. It's not used right
now but it will be required. There is a static field that is a Random object. It will be used to generate
the gold that a chest contains. There is a ChestData field that holds the ChestData associated with the
chest. The IsLocked property exposes the IsLocked field in the ChestData object. The IsTrapped
property works with the IsTrapped field in the ChestData object. The Gold property is interesting. It
checks to see if MinGold and MaxGold are both 0 and if they the property returns 0. It then generates
a random number between MinGold and MaxGold. They are then set to be 0 and the number
generated is returned. What that boils down to is that the chest will remain after it is opened, in case the
player can't carry all of the items for example, and if the player goes back to the chest and opens it
again they won't get gold from it multiple times. That would quickly make your most expensive items
in a shop purchasable and blow your game balance out of the water.
The constructor of the Chest class takes a ChestData parameter. For the call to the constructor of the
base class it passes in the Name field, an empty string for the type, and 0 for the price and weight. It
then sets the ChestData field.
The Clone method first creates a new ChestData object. It then assigns the fields from the field. To
handle the Dictionary<string, string> I loop through all of the KeyValuePair<string, string> in the
collection. I then add a new item to the ItemCollection of the new ChestData object. I then create a
new chest using the new ChestData object and return it. It might not be necessary to create a new
ChestData object but it is good programming practise to reduce side effects. I say that because
ChestData is a class and that makes it a reference type. Passing around references directly you can
inadvertently change the original reference and that would be a hard bug to track down. When you are
passing around reference types and you think the original could be changed it is always better to pass a
copy.
To actually create chests you will really want to update the editor, and add a class for keys. I'm going to
go a head and add chests to the Level class though. You are going to need a few graphics for chests and
other containers then. I created a PNG file from a tile set off the web that has a chest in it. I will add
other containers as I find them and when I use them in future tutorials I'll link to the graphics. You can
find my chest at http://xnagpa.net/xna4/downloads/containers1.zip. Download and extract the files.
Right click the the EyesOfTheDragonContent project, select Add and then New Folder. Name this
new folder ObjectSprites. To this folder you want to add the containers.png file. Right click the
ObjectSprites folder, select Add and then Existing Item. Navigate to the containers.png file and
select it.
Chests are tied to a specific map, and maps are tied to specific levels. So chests, like characters, belong
in the Level class. Update the Level class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using XRpgLibrary.TileEngine;
using XRpgLibrary.CharacterClasses;
using XRpgLibrary.ItemClasses;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.WorldClasses

{
public class Level
{
#region Field Region
readonly TileMap map;
readonly List<Character> characters;
readonly List<ItemSprite> chests;
#endregion
#region Property Region
public TileMap Map
{
get { return map; }
}
public List<Character> Characters
{
get { return characters; }
}
public List<ItemSprite> Chests
{
get { return chests; }
}
#endregion
#region Constructor Region
public Level(TileMap tileMap)
{
map = tileMap;
characters = new List<Character>();
chests = new List<ItemSprite>();
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
foreach (Character character in characters)
character.Update(gameTime);

foreach (ItemSprite sprite in chests)


sprite.Update(gameTime);

public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera)


{
map.Draw(spriteBatch, camera);
foreach (Character character in characters)
character.Draw(gameTime, spriteBatch);
foreach (ItemSprite sprite in chests)
sprite.Draw(gameTime, spriteBatch);
}
}

#endregion

The first new thing is that there is a using statement bring the ItemClasses of the XRpgLibrary into
scope. I added a List<ItemSprite> called chests to hold the chests in a level. It is readonly so it can't

be assigned to outside of the constructor or as an initializer in the class. In the constructor I create the
List<ItemSprite>. In the Update method I loop through all of the ItemSprite objects in the list of
ItemSprites and call their Update method passing in the GameTime parameter. I do the same in the
Draw method, except I Draw instead of Update, passing in the SpriteBatch parameter as well.
The last thing I want to do in this tutorial is to actually have a chest show up. I thought the best place to
do that would be in the CreateWorld method of the CharacterGeneratorScreen. You could also
make the same changes to the LoadGameScreen but I'm not going to do that officially. The first thing
you are going to want to do is to add using statements for both ItemClasses name spaces of the
libraries. Add these two using statements to the CharacterGeneratorScreen.
using XRpgLibrary.ItemClasses;
using RpgLibrary.ItemClasses;

It would be a good idea to load the texture for containers in the LoadContent method. Add the
following field and change the LoadContent method to the following.
Texture2D containers;
protected override void LoadContent()
{
base.LoadContent();
LoadImages();
CreateControls();
containers = Game.Content.Load<Texture2D>(@"ObjectSprites\containers");
}

Now, in the CreateWorld method you want to create a chest and added it to the list of chests for the
level. Change the CreateWorld method to the following.
private void CreateWorld()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

MapLayer splatter = new MapLayer(100, 100);


Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);

int y = random.Next(0, 100);


int index = random.Next(2, 14);
Tile tile = new Tile(index, 0);
splatter.SetTile(x, y, tile);
}
splatter.SetTile(1, 0, new Tile(0, 1));
splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
TileMap map = new TileMap(tilesets, mapLayers);
Level level = new Level(map);
ChestData chestData = new ChestData();
chestData.Name = "Some Chest";
chestData.MinGold = 10;
chestData.MaxGold = 101;
Chest chest = new Chest(chestData);
BaseSprite chestSprite = new BaseSprite(
containers,
new Rectangle(0, 0, 32, 32),
new Point(10, 10));
ItemSprite itemSprite = new ItemSprite(
chest,
chestSprite);
level.Chests.Add(itemSprite);
World world = new World(GameRef, GameRef.ScreenRectangle);
world.Levels.Add(level);
world.CurrentLevel = 0;
}

GamePlayScreen.World = world;

The new code starts just after creating the Level object. What I did was create a ChestData object and
set the Name property to Some Chest and MinGold to 10 and MaxGold to 101. I then created a
new Chest object. The next step was to create a BaseSprite for the chest. For the parameters to the
constructor I use the containers Textur2D for, a Rectangle with 0 for X and Y, and 32 for the width
and height. I also placed it at tile (10, 10). I then create an ItemSprite passing in the chest I created and
the base sprite. It is then added to the Chests field of the Level.
So, if you build and run your game and start a new game you will see a chest! The player isn't
interacting with the chest but it is a start.
I'm going to end this tutorial here. The plan was just to add in some place holder classes that will be
needed shortly. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 18A
Finding Loot Part 2
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In the last tutorial I worked on adding chests for the player to interact with to find loot. In this tutorial I
going to continue on with that. The first thing I want to do is to add in two classes for keys for
unlocking locks. Like you can find on chests, doors, and other objects. Keys are just another type of
item so they belong in the ItemClasses folder of the RpgLibrary. Like other types of items you will
want a data class for use in editors and reading in data and a class for the keys themselves that inherits
from the BaseItem class. Right click the ItemClasses folder, select Add and then Class. Name this
new class KeyData. Repeat the process and name the new class Key. The code for both of those
classes follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class KeyData
{
#region Field Region
public string Name;
public string Type;
#endregion
#region Constructor Region
public KeyData()
{
}
#endregion
#region Method Region
public override string ToString()
{
string toString = Name + ", ";
toString += Type;
}

return toString;

#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class Key : BaseItem
{
#region Field region
#endregion
#region Property Region
#endregion
#region Constructor Region
public Key(string name, string type)
: base(name, type, 0, 0, null)
{
}
#endregion
#region Virtual Method Region
public override object Clone()
{
Key key = new Key(this.Name, this.Type);
return key;
}
}

#endregion

Not much you haven't seen before. The KeyData class has two public fields. The Name of the key and
the Type of the key. I debated about including other fields like a price and a weight. I didn't think they
were necessary. The Type field can be of great use. If you've played the Fable games they have special
silver keys that can be used to open special chests. Each chest requires a certain number of these keys.
Using the Type field you can easily add this functionality into your games. It will require a minor
tweak that I will make later. The ToString method of the KeyData class just combines the name of the
key and the type. The Key class is also simplistic. It inherits from the BaseItem class. It takes just two
string parameters for the name and the type. In the call to the base constructor I pass in the parameters
passed to the Key class and 0 for the price and weight and null for allowableClasses. The Clone
method just creates a new key and returns it.
The first tweak for handling multiple keys of the same type is to add in two fields to the ChestData
class. They are KeyType which is a string and KeysRequired which is an integer. I'm going to also
associate a DifficultyLevel with a chest from the SkillClasses folder for the lock on the chest. In the
ToString method you add in these three new fields, calling their ToString methods. I also removed the
TextureName field from the class. I will be handling associating an image with a chest when I get to
the level editor as it applies more to levels. Change the ChestData method to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using RpgLibrary.SkillClasses;

namespace RpgLibrary.ItemClasses
{
public class ChestData
{
public string Name;
public DifficultyLevel DifficultyLevel;
public bool IsLocked;
public bool IsTrapped;
public string TrapName;
public string KeyName;
public string KeyType;
public int KeysRequired;
public int MinGold;
public int MaxGold;
public Dictionary<string, string> ItemCollection;
public ChestData()
{
ItemCollection = new Dictionary<string, string>();
}
public override string ToString()
{
string toString = Name + ", ";
toString += DifficultyLevel.ToString() + ", ";
toString += IsLocked.ToString() + ", ";
toString += IsTrapped.ToString() + ", ";
toString += TrapName + ", ";
toString += KeyName + ", ";
toString += KeyType + ", ";
toString += KeysRequired.ToString() + ", ";
toString += MinGold.ToString() + ", ";
toString += MaxGold.ToString();
foreach (KeyValuePair<string, string> pair in ItemCollection)
{
toString += ", " + pair.Key + "+" + pair.Value;
}
}
}

return toString;

You also need update the Chest class, specifically the Clone method. Change the Clone of the Chest
class to the following.
public override object Clone()
{
ChestData data = new ChestData();
data.Name = chestData.Name;
data.DifficultyLevel = chestData.DifficultyLevel;
data.IsLocked = chestData.IsLocked;
data.IsTrapped = chestData.IsTrapped;
data.TrapName = chestData.TrapName;
data.KeyName = chestData.KeyName;
data.KeyType = chestData.KeyType;
data.KeysRequired = chestData.KeysRequired;
data.MinGold = chestData.MinGold;
data.MaxGold = chestData.MaxGold;
foreach (KeyValuePair<string, string> pair in chestData.ItemCollection)
data.ItemCollection.Add(pair.Key, pair.Value);

Chest chest = new Chest(data);


return chest;

I will handle opening chests with keys in a future tutorial. I just wanted to add in the functionality to
this tutorial so it will be available later when it is needed. I'm going to work on the editor a bit on
creating keys and chests. To do that I'm first going to update the ItemDataManager class to hold
dictionaries of <string, ItemDataType> where ItemDataType is the data class associated with the
item. Change the ItemDataManager to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.ItemClasses
{
public class ItemDataManager
{
#region Field Region
readonly
readonly
ShieldData>();
readonly
WeaponData>();
readonly
ReagentData>();
readonly
readonly

Dictionary<string, ArmorData> armorData = new Dictionary<string, ArmorData>();


Dictionary<string, ShieldData> shieldData = new Dictionary<string,
Dictionary<string, WeaponData> weaponData = new Dictionary<string,
Dictionary<string, ReagentData> reagentData = new Dictionary<string,
Dictionary<string, KeyData> keyData = new Dictionary<string, KeyData>();
Dictionary<string, ChestData> chestData = new Dictionary<string, ChestData>();

#endregion
#region Property Region
public Dictionary<string, ArmorData> ArmorData
{
get { return armorData; }
}
public Dictionary<string, ShieldData> ShieldData
{
get { return shieldData; }
}
public Dictionary<string, WeaponData> WeaponData
{
get { return weaponData; }
}
public Dictionary<string, ReagentData> ReagentData
{
get { return reagentData; }
}
public Dictionary<string, KeyData> KeyData
{
get { return keyData; }
}
public Dictionary<string, ChestData> ChestData
{
get { return chestData; }
}
#endregion
#region Constructor Region
#endregion
#region Method Region

#endregion
}

Nothing there that you haven't seen before. Build your project to make sure it builds correctly. Right
click the RpgEditor project in the solution explorer, select Add and then Windows Form. Name this
new form FormKey. Set the Size property of the form to be the Size property of your FormDetails.
Set the MinimizeBox property to false and the Text property to Keys. You will now want to have
FormKey inherit from FormDetails instead of Form. Right click FormKey in the solution explorer
and select View Code. Change the code to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormKey : FormDetails
{
public FormKey()
{
InitializeComponent();
}
}
}

I'm going to add another form before I get to the code for FormKey. Right click RpgEditor in the
solution explorer, select Add and then Windows Form. Name this new form FormKeyDetails. I'm
going to add a couple controls and set some properties for FormKeyDetails. Your finished form in the
designer should resemble the one below.

Start by changing the size of the form so it is a bigger than what you need. Drag a Label onto the form
near the top then a Text Box and position it to the right of the Label. Drag another Label onto the form
positioning it below the first. Drag a second Text Box onto the form and position it below the first Text
Box. Now drag two Buttons onto the form. Position the first to the left and the second to the right. Set
the Text property of the form to Key Details, the StartPosition to CenterParent, the ControlBox to
False, and the FormBorderStyle to FixedDialog. Set the Name property of the first Text Box to
tbName and the second to tbType. Set the Text property of the first Label to Name: and the second to
Type:. For the buttons the one of the left has Name and Text properties of btnOK and OK. The second
btnCancel and Cancel for the Name and Text properties.
Now lets add the logic to FormKeyDetails. Right click FormKeyDetails in the solution explorer and

select View Code. Change the code to the following.


using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormKeyDetails : Form
{
#region Field Region
KeyData key;
#endregion
#region Property Region
public KeyData Key
{
get { return key; }
set { key = value; }
}
#endregion
#region Constructor Region
public FormKeyDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormKeyDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormKeyDetails_FormClosing);

btnOK.Click += new EventHandler(btnOK_Click);


btnCancel.Click += new EventHandler(btnCancel_Click);

#endregion
#region Event Handler Region
void FormKeyDetails_Load(object sender, EventArgs e)
{
if (key != null)
{
tbName.Text = key.Name;
tbType.Text = key.Type;
}
}
void FormKeyDetails_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
void btnOK_Click(object sender, EventArgs e)
{

if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must enter a name for the item.");
return;
}
key = new KeyData();
key.Name = tbName.Text;
key.Type = tbType.Text;

this.FormClosing -= FormKeyDetails_FormClosing;
this.Close();

void btnCancel_Click(object sender, EventArgs e)


{
key = null;
this.FormClosing -= FormKeyDetails_FormClosing;
this.Close();
}
}

#endregion

This should look familiar to you. It follows the other detail forms that I created. There is a using
statement to bring the ItemClasses name space from the RpgLibrary into scope. There is a field of
type KeyData for the key that is being created or edited. If the key is being edited you can use the
property to set the fields of the key. The constructor wires handlers for the Load and FormClosing
events of the form. It also wires handlers for the Click event of btnOK and btnCancel.
The event handler for the Load event of the form checks to see if the field key is not null. If it isn't it
sets the Text properties of tbName and tbType to be the Name and Type fields of key. The event
handler for FormClosing is the same as before. If the reason for closing the form is the user is trying to
close the form the event is canceled.
In the Click event handler for btnOK I check to see if the Text property of tbName is null or empty. If
it is I display a message box stating that you need to give the key a name and then exit the method. I'm
not enforcing that a key have a type associated with it. If you want to add the functionality in you can
do the same as I did for tbName with tbType. If tbName had a value I assign the key field a new
instance of KeyData. I then set the Name and Type fields to be the Text property of tbName and
tbType respectively. To allow the form to close I unsubscribe the FormClosing event handler and call
the Close method to close the form.
The Click event handler for btnCancel sets the key field to null. It then unsubscribes the FormClosing
event and calls the Close method of the form. Just like in the other forms for a specific type of item.
Time to code the logic for FormKey. Right click FormKey in the solution explorer and select View
Code. Change the code for FormKey to the following.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormKey : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormKey()
{
InitializeComponent();
btnAdd.Click += new EventHandler(btnAdd_Click);
btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);
}
#endregion
#region Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormKeyDetails frmKeyDetails = new FormKeyDetails())
{
frmKeyDetails.ShowDialog();
if (frmKeyDetails.Key != null)
{
AddKey(frmKeyDetails.Key);
}
}

void btnEdit_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
KeyData data = itemManager.KeyData[entity];
KeyData newData = null;
using (FormKeyDetails frmKeyData = new FormKeyDetails())
{
frmKeyData.Key = data;
frmKeyData.ShowDialog();
if (frmKeyData.Key == null)
return;
if (frmKeyData.Key.Name == entity)
{
itemManager.KeyData[entity] = frmKeyData.Key;
FillListBox();
return;
}
newData = frmKeyData.Key;
}

DialogResult result = MessageBox.Show(


"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (itemManager.KeyData.ContainsKey(newData.Name))
{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}
lbDetails.Items.Add(newData);
itemManager.KeyData.Add(newData.Name, newData);
}

void btnDelete_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.KeyData.Remove(entity);

}
}

if (File.Exists(FormMain.ItemPath + @"\Key\" + entity + ".xml"))


File.Delete(FormMain.ItemPath + @"\Key\" + entity + ".xml");

#endregion
#region Method Region
public void FillListBox()
{
lbDetails.Items.Clear();

foreach (string s in FormDetails.ItemManager.KeyData.Keys)


lbDetails.Items.Add(FormDetails.ItemManager.KeyData[s]);

private void AddKey(KeyData keyData)


{
if (FormDetails.ItemManager.KeyData.ContainsKey(keyData.Name))
{
DialogResult result = MessageBox.Show(
keyData.Name + " already exists. Overwrite it?",
"Existing key",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
itemManager.KeyData[keyData.Name] = keyData;
FillListBox();

return;
}
itemManager.KeyData.Add(keyData.Name, keyData);
lbDetails.Items.Add(keyData);
}
}

#endregion

Again, this should look familiar as other forms use the same code. The difference is that instead of
working with armor, shields, or weapons, I'm working with keys. There is the using statement for the
System.IO name space. Event handlers for the click event of the buttons are wired in the constructors.
The Click event handler of btnAdd creates a form in a using block. I show the form. If the Key
property of the form is not null I call the AddKey method passing in the Key property. The Click event
handler of btnEdit checks to see if the SelectedItem of lbDetails is not null. It parses the string to get
the name of the key. It then gets the KeyData for the selected item and sets newData to null. In a using
statement a form is created. The Key property of the form is set to the KeyData of SelectedItem. I call
the ShowDialog method to display the form. If the Key property of form is null I exit the method. If
the name is the same as before I assign the entry in the item manager to be the new key, call
FillListBox to update the key and exit the method. I then set newData to be the Key property of the
form. The name of the key changed so I display a message box asking if the new key should be added.
If the result is no I exit the method. If there is a key with that name already I display a message box and
exit. If there wasn't I add the new key to the list box and the item manager. The Click event handler for
btnDelete checks to make sure that the SelectedItem of the list box is not null. It parses the selected
item and displays a message box asking if the key should be deleted. If the result of the message box is
Yes I remove the key from the list box and I remove if from the item manager as well. I then delete the
file, if it exists.
I noticed something in my code that really should be fixed. In the code of FormWeapon I have as the
class name Weapons instead of FormWeapon. If you have that problem as well this is a good time to
fix it. Right click FormWeapon in the solution explorer and select View Code. Place your cursor over
Weapons and press F2 to rename it. In the dialog box that pops up replace Weapons with
FormWeapon and press OK. In the second dialog box that pops up just select OK.
Before I get to adding these forms to the editor I want to add in forms that work with ChestData. Right
click RpgEditor in the solution explorer, select Add and then Windows Form. Name this new form
FormChest. Set the Size property of the form to be the Size property of your FormDetails. Set the
MinimizeBox property to false and the Text property to Chests. You will now want to have
FormChest inherit from FormDetails instead of Form. Right click FormChest in the solution
explorer and select View Code. Change the code to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormChest : FormDetails
{

public FormChest()
{
InitializeComponent();
}

You will also want a form for creating chests. Right click the RpgEditor project in the solution
explorer, select Add and then Windows Form. Name this new form FormChestDetails. My finished
form in the designer is next.

I set a few properties for the form. I set ControlBox to False, FormBorderStyle to FixedDialog and
Text to Chest. Set StartPosition to CenterParent as well.
There are a lot of controls on the form and I tried to group them logically. First, make your form a lot
bigger to house all of the controls. The first control I dragged onto the form was a Label and set its
Text property to Chest Name:. I then dragged a Text Box beside the Label and set its Name property
to tbName. I also made the Text Box a little wider. Below the Text Box I dragged a Group Box to
group the items related to the lock on the chest together. I set that Group Box's Text property to Lock
Properties. I dragged a Check Box onto that Group Box and set its Name to cbLock and its Text to
Locked. Under the Check Box I dragged a Label and Combo Box. Set the Label's Text property to
Lock Difficulty: and the Combo Box's Name property to cboDifficulty. I then dragged a Label and
set its Text property to Key Name: and a Text Box beside that and set its Name property to
tbKeyName. I dragged another Label and Text Box under those two. The Label's Text property was

set to Key Type: and the Text Box's Name was set to tbKeyType. I then dragged a Label and
Numeric Up Down onto the Group Box. I set the Text property of the Label to Keys Needed: and the
Name property of the Numeric Up Down to nudKeys. I also set the Enabled property of the Combo
Box, Text Boxes, and Numeric Up Down to False initially. The will be enabled if the user checks the
Check Box to be checked.
I dragged a second Group Box onto the form below the Lock Properties Group Box and set the width
to be the same width. I set the Text property of the second Group Box to Trap Properties. I dragged a
Check Box onto that Group Box and set its Name property to be cbTrap. I set then dragged a Label
and Text Box onto the second Group Box. I set the Text property of the Label to Trap Name: and the
Name property of the Text Box to tbTrap. I set the Enabled property of tbTrap to False as well.
I then dragged a third Group Box onto the form and sized the width to be the same as the other two. I
set its Text property to Gold Properties. I dragged a Label and Numeric Up Down onto this Group
Box. I set the Text Property of the Label to Minimum Gold: and the Name property of the Numeric
Up Down to nudMinGold. I dragged another Label and Numeric Up Down onto this Group Box. I
set the Text property of the Label to Maximum Gold: and the Name property of the Numeric Up
Down to nudMaxGold. I set the Maximum property of nudMinGold and nudMaxGold to be 10000.
You will want to tweak this number as you test your game to make sure that it is not unbalanced. If the
player accumulates too much gold and can buy really expensive and powerful items then your balance
goes out the window. The same is true if you give the player too little gold. They won't be able to
defend themselves against more powerful monsters.
I then dragged on another Group Box and set its Text property to Item Properties. I dragged a List
Box onto the Group Box and made it wider and taller. I set its Name property to lbItems. I then
dragged two Buttons below the List Box. The one on the left I named btnAdd and set its Text
property to Add. The one on the right I named btnRemove and set its Text property to Remove.
The last two controls I dragged onto the form were two Buttons. The first button, the one on the left, I
set its Name to btnOK and its Text to OK. The second I set its Name property to btnCancel and its
Text property to Cancel.
I'm going to add some logic to the form now. I'm going to skip the items for now and add that in a
future tutorial. Right click FormChestDetails in the solution explorer and select View Code. Change
the code for FormChestDetails to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
using RpgLibrary.TrapClasses;
using RpgLibrary.SkillClasses;
namespace RpgEditor
{
public partial class FormChestDetails : Form
{
#region Field Region

ChestData chest;
#endregion
#region Property Region
public ChestData Chest
{
get { return chest; }
set { chest = value; }
}
#endregion
#region Constructor Region
public FormChestDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormChestDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormChestDetails_FormClosing);
foreach (string s in Enum.GetNames(typeof(DifficultyLevel)))
{
cboDifficulty.Items.Add(s);
}
cboDifficulty.SelectedIndex = 0;
cbLock.CheckedChanged += new EventHandler(cbLock_CheckedChanged);
cbTrap.CheckedChanged += new EventHandler(cbTrap_CheckedChanged);
btnAdd.Click += new EventHandler(btnAdd_Click);
btnRemove.Click += new EventHandler(btnRemove_Click);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Form Event Handler Region
void FormChestDetails_Load(object sender, EventArgs e)
{
if (chest != null)
{
tbName.Text = chest.Name;
cbLock.Checked = chest.IsLocked;
tbKeyName.Text = chest.KeyName;
tbKeyType.Text = chest.KeyType;
nudKeys.Value = (decimal)chest.KeysRequired;
tbKeyName.Enabled = chest.IsLocked;
tbKeyType.Enabled = chest.IsLocked;
nudKeys.Enabled = chest.IsLocked;
cbTrap.Checked = chest.IsTrapped;
tbTrap.Text = chest.TrapName;
tbTrap.Enabled = chest.IsTrapped;
nudMinGold.Value = (decimal)chest.MinGold;
nudMaxGold.Value = (decimal)chest.MaxGold;
}

void FormChestDetails_FormClosing(object sender, FormClosingEventArgs e)


{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
#endregion
#region Check Box Event Handler Region
void cbLock_CheckedChanged(object sender, EventArgs e)
{
cboDifficulty.Enabled = cbLock.Checked;
tbKeyName.Enabled = cbLock.Checked;
tbKeyType.Enabled = cbLock.Checked;
nudKeys.Enabled = cbLock.Checked;
}
void cbTrap_CheckedChanged(object sender, EventArgs e)
{
tbTrap.Enabled = cbTrap.Checked;
}
#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
}
void btnRemove_Click(object sender, EventArgs e)
{
}
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must enter a name for the chest.");
return;
}
if (cbTrap.Checked && string.IsNullOrEmpty(tbTrap.Text))
{
MessageBox.Show("You must supply a name for the trap on the chest.");
return;
}

gold.");

if (nudMaxGold.Value < nudMinGold.Value)


{
MessageBox.Show("Maximum gold in chest must be greater or equal to minimum
}

return;

ChestData data = new ChestData();


data.Name = tbName.Text;
data.IsLocked = cbLock.Checked;
if (cbLock.Checked)
{
data.DifficultyLevel = (DifficultyLevel)cboDifficulty.SelectedIndex;
data.KeyName = tbKeyName.Text;
data.KeyType = tbKeyType.Text;
data.KeysRequired = (int)nudKeys.Value;
}

data.IsTrapped = cbTrap.Checked;
if (cbTrap.Checked)
{
data.TrapName = tbTrap.Text;
}
data.MinGold = (int)nudMinGold.Value;
data.MaxGold = (int)nudMaxGold.Value;

chest = data;
this.FormClosing -= FormChestDetails_FormClosing;
this.Close();

void btnCancel_Click(object sender, EventArgs e)


{
chest = null;
this.FormClosing -= FormChestDetails_FormClosing;
this.Close();
}
}

#endregion

The code should look some what familiar. I've used the same basic code in all of the forms for creating
a specific item. There is some new stuff though. As usual there is a field and property for the type of
item that is being created, ChestData in this case.
The constructor wires several event handlers. The Load and FormClosing events are wired first. I also
fill the Combo Box with the names from the DifficultyLevel enumeration and set the SelectedIndex
of the Combo Box to 0. I then wire the handlers for the Check event of the two Check Boxes. I also
wire the handlers for all four of the buttons.
In the Load event handler I check to see if the chest field is not null. If it has a value I fill the form
with values. I set the Enabled properties of controls associated with a chest being locked to be the
IsLocked field of the chest field. So, if the chest is locked the controls will be enabled and disabled if
the chest is not locked. I do the same with the IsTrapped field and the controls associated with a chest
being trapped. I'm not worrying about items at the moment. That will be added in down the road.
There is nothing you haven't seen before in the FormClosing event handler. It just cancels the event if
the reason for closing the form is UserClosing. The event will be unsubscribed from if a chest is
successfully created or the user cancels the changes.
In the CheckChanged handlers I set the Enabled property of the controls associated with the Check
Box to the Checked property of the Check Box. If the Check Box is checked then the controls will be
enabled and disabled if the Check Box is not checked.
The Click event handler for btnOK does a little validation of the form. It checks to see if the chest has
a name. If cbTrap is checked and the Text property of tbTrap is null or empty then the user must
supply a name for the trap. Ideally you will want to confirm that the trap actually exists or in game
ignore the IsTrapped property if no trap exists. I also check to make sure that the value of max gold is
not less than min gold. I then create a new chest using the values on the form. I set the field to be the
new chest, unsubscribe from the FormClosing event handler and then close the form.

The event handler for the Click event of btnCancel holds nothing new or interesting. It just assigns the
chest field to be null, unsubscribes the FormClosing event handler and closes the form.
I'm now going to add the code to FormChest. Right click FormChest in the solution explorer and
select View Code to bring up the code. This is the code for FormChest.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormChest : FormDetails
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
public FormChest()
{
InitializeComponent();
btnAdd.Click += new EventHandler(btnAdd_Click);
btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);
}
#endregion
#region Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormChestDetails frmChestDetails = new FormChestDetails())
{
frmChestDetails.ShowDialog();
if (frmChestDetails.Chest != null)
{
AddChest(frmChestDetails.Chest);
}
}

void btnEdit_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
ChestData data = itemManager.ChestData[entity];
ChestData newData = null;

using (FormChestDetails frmChestData = new FormChestDetails())


{
frmChestData.Chest = data;
frmChestData.ShowDialog();
if (frmChestData.Chest == null)
return;
if (frmChestData.Chest.Name == entity)
{
itemManager.ChestData[entity] = frmChestData.Chest;
FillListBox();
return;
}
}

newData = frmChestData.Chest;

DialogResult result = MessageBox.Show(


"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (itemManager.ChestData.ContainsKey(newData.Name))
{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}

lbDetails.Items.Add(newData);
itemManager.ChestData.Add(newData.Name, newData);

}
void btnDelete_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.ChestData.Remove(entity);
if (File.Exists(FormMain.ItemPath + @"\Chest\" + entity + ".xml"))
File.Delete(FormMain.ItemPath + @"\Chest\" + entity + ".xml");
}

}
#endregion
#region Method Region
public void FillListBox()
{
lbDetails.Items.Clear();
foreach (string s in FormDetails.ItemManager.ChestData.Keys)

lbDetails.Items.Add(FormDetails.ItemManager.ChestData[s]);
}
private void AddChest(ChestData ChestData)
{
if (FormDetails.ItemManager.ChestData.ContainsKey(ChestData.Name))
{
DialogResult result = MessageBox.Show(
ChestData.Name + " already exists. Overwrite it?",
"Existing Chest",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;

itemManager.ChestData[ChestData.Name] = ChestData;
FillListBox();
return;

itemManager.ChestData.Add(ChestData.Name, ChestData);
lbDetails.Items.Add(ChestData);

#endregion
}

Again, this should look familiar as other forms use the same code. The difference is that instead of
working with armor, shields, or weapons, I'm working with chests. There is the using statement for the
System.IO name space. Event handlers for the click event of the buttons are wired in the constructors.
The Click event handler of btnAdd creates a form in a using block. I show the form. If the Chest
property of the form is not null I call the AddChest method passing in the Chest property. The Click
event handler of btnEdit checks to see if the SelectedItem of lbDetails is not null. It parses the string
to get the name of the chest. It then gets the ChestData for the selected item and sets newData to null.
In a using statement a form is created. The Chest property of the form is set to the ChestData of
SelectedItem. I call the ShowDialog method to display the form. If the Chest property of form is null I
exit the method. If the name is the same as before I assign the entry in the item manager to be the new
key, call FillListBox to update the key and exit the method. I then set newData to be the Chest
property of the form. The name of the chest changed so I display a message box asking if the new chest
should be added. If the result is no I exit the method. If there is a chest with that name already I display
a message box and exit. If there wasn't I add the new chest to the list box and the item manager. The
Click event handler for btnDelete checks to make sure that the SelectedItem of the list box is not null.
It parses the selected item and displays a message box asking if the chest should be deleted. If the result
of the message box is Yes I remove the chest from the list box and I remove if from the item manager
as well. I then delete the file, if it exists.
The last thing I'm going to tackle in the editor is adding the functionality for the new forms. That will
be done in FormMain. I need to update the form a little to handle chests and keys. They will have
menu entries of their own. Right click FormMain in the solution explorer and select View Designer.
Click the Menu Strip on the form. Beside the Items entry add a new entry &Keys. Set the Enabled
property of this item to False. Beside the Keys entry add a new entry C&hests and set its Enabled
proeprty to False as well. Your menu bar should resemble the following.

I'm going to end this tutorial here and continue it in a part B. The plan was to add in keys and chests to
the editor and read them in to the game. I encourage you to visit the news page of my site, XNA Game
Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 18B
Finding Loot Part 2
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is part B of tutorial 18. I was working on adding chests and keys to the editor and being able to
read in chests at run time. At the end of the last tutorial I had add in new items to FormMain in the
designer but I haven't added in the code to handle them. Right click FormMain in the editor and select
View Code. This is the code for FormMain.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary;
using RpgLibrary.CharacterClasses;
using RpgLibrary.ItemClasses;
namespace RpgEditor
{
public partial class FormMain : Form
{
#region Field Region
RolePlayingGame rolePlayingGame;
FormClasses frmClasses;
FormArmor frmArmor;
FormShield frmShield;
FormWeapon frmWeapon;
FormKey frmKey;
FormChest frmChest;
static
static
static
static
static

string
string
string
string
string

gamePath = "";
classPath = "";
itemPath = "";
chestPath = "";
keyPath = "";

#endregion
#region Property Region
public static string GamePath
{
get { return gamePath; }
}

public static string ClassPath


{
get { return classPath; }
}
public static string ItemPath
{
get { return itemPath; }
}
public static string ChestPath
{
get { return chestPath; }
}
public static string KeyPath
{
get { return keyPath; }
}
#endregion
#region Constructor Region
public FormMain()
{
InitializeComponent();
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
newGameToolStripMenuItem.Click += new EventHandler(newGameToolStripMenuItem_Click);
openGameToolStripMenuItem.Click += new EventHandler(openGameToolStripMenuItem_Click);
saveGameToolStripMenuItem.Click += new EventHandler(saveGameToolStripMenuItem_Click);
exitToolStripMenuItem.Click += new EventHandler(exitToolStripMenuItem_Click);
classesToolStripMenuItem.Click += new EventHandler(classesToolStripMenuItem_Click);
armorToolStripMenuItem.Click += new EventHandler(armorToolStripMenuItem_Click);
shieldToolStripMenuItem.Click += new EventHandler(shieldToolStripMenuItem_Click);
weaponToolStripMenuItem.Click += new EventHandler(weaponToolStripMenuItem_Click);
keysToolStripMenuItem.Click += new EventHandler(keysToolStripMenuItem_Click);
chestsToolStripMenuItem.Click += new EventHandler(chestsToolStripMenuItem_Click);
}
#endregion
#region Menu Item Event Handler Region
void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
DialogResult result = MessageBox.Show(
"Unsaved changes will be lost. Are you sure you want to exit?",
"Exit?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (result == DialogResult.No)
e.Cancel = true;
}
void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewGame frmNewGame = new FormNewGame())
{
DialogResult result = frmNewGame.ShowDialog();
if (result == DialogResult.OK && frmNewGame.RolePlayingGame != null)
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();

folderDialog.Description = "Select folder to create game in.";


folderDialog.SelectedPath = Application.StartupPath;
DialogResult folderResult = folderDialog.ShowDialog();
if (folderResult == DialogResult.OK)
{
try
{
gamePath = Path.Combine(folderDialog.SelectedPath, "Game");
classPath = Path.Combine(gamePath, "Classes");
itemPath = Path.Combine(gamePath, "Items");
keyPath = Path.Combine(gamePath, "Keys");
chestPath = Path.Combine(gamePath, "Chests");
if (Directory.Exists(gamePath))
throw new Exception("Selected directory already exists.");
Directory.CreateDirectory(gamePath);
Directory.CreateDirectory(classPath);
Directory.CreateDirectory(itemPath + @"\Armor");
Directory.CreateDirectory(itemPath + @"\Shield");
Directory.CreateDirectory(itemPath + @"\Weapon");
Directory.CreateDirectory(keyPath);
Directory.CreateDirectory(chestPath);
rolePlayingGame = frmNewGame.RolePlayingGame;
XnaSerializer.Serialize<RolePlayingGame>(gamePath + @"\Game.xml",

rolePlayingGame);

}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return;
}

}
}

classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;

}
void openGameToolStripMenuItem_Click(object sender, EventArgs e)
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Select Game folder";
folderDialog.SelectedPath = Application.StartupPath;
bool tryAgain = false;
do
{

DialogResult folderResult = folderDialog.ShowDialog();


DialogResult msgBoxResult;
if (folderResult == DialogResult.OK)
{
if (File.Exists(folderDialog.SelectedPath + @"\Game\Game.xml"))
{
try
{
OpenGame(folderDialog.SelectedPath);
tryAgain = false;
}

catch (Exception ex)


{
msgBoxResult = MessageBox.Show(
ex.ToString(),
"Error opening game.",
MessageBoxButtons.RetryCancel);
if (msgBoxResult == DialogResult.Cancel)
tryAgain = false;
else if (msgBoxResult == DialogResult.Retry)
tryAgain = true;
}
}
else
{
msgBoxResult = MessageBox.Show(
"Game not found, try again?",
"Game does not exist",
MessageBoxButtons.RetryCancel);
if (msgBoxResult == DialogResult.Cancel)
tryAgain = false;
else if (msgBoxResult == DialogResult.Retry)
tryAgain = true;
}

} while (tryAgain);
}
void saveGameToolStripMenuItem_Click(object sender, EventArgs e)
{
if (rolePlayingGame != null)
{
try
{
XnaSerializer.Serialize<RolePlayingGame>(gamePath + @"\Game.xml",
rolePlayingGame);
FormDetails.WriteEntityData();
FormDetails.WriteItemData();
FormDetails.WriteChestData();
FormDetails.WriteKeyData();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error saving game.");
}
}
}
void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
void classesToolStripMenuItem_Click(object sender, EventArgs e)
{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}

frmClasses.Show();
frmClasses.BringToFront();

void armorToolStripMenuItem_Click(object sender, EventArgs e)


{

if (frmArmor == null)
{
frmArmor = new FormArmor();
frmArmor.MdiParent = this;
}

frmArmor.Show();
frmArmor.BringToFront();

void shieldToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmShield == null)
{
frmShield = new FormShield();
frmShield.MdiParent = this;
}

frmShield.Show();
frmShield.BringToFront();

void weaponToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmWeapon == null)
{
frmWeapon = new FormWeapon();
frmWeapon.MdiParent = this;
}

frmWeapon.Show();
frmWeapon.BringToFront();

void chestsToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmChest == null)
{
frmChest = new FormChest();
frmChest.MdiParent = this;
}

frmChest.Show();
frmChest.BringToFront();

void keysToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmKey == null)
{
frmKey = new FormKey();
frmKey.MdiParent = this;
}

frmKey.Show();
frmKey.BringToFront();

#endregion
#region Method Region
private void OpenGame(string path)
{
gamePath = Path.Combine(path, "Game");
classPath = Path.Combine(gamePath, "Classes");
itemPath = Path.Combine(gamePath, "Items");
keyPath = Path.Combine(gamePath, "Keys");
chestPath = Path.Combine(gamePath, "Chests");

if (!Directory.Exists(keyPath))
{
Directory.CreateDirectory(keyPath);
}
if (!Directory.Exists(chestPath))
{
Directory.CreateDirectory(chestPath);
}
rolePlayingGame = XnaSerializer.Deserialize<RolePlayingGame>(
gamePath + @"\Game.xml");

FormDetails.ReadEntityData();
FormDetails.ReadItemData();
FormDetails.ReadKeyData();
FormDetails.ReadChestData();
PrepareForms();

private void PrepareForms()


{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}
frmClasses.FillListBox();
if (frmArmor == null)
{
frmArmor = new FormArmor();
frmArmor.MdiParent = this;
}
frmArmor.FillListBox();
if (frmShield == null)
{
frmShield = new FormShield();
frmShield.MdiParent = this;
}
frmShield.FillListBox();
if (frmWeapon == null)
{
frmWeapon = new FormWeapon();
frmWeapon.MdiParent = this;
}
frmWeapon.FillListBox();
if (frmKey == null)
{
frmKey = new FormKey();
frmKey.MdiParent = this;
}
frmKey.FillListBox();
if (frmChest == null)
{
frmChest = new FormChest();
frmChest.MdiParent = this;
}
frmChest.FillListBox();

classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;

#endregion
}

Quite a few new additions to the code of FormMain. I added in fields for FormKey and FormChest. I
also static fields for the path to keys and chests. The fields are keyPath and chestPath. The properties
are KeyPath and ChestPath. The properties, like the other static properties, are read only.
What is new in the constructor is that I wire event handlers for Click events for the two new menu
items, like the other menu items. In the event handler for the new game I create paths for keyPath and
chestPath like the other paths. Keys and chests reside in their own directories. I also create the new
directories like the other paths. I also enable the two new menu items. The event handler for the Click
event for saving a game calls two static methods that I haven't written yet on FormDetails called
WriteChestData and WriteKeyData. As you can guess they will write out chest and key data.
In the event handlers for the Click event of the new menu items I first check to see if the form for that
menu item is null. If it is null then I create a new form and set its MdiParent property to be the current
form. I then call the Show and BringToFront methods to display the forms and bring them to the front.
Since the directory structure for a game has changed in the OpenGame method I check to see if the
directories pointed to by keyPath and chestPath don't exist. If they don't exists then I create them. I
then call the ReadKeyData and ReadChestData methods of FormDetails that I also haven't written
yet and will get to soon.
The last change is in the PrepareForms method. I check to see if the forms are null. If they are null I
create new instances and set their MdiParent properties to this. I also call their FillListBox methods to
fill them with the appropriate items.
I need to update the code for FormDetails. I have to add the write and read methods for writing and
reading chests and keys. Right click FormDetails and select View Code to view the code. Add the
following methods.
public static void WriteKeyData()
{
foreach (string s in ItemManager.KeyData.Keys)
{
XnaSerializer.Serialize<KeyData>(
FormMain.KeyPath + @"\" + s + ".xml",
ItemManager.KeyData[s]);
}
}
public static void WriteChestData()
{
foreach (string s in ItemManager.ChestData.Keys)
{
XnaSerializer.Serialize<ChestData>(
FormMain.ChestPath + @"\" + s + ".xml",
ItemManager.ChestData[s]);
}
}

public static void ReadKeyData()


{
string[] fileNames = Directory.GetFiles(FormMain.KeyPath, "*.xml");
foreach (string s in fileNames)
{
KeyData keyData = XnaSerializer.Deserialize<KeyData>(s);
itemManager.KeyData.Add(keyData.Name, keyData);
}
}
public static void ReadChestData()
{
string[] fileNames = Directory.GetFiles(FormMain.ChestPath, "*.xml");

foreach (string s in fileNames)


{
ChestData chestData = XnaSerializer.Deserialize<ChestData>(s);
itemManager.ChestData.Add(chestData.Name, chestData);
}

The code is similar to the other writing and reading code. For writing in a foreach loop I loop through
all of the keys in the ItemManager for that type of data. I then call the Serialize method of the
XnaSerializer class with the right data type, path to the file, and the actual item to write. To create the
path you take the path to that type of object and append a \ the name of the item and .xml for the
extension. To read in the items you first need to get all of the items of that type in the directory. You use
the GetFiles method of the Directory class to call all of the files with an xml extension. In a foreach
loop I loop through all of the file names. I then call the Deserialize method of the XnaSerilializer class
with the proper data type and the file name storing the object in a variable. I then add the object to the
ItemManager passing in the name of the object for the key and the actual object for the value.
Right click the RpgEditor and select Set As StartUp Project if the editor isn't already the start up
project. Build and run to launch the editor. Once you've done that select the Open menu item and
navigate to your game data from tutorial 14. I'm going to add a couple keys and chests to the editor.
Add the following keys and chests then save the game.

Keys
Key Name

Key Type

Rusty Key
Golden Key

Golden Key

Chests
Name

Difficulty Locked
Level

Key
Name

Key
Type

Keys
Required

Trapped

Trap
Name

Min
Gold

Max
Gold

No

100

150

Rusty Chest

Normal

Yes

Rusty Key

Gold Chest

Normal

Yes

Golden Key

Golden Key

No

200

250

Big Gold Chest

Normal

Yes

Golden Key

Golden Key

No

500

750

Plain Chest

Easy

Yes

No

10

50

Broken Crate

Normal

No

No

10

Item
Collection

The next step will be to get a chest we created with the editor into the game. The first step will be to
add the game data to the EyesOfTheDragonContent project. The easiest way will be to drag the

Game folder from windows explorer onto the EyesOfTheDragonContent project. Open a windows
explorer window so that Visual Studio is visible below it and browse to where your Game directory
can be found as seen below. Sorry it isn't all that clear. Now, drag the Game folder onto the
EyesOfTheDragonContent project.

You should now have a Game folder in your EyesOfTheDragonContent project as seen in the next
image. Right click the EyesOfTheDragon project and select Set As StartUp Project to make your
game the start up project. You will need to add a reference to the EyesOfTheDragonContent project
for your RpgLibrary to compile the XML files into XNB files that can be read using the Content
Pipeline. Right click the EyesOfTheDragonContent project and select Add Reference. From the
Projects tab select RpgLibrary.

The last thing I want to do is show how easy it is to read in a ChestData object using the Content
Pipeline. Change the CreateWorld method of the CharacterGeneratorScreen to the following.
private void CreateWorld()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");

Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);


tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
List<Tileset> tilesets = new List<Tileset>();
tilesets.Add(tileset1);
tilesets.Add(tileset2);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
}

layer.SetTile(x, y, tile);

}
MapLayer splatter = new MapLayer(100, 100);
Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);
Tile tile = new Tile(index, 0);
splatter.SetTile(x, y, tile);
}
splatter.SetTile(1, 0, new Tile(0, 1));
splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
List<MapLayer> mapLayers = new List<MapLayer>();
mapLayers.Add(layer);
mapLayers.Add(splatter);
TileMap map = new TileMap(tilesets, mapLayers);
Level level = new Level(map);
ChestData chestData = Game.Content.Load<ChestData>(@"Game\Chests\Plain Chest");
Chest chest = new Chest(chestData);
BaseSprite chestSprite = new BaseSprite(
containers,
new Rectangle(0, 0, 32, 32),
new Point(10, 10));
ItemSprite itemSprite = new ItemSprite(
chest,
chestSprite);
level.Chests.Add(itemSprite);
World world = new World(GameRef, GameRef.ScreenRectangle);
world.Levels.Add(level);
world.CurrentLevel = 0;
GamePlayScreen.World = world;
}

As you can see I replaced the code where I created a ChestData object by hand with a call to Load of
the ContentManager of our Game class. I passed in the Plain Chest that I created in the editor. We

went through a little work to get there but it is well worth it in the end. Most classes that you create can
be serialized using the IntermediateSerializer and then read in using the Content Pipeline at run time
with out having to create a custom Content Pipeline extension.
I'm going to end this tutorial here. The plan was to show how easy it is to read in our custom content at
run time using the Content Pipeline. I encourage you to visit the news page of my site, XNA Game
Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 19
Skills Continued
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In tutorial 15 I added in some place holder classes for skills, talents, and spells. In this tutorial I'm
going to flesh out skills more. As I mentioned in tutorial 15 skills can be learned by any intelligent
creature. The way I'm handling skills is that skills have a rank associated with them between 1 and 100.
A rank of 0 means that the character has no knowledge of that skill. A rank of 100 means the character
is master at that skill. When a character is first created they get 25 points to spend on skills. Every time
a character levels up they get 10 additional points to spend on skills. There is also a difficulty involved
when a character tries to apply a skill. It can be very easy to use a skill or hard even for a master. To
successfully use a skill a random number between 1 and 100 is generated. If the character's rank in the
skill plus the difficulty plus any modifiers is greater than or equal to the random number the character
is successful in using the skill. Skills will have modifiers based on a primary attribute, like strength,
and the character's class. A thief will have a better understanding of poisons than a fighter.
So, what exactly are the skills I'm planning on implementing? I'm going to implement some crafting
skills. Crafting skills require a recipe to use and the reagents required by the recipe. It is the recipes that
have a difficulty associated with them. Making a simple health potion wouldn't be too difficult but
creating a deadly poison would be quite challenging. I'm going to be adding a bartering skill as well
that will help the character deal with trading. Be careful using it though. If you upset the merchant you
could get tossed out of their shop! I will add a few others as well.
The first step will be to flesh out the Skill and SkillData classes. Like the other Data classes SkillData
will define a basic skill, with out any modifiers. The Skill class will be created and be specific to a
character. Change the SkillData class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SkillClasses
{
public class SkillData
{
#region Field Region
public string Name;
public string PrimaryAttribute;
public Dictionary<string, int> ClassModifiers;
#endregion

#region Constructor Region


public SkillData()
{
ClassModifiers = new Dictionary<string, int>();
}
#endregion
#region Virtual Method Region
public override string ToString()
{
string toString = Name + ", ";
toString += PrimaryAttribute;
foreach (string s in ClassModifiers.Keys)
toString += ", " + s + "+" + ClassModifiers[s].ToString();
}

return toString;

#endregion
}

There are three fields in the class. There is Name for the name of the skill. PrimaryAttribute is the
primary attribute that affects the skill. ClassModifiers is a Dictionary<string, int>. The string is the
name of the class and the int is the amount of the modifier. The IntermediateSerializer class does
allow for Dictionary fields to be serialized and deserialzed so there is no worries about that. I included
an override of the ToString method, for use in the editor.
Speaking of the editor now would be a good time to add skills to the editor. For the next little bit you
will want the editor to be the start up project. Right click the RpgEditor in the solution explorer and
select Set As StartUp Project. You are going to want to add two forms to the RpgEditor. One to hold
all of the skills in the game and one creating and editing skills. Right click the RpgEditor in the
solution explorer, select Add and then Windows Form. Name the new form FormSkill.
First, set the Size property of FormSkill to be the Size property of FormDetails. Set the Text property
to be Skills and the MinimizeBox property to False. Right click FormSkill in the solution explorer
and select View Code. Modify the code of FormSkill to inherit from FormDetails.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

namespace RpgEditor
{
public partial class FormSkill : FormDetails
{
public FormSkill()
{
InitializeComponent();
}
}
}

Before I get to the form for creating and editing a skill you will want to update FormDetails to include
a SkillDataManager field. You will also want to add methods to read and write skills. Right click
FormDetails in the solution explorer and select View Code. First, add this field to the Field Region
and property to the Property Region. You will also want a using statement for the SkillClasses name
space in the RpgLibrary.
using RpgLibrary.SkillClasses;
protected static SkillDataManager skillManager;
public static SkillDataManager SkillManager
{
get { return skillManager; }
}

To the Method Region you will want to add these methods to write and read the skill data.
public static void WriteSkillData()
{
foreach (string s in SkillManager.SkillData.Keys)
{
XnaSerializer.Serialize<SkillData>(
FormMain.SkillPath + @"\" + s + ".xml",
SkillManager.SkillData[s]);
}
}
public static void ReadSkillData()
{
skillManager = new SkillDataManager();
string[] fileNames = Directory.GetFiles(FormMain.SkillPath, "*.xml");
foreach (string s in fileNames)
{
SkillData chestData = XnaSerializer.Deserialize<SkillData>(s);
skillManager.SkillData.Add(chestData.Name, chestData);
}
}

Visual Studio is going to give you two errors, that FormMain.SkillPath does not exist. Let's fix that in
FormMain. Right click FormMain in the solution explorer and select View Code. First, you are going
to want a field for FormSkill and a field for the path to write skills. You will also want to add a
property to expose the path for skills. Change the Field and Property regions to the following.
#region Field Region
RolePlayingGame rolePlayingGame;
FormClasses frmClasses;
FormArmor frmArmor;
FormShield frmShield;
FormWeapon frmWeapon;
FormKey frmKey;
FormChest frmChest;
FormSkill frmSkill;
static
static
static
static
static
static

string
string
string
string
string
string

gamePath = "";
classPath = "";
itemPath = "";
chestPath = "";
keyPath = "";
skillPath = "";

#endregion
#region Property Region
public static string GamePath
{
get { return gamePath; }
}
public static string ClassPath
{
get { return classPath; }
}
public static string ItemPath
{
get { return itemPath; }
}
public static string ChestPath
{
get { return chestPath; }
}
public static string KeyPath
{
get { return keyPath; }
}
public static string SkillPath
{
get { return skillPath; }
}
#endregion

The next step is going to be designing the form for creating and editing the individual skills. Right click
RpgEditor, select Add and then Windows Form. Name this new form FormSkillDetails. My form in
the solution explorer appears next.

First, you will want to make the form bigger to house all of the controls. You can also set the
ControlBox property to False, the StartPosition to CenterParent, and the Text property to Skill.
First, drag a Label onto the form and set its Text property to Skill Name:. Drag a Text Box beside the
Label and set its Name property to tbName. Drag a Group Box onto the form next and change the
size. You can set its Text property to Primary Attribute. Onto the Group Box you will want to drag
on six Radio Buttons. For the first one set the Name property to rbStrength and the Text property to
Strength. Also, set the Checked property of the Radio Button to True. For the other Radio Buttons
set their Name and Text properties to the following, in order: rbDexterity and Dexterity, rbCunning
and Cunning, rbWillpower and Willpower, rbMagic and Magic, and rbConstitution and
Constitution. I then dragged another Group Box onto the form and made it larger. I also set the Text
property of the Group Box to Class Modifiers. I dragged a List Box onto the Group Box and set its
Name property to lbModifiers. Under the List Box, but still on the Group Box, I dragged three
Buttons. I set their Name and Text properties to the following, again in order: btnAdd and Add,
btnRemove and Remove, and btnEdit and Edit. Under the Group Boxes I dragged on two more
Buttons. The first one I set Name to btnOK and Text to OK. The second I set Name to btnCancel
and Text to Cancel.
I'm going to add in some of the logic to the form now. Right click FormSkillDetails in the solution
explorer and select View Code. The code for FormSkillDetails follows next.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
RpgLibrary.SkillClasses;

namespace RpgEditor
{
public partial class FormSkillDetails : Form
{
#region Field Region
SkillData skill;
#endregion
#region Property Region
public SkillData Skill
{
get { return skill; }
set { skill = value; }
}
#endregion
#region Constructor Region
public FormSkillDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormSkillDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormSkillDetails_FormClosing);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);

}
#endregion
#region Form Event Handler Region
void FormSkillDetails_Load(object sender, EventArgs e)
{
if (Skill != null)
{
tbName.Text = Skill.Name;
switch (Skill.PrimaryAttribute.ToLower())
{
case "strength":
rbStrength.Checked = true;
break;
case "dexterity":
rbDexterity.Checked = true;
break;
case "cunning":
rbCunning.Checked = true;
break;
case "willpower":
rbWillpower.Checked = true;
break;
case "magic":
rbMagic.Checked = true;
break;
case "constitution":
rbConstitution.Checked = true;
break;
}

foreach (string s in Skill.ClassModifiers.Keys)


{
string data = s + ", " + Skill.ClassModifiers[s].ToString();
lbModifiers.Items.Add(data);
}

}
void FormSkillDetails_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
#endregion
#region Button Event Handler Region
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must provide a name for the skill.");
return;
}
SkillData newSkill = new SkillData();
newSkill.Name = tbName.Text;
if (rbStrength.Checked)
newSkill.PrimaryAttribute = "Strength";
else if (rbDexterity.Checked)
newSkill.PrimaryAttribute = "Dexterity";
else if (rbCunning.Checked)

newSkill.PrimaryAttribute = "Cunning";
else if (rbWillpower.Checked)
newSkill.PrimaryAttribute = "Willpower";
else if (rbMagic.Checked)
newSkill.PrimaryAttribute = "Magic";
else if (rbConstitution.Checked)
newSkill.PrimaryAttribute = "Constitution";
skill = newSkill;
this.FormClosing -= FormSkillDetails_FormClosing;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
skill = null;
this.FormClosing -= FormSkillDetails_FormClosing;
this.Close();
}
#endregion
}

There is a lot that you've seen here before. There is a using statement to bring the SkillClasses classes
into scope. There is a field of type SkillData and a property to expose the field. The constructor wires
event handlers for the Load and FormClosing events of the form. It also wires handlers for btnOK
and btnCancel. I didn't wire the handlers for the other buttons. That I will do in a future tutorial. The
Load event handler checks to see if the Skill property is not null. If it isn't I set the Text property of
tbName to the Name field. There is a switch on the the PrimaryAttribute field of the skill converted
to a lower case string. The cases check for each of the primary attributes. If that value is found I set the
Checked property of the appropriate Radio Button to True. In a foreach loop I loop over all of the
keys in the ClassModifiers dictionary. I create a string consisting of the class name, a comma, and the
value of the modifier as a string. I then add the string to the Items of lbModifier. There is nothing you
haven't seen in the FormClosing event handler. The Click handler of btnOK does a little validation on
the form. If the Text property of tbName is null or empty I show a message box stating it must have a
value and break out of the method. I create a new instance of SkillData and set the Name field to be
the Text property of tbName. For determining what the primary attribute should be there is a series of
if-else-it statements that check the Checked property of the radio buttons. I set the PrimaryAttribute
field base on which radio button was checked. I then set the skill field and then unsubscribe from the
FormClosing event. Finally the form closes. I believe the Click event handler for btnCancel doesn't
need to be explained either.
I'm going to add in the code for dealing with FormSkill now. Right click FormSkill in the solution
explorer and select View Code. The code for that form follows next.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using RpgLibrary.SkillClasses;
namespace RpgEditor
{

public partial class FormSkill : FormDetails


{
#region Constructor Region
public FormSkill()
{
InitializeComponent();

btnAdd.Click += new EventHandler(btnAdd_Click);


btnEdit.Click += new EventHandler(btnEdit_Click);
btnDelete.Click += new EventHandler(btnDelete_Click);

#endregion
#region Button Event Handler Region
void btnAdd_Click(object sender, EventArgs e)
{
using (FormSkillDetails frmSkillDetails = new FormSkillDetails())
{
frmSkillDetails.ShowDialog();
if (frmSkillDetails.Skill != null)
{
AddSkill(frmSkillDetails.Skill);
}
}

void btnEdit_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = lbDetails.SelectedItem.ToString();
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
SkillData data = skillManager.SkillData[entity];
SkillData newData = null;
using (FormSkillDetails frmSkillData = new FormSkillDetails())
{
frmSkillData.Skill = data;
frmSkillData.ShowDialog();
if (frmSkillData.Skill == null)
return;
if (frmSkillData.Skill.Name == entity)
{
skillManager.SkillData[entity] = frmSkillData.Skill;
FillListBox();
return;
}
newData = frmSkillData.Skill;
}
DialogResult result = MessageBox.Show(
"Name has changed. Do you want to add a new entry?",
"New Entry",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
if (skillManager.SkillData.ContainsKey(newData.Name))

{
MessageBox.Show("Entry already exists. Use Edit to modify the entry.");
return;
}
lbDetails.Items.Add(newData);
skillManager.SkillData.Add(newData.Name, newData);
}

void btnDelete_Click(object sender, EventArgs e)


{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
skillManager.SkillData.Remove(entity);

}
}

if (File.Exists(FormMain.SkillPath + @"\" + entity + ".xml"))


File.Delete(FormMain.SkillPath + @"\" + entity + ".xml");

#endregion
#region Method Region
public void FillListBox()
{
lbDetails.Items.Clear();

foreach (string s in FormDetails.SkillManager.SkillData.Keys)


lbDetails.Items.Add(FormDetails.SkillManager.SkillData[s]);

private void AddSkill(SkillData skillData)


{
if (FormDetails.SkillManager.SkillData.ContainsKey(skillData.Name))
{
DialogResult result = MessageBox.Show(
skillData.Name + " already exists. Overwrite it?",
"Existing armor",
MessageBoxButtons.YesNo);
if (result == DialogResult.No)
return;
skillManager.SkillData[skillData.Name] = skillData;
FillListBox();
return;
}
skillManager.SkillData.Add(skillData.Name, skillData);
lbDetails.Items.Add(skillData);
}
}
}

#endregion

You may be getting tired of seeing this code over and over again. It is a lot like the code for the other
forms that house all of a specific type of data. The main difference is that instead of working with items
this form works with skills. The basics were to add using statements to bring some classes into scope
for the form. I then wired the event handlers for the buttons.
In the Click event of the Add button I create a new FormSkillDetails in a using statement so it will be
disposed of when I'm done with it. I then display the form using ShowDialog so the user must finish
with the form before working on the editor again. If the Skill property of the form is not null I call the
AddSkill method.
The Click event of the Edit button checks to see if the SelectedItem property of lbDetails is not null.
It then splits the item into the different parts to get the Name of the skill. I then get the skill from the
SkillDataManager. I also have another local variable of SkillData that is set to null. I create a new
form of type FormSkillDetails in a using statement so it will be disposed of when I close the form. I
then set the Skill property of form to be the skill retrieved from the SkillDataManager. It is is null the
user canceled the edit and I break out of the method. If the Name of the skill on the form is the same as
the name of the skill from the list box I update the skill in the skill data manager and call the
FillListBox method to update it and return out of the method. Otherwise I set the newData local
variable to be the Skill property of the form. I display a message box stating that the name of the skill
has changed and if the user wants to add a new skill with that name. If the response is no I break out of
the method. If there is already a skill with the new name I display a message box and break out of the
method. Otherwise I add the new skill to lbDetails and skillManager.
The Click event of the Delete button checks to see if the SelectedItem property of lbDetails is not null
like before. I get the name of the selected item as before and display a message box asking if the entry
should be deleted. If the answer was yes I remove the entry from the list box and from the skill data
manager class. I also check to see if a file exists in the directory containing skills. If it does exist I
delete it. I will point out I missed something in tutorial 18. I neglected to update the code for deleting
items in FormKey and FormChest. I will update that code shortly.
The FillListBox method works like previous versions of the FillListBox method. It just works with
SkillData rather than other data classes. Same with the AddSkill method. It just works with skills
rather than other data classes.
For FormKey you want to check the directory for keys, not a directory inside the directory for items.
Change the btnDelete_Click method of FormKey to the following.
void btnDelete_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.KeyData.Remove(entity);

if (File.Exists(FormMain.KeyPath + @"\" + entity + ".xml"))


File.Delete(FormMain.KeyPath + @"\" + entity + ".xml");
}

The same thing is true for FormChest. Rather than keys you want to work with chests. Change the
btnDelete_Click method in FormChest to the following.
void btnDelete_Click(object sender, EventArgs e)
{
if (lbDetails.SelectedItem != null)
{
string detail = (string)lbDetails.SelectedItem;
string[] parts = detail.Split(',');
string entity = parts[0].Trim();
DialogResult result = MessageBox.Show(
"Are you sure you want to delete " + entity + "?",
"Delete",
MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
lbDetails.Items.RemoveAt(lbDetails.SelectedIndex);
itemManager.ChestData.Remove(entity);

}
}

if (File.Exists(FormMain.ChestPath + @"\" + entity + ".xml"))


File.Delete(FormMain.ChestPath + @"\" + entity + ".xml");

The next thing to do is to update the main form to work with skills. You will want to add a new menu
item for skills. Right click FormMain and select View Designer to bring up the design view. Like you
did in the last tutorial you want to add a new menu item. Beside the Chests item add a new item
&Skills. Set the Enabled property to be False as well. Your menu bar should now look like this.

You can close the design view and go back to the code view. If you closed the code view before open it
again by right clicking FormMain and selecting View Code. You will want to add in an event handler
for the new menu item. Change the constructor of FormMain to the following. Add the following
method just below the other handlers for menu item clicks.
public FormMain()
{
InitializeComponent();
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
newGameToolStripMenuItem.Click += new EventHandler(newGameToolStripMenuItem_Click);
openGameToolStripMenuItem.Click += new EventHandler(openGameToolStripMenuItem_Click);
saveGameToolStripMenuItem.Click += new EventHandler(saveGameToolStripMenuItem_Click);
exitToolStripMenuItem.Click += new EventHandler(exitToolStripMenuItem_Click);
classesToolStripMenuItem.Click += new EventHandler(classesToolStripMenuItem_Click);
armorToolStripMenuItem.Click += new EventHandler(armorToolStripMenuItem_Click);
shieldToolStripMenuItem.Click += new EventHandler(shieldToolStripMenuItem_Click);
weaponToolStripMenuItem.Click += new EventHandler(weaponToolStripMenuItem_Click);

keysToolStripMenuItem.Click += new EventHandler(keysToolStripMenuItem_Click);


chestsToolStripMenuItem.Click += new EventHandler(chestsToolStripMenuItem_Click);
}

skillsToolStripMenuItem.Click += new EventHandler(skillsToolStripMenuItem_Click);

void skillsToolStripMenuItem_Click(object sender, EventArgs e)


{
if (frmSkill == null)
{
frmSkill = new FormSkill();
frmSkill.MdiParent = this;
}

frmSkill.Show();
frmSkill.BringToFront();

The handler works like the other handlers. Check to see if the form is null. If it is null create one and
set the MdiParent to be this. Then you call the Show method to show the form and the BringToFront
method to make it the top form.
You are going to want to update the code for creating a new game, saving a game, and opening a game.
I will start with creating a new game. Change the code for the click event handler of the new game
menu item to the following.
void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewGame frmNewGame = new FormNewGame())
{
DialogResult result = frmNewGame.ShowDialog();
if (result == DialogResult.OK && frmNewGame.RolePlayingGame != null)
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "Select folder to create game in.";
folderDialog.SelectedPath = Application.StartupPath;
DialogResult folderResult = folderDialog.ShowDialog();
if (folderResult == DialogResult.OK)
{
try
{
gamePath = Path.Combine(folderDialog.SelectedPath, "Game");
classPath = Path.Combine(gamePath, "Classes");
itemPath = Path.Combine(gamePath, "Items");
keyPath = Path.Combine(gamePath, "Keys");
chestPath = Path.Combine(gamePath, "Chests");
skillPath = Path.Combine(gamePath, "Skills");
if (Directory.Exists(gamePath))
throw new Exception("Selected directory already exists.");
Directory.CreateDirectory(gamePath);
Directory.CreateDirectory(classPath);
Directory.CreateDirectory(itemPath + @"\Armor");
Directory.CreateDirectory(itemPath + @"\Shield");
Directory.CreateDirectory(itemPath + @"\Weapon");
Directory.CreateDirectory(keyPath);
Directory.CreateDirectory(chestPath);
Directory.CreateDirectory(skillPath);
rolePlayingGame = frmNewGame.RolePlayingGame;

XnaSerializer.Serialize<RolePlayingGame>(gamePath + @"\Game.xml",
rolePlayingGame);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return;
}

}
}

classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
skillsToolStripMenuItem.Enabled = true;

The changes were that I make a new path like the other paths. I create a directory like the others as
well. I also set the Enabled property of the menu item to true as well.
For saving a game you will want to write out the SkillData in the SkillDataManager. All you need to
do is to call the static method WriteSkillData of FormDetails. Change the click event handler for the
save menu item to the following.
void saveGameToolStripMenuItem_Click(object sender, EventArgs e)
{
if (rolePlayingGame != null)
{
try
{
XnaSerializer.Serialize<RolePlayingGame>(gamePath + @"\Game.xml", rolePlayingGame);
FormDetails.WriteEntityData();
FormDetails.WriteItemData();
FormDetails.WriteChestData();
FormDetails.WriteKeyData();
FormDetails.WriteSkillData();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error saving game.");
}
}
}

There are two parts to opening games. You will want to change the OpenGame and PrepareForms
methods. You can change those methods to the following.
private void OpenGame(string path)
{
gamePath = Path.Combine(path, "Game");
classPath = Path.Combine(gamePath, "Classes");
itemPath = Path.Combine(gamePath, "Items");
keyPath = Path.Combine(gamePath, "Keys");
chestPath = Path.Combine(gamePath, "Chests");
skillPath = Path.Combine(gamePath, "Skills");
if (!Directory.Exists(keyPath))
{
Directory.CreateDirectory(keyPath);
}
if (!Directory.Exists(chestPath))
{

Directory.CreateDirectory(chestPath);
}
if (!Directory.Exists(skillPath))
{
Directory.CreateDirectory(skillPath);
}
rolePlayingGame = XnaSerializer.Deserialize<RolePlayingGame>(
gamePath + @"\Game.xml");
FormDetails.ReadEntityData();
FormDetails.ReadItemData();
FormDetails.ReadKeyData();
FormDetails.ReadChestData();
FormDetails.ReadSkillData();
}

PrepareForms();

private void PrepareForms()


{
if (frmClasses == null)
{
frmClasses = new FormClasses();
frmClasses.MdiParent = this;
}
frmClasses.FillListBox();
if (frmArmor == null)
{
frmArmor = new FormArmor();
frmArmor.MdiParent = this;
}
frmArmor.FillListBox();
if (frmShield == null)
{
frmShield = new FormShield();
frmShield.MdiParent = this;
}
frmShield.FillListBox();
if (frmWeapon == null)
{
frmWeapon = new FormWeapon();
frmWeapon.MdiParent = this;
}
frmWeapon.FillListBox();
if (frmKey == null)
{
frmKey = new FormKey();
frmKey.MdiParent = this;
}
frmKey.FillListBox();
if (frmChest == null)
{
frmChest = new FormChest();
frmChest.MdiParent = this;
}
frmChest.FillListBox();

if (frmSkill == null)
{
frmSkill = new FormSkill();
frmSkill.MdiParent = this;
}
frmSkill.FillListBox();
classesToolStripMenuItem.Enabled = true;
itemsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
skillsToolStripMenuItem.Enabled = true;
}

So, what are the changes to OpenGame. The first is you want to create a path for skills. Like for chests
and keys and since skills are new I check to see if a directory for skills exists. If it doesn't I create a
directory for them. I then call the ReadSkillData method of FormDetails to read in the skills.
The change to PrepareForms is I check to see if frmSkills is null. If it is I create a new form and set
the MdiParent to be this the current form. I then call the FillListBox method of FormSkill to fill it
with items. I set the Enabled property of the menu item to True as well.
So, build and run the editor. Choose to open a game from the Game menu and navigate to the
EyesOfTheDragonContent folder where you added the Game to last time. Bring up the Skills form
and add the following skills.
Skill Name

Primary Attribute

Bartering

Cunning

Herbalism

Magic

Poison Making

Cunning

Trap Making

Dexterity

The first skill, Bartering, is influenced by a character's Cunning. If you recall Cunning measures a
character's ability to read a situation, pick up on subtleties and reason. Bartering can improve
interaction with merchants. They will give you a lower price if you successfully barter with them when
you are buying something for example. If you fail drastically they might throw you out of their store!
Herbalism is combining ingredients to make magical potions and is influenced by a character's Magic
attribute. The higher the character's magic the better quality the potion will be. Poison Making allows
the player to create poisons. They are more mundane than potions so it required great cunning when
crafting a poison. Trap Making allows the character to craft traps. It takes a lot of dexterity to keep a
trap from going off while you are crafting it.
I'm going to end this tutorial here. The plan was to add in more to dealing with skills in the game. I
encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest
news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 20
More on Skills
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to do a little more work on skills. What I plan to do is to add in a screen to
allow the player to spend their skill points. Initially I will move to this screen from the main screen for
creating characters, CharacterGeneratorScreen. First thing though, I kind of goofed. I shouldn't have
had you navigate to the EyesOfTheDragonContent folder for entering skills. You should have added a
Skills folder inside of the Game folder in the EyesOfTheDragonContent folder. Easy enough to fix
though. Like you did for adding the Game folder to the EyesOfTheDragonContent folder open up a
windows explorer window and navigate to the Game folder in the EyesOfTheDragonContent folder.
Select the Skills folder from the Game folder in windows explorer and drag it onto the Game folder in
the solution explorer.
I want to first add a new control similar to the LeftRightSelector. The difference is that this control
will work with numeric values instead of strings, much like the Numeric Up Down control I've been
using in the editor, which is also called a SpinBox. In the XRpgLibrary right click the Controls
folder, select Add and then Class. Name this new class SpinBox. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XRpgLibrary.Controls
{
public class SpinBox : Control
{
#region Event Region
public event EventHandler SelectionChanged;
#endregion
#region Field Region
int
int
int
int

current;
minValue;
maxValue;
increment;

Texture2D leftTexture;
Texture2D rightTexture;

Texture2D stopTexture;
Color selectedColor = Color.Red;
int width;
#endregion
#region Property Region
public int MinimumValue
{
get { return minValue; }
set
{
if (value > maxValue)
minValue = maxValue;
else
minValue = value;
}
}
public int MaximumValue
{
get { return maxValue; }
set
{
if (value < minValue)
maxValue = minValue;
else
maxValue = value;
}
}
public int Value
{
get { return current; }
set
{
if (value < minValue)
current = minValue;
else if (value > maxValue)
current = maxValue;
else
current = value;
}
}
public int Increment
{
get { return increment; }
set { increment = value; }
}
public int Width
{
get { return width; }
set { width = value; }
}
public Color SelectedColor
{
get { return selectedColor; }
set { selectedColor = value; }
}
#endregion
#region Constructor Region

public SpinBox(Texture2D leftArrow, Texture2D rightArrow, Texture2D stop)


{
minValue = 0;
maxValue = 100;
increment = 1;
width = 50;
leftTexture = leftArrow;
rightTexture = rightArrow;
stopTexture = stop;
TabStop = true;
Color = Color.White;
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public override void Update(GameTime gameTime)
{
}
public override void Draw(SpriteBatch spriteBatch)
{
Vector2 drawTo = position;
if (current != minValue)
spriteBatch.Draw(leftTexture, drawTo, Color.White);
else
spriteBatch.Draw(stopTexture, drawTo, Color.White);
drawTo.X += leftTexture.Width + 5f;
string currentValue = current.ToString();
float itemWidth = spriteFont.MeasureString(currentValue).X;
float offset = (width - itemWidth) / 2;
drawTo.X += offset;
if (hasFocus)
spriteBatch.DrawString(spriteFont, currentValue, drawTo, selectedColor);
else
spriteBatch.DrawString(spriteFont, currentValue, drawTo, Color);
drawTo.X += -1 * offset + width + 5f;

if (current != maxValue)
spriteBatch.Draw(rightTexture, drawTo, Color.White);
else
spriteBatch.Draw(stopTexture, drawTo, Color.White);

public override void HandleInput(PlayerIndex playerIndex)


{
if (InputHandler.ButtonReleased(Buttons.LeftThumbstickLeft, playerIndex) ||
InputHandler.ButtonReleased(Buttons.DPadLeft, playerIndex) ||
InputHandler.KeyReleased(Keys.Left))
{
current -= increment;
if (current < minValue)
current = minValue;
OnSelectionChanged();
}
if (InputHandler.ButtonReleased(Buttons.LeftThumbstickRight, playerIndex) ||
InputHandler.ButtonReleased(Buttons.DPadRight, playerIndex) ||

InputHandler.KeyReleased(Keys.Right))
{

current += increment;
if (current > maxValue)
current = maxValue;
OnSelectionChanged();

}
protected virtual void OnSelectionChanged()
{
if (SelectionChanged != null)
SelectionChanged(this, null);
}
}

#endregion

This should look a little familiar. The biggest difference is that you are working with numbers rather
than strings. There are using statements to bring some of the XNA framework classes into scope. I
included an event like in the LeftRightSelector called SelectionChanged. This event will be triggered
if the selection changed, much like the LeftRightSelector.
There are four integer fields: current, minValue, maxValue, and increment. They hold the current
value of the SpinBox, the minimum and maximum values and how much to increment when right is
pressed or decrement when left is pressed. The fields leftTexture, rightTexture, and stopTexture are
from the LeftRightSelector. The left and right ones will let the player know they can move left or right
and the stop they can't move in a specific direction. The last field, width, is the width of the SpinBox.
There are a number of properties to expose the values and many do error checking. MinimumValue is
for the minimum value. The set part checks to see if the value passed to the property is greater than the
maximum value. If it is it sets the minimum value to the maximum value. MaximumValue is for the
maximum value. The set part checks if the value passed in is less than the minimum value. If it is it sets
the maximum value to be the minimum value. Otherwise they set the minimum or maximum to the
value passed in. The Value property exposed the current field. If the value is less than the minimum
value current is set to be the minimum value. If it is greater than the maximum value it is set to be the
maximum value. Otherwise it is set to the value passed in. The Increment property should do some
sort of validation. You don't want the increment to be greater than the difference between the minimum
and maximum values. You also don't want it to be less than or equal to zero. For now it is fine but
something that should probably be looked into. The Width property exposes the width field. Again,
you should probably do a little validation here but we will just be careful. The last property is for the
selected color and is called SelectedColor. It is just a simple get and set property.
The constructor for this class takes three Texture2D parameter. The first is a left pointing arrow, the
second a right pointing arrow, and the third a stop symbol. I set a few default values for the SpinBox
first. The minimum and maximum values are set to 0 and 100 respectively. The increment field is set
to be 1 and the width field to 50. I set the Texture2D fields to the values passed to the constructor. I
also set TabStop to true and Color to white.
The Draw method is similar to that of the LeftRightSelector. There is a local variable to hold where to
draw an element of the SpinBox. I check to see if current is not equal to minValue. If it isn't then I
draw the left arrow. Otherwise I draw the stop bar. I increment the X property of drawTo by the width
of the left arrow and 5 pixels for padding. I create a string from current called currentValue. I then

measure the width of currentValue using the MeasureString method. I then create an offset that will
center the item and increment the X value of drawTo. I check the hasFocus field to determine what
color to draw the item in and draw it in the appropriate color. By comparing current to maxValue I
determine if I should draw the right arrow or the stop bar.
The HandleInput method first checks to see if the player released the left thumb stick left, direction
pad left, or left arrow key left. I then decrement current by increment. If current is less than
minValue I set current to be minValue. I also call the OnSelectionChanged method that will check if
the event SelectionChanged is subscribed to and should be fired. I then check if the player released the
left thumb stick right, the direction pad right, or the right arrow key. If so, I increment current by the
icrement field. If current is greater than maxValue I set current to be maxValue. I then call the
OnSelectionChanged method to see if the event should be fired.
I had planned on using the SpinBox control in this tutorial. After trying to work with it for a bit I found
it wasn't really the best choice for the job and I decided to go a different route that was simpler to
implement. The control will be useful down the road so I kept it in the tutorial.
The next step is to add a screen to handle distributing skill points. Right click the GameScreens in the
EyesOfTheDragon project, select Add and then Class. Name this new class SkillScreen. The code for
that screen follows next.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
System.IO;

using
using
using
using

Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Content;

using XRpgLibrary;
using XRpgLibrary.Controls;
using RpgLibrary.SkillClasses;
namespace EyesOfTheDragon.GameScreens
{
internal class SkillLabelSet
{
internal Label Label;
internal LinkLabel LinkLabel;
internal SkillLabelSet(Label label, LinkLabel linkLabel)
{
Label = label;
LinkLabel = linkLabel;
}
}
public class SkillScreen : BaseGameState
{
#region Field Region
int skillPoints;
int unassignedPoints;
PictureBox backgroundImage;
Label pointsRemaining;
List<SkillLabelSet> skillLabels = new List<SkillLabelSet>();

Stack<string> undoSkill = new Stack<string>();


EventHandler linkLabelHandler;
#endregion
#region Property Region
public int SkillPoints
{
get { return skillPoints; }
set
{
skillPoints = value;
unassignedPoints = value;
}
}
#endregion
#region Constructor Region
public SkillScreen(Game game, GameStateManager manager)
: base(game, manager)
{
linkLabelHandler = new EventHandler(addSkillLabel_Selected);
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
ContentManager Content = GameRef.Content;
CreateControls(Content);
}
private void CreateControls(ContentManager Content)
{
backgroundImage = new PictureBox(
Game.Content.Load<Texture2D>(@"Backgrounds\titlescreen"),
GameRef.ScreenRectangle);
ControlManager.Add(backgroundImage);
string skillPath = Content.RootDirectory + @"\Game\Skills\";
string[] skillFiles = Directory.GetFiles(skillPath, "*.xnb");
for (int i = 0; i < skillFiles.Length; i++)
skillFiles[i] = @"Game\Skills\" +
Path.GetFileNameWithoutExtension(skillFiles[i]);
List<SkillData> skillData = new List<SkillData>();
Vector2 nextControlPosition = new Vector2(300, 150);
pointsRemaining = new Label();

pointsRemaining.Text = "Skill Points: " + unassignedPoints.ToString();


pointsRemaining.Position = nextControlPosition;
nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;
ControlManager.Add(pointsRemaining);
foreach (string s in skillFiles)
{
SkillData data = Content.Load<SkillData>(s);
Label label = new Label();
label.Text = data.Name;
label.Type = data.Name;
label.Position = nextControlPosition;
LinkLabel linkLabel = new LinkLabel();
linkLabel.TabStop = true;
linkLabel.Text = "+";
linkLabel.Type = data.Name;
linkLabel.Position = new Vector2(
nextControlPosition.X + 350,
nextControlPosition.Y);
nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;
linkLabel.Selected += addSkillLabel_Selected;
ControlManager.Add(label);
ControlManager.Add(linkLabel);
}

skillLabels.Add(new SkillLabelSet(label, linkLabel));

nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;


LinkLabel undoLabel = new LinkLabel();
undoLabel.Text = "Undo";
undoLabel.Position = nextControlPosition;
undoLabel.TabStop = true;
undoLabel.Selected += new EventHandler(undoLabel_Selected);
nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;
ControlManager.Add(undoLabel);
LinkLabel acceptLabel = new LinkLabel();
acceptLabel.Text = "Accept Changes";
acceptLabel.Position = nextControlPosition;
acceptLabel.TabStop = true;
acceptLabel.Selected += new EventHandler(acceptLabel_Selected);
ControlManager.Add(acceptLabel);
ControlManager.NextControl();
}
void acceptLabel_Selected(object sender, EventArgs e)
{
undoSkill.Clear();
StateManager.ChangeState(GameRef.GamePlayScreen);
}
void undoLabel_Selected(object sender, EventArgs e)
{
if (unassignedPoints == skillPoints)
return;
string skillName = undoSkill.Peek();
undoSkill.Pop();

unassignedPoints++;

// Update the skill points for the appropriate skill


pointsRemaining.Text = "Skill Points: " + unassignedPoints.ToString();

void addSkillLabel_Selected(object sender, EventArgs e)


{
if (unassignedPoints <= 0)
return;
string skillName = ((LinkLabel)sender).Type;
undoSkill.Push(skillName);
unassignedPoints--;
// Update the skill points for the appropriate skill
}

pointsRemaining.Text = "Skill Points: " + unassignedPoints.ToString();

public override void Update(GameTime gameTime)


{
ControlManager.Update(gameTime, PlayerIndex.One);
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin();
base.Draw(gameTime);
ControlManager.Draw(GameRef.SpriteBatch);
GameRef.SpriteBatch.End();
}
}

#endregion

There are several using statements that I added to this class. I needed class from the System.IO name
space for finding all of the skills in the game. There are also using statements to bring a few of the
XNA framework classes into scope. I also added using statements to bring some of the XRpgLibrary
and RpgLibrary classes into scope as well.
There is a second class in the code for the SkillScreen called SkillLabelSet. Its purpose is to hold a
pair of values: a Label and a LinkLabel. For this tutorial it isn't too important but it will be down the
road. It is an internal class. The internal access modifier means that the member is public inside of the
assembly and private outside of the assembly. So, what is an assembly? Example of assemblies are our
game and our libraries. They are a collection of types and resources that are built to work together and
form a logical unit of functionality.
The SkillScreen class itself inherits from the BaseGameState class so it can be used in the state
manager and gives us access to a few protected fields. There are several fields in this class. The first,
skillPoints, is the total number of skill points the player has to spend. The next, unassignedPoints, is
the number of points the player has left to spend. The backgroundImage field is for the background
image of the screen, exactly like the other screens that use a background image. The pointsRemaining
Label will be used to let the player know how many points they have left to spend on skills.
The next field is a List<SkillLabelSet> called skillLabels. The reason this field is hear is that we are
reading in our skills at run time rather than having them hard coded. If they were hard coded our job

would be simpler as we could just design the screen statically rather than dynamically. This is an
example of how we are making more of an engine than a game. The engine can be easily modified
using data files to make the game you want rather than modifying the code. The next field is a
Stack<string> called undoSkill. I decided it would be nice to include an undo feature when spending
skill points. Using a stack is an easy way to implement an undo system. When you perform an action
you push that action onto the stack. If you want to undo the action, you pop it off the stack and reverse
the action.
The last field may have many of you scratching your heads. It is an EventHandler field named
linkLabelHandler. This field will be used to wire event handlers for the LinkLabels in the
SkillLabelSet. The LinkLabels will be generated dynamically. You don't know how many of them
there will be. You want to wire events to them but how do you create an event handler dynamically?
The way I decided to handle it was to have an EventHandler field and create an instance of that event
handler in the constructor. Then for any control that wants to implement that event you can assign the
field.
I'm sure there are a lot of you scratching your heads here. This is an advanced topic and even people
who've been programming for years struggle with it when they see it. I'm not going into events and
event handlers in this tutorial. I'm going to be writing a tutorial, out side of the RPG tutorials, that deals
with events and event handlers. As I said, they cam be a complex topic and deserve a tutorial of their
own.
I also included a single property call SkillPoints. This property is used to get the skillPoints field and
set the skillPoints and unassignedPoints. I don't want to allow setting the unassignedPoints directly.
That could lead to extra skill points being available. Using the property to set them makes sure that
initially they are the same as the skill points.
What the constructor does is create a new EventHandler called addSkillLabel_Selected. Later on in
the class you will see there is a method addSkillLabel_Selected. That is the implementation of the
event handling code.
The LoadContent method creates a local variable Content of type ContentManager. I did this as I
will be using it a lot. It then calls a method, CreateControls, passing in the variable Content.
CreateControls, as the name applies, creates the controls on the screen. Like in other screens it creates
a PictureBox for the background of the screen and adds it to the ControlManager. There is next a
string that I set to be the RootDirectory property of Content plus \Game\Skills\ which is where the
skills are compiled to by the Content Pipeline. There is next an array of strings called skillFiles that
will hold all of the files in the Skills directory. I use the GetFiles method of the Directory class to get
the file name passing in the path and *.xnb for the type of files. xnb is the extension that the Content
Pipeline gave our content when it compiled it. Using xnb files protects your assets from being meddled
with. If you were storing things as straight text an industrious person could get into the text and make
changes to the text. Your game could either be, at best, broken, at worst perverted and passed around
with your name attached to it. That is why I called the second state the worst state as it is almost a form
of identity theft. There is next a for loop that loops through all of the file names that were returned. I
create a new path to the file with out the extension using the GetFileNameWithoutExtension method
of the Path class.

Next I have a local variable that is a List<SkillData> called skillData that will hold all of the skills
temporarily. The nextControlPostion variable is a Vector2 and is used for positioning controls on the
screen. I then create the Label for pointsRemaining. It set its Text property to be Skill Points: plus
the value of unassignedPoints converted to a string. I set its Position property to nextControlPosition
and then increase the Y value of nextControlPosition by the LineSpacing property of the SpriteFont
of ControlManager plus 10 pixels. I then add the Label to the ControlManager.
There is then a foreach loop that loops through all of the strings in skillFiles. I use the Load method of
the Content Manager to load in the SkillData with that name. I create a Label and set its Text and
Type properties to the Name property of the SkillData object. I set the Position property of the Label
to be nextControlPosition. I then create a LinkLabel, set its TabStop property to true, its Text
property to + and its Type property to the Name of the SkillData object. I set the Position property of
the LinkLabel to be the X value of nextControlPosition plus 350 pixel and the same Y value of
nextControlPosition. I then increment the Y value of nextControPosition by LineSpacing and 10
pixels. I then wire the event handler of the Selected event to be addSkillLabel_Selected. I don't have
to use new as I already created a handler in the constructor. I can just assign the handler that I created
earlier. I add the Label and LinkLabel to the Control Manager.
After the loop I increment the Y value of nextControlPosition by LineSpacing plus 10 pixels. I then
create another LinkLabel called undoLabel that will handle if the player changes their mind about
assigning skill points. I set its Text property to Undo, its Position to nextControlPosition, and
TabStop to true. I then wire a new event handler for the Selected event. I increase the Y value of
nextControlPosition by LineSpacing plus 10 pixels. I then add the LinkLabel to the Control
Manager.
I then create one more LinkLabel called acceptLabel that allows the player to accept their choices. I
set Text to Accept Changes, Position to nextControlPosition and TabStop to true. I then wire the
handler for the Selected Event. I then add the control to the Control Manager and call the
NextControl method of the Control Manager to move to the first control.
Next there are the event handlers. The acceptLabel_Selected method handles if the player has
accepted changes. If they have, I clear the undoSkill stack so there is nothing to be undone. I then call
the ChangeState method passing in the GamePlayScreen.
The undoLabel_Selected method handles the player wanting to undo an assignment of skill points. If
the unassignedPoints and skillPoints fields are the same there is nothing to undo so I exit the method.
I use the Peek method of the Stack class to get the value on top of the stack. I then pop the top member
off of the stack. I increment the unassignedPoints field. There is a comment next that you will want to
update the skill points for the skill of the player. I haven't added the data for the player yet so I can't do
that. I then update the Text property of the pointsRemaining label to be the points left.
The addSkillLabel_Selected method handles the player wanting to assign a skill point to a skill. If
unassignedPoints is less than or equal to 0 you don't want to assign any points. So I exit the method. I
get the name of the skill using the Type property of the LinkLabel. The sender parameter is what
triggered the event, in this case a LinkLabel. So, the order of operations is first to cast sender to be a
LinkLabel and then use that to get the Type property. The inner brackets will be assessed before the
other one. I then push skillName onto the stack and decrement unassignedPoints. There is another
comment about work having to be done like in the previous method. I then update the Text property of
the pointsRemaining label to be the points left.

Let's add this new screen to the game. The first thing to do is to add a field of SkillScreen to the
Game1 class and create it in the constructor. Add this field to the Game State Region and change the
constructor to the following.
public SkillScreen SkillScreen;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
ScreenRectangle = new Rectangle(
0,
0,
screenWidth,
screenHeight);
Content.RootDirectory = "Content";
Components.Add(new InputHandler(this));
stateManager = new GameStateManager(this);
Components.Add(stateManager);
TitleScreen = new TitleScreen(this, stateManager);
StartMenuScreen = new StartMenuScreen(this, stateManager);
GamePlayScreen = new GamePlayScreen(this, stateManager);
CharacterGeneratorScreen = new CharacterGeneratorScreen(this, stateManager);
LoadGameScreen = new LoadGameScreen(this, stateManager);
SkillScreen = new GameScreens.SkillScreen(this, stateManager);
}

stateManager.ChangeState(TitleScreen);

Nothing really new there. There is just a public field that can be accessed using the GameRef field in
the BaseGameState. In the constructor I just create a new instance of SkillScreen.
The last thing is that you want to jump from the CharacterGeneratorScreen to the SkillScreen rather
than to the GamePlayScreen. I did that in the linkLabel1_Selected method. Change that method to
the following.
void linkLabel1_Selected(object sender, EventArgs e)
{
InputHandler.Flush();
CreatePlayer();
CreateWorld();

GameRef.SkillScreen.SkillPoints = 25;
StateManager.ChangeState(GameRef.SkillScreen);

What I did here was call the CreatePlayer and CreateWorld methods to create a Player and World
object. If you recall from the beginning I give players 25 points to spend on skills when they are first
created. I assign the SkillPoints property of the SkillScreen class to 25. I then call the ChangeState
method of the GameStateManager passing in the SkillScreen.

I'm going to end this tutorial here. The plan was to add in more to dealing with skills in the game. I
encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest
news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 21
Items in the Game
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
We have been working mostly with data for items and the editors. In this tutorial I'm going to work on
how items will be handled in the game. So a brief discussion on how I intend to implement items is
needed.
In the RpgLibrary there are classes that hold the basic data of items and classes that represent the
actual items. I'm going to add a class to the XRpgLibrary that represent items in the game. This class
will have a Texture2D associated with it so you can have an icon associated with an item. A character
can have these items equipped in different slots. They can have armor in the different armor locations
for example. You are going to want to manage all of the items in the game as well as the items the party
is carrying. This will be a party based engine but you can easily use it with just a single character if you
so desire. There will be a party backpack that will hold items the party is carrying.
The first step is to create a class that will represent an item in the game. Right click the ItemClasses
folder in the XRpgLibrary, select Add and then Class. Name this new class GameItem. The code for
this class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RpgLibrary.ItemClasses;
namespace XRpgLibrary.ItemClasses
{
public class GameItem
{
#region Field Region
public Vector2 Position;
private
private
private
private

Texture2D image;
Rectangle? sourceRectangle;
readonly BaseItem baseItem;
Type type;

#endregion
#region Property Region

public Texture2D Image


{
get { return image; }
}
public Rectangle? SourceRectangle
{
get { return sourceRectangle; }
set { sourceRectangle = value; }
}
public BaseItem Item
{
get { return baseItem; }
}
public Type Type
{
get { return type; }
}
#endregion
#region Constructor Region
public GameItem(BaseItem item, Texture2D texture, Rectangle? source)
{
baseItem = item;
image = texture;
sourceRectangle = source;
}

type = item.GetType();

#endregion
#region Method Region
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(image, Position, sourceRectangle, Color.White);
}
#endregion
#region Virtual Method region
#endregion
}

I will be adding in more to this class as the game evolves. It is just to get things started. The reason that
this class is in the XRpgLibrary rather than the RpgLibrary is that you may want to draw the item. In
that case you will want access to the XNA framework classes. We could move the RpgLibrary into the
XRpgLibrary, that would certainly be a viable option. I'm trying to keep the data separate and at this
time it would be a major headache to try and fix everything. The way things are organized will work
well enough.
In the class itself I added a couple using statements. There are two to bring some XNA framework
classes into scope and one for the ItemClasses from the RpgLibrary.
There are a few fields in the class. The first is public and is a Vector2 called Position. We really don't
care where the item will be drawn so I decided to simplify things and just make it public. Another
reason is how C# deals with properties that are structures, like Vector2. If you have a Vector2 property

you can't assign to the individual properties of the Vector2, the X and Y values for example. It has to
do with how C# handles value types and reference types. Since Vector2 is a struct rather than a class it
is a value type. The other four fields are all private. The image field is a Texture2D. The next field is a
nullable field, indicated by the ? after the type, and is the source rectangle of the image field. In the
overload of the Draw method of SpriteBatch that I use it will accept null for the source rectangle.
There is then a BaseItem field for the actual item. This field is marked readonly so it can only be
assigned to as a class initializer or in the constructor. There is also a Type field for the type of the item.
This isn't to be confused with the string type field from BaseItem. This is the actual type associated
with the BaseItem like Weapon, Armor, or Shield.
There are properties to expose some of the fields. The Image property is read only, or just has a get
part, and returns the image property. I included it as it may be useful. The property is a read and write,
get and set, property and is also nullable. It either returns or sets the sourceRectangle field. The Item
property is also read only. The reason is the baseItem field is marked readonly and can't be set using
the property anyway. The Type property is read only and returns the type field.
There is just one constructor and it takes three parameters. The first is a BaseItem which is the actual
item, a Texture2D for the texture of the image, and a nullable Rectangle for the source rectangle. The
constructor sets the fields based on the values passed in. To set the type field I use the GetType method
that returns the type associated with a variable. It won't return BaseItem if you pass a Weapon in. It
will return Weapon. Even if the variable is a BaseItem variable that you assign a Weapon to. C# is
smart enough to know the inherited type when you're using polymorphism.
At the moment there is just the one method in this class, the Draw method that takes as a parameter an
active SpriteBatch. What I mean by active is in between calls to Begin and End. It calls the Draw
method of the SpriteBatch with the Texture2D, Vector2, Rectangle?, and Color.
I'm going to add a global item manager first that holds all items in the game. Right click the
ItemClasses folder in the XrpgLibrary project, select Add and then class. Name this new class
GameItemManager. The code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RpgLibrary.ItemClasses;
namespace XRpgLibrary.ItemClasses
{
public class GameItemManager
{
#region Field Region
readonly Dictionary<string, GameItem> gameItems = new Dictionary<string, GameItem>();
static SpriteFont spriteFont;
#endregion
#region Property Region
public Dictionary<string, GameItem> GameItems
{

get { return gameItems; }


}
public static SpriteFont SpriteFont
{
get { return spriteFont; }
private set { spriteFont = value; }
}
#endregion
#region Constructor Region
public GameItemManager(SpriteFont spriteFont)
{
SpriteFont = spriteFont;
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
#endregion
}

It is a fairly basic class. I added in using statement for a couple XNA framework name spaces and for
the ItemClasses name space of the RpgLibrary. There are two fields in the class. The first is a
Dictionary<string, GameItem> and is readonly. The second is a SpriteFont and it is static. There are
properties to expose both fields. For the SpriteFont property I include a private set, or write. The
constructor takes a SpriteFont parameter and sets the spriteFont field using the private write property.
This class is not for inventory. It is to hold all of the basic items in the game. Items in inventory will be
handled a little differently. What this class allows is you can read in all items at run time and if you
need to add an item to the player's inventory you can retrieve it from this class. I've been considering
allowing the player to be able to upgrade items. Some items can contain sockets that can be filled to
create interesting effects. The SpriteFont field will be useful if you want to draw text related to an
item.
You are going to need images for item icons. I went to one of my favorite spots when looking for place
holder graphics, http://opengameart.org. They have an excellent collection of free art for open source
games. I found an image by an artist there, Jetrel, that I modified to take the background color out of
the image. You can find my image at http://xnagpa.net/xna4/downloads/itemimages.zip. Download the
file and extract it to a directory. Right click the ObjectSprites in the EyesOfTheDragonContent
folder, select Add and then Existing Item. Navigate to where you extracted the file and add in the
itemimages.png file.
I'm going to expand the Character class so that a character can have some items equipped as well
being able to equip and unequip items. Change the Character class in the XRpgLibrary project to the
following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using RpgLibrary.CharacterClasses;

using XRpgLibrary.SpriteClasses;
using XRpgLibrary.ItemClasses;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.CharacterClasses
{
public class Character
{
#region Field Region
protected Entity entity;
protected AnimatedSprite sprite;
// Armor fields
protected
protected
protected
protected

GameItem
GameItem
GameItem
GameItem

head;
body;
hands;
feet;

// Weapon/Shield fields
protected GameItem mainHand;
protected GameItem offHand;
protected int handsFree;
#endregion
#region Property Region
public Entity Entity
{
get { return entity; }
}
public AnimatedSprite Sprite
{
get { return sprite; }
}
// Armor properties
public GameItem Head
{
get { return head; }
}
public GameItem Body
{
get { return body; }
}
public GameItem Hands
{
get { return hands; }
}
public GameItem Feet
{
get { return feet; }
}
// Weapon/Shield properties
public GameItem MainHand
{
get { return mainHand; }

}
public GameItem OffHand
{
get { return offHand; }
}
public int HandsFree
{
get { return handsFree; }
}
#endregion
#region Constructor Region
public Character(Entity entity, AnimatedSprite sprite)
{
this.entity = entity;
this.sprite = sprite;
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public virtual void Update(GameTime gameTime)
{
entity.Update(gameTime.ElapsedGameTime);
sprite.Update(gameTime);
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
sprite.Draw(gameTime, spriteBatch);
}
public virtual bool Equip(GameItem gameItem)
{
bool success = false;
return success;
}
public virtual bool Unequip(GameItem gameItem)
{
bool success = false;
}

return success;

#endregion
}

So, what is new here. I added in a using statement to bring the ItemClasses of the XRpgLibrary into
scope. I included several new GameItem fields and properties to expose the fields. I place comments
above the two sets to divide them logically. The first set of fields has to do with armor. There a four
regions that armor will fit: head, body, hands, and feet. So, I have four fields for those regions named
head, body, hands, and feet. Since characters have two hands I have two fields for hands, mainHand
and offHand. I could have used right and left hand here but decided to go with main and off. There is
also another field, handsFree, that is an integer that represents the number of free hands. There are
read only, get only, properties to expose all of the new fields. I also included two new virtual methods

Equip and Unequip that return a bool and take a GameItem parameter. These method can be called to
equip and unequip items. They will be expanded on later. Since they are virtual can override them in
any class that inherits from Character if needed. I have them returning a bool value that tells if
equipping or unequipping the item was successful. By default it will be unsuccessful as you can see I
set the success local variable to false and return it at the end of the method.
The last class I want to add is a class for the items the party is carrying. You may want to limit the
number of items in inventory. I'm not going to but it could easily be added. For this I'm going to create
a class called Backpack. The Backpack will be added to the class that will represent the party of
characters in the game. This is going to be a basic class for the moment and is more of a place holder
that will be expanded upon. The Backpack will allow for unique items, like potions that are made by a
character with a high herbalism skill will be more potent than a less skilled character. It also allows for
items with sockets. Right click the ItemClasses folder in the XRpgLibrary folder, select Add and then
Class. Name this new class Backpack. The code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace XRpgLibrary.ItemClasses
{
public class Backpack
{
#region Field Region
readonly List<GameItem> items;
#endregion
#region Property Region
public List<GameItem> Items
{
get { return items; }
}
public int Capacity
{
get { return items.Count; }
}
#endregion
#region Constructor Region
public Backpack()
{
items = new List<GameItem>();
}
#endregion
#region Method Region
public void AddItem(GameItem gameItem)
{
items.Add(gameItem);
}
public void RemoveItem(GameItem gameItem)
{
items.Remove(gameItem);

}
#endregion

#region Virtual Method region


#endregion

This is just a basic class to get us up and going. There is just the one field in the class that is a
List<GameItem> that represents the items in the backpack. I used a generic List rather than a
Dictionary as you can have multiple items by the same name. These multiple items can also have
different properties. I will explain more in a bit. The items field is readonly so that it can only be
assigned to as an initializer or in the constructor. There are two properties in the class. The first is used
to return the items field. The second may be a bit of a misnomer. I added a property, Capacity, that
returns the number of items in the items field. You would expect capacity to be the amount an object
can hold, not the amount in the object. It will work well enough though. The constructor just creates a
new List<GameItem>. I added in two methods: GetItem and RemoveItem. There purpose is to get
and remove items from the backpack. Here you don't want to work on copies of the items. You want to
work on the items themselves. For example, if you fill a socket on a weapon you want to affect the
actual weapon in the backpack and not a copy of the weapon. I will get into that more in a future
tutorial.
I'm going to end this tutorial here. The plan was to add in some basic functionality for handling
inventory in the game. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 22
Reading Data
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to work on reading data into the game and expand a few things. We have all of
those wonderful manager classes in the RpgLibrary but I decided against using them. I instead added
a class to the EyesOfTheDragon project to manage all data in the game called DataManager. Right
click the EyesOfTheDragon project, select Add and then Class. Name this new class DataManager.
The code for that class follows next.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using
using
using
using
using
using

RpgLibrary.CharacterClasses;
RpgLibrary.ItemClasses;
RpgLibrary.SkillClasses;
RpgLibrary.SpellClasses;
RpgLibrary.TalentClasses;
RpgLibrary.TrapClasses;

namespace EyesOfTheDragon.Components
{
static class DataManager
{
#region Field Region
static Dictionary<string, ArmorData> armor = new Dictionary<string,ArmorData>();
static Dictionary<string, WeaponData> weapons = new Dictionary<string,WeaponData>();
static Dictionary<string, ShieldData> shields = new Dictionary<string,ShieldData>();
static Dictionary<string, KeyData> keys = new Dictionary<string, KeyData>();
static Dictionary<string, ChestData> chests = new Dictionary<string, ChestData>();
static Dictionary<string, EntityData> entities = new Dictionary<string,EntityData>();
static Dictionary<string, SkillData> skills = new Dictionary<string, SkillData>();
#endregion
#region Property Region
public static Dictionary<string, ArmorData> ArmorData
{
get { return armor; }
}

public static Dictionary<string, WeaponData> WeaponData


{
get { return weapons; }
}
public static Dictionary<string, ShieldData> ShieldData
{
get { return shields; }
}
public static Dictionary<string, EntityData> EntityData
{
get { return entities; }
}
public static Dictionary<string, ChestData> ChestData
{
get { return chests; }
}
public static Dictionary<string, KeyData> KeyData
{
get { return keys; }
}
public static Dictionary<string, SkillData> SkillData
{
get { return skills; }
}
#endregion
#region Constructor Region
#endregion
#region Method Region
public static void ReadEntityData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Game\Classes", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Classes\" + Path.GetFileNameWithoutExtension(name);
EntityData data = Content.Load<EntityData>(filename);
EntityData.Add(data.EntityName, data);
}
}
public static void ReadArmorData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Game\Items\Armor", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Items\Armor\" + Path.GetFileNameWithoutExtension(name);
ArmorData data = Content.Load<ArmorData>(filename);
ArmorData.Add(data.Name, data);
}
}
public static void ReadWeaponData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Game\Items\Weapon", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Items\Weapon\" + Path.GetFileNameWithoutExtension(name);

WeaponData data = Content.Load<WeaponData>(filename);


WeaponData.Add(data.Name, data);

}
public static void ReadShieldData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Game\Items\Shield", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Items\Shield\" + Path.GetFileNameWithoutExtension(name);
ShieldData data = Content.Load<ShieldData>(filename);
ShieldData.Add(data.Name, data);
}
}
public static void ReadKeyData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Game\Keys", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Keys\" + Path.GetFileNameWithoutExtension(name);
KeyData data = Content.Load<KeyData>(filename);
KeyData.Add(data.Name, data);
}
}
public static void ReadChestData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Game\Chests", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Chests\" + Path.GetFileNameWithoutExtension(name);
ChestData data = Content.Load<ChestData>(filename);
ChestData.Add(data.Name, data);
}
}
public static void ReadSkillData(ContentManager Content)
{
string[] filenames = Directory.GetFiles(@"Content\Skills", "*.xnb");
foreach (string name in filenames)
{
string filename = @"Game\Skills\" + Path.GetFileNameWithoutExtension(name);
SkillData data = Content.Load<SkillData>(filename);
SkillData.Add(data.Name, data);
}
}
#endregion
#region Virtual Method region
#endregion
}

There is a lot of code there but most of it is the same with just a few minor differences. First off, this is
a static class. When you make a class static it is created the first time it is referenced, you don't need to
call its constructor to create it. You can provide a static constructor for a static class but I instead
created the instances of the fields explicitly.
I added in a using statement for the System.IO name space to bring a few classes into scope for

manipulating files and paths. I also added a using statement for a couple XNA framework name spaces
and for many of the RpgLibrary name spaces as well.
There are a number of Dictionary<string, T> fields and properties where T is the type of data
associated with the field or property. ArmorData works with armor for example. The fields are private
and the properties are public. The properties are also read only, or get only. There are also a number of
methods ReadT(ContentManager Content) where T is again a type of data. I need the
ContentManager associated with the game to read in the data as it has been compiled by the Content
Pipeline.
Each of the ReadT methods works the same way. I first get all of the files in a specific directory with
an xnb extension, the extension given to content when it is processed by the Content Pipeline, that is
read into a local variable filenames. In a foreach loop I loop through of the file names. I then create a
string that is the same as the asset name given to the file when it is processed by the Content Pipeline.
I then use the Load method of the ContentManager to load the asset into the variable data. The data
variable is then added to the appropriate Dictionary<string, T> where the string is the name of the
data and T is the actual data.
Reading in the data in the game is relatively painless. First, make sure you have a using statement for
the Components name space of EyesOfTheDragon. Then in the LoadContent method of the Game1
class call each of the Read methods. Add the following using statement to the Game1 class and change
the LoadContent method to the following.
using EyesOfTheDragon.Components;
protected override void LoadContent()
{
SpriteBatch = new SpriteBatch(GraphicsDevice);
DataManager.ReadEntityData(Content);
DataManager.ReadArmorData(Content);
DataManager.ReadShieldData(Content);
DataManager.ReadWeaponData(Content);
DataManager.ReadChestData(Content);
DataManager.ReadKeyData(Content);
}

DataManager.ReadSkillData(Content);

Now we have access to all of the data we created in the game. There is one small drawback to the way
things work right now. Every time you add new items using the editor you need to add it to the
EyesOfTheDragonContent project. The easiest way to do that is to just drag the Game folder you
data lives in from a Windows Explorer window onto the EyesOfTheDragonContent project in Visual
Studio.
Now that we have all of our data it is time to update a few things. The first thing I want to update is the
Player class. Instead of having an AnimatedSprite field, I want to have a Character field. This will
break a few things but now is the best time to tackle this. Change the code of the Player class of the
EyesOfTheDragon project to the following.
using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using
using
using
using

XRpgLibrary;
XRpgLibrary.TileEngine;
XRpgLibrary.SpriteClasses;
XRpgLibrary.CharacterClasses;

namespace EyesOfTheDragon.Components
{
public class Player
{
#region Field Region
Camera camera;
Game1 gameRef;
readonly Character character;
#endregion
#region Property Region
public Camera Camera
{
get { return camera; }
set { camera = value; }
}
public AnimatedSprite Sprite
{
get { return character.Sprite; }
}
public Character Character
{
get { return character; }
}
#endregion
#region Constructor Region
public Player(Game game, Character character)
{
gameRef = (Game1)game;
camera = new Camera(gameRef.ScreenRectangle);
this.character = character;
}
#endregion
#region Method Region
public void Update(GameTime gameTime)
{
camera.Update(gameTime);
Sprite.Update(gameTime);
if (InputHandler.KeyReleased(Keys.PageUp) ||
InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One))
{
camera.ZoomIn();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);
}
else if (InputHandler.KeyReleased(Keys.PageDown) ||
InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One))

camera.ZoomOut();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);

Vector2 motion = new Vector2();


if (InputHandler.KeyDown(Keys.W) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Up;
motion.Y = -1;
}
else if (InputHandler.KeyDown(Keys.S) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Down;
motion.Y = 1;
}
if (InputHandler.KeyDown(Keys.A) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Left;
motion.X = -1;
}
else if (InputHandler.KeyDown(Keys.D) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Right;
motion.X = 1;
}
if (motion != Vector2.Zero)
{
Sprite.IsAnimating = true;
motion.Normalize();
Sprite.Position += motion * Sprite.Speed;
Sprite.LockToMap();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);

}
else
{
Sprite.IsAnimating = false;
}

if (InputHandler.KeyReleased(Keys.F) ||
InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One))
{
camera.ToggleCameraMode();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);
}
if (camera.CameraMode != CameraMode.Follow)
{
if (InputHandler.KeyReleased(Keys.C) ||
InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One))
{
camera.LockToSprite(Sprite);
}
}
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)

{
}

character.Draw(gameTime, spriteBatch);

#endregion
}

What has changed here is first I added in a using statement for the CharacterClasses name space from
the XRpgLibrary. I then replaced the AnimatedSprite field with a Character field character. In the
Sprite property instead of returning the AnimatedSprite field I return the Sprite property of the
character field. I change the constructor to take a Character argument rather than an AnimatedSprite
argument. I set the character field to the character parameter. In the Update method I replace the
sprite field with the Sprite property. In the Draw method I call the Draw method of the character
field.
That is going to break the LoadGameScreen and the CharacterGeneratorScreen because when they
created the Player component they were passing in an AnimatedSprite and now it is expecting a
Character. I will fix the LoadGameScreen first. What you will want to do is to create an Entity in the
CreatePlayer method and then a Character object. When you create the Player object pass in the
Character object. Change the CreatePlayer method of the LoadGameScreen class to the following.
private void CreatePlayer()
{
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
AnimatedSprite sprite = new AnimatedSprite(
GameRef.Content.Load<Texture2D>(@"PlayerSprites\malefighter"),
animations);
Entity entity = new Entity(
"Encelwyn",
DataManager.EntityData["Fighter"],
EntityGender.Male,
EntityType.Character);
Character character = new Character(entity, sprite);
GamePlayScreen.Player = new Player(GameRef, character);
}

When I created the Entity I used some magic values. For the name I used, Encelwyn, a name I use a
lot in games. I also specified that the character will be a fighter and a male. You do want the Entity to
be a Character though so we are fine there. Eventually we will be writing out the player and reading
the player back in. It does make handling creating the entity in the CharacterGeneratorScreen is a
little more problematic. You would like selections on the CharacterGeneratorScreen to reflect the
data that you have created. The name of the character will be the hardest to deal with as you may want
the player to be able to choose a name for their character. It can be done easier on the Xbox than in
Windows as you can bring up the keyboard on the Xbox. In Windows you will want to create a text box

to enter data into. For the name we will just use a fixed value for now. Filling the class selector with the
available classes won't be that hard. Loading the sprites also won't be that difficult because of the way I
named the sprites.
Let's get to the changes. The first change is you want to remove the initialization of the classItems field
in the CharacterGeneratorScreen. Change that field to the following and add a using statement for
the CharacterClasses name space of the RpgLibrary and XRpgLibrary.
using RpgLibrary.CharacterClasses;
using XrpgLibrary.CharacterClasses;
string[] classItems;

Now, in the LoadContent method you will want create the array of strings for classItems and fill it.
You will do that in the LoadContent method. Change the LoadContent method to the following.
protected override void LoadContent()
{
base.LoadContent();
classItems = new string[DataManager.EntityData.Count];
int counter = 0;
foreach (string className in DataManager.EntityData.Keys)
{
classItems[counter] = className;
counter++;
}
LoadImages();
CreateControls();
containers = Game.Content.Load<Texture2D>(@"ObjectSprites\containers");
}

What the new code does is create a new array that is the size of the Count property of EntityData in
the DataManager. You create the CharacterGeneratorScreen in the constructor of Game1 before the
LoadContent method is called. How can you use those values here? The reason is that because you
created the instance of CharacterGeneratorScreen in the constructor of Game1 its LoadContent
method isn't called until its Initialize method is called. That will be when it is added to the list of
components in the game by the state manager. If you set a break point at the call to base.LoadContent
in Visual Studio you will see that the method is not called until after the player selects the menu item
for creating a new game.
What the new code is doing is creating a new string array the size of the Count property of the
EntityData property of the DataManager class. There is then a variable I called counter set to zero
initially that will hold what index I am on. In a foreach loop I loop through all of the keys in the Keys
collection of EntityData. I assign the classItems array value at index counter to the current key and
then I increment the counter variable.
The last thing to do is to update the CreatePlayer method of CharacterGeneratorScreen. Like in the
LoadGameScreen class you want to create a Character object and pass it to the call to the constructor
of the Player class. The difference is that you will use the values from the screen. Change the
CreatePlayer method of the CharacterGeneratorScreen to the following.

private void CreatePlayer()


{
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
AnimatedSprite sprite = new AnimatedSprite(
characterImages[genderSelector.SelectedIndex, classSelector.SelectedIndex],
animations);
EntityGender gender = EntityGender.Male;
if (genderSelector.SelectedIndex == 1)
gender = EntityGender.Female;
Entity entity = new Entity(
"Pat",
DataManager.EntityData[classSelector.SelectedItem],
gender,
EntityType.Character);
Character character = new Character(entity, sprite);
GamePlayScreen.Player = new Player(GameRef, character);
}

What the new code does is first create a local variable of type EntityGender and assign it to be Male
by default. Then in an if statement I check to see if the SelectedIndex of the genderSelector is 1. If it
is, I set the local variable gender to Female. I then create a new Entity passing in, Pat, for the name,
the EntityData selected in the classSelectror, the gender variable, and Character for the EntityType.
I then create a new Character and using it in the call to the constructor of the Player class. I know Pat
isn't very RPGish but it will work for now. A better solution will be after the player has confirmed their
choices of gender and class to move to a screen they can select a name from.
I'm going to end this tutorial here. The plan was to add in some basic functionality for reading data into
the game. I encourage you to visit the news page of my site, XNA Game Programming Adventures, for
the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 23A
Level Editor
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
You all knew that this was coming at some point. You will want to be able to create your world at
design time and read it in at run time. For that you will need a world, or level, editor. To accomplish
that I'm going to add a new project that will be used to create your levels and other content that is XNA
related. The RpgEditor is great for creating data to be used with the game but you are going to want to
associated XNA related content to NPCs as an example. You will want a sprite to represent the NPC in
the game.
To get started right click the EyesOfTheDragon solution, not the project, in the solution explorer.
Select Add and then New Project. Choose a Windows Game (4.0) from the list of XNA projects.
Name this new project XLevelEditor. You might be wondering why I'm adding a new game project
and not a Windows Forms project. The reason has to do with Windows 7 and it not finding the XNA
assemblies at run time. The alternative is to create an XNA Windows game and add a reference to
System.Windows.Forms.
Do that now by right clicking XLevelEditor in the solution explorer and selected Add Reference.
From the .NET tab select System.Windows.Forms. You are also going to want references to the
libraries we created. Right click XRpgEditor again and select Add Reference. From the Project tab
select the RpgLibrary and XRpgLibrary projects.
There is a problem however. This project is set to launch an XNA Windows game and not Windows
Forms application. You are first going to want to add a Windows Form to the XRpgEditor project.
Right click the XRpgEditor, select Add and then Windows Form. Name this new form FormMain.
Now, open the Program.cs file in the XRpgEditor project and chance it to the following.
using System;
using System.Windows.Forms;
namespace XRpgEditor
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

using (FormMain frmMain = new FormMain())


{
Application.Run(frmMain);
}
}

What this code is doing is similar to the Program.cs file in the RpgEditor. There is a using statement
to bring the System.Windows.Forms name space into scope for the Application class. Inside the
Main method, the first method called by C# when a program is executed, calls a few methods of the
Application class to set some styles for the form. Then in a using statement I create a new instance of
FormMain and inside of the using statement call Application.Run to start the form. When the form
closes all disposable resources held by the form will automatically be released instead of waiting for
the garbage collector to release them. I had to mark Main with an attribute, STAThread, to allow
certain thread related tasks to work.
There are a few more things to do to prepare the project. One is that you want to change the project so
that it uses the Reach profile. I'm doing this because sometimes I will be using my laptop for the
tutorials and it doesn't support the HiDef profile. Right click the XRpgEditor and select Properties.
On the XNA Game Studio tab set the Game Profile to Use Reach. While you have the properties for
the properties select the Application tab. Change the Target framework to be .NET Framework 4
instead of .NET Framework 4 Client Profile. Reply Yes to the dialog that pops up. You can close the
Properties now. The last step is to add a reference to the project for the IntermediateSerializer so you
can read and write your data from the editor. Right click the XRpgEditor project and select Add
Reference. From the .NET tab add Microsoft.Xna.Framework.Content.Pipeline and make sure you
add version 4.0.0.0 for XNA 4.0.
You are going to want to host XNA inside of the editor. For that you will want a few classes from the
WinForms Series 1: Graphics sample from App Hub. You can find the sample from the following
link: http://create.msdn.com/en-US/education/catalog/sample/winforms_series_1 Download and extract
the files to a directory. Navigate to that directory and drag the following files from windows explorer
onto the XRpgEditor project: GraphicsDeviceControl.cs, GraphicsDeviceService.cs, and
ServiceContainer.cs.
The next step is to create a class for drawing the map in. Right click the XRpgEditor project, select
Add and then Class. Name this new class MapDisplay. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace XLevelEditor
{
class MapDisplay : WinFormsGraphicsDevice.GraphicsDeviceControl
{
public event EventHandler OnInitialize;
public event EventHandler OnDraw;
protected override void Initialize()
{
if (OnInitialize != null)
OnInitialize(this, null);
}

protected override void Draw()


{
if (OnDraw != null)
OnDraw(this, null);
}

The first step is that I inherit the class from WinFormsGraphicsDevice.GraphicsDeviceControl that
is from the WinForms series from App Hub that represents a control that can be rendered to using
XNA. This is an abstract class and you need to implement two methods: Initialize and Draw. I also
added in two event handlers called OnInitialize and OnDraw. In the Initialize method I check to see if
OnIntialize is not null and if it isn't I call the OnInitialize method. I do something similar for the
Draw method but with OnDraw instead of OnInitialize.
Build your project now. After building it bring up the design view of FormMain by right clicking it
and selecting View Designer. If you expand the Toolbox you should see something similar to the
following at the top of the Toolbox.

The next step is going to be to design some forms. The main form will be the actual editor. The child
forms will be for adding items to a level. I found using tables for designing forms is much nicer than
just having a bunch of scrawling text. Set the following properties for FormMain.
FormMain
Size

1024, 720

StartPosition

CenterParent

Now, drag a Menu Strip onto FormMain. The items in the first row are the menu items along the top.
Any items in a column under item are the menu items below it.
&Level

&Tileset

&Map Layer

&New Level

&New Tileset

&New Layer

&Open Level

&Open Tileset

&Open Layer

&Save Level

&Save Tileset

&Save Layer

&Remove Tileset

&Characters

C&hests

&Keys

E&xit

The next control that I added to the form was a Split Container. The Split Container has two panels
that can hold controls. In the one panel I added in a MapDisplay control that I created earlier. The
other panel holds a Tab Control that has tabs for the different parts of the map, tiles, map layers,
characters, etc.

Drag a Split Container onto FormMain. Set the following properties for the Split Container. The
default values for the other properties are fine.
Property

Value

(Name)

splitContainer1

Dock

Fill

SplitterDistance

800

Onto Panel1 of the Split Container, the one of the left, drag on a Map Display control that you
created earlier. Set the following properties for the Map Display control.
Property

Value

(Name)

mapDisplay

Dock

Fill

TabIndex

Onto Panel2 of the Split Container, drag on a Tab Control. The properties you want to set for the Tab
Control are next.

Property

Value

(Name)

tabProperties

Dock

Fill

TabIndex

In the properties window for the Tab Control there is an entry TabPages. Click the (Collection) part
will bring up a dialog for the pages. Select the default pages and click the Remove button to remove
them. You will now add in pages for the tabs. There are 5 tabs in total. Set the following properties for
the tabs.
#

(Name)

Text

tabTilesets

Tiles

tabLayers

Map Layers

tabCharacters

Characters

tabChests

Chests

tabKeys

Keys

That takes up more room but I think that it looks a little nicer and is a little more precise when it comes
to designing forms. The next step is to design the tabs. Bring up the Tiles tab in the editor. You can do
that by clicking the Tiles tab in the Tab Control. If that tab is not visible clicking the arrow buttons
will move between tabs. What my Tiles tab looks like is on the next page.
The tab is made up of three Labels, one Group Box, two Picture Boxes, two Radio Buttons, one List
Box, and a Numeric Up Down control. The first control to drag onto the tab is a Label. Set the
following properties of the Label.
Property

Value

(Name)

lblTile

AutoSize

FALSE

Location

7, 7

Size

50, 17

Text

Tile

TextAlign

TopCenter

Now, drag on a Picture Box under lblTile. Set these properties of the Picture Box.
Property

Value

(Name)

pbTilePreview

Location

7, 27

Size

50, 50

Now, drag a Group Box to the right of lblTile. Set the following
properties for the Group Box.
Property

Value

(Name)

gbDrawMode

Location

63, 7

Size

128, 70

Text

Draw Mode

Onto the Group Box you are going to drag on two Radio Buttons. Set
the following properties for the Radio Buttons.
Property

Value

(Name)

rbDraw

AutoSize

TRUE

Checked

TRUE

Location

7, 20

Text

Draw

Property

Value

(Name)

rbErase

AutoSize

TRUE

Location

7, 43

Text

Erase

Under the other controls drag on a Numeric Up Down control. Set the
following properties for that control.
Property

Value

(Name)

nudCurrentTile

Location

7, 83

Size

180, 22

Under the Numeric Up Down control drag a Label control. Set the following properties for the Label.
Property

Value

(Name)

lblCurrentTileset

AutoSize

FALSE

Location

7, 112

Size

180, 23

Text

Current Tileset

TextAlign

TopCenter

Under that you will drag on a Picture Box. Set the following properties for the Picture Box.
Property

Value

(Name)

pbTilesetPreview

Location

7, 138

Size

180, 180

Under the Picture Box control drag a Label control. Set the following properties for the Label.
Property

Value

(Name)

lblTilesets

AutoSize

FALSE

Location

7, 321

Size

180, 23

Text

Tilesets

TextAlign

TopCenter

The last control to drag on is a List Box. Set the following properties for the List Box.
Property

Value

(Name)

lbTileset

Location

7, 352

Size

180, 260

The last thing I'm going to design is the Map Layers tab. There is just the one control on the Map
Layers tab, a Checked List Box. I used a Checked List Box rather than a List Box to control how the
layers are drawn. If you have a layer in the list box checked it will be drawn, otherwise it won't be
drawn. Select the Map Layers tab and drag a Checked List Box onto the tab. Set the following
properties of the Checked List Box.
Property

Value

(Name)

clbLayers

Dock

Fill

To be able to use this feature I need to make a quick change to the TileMap and MapLayer classes.
What I'm going to do is instead of drawing the tiles in the TileMap class, have the MapLayer class

draw the tiles. I will start with the MapLayer class. You will want to add using statements for the basic
XNA framework and the XNA framework Graphics classes. Add the following using statements and
method to the MapLayer class.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
public void Draw(SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets)
{
Point cameraPoint = Engine.VectorToCell(camera.Position * (1 / camera.Zoom));
Point viewPoint = Engine.VectorToCell(
new Vector2(
(camera.Position.X + camera.ViewportRectangle.Width) * (1 / camera.Zoom),
(camera.Position.Y + camera.ViewportRectangle.Height) * (1 / camera.Zoom)));
Point min = new Point();
Point max = new Point();
min.X
min.Y
max.X
max.Y

=
=
=
=

Math.Max(0, cameraPoint.X
Math.Max(0, cameraPoint.Y
Math.Min(viewPoint.X + 1,
Math.Min(viewPoint.Y + 1,

- 1);
- 1);
Width);
Height);

Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);


Tile tile;
for (int y = min.Y; y < max.Y; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = min.X; x < max.X; x++)
{
tile = GetTile(x, y);
if (tile.TileIndex == -1 || tile.Tileset == -1)
continue;
destination.X = x * Engine.TileWidth;

}
}

spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);

Basically what I did was move the code for drawing a map to the layer and fixed up a couple syntax
errors caused by moving things. I needed to pass the List<Tileset> to the method as the tilesets are part
of the map. I could have moved the List<Tileset> to the layer but that would make the changes more
difficult to do.
The change to the TileMap class was much simpler. There is now just a foreach loop that loops
through all of the layers calling the Draw method of each layer passing in the appropriate values.
Change the Draw method of the TileMap class to the following.
public void Draw(SpriteBatch spriteBatch, Camera camera)
{
foreach (MapLayer layer in mapLayers)
{
layer.Draw(spriteBatch, camera, tilesets);
}
}

Rather than reading and writing the classes in the XRpgLibrary directly, I'm going to add some data
classes to the RpgLibrary. You read and write the data classes from the editor. When the player
unlocks an area of your world you read in the data using the Content Pipeline. Since they are changing
the world as they explore it you will be using a different mechanism for saving and loading games.
To get started, right click the RpgLibrary and select New Folder. Name the new folder WorldClasses.
Right click the WorldClasses folder, select Add and then Class. Name the new class TilesetData. The
code for that class follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.WorldClasses
{
public class TilesetData
{
public string TilesetName;
public string TilesetImageName;
public int TileWidthInPixels;
public int TileHeightInPixels;
public int TilesWide;
public int TilesHigh;
}
}

Just a basic public class that has enough data to create a Tileset from the XRpgLibrary. There are two
string fields, TilesetName and TilesetImageName. TilesetName is the name of the tileset and will be
used in a manager class. TilesetImageName is the name of the file that holds the image for the tileset.
There are then four integer fields that describe the tiles in a tileset. There are fields for the width and
height of the tiles in the tileset, TileWidthInPixels and TileHeightInPixels. There are also fields for
the number of tiles wide the tile set is and how many tiles high, TilesWide and TilesHigh. With those
values you have enough information to pass to the constructor of the Tileset class to create a Tileset.
The next class to add is a class to represent a map layer as maps are made up of lists of Tileset and
MapLayer objects. Right click the WorldClasses folder in the RpgLibrary folder, select Add and
then Class. Name this new class MapLayerData. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.WorldClasses
{
public struct Tile
{
public int TileIndex;
public int TileSetIndex;

public Tile(int tileIndex, int tileSetIndex)


{
TileIndex = tileIndex;
TileSetIndex = tileSetIndex;
}

public class MapLayerData

{
public
public
public
public

string MapLayerName;
int Width;
int Height;
Tile[] Layer;

private MapLayerData()
{
}
public MapLayerData(string mapLayerName, int width, int height)
{
MapLayerName = mapLayerName;
Width = width;
Height = height;
Layer = new Tile[height * width];
}
public void SetTile(int x, int y, Tile tile)
{
Layer[y * Width + x] = tile;
}

public Tile GetTile(int x, int y)


{
return Layer[y * Width + x];
}

I added a structure to this class for tiles. It has public integer fields for the index of the tile and the
index of the tileset. I included a constructor that requires two parameters: tileIndex and tileSetIndex.
They are for the index of the tile and the index of the tileset respectively. Making it a structure rather
than a class makes it a value type rather than a reference type. So, when I create the array for tiles in the
MapLayerData the tiles automatically have values.
The MapLayerData class has four public fields: Layer that is a single dimension array of Tile,
MapLayerName that is a string for the name of the layer, Width and Height that are integers and are
the width and height of the map. Why did I choose a single dimension array rather than a two
dimension array like in the tile engine? You can't serialize arrays with more than one dimension. You
can simulate a multidimension array in a one dimension array though.
There are two constructors for the MapLayerData class. There is a private constructor that takes no
parameters that will be used in serializing and deserializing the class. The public constructor takes three
paramaters: mapLayerName that is the name of the map layer, width and height that are the width
and height of the map. The public constructor first sets the MapLayerName, Width, and Height fields
to the values passed in. I then create a single dimension array of Tile that is height times width. To find
out how many elements are in a 2D array you multiple the width of the array by the height of the array
so to represent a 2D array in a 1D array you need an array that is that size.
There are two public methods in this class. The first, SetTile, sets that tile that is represented at
coordinates x and y to the Tile passed in. To set the element, and retrieve the element, you need to be
consistent in determining the index. I used the calculation y * Width + x. Lets think about this a bit.
Take a small 2D array, [4, 3]. You can represent it using a 1D array [12]. You have y between 0 and 3
and x between 0 and 2 in loops like this.
for (y = 0; y < 4; y++)

for (x = 0; x < 3; x++)

When you y at 0 the calculation for the 1D array will be 0 * 3 + 0 = 0, 0 * 3 + 1 = 1, and 0 * 3 + 2 = 2.


Then if you move to y at 1 you will get 1 * 3 + 0 = 3, 1 * 3 + 1 = 4, and 1 * 3 + 2 = 5. When you then
move to y at 2 you will get 2 * 3 + 0 = 6, 2 * 3 + 1 = 7, and 2 * 3 + 2 = 8. Finally, when you move y to
3 you will get 3 * 3 + 0 = 9, 3 * 3 + 1 = 10, and 3 * 3 + 2 = 11. You can see that using the calculation y
* WidthOfArray + x you can determine the position of a 2D element at (x, y) in 1D array. This is
pretty much what the computer does when you declare a 2D array. It sets aside an area of memory the
size of the array. It uses calculations like above to decide where the element resides in memory.
Now that you have classes that represent a tileset and a map layer you can create a class that can
represent an entire map. Right click the WorldClasses in the RpgLibrary project, select Add and then
Class. Name this new class MapData. The code for that class follows next.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RpgLibrary.WorldClasses
{
public class MapData
{
public string MapName;
public MapLayerData[] Layers;
public TilesetData[] Tilesets;
private MapData()
{
}

public MapData(string mapName, List<TilesetData> tilesets, List<MapLayerData> layers)


{
MapName = mapName;
Tilesets = tilesets.ToArray();
Layers = layers.ToArray();
}

Not a very complex class. There are just three fields. A string for the name of the map, an array of
MapLayerData for the layers in the map, and an array of TilesetData for the tilesets in the map. There
is a private constructor that will be used in serializing and deserializing the map and a public
constructor that takes three parameters: the name of the map, a List<TilesetData> for the tilesets the
maps uses, and a List<MapLayerData> for the layers in the map. It sets the fields using the values
that are passed to the constructor. I use the ToArray method of the List<T> class to convert the lists to
arrays.
I want to add another data class, for levels. Right click the WorldClasses in the RpgLibrary project,
select Add and then Class. This is the code for that class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.WorldClasses
{
public class LevelData

{
public
public
public
public
public
public
public

string LevelName;
string MapName;
int MapWidth;
int MapHeight;
string[] CharacterNames;
string[] ChestNames;
string[] TrapNames;

private LevelData()
{
}

public LevelData(
string levelName,
string mapName,
int mapWidth,
int mapHeight,
List<string> characterNames,
List<string> chestNames,
List<string> trapNames)
{
LevelName = levelName;
MapName = mapName;
MapWidth = mapWidth;
MapHeight = mapHeight;
CharacterNames = characterNames.ToArray();
ChestNames = chestNames.ToArray();
TrapNames = trapNames.ToArray();
}

Another rather straight forward class. There are fields for the name of the level, LevelName, the map
for the level, MapName, the width and height of the map, MapWidth and MapHeight, the names of
characters on the map, CharacterNames, the name of chests on the map, ChestNames, and the names
of traps on the map, TrapNames. The private constructor will be used in serializing and deserializing
objects. The public constructor takes a parameter for each of the fields. For the arrays of strings I pass
in List<string> and use the ToArray method of List<T>.
I'm going to move back to the editor now. I want to add in a form for creating a new level. Right click
the XLevelEditor project in the solution explorer, select Add and then Windows Form. Name this
new form FormNewLevel. My finished form looked like this.

To start you are going to want to set some of the properties of the form itself. They are in the following
table.

Property

Value

ControlBox

FALSE

FormBorderStyle

FixedDialog

Size

225, 210

StartUpPosition

CenterParent

Text

New Level

Onto the form I dragged four Labels, two Text Boxes, two Masked Text Boxes and two Buttons. I
will do the controls in pairs that are in the same row of the form. Drag on a Label and Text Box onto
the form. Set the following properties.
Label
Property

Value

(Name)

lblLevelName

Location

13, 15

Text

Level Name:

Text Box
Property

Value

(Name)

tbLevelName

Location

106, 12

Drag on another Label and Text Box setting the following properties for them.
Label
Property

Value

(Name)

lblMapName

Location

17, 42

Text

Map Name:

Text Box
Property

Value

(Name)

tbMapName

Location

106, 42

The next pair of controls are a Label and a Masked Text Box. Drag them on and set these properties.
Label
Property

Value

(Name)

lblMapWidth

Location

21, 75

Text

Map Width:

Masked Text Box


Property

Value

(Name)

mtbWidth

Location

106, 72

Mask

0000 (four zeros)

Size

45, 22

The next pair of controls are a Label and a Masked Text Box. Drag them on and set these properties.
Label
Property

Value

(Name)

lblMapHeight

Location

13, 106

Text

Map Height:

Masked Text Box


Property

Value

(Name)

mtbHeight

Location

106, 103

Mask

0000 (four zeros)

Size

45, 22

The last two controls to add are two Button controls. Add them and set these properties.
(Name)

Location

Size

Text

btnOK

13, 142

75, 23

OK

btnCancel

106, 142

75, 23

Cancel

Now that the form has been designed it is time to code its logic. Right click FormNewLevel in the
XLevelEditor project and select View Code. Change the code for the form to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.WorldClasses;
namespace XLevelEditor
{
public partial class FormNewLevel : Form
{
#region Field Region
bool okPressed;
LevelData levelData;
#endregion
#region Property Region
public bool OKPressed
{
get { return okPressed; }
}
public LevelData LevelData
{
get { return levelData; }
}
#endregion
#region Constructor Region
public FormNewLevel()
{
InitializeComponent();
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Button Event Handler Region
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbLevelName.Text))
{
MessageBox.Show("You must enter a name for the level.", "Missing Level Name");
return;
}

Name");

if (string.IsNullOrEmpty(tbMapName.Text))
{
MessageBox.Show("You must enter a name for the map of the level.", "Missing Map
}

return;

int mapWidth = 0;
int mapHeight = 0;
if (!int.TryParse(mtbWidth.Text, out mapWidth) || mapWidth < 1)
{
MessageBox.Show("The width of the map must be greater than or equal to one.",
"Map Size Error");
return;
}
if (!int.TryParse(mtbHeight.Text, out mapHeight) || mapHeight < 1)
{
MessageBox.Show("The height of the map must be greater than or equal to one.",
"Map Size Error");

return;
}
levelData = new RpgLibrary.WorldClasses.LevelData(
tbLevelName.Text,
tbMapName.Text,
mapWidth,
mapHeight,
new List<string>(),
new List<string>(),
new List<string>());

okPressed = true;
this.Close();

void btnCancel_Click(object sender, EventArgs e)


{
okPressed = false;
this.Close();
}
#endregion
}
}

There is a using statement to bring the LevelData class that I added into scope. There are two fields in
this class and properties to read their values, but not write them. The first, okPressed, determines how
the form was closed, by the OK button or the Cancel button. The second, LevelData, holds the
information off the form if the form was closed successfully. The constructor wires event handlers for
the Click events of the buttons.
In the handler for the Click event for btnOK I validate the form. If the Text property of tbLevelName
is null or empty I display a message box stating that a name must be entered for the level and exit the
method. Similarly, I display a message and exit if the Text property of tbMapName is null or empty.
There are then two local variables that will hold the conversion of the Text property of the Masked
Text Boxes into integers. I use the TrParse method to attempt to parse the Text property of mtbWidth.
If the return value is false the second half of the expression will not be evaluated. If the attempt was
successful I then check if the result is less than 1. If either evaluate to true I display a message and exit
the method. I do something similar for mtbHeight but work with height instead of width. You should
really do better evaluation than the map width and height less than 1. If the form passed validation I
create a new LevelData object using the Text properties of the two Text Boxes, the mapWidth and
mapHeight local variables, and new List<string> instance for the List<string> parameters. I then set
okPressed to true and close the form.
The handler for the Click event for btnCancel is much simpler. It just sets the okPressed field to false
and then closes the form.
I want to create a form for creating TilesetData objects. Right click the XLevelEditor, select Add and
then Windows Form. Name this new form FormNewTileset. My form looked like is on the next page.
There are quite a few controls on the form. There are six Labels, two Text Boxes, three Buttons, and
four Masked Text Boxes. The controls are again positioned in rows of two columns, except for the row
for selecting a tileset image name. This row has a third column, a Button, that is used to select the
image file associated with the tileset.

I set several of the properties for the form itself, shown in the next table.
Property

Value

ControlBox

FALSE

FormBorderStyle

FixedDialog

Size

300, 240

StartPosition

CenterParent

Text

New Tileset

I'm going to do the controls like the last form a row at a time. The first controls to drag on are a Label
and a Text Box. Set the following properties for those controls.
Label
Property

Value

(Name)

lblTilesetName

Location

54, 10

Text

Tileset Name:

Text Box
Property

Value

(Name)

tbTilesetName

Location

155, 5

For the next row you will need three controls, a Label, a Text Box, and a Button. Set these properties.
Label
Property

Value

(Name)

lblTilesetImageName

Location

12, 40

Text

Tileset Image Name:

Text Box
Property

Value

(Name)

tbTilesetImage

Enabled

FALSE

Location

155, 34

Button
Property

Value

(Name)

btnSelectImage

Location

261, 34

Size

28, 23

Text

...

The next four rows are sets of Labels and Masked Text Boxes. So, drag a Label and Masked Text
Box onto the form and set the following properties.
Label
Property

Value

(Name)

lblTileWidth

Location

74, 69

Text

Tile Width:

Masked Text Box


Property

Value

(Name)

mtbTileWidth

Location

155, 62

Mask

000 (three zeros)

Size

34, 22

Drag another Label and Masked Text Box onto the form and set the following properties.
Label
Property

Value

(Name)

lblTileHeight

Location

74, 69

Text

Tile Height:

Masked Text Box


Property

Value

(Name)

mtbTileHeight

Location

155, 90

Mask

000 (three zeros)

Size

34, 22

Drag another Label and Masked Text Box onto the form and set the following properties.
Label
Property

Value

(Name)

lblTilesWide

Location

17, 121

Text

Number Tiles Wide:

Masked Text Box


Property

Value

(Name)

mtbTilesWide

Location

155, 118

Mask

000 (three zeros)

Size

34, 22

Drag another Label and Masked Text Box onto the form and set the following properties.
Label
Property

Value

(Name)

lblTilesHigh

Location

20, 150

Text

Number Tiles High:

Masked Text Box


Property

Value

(Name)

mtbTilesHigh

Location

155, 147

Mask

000 (three zeros)

Size

34, 22

The last two controls to add are two Button controls. Add them and set these properties.

(Name)

Location

Size

Text

btnOK

13, 142

75, 23

OK

btnCancel

106, 142

75, 23

Cancel

Now I'm going to add some code to FormNewTileset. Right click FormNewTileset in the solution
explorer and select View Code. Change the code to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.WorldClasses;
namespace XLevelEditor
{
public partial class FormNewTileset : Form
{
#region Field Region
bool okPressed;
TilesetData tilesetData;
#endregion
#region Property Region
public TilesetData TilesetData
{
get { return tilesetData; }
}
public bool OKPressed
{
get { return okPressed; }
}
#endregion
#region Constructor Region
public FormNewTileset()
{
InitializeComponent();

btnSelectImage.Click += new EventHandler(btnSelectImage_Click);


btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);

#endregion
#region Button Event Handler Region
void btnSelectImage_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Image Files|*.BMP;*.GIF;*.JPG;*.TGA;*.PNG";
ofDialog.CheckFileExists = true;

ofDialog.CheckPathExists = true;
ofDialog.Multiselect = false;
DialogResult result = ofDialog.ShowDialog();
if (result == DialogResult.OK)
{
tbTilesetImage.Text = ofDialog.FileName;
}
}
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbTilesetName.Text))
{
MessageBox.Show("You must enter a name for the tileset.");
return;
}
if (string.IsNullOrEmpty(tbTilesetImage.Text))
{
MessageBox.Show("You must select an image for the tileset.");
return;
}
int
int
int
int

tileWidth = 0;
tileHeight = 0;
tilesWide = 0;
tilesHigh = 0;

if (!int.TryParse(mtbTileWidth.Text, out tileWidth))


{
MessageBox.Show("Tile width must be an integer value.");
return;
}
else if (tileWidth < 0)
{
MessageBox.Show("Tile width must me greater than zero.");
return;
}
if (!int.TryParse(mtbTileHeight.Text, out tileHeight))
{
MessageBox.Show("Tile height must be an integer value.");
return;
}
else if (tileHeight < 0)
{
MessageBox.Show("Tile height must be greater than zero.");
return;
}
if (!int.TryParse(mtbTilesWide.Text, out tilesWide))
{
MessageBox.Show("Tiles wide must be an integer value.");
return;
}
else if (tilesWide < 0)
{
MessageBox.Show("Tiles wide must me greater than zero.");
return;
}
if (!int.TryParse(mtbTilesHigh.Text, out tilesHigh))
{
MessageBox.Show("Tiles high must be an integer value.");
return;
}
else if (tilesHigh < 0)
{

MessageBox.Show("Tiles high must be greater than zero.");


return;

tilesetData = new TilesetData();


tilesetData.TilesetName = tbTilesetName.Text;
tilesetData.TilesetImageName = tbTilesetImage.Text;
tilesetData.TileWidthInPixels = tileWidth;
tilesetData.TileHeightInPixels = tileHeight;
tilesetData.TilesWide = tilesWide;
tilesetData.TilesHigh = tilesHigh;
okPressed = true;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
okPressed = false;
this.Close();
}
}

#endregion

There is a using statement to bring the TilesetData class into scope. The first field, okPressed, and is a
bool that will hold how the form was closed. The other field, tilesetData, is of type TilesetData will
hold an object if the form is successfully closed using the OK button. There are properties to expose the
values of the fields as read only, or get only. The constructor wires event handlers for the Click event of
all three buttons.
In the handler for btnSelectImage I create a OpenFileDialog object. I set the Filter property so that
the dialog will filter will show common image files. I set the CheckFileExsists and CheckPathExists
properties to true so that we are sure the file exists. I also set Multiselect to false so that the user can
only select one file. I capture the result of the ShowDialog method and compare it to OK. If the dialog
was closed using the OK button I set the Text property of tbTilesetImage to the FileName property of
the dialog.
The handler for btnOK does a lot of validation on the values on the form. If the Text property of either
tbTilesetName or tbTilesetImage is null or empty I display a message box and exit the method. There
are four local variables to hold the result of converting the Text property of the Masked Text Boxes to
integers. I use the TryParse method of the int class try and convert the Text properties. If the
conversion fails I display a message saying the value must be an integer value and exit the method. If it
succeeded I check to see if the value is less than 1. If it is less than 1 I display a message saying the
value must be greater than zero and exit the method. If the data on the form is valid I create a
TilesetData object and set the fields to the values from the form. I set the okPressed field to true and
then close the form. The handler for btnCancel sets the okPressed field to false and closes the form.
The last form that I want to create is a form for making new layers of the map. Right click the
XLevelEditor project in the solution explorer, select Add and then Windows Form. Name this new
form FormNewLayer. My finished form looked like is on the next page. Set the properties in the table
on the next page for the form.

Property

Value

ControlBox

FALSE

FormBorderStyle

FixedDialog

Size

240, 230

StartPosition

CenterParent

Text

New Layer

There are a few controls on the form. There is a Label and a Text Box that are for getting the name of
the layer. There is a Check Box that if checked the layer will be filled with a specific tile and tileset.
There is a Group Box that I placed a pair of Label and Numeric Up And Down rows. The last
controls are buttons for closing the form. The way the form will work is that if the Check Box is
checked the map will be filled using the values from the Numeric Up And Down controls for the tile
index and tileset. Remember when you are drawing a layer that if a tile index or tileset index are -1 that
tile is skipped. So for the Numeric Up And Down controls I set the minimum value to -1 and the
starting value to -1 as well. I also set the maximum value to 512. You may want it higher depending on
the number of tiles in your tileset. It seemed to be a logical choice to me.
The first controls to drag onto the form are a Label and Text Box. Set the following properties for the
controls.
Label
Property

Value

(Name)

lblLayerName

Location

27, 15

Text

Layer Name:

Text Box
Property

Value

(Name)

tbLayerName

Location

122, 12

Next drag a Check Box onto the form and set these properties.
Check Box
Property

Value

(Name)

cbFill

Checked

TRUE

Location

12, 44

Text

Fill Layer?

The next control to add is a Group Box. Set the following properties for the Group Box.
Group Box
Property

Value

(Name)

gbFillLayer

Location

12, 71

Size

210, 79

Text

Fill With

Onto the Group Box drag a Label and a Numeric Up Down. Set the following properties for those
controls.
Label
Property

Value

(Name)

lblTileIndex

Location

23, 22

Text

Tile Index:

Numeric Up Down
Property

Value

(Name)

nudTile

Location

101, 20

Maximum

512

Minimum

-1

Size

84, 22

Value

-1

Drag a second Label and Numeric Up Down onto the form and set these properties.
Label
Property

Value

(Name)

lblTilesetIndex

Location

4, 50

Text

Tileset Index:

Numeric Up Down
Property

Value

(Name)

nudTileset

Location

101, 48

Maximum

512

Minimum

-1

Size

84, 22

Value

-1

The last two controls are the Button controls. Drag the two Buttons onto the form and set the
following values.
Buttons
(Name)

Location

Size

Text

btnOK

30, 169

75, 23

OK

btnCancel

122, 169

75, 23

Cancel

Before I get to the code for this form I want to add another public constructor to the MapLayerData
class. I will be adding in two more parameters. One will be a tile index and the other will be a tilset
index. In that constructor I will fill the layer with tiles using the tile index and tileset index. Add the
following constructor to the MapLayerData class.
public MapLayerData(string mapLayerName, int width, int height, int tileIndex, int tileSet)
{
MapLayerName = mapLayerName;
Width = width;
Height = height;
Layer = new Tile[height * width];
Tile tile = new Tile(tileIndex, tileSet);

for (int y = 0; y < height; y++)


for (int x = 0; x < width; x++)
SetTile(x, y, tile);

There is a Tile variable called tile that is created using the parameters passed in. It is safe to do this
because the Tile is a structure and not a class. That makes it a value type and not a reference type. So if
you modify one of the Tile objects in the array it will not affect the others. There is then a set of nested
loops added to this constructor. The outer loop is for the Y coordinate and the inner loops is for the X
coordinate. I call the SetTile method passing in x, y, and the Tile I created before.
I want to extend the MapLayer class in the XRpgLibrary to include a static method that will return a
MapLayer from a MapLayerData. Add the following using statement and method to the MapLayer
class in the XRpgLibrary.
using RpgLibrary.WorldClasses;
public static MapLayer FromMapLayerData(MapLayerData data)
{
MapLayer layer = new MapLayer(data.Width, data.Height);
for (int y = 0; y < data.Height; y++)
for (int x = 0; x < data.Width; x++)
{
layer.SetTile(

x,
y,
data.GetTile(x, y).TileIndex,
data.GetTile(x, y).TileSetIndex);

return layer;
}

The new method takes a MapLayerData parameter that is the object you want to convert. Inside the
method I create a new MapLayer instance using the Height and Width properties of the object passed
in. There is then a set of nested for loops. The outer loop will loop through the height of the layer and
the inner array will loop through the width of the layer. Inside the loop I use the SetTile method of the
MapLayer class to set the tile passing in values from the GetTile method of MapLayerData to get the
tile at the (x, y) coordinates from the loop variables.
I'm now going to add the code for FormNewLayer. Right click FormNewLayer in the XLevelEditor
and select View Code. Change the code to the following.
using
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;
RpgLibrary.WorldClasses;

namespace XLevelEditor
{
public partial class FormNewLayer : Form
{
#region Field Region
bool okPressed;
int LayerWidth;
int LayerHeight;
MapLayerData mapLayerData;
#endregion
#region Property Region
public bool OKPressed
{
get { return okPressed; }
}
public MapLayerData MapLayerData
{
get { return mapLayerData; }
}
#endregion
#region Constructor Region
public FormNewLayer(int width, int height)
{
InitializeComponent();
LayerWidth = width;

LayerHeight = height;

btnOK.Click += new EventHandler(btnOK_Click);


btnCancel.Click += new EventHandler(btnCancel_Click);

#endregion
#region Button Event Handler Region
void btnOK_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(tbLayerName.Text))
{
MessageBox.Show("You must enter a name for the layer.", "Missing Layer Name");
return;
}
if (cbFill.Checked)
{
mapLayerData = new MapLayerData(
tbLayerName.Text,
LayerWidth,
LayerHeight,
(int)nudTile.Value,
(int)nudTileset.Value);
}
else
{
mapLayerData = new MapLayerData(
tbLayerName.Text,
LayerWidth,
LayerHeight);
}
okPressed = true;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
okPressed = false;
this.Close();
}
#endregion
}

There is a using statement to bring the WorldClasses from the RpgLibrary into scope, namely the
MapLayerData class. There are four fields in the class. The first, okPressed, will be used to determine
how the form closed. Then next two, LayerWidth and LayerHeight, are the width and height of the
map. I'm not allowing layers of varying sizes so all layers should have the same height and width. The
last field is a MapLayerData object. If the OK button is pressed I will set it to a new MapLayerData
object. There are properties to expose the okPressed and mapLayerData fields as get only.
Forms are just classes and because they are just classes you can create new constructors for them to get
values needed by the form to the form. I changed the constructor to take two integer parameters, the
width and height of the map. In the constructor I set the LayerWidth and LayerHeight fields with the
values passed in. I then wire the Click event for both of the buttons.
In the event handler for the Click event of btnOK I check to see if the Text property of tbLayerName

is null or empty. If it is I display a message box saying that a name for the layer is required and exit the
method. I then check the Checked property of cbFill to see if the map should be filled with a specific
tile. If that is true I create a new map using the Text property of tbLayerName, the LayerWidth and
LayerHeight fields, and the Value property of nudTile and nudTileset cast to an integer. The Value
property of the Numeric Up and Down control is a decimal so you need to convert it to an integer. If it
wasn't clicked I create a new layer using the Text property of tbLayerName and the LayerWidth and
LayerHeight fields. I then set okPressed to true and close the form.
The code for the Click event of btnCancel you've seen several times now. I set okPressed to false and
close the form.
I'm going to stop this tutorial here and continue it in a B part. The plan for this tutorial was to get
started with the level editor and we are well under way. I encourage you to visit the news page of my
site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 23B
Level Editor
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is part B in my tutorial on creating a level editor to use with your game. In this part I will start
coding the actual level editor now that all of the basic pieces we need are in place. In this tutorial we
will be working mostly in the code for FormMain, the actual level editor. Right click FormMain in
the XLevelEditor project and select View Code. A lot of code is going to go into the code for the form
so I'm going to do it in stages. To start with I'm going to set up the using statements, a few fields and
properties, wire some event handlers, and set some properties of controls. Change the code for
FormMain to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Linq;
System.Text;
System.Windows.Forms;
System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using
using
using
using
using
using

GDIBitmap = System.Drawing.Bitmap;
GDIColor = System.Drawing.Color;
GDIImage = System.Drawing.Image;
GDIGraphics = System.Drawing.Graphics;
GDIGraphicsUnit = System.Drawing.GraphicsUnit;
GDIRectangle = System.Drawing.Rectangle;

using RpgLibrary.WorldClasses;
using XRpgLibrary.TileEngine;
namespace XLevelEditor
{
public partial class FormMain : Form
{
#region Field Region
SpriteBatch spriteBatch;
LevelData levelData;
TileMap map;
List<Tileset> tileSets = new List<Tileset>();
List<MapLayer> layers = new List<MapLayer>();
#endregion

#region Property Region


public GraphicsDevice GraphicsDevice
{
get { return mapDisplay.GraphicsDevice; }
}
#endregion
#region Constructor Region
public FormMain()
{
InitializeComponent();
this.Load += new EventHandler(FormMain_Load);
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
tilesetToolStripMenuItem.Enabled = false;
mapLayerToolStripMenuItem.Enabled = false;
charactersToolStripMenuItem.Enabled = false;
chestsToolStripMenuItem.Enabled = false;
keysToolStripMenuItem.Enabled = false;
newLevelToolStripMenuItem.Click += new EventHandler(newLevelToolStripMenuItem_Click);
newTilesetToolStripMenuItem.Click += new
EventHandler(newTilesetToolStripMenuItem_Click);
newLayerToolStripMenuItem.Click += new EventHandler(newLayerToolStripMenuItem_Click);

mapDisplay.OnInitialize += new EventHandler(mapDisplay_OnInitialize);


mapDisplay.OnDraw += new EventHandler(mapDisplay_OnDraw);

#endregion
#region Form Event Handler Region
void FormMain_Load(object sender, EventArgs e)
{
Application.Idle += new EventHandler(Application_Idle);
}
void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
}
void Application_Idle(object sender, EventArgs e)
{
mapDisplay.Invalidate();
}
#endregion
#region New Menu Item Event Handler Region
void newLevelToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewLevel frmNewLevel = new FormNewLevel())
{
frmNewLevel.ShowDialog();
if (frmNewLevel.OKPressed)
{
levelData = frmNewLevel.LevelData;
tilesetToolStripMenuItem.Enabled = true;
}
}

void newTilesetToolStripMenuItem_Click(object sender, EventArgs e)


{
using (FormNewTileset frmNewTileset = new FormNewTileset())
{
frmNewTileset.ShowDialog();
if (frmNewTileset.OKPressed)
{
TilesetData data = frmNewTileset.TilesetData;
mapLayerToolStripMenuItem.Enabled = true;
}

}
void newLayerToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewLayer frmNewLayer = new FormNewLayer(levelData.MapWidth,
levelData.MapHeight))
{
frmNewLayer.ShowDialog();
if (frmNewLayer.OKPressed)
{
MapLayerData data = frmNewLayer.MapLayerData;
charactersToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
}

}
#endregion
#region Map Display Event Handler Region
void mapDisplay_OnInitialize(object sender, EventArgs e)
{
spriteBatch = new SpriteBatch(GraphicsDevice);

mapDisplay.MouseEnter += new EventHandler(mapDisplay_MouseEnter);


mapDisplay.MouseLeave += new EventHandler(mapDisplay_MouseLeave);
mapDisplay.MouseMove += new MouseEventHandler(mapDisplay_MouseMove);
mapDisplay.MouseDown += new MouseEventHandler(mapDisplay_MouseDown);
mapDisplay.MouseUp += new MouseEventHandler(mapDisplay_MouseUp);

void mapDisplay_OnDraw(object sender, EventArgs e)


{
GraphicsDevice.Clear(Color.CornflowerBlue);
Render();
Logic();
}
void mapDisplay_MouseUp(object sender, MouseEventArgs e)
{
}
void mapDisplay_MouseDown(object sender, MouseEventArgs e)
{
}
void mapDisplay_MouseMove(object sender, MouseEventArgs e)
{
}
void mapDisplay_MouseLeave(object sender, EventArgs e)
{
}

void mapDisplay_MouseEnter(object sender, EventArgs e)


{
}
#endregion
#region Display Rendering and Logic Region
private void Render()
{
}
private void Logic()
{
}
}

#endregion

There was going to be confusion between some of the classes in the System.Drawing name space and
the XNA Framework so I removed the using statement for System.Drawing. I added in a using
statement for System.IO as we will be doing some file input and output. I also added in using
statements for the basic XNA framework and the XNA framework Graphics classes. There is then
several qualified using statements. A qualified using statement allows you to swap a fully qualified
class with the qualifier. So, when you need to use the Bitmap class from System.Drawing you can use
GDIBitmap. You may also hear it called aliasing, giving a class an alternative name.
I added in a few fields that I'm sure we are going to want to work with. There is a SpriteBatch field
because we are going to be drawing maps and you need a SpriteBatch to draw the layers. There is a
LevelData field that will hold information about the level currently being edited. I added in a TileMap
field, map, to hold the map. I also have two List<T> fields. The first is for the tilesets and the second
is the layers of the map.
At the moment there is just the one property, GraphicsDevice. I included that because you will need
access to the GraphicsDevice associated with the MapDisplay. You need it to create a SpriteBatch, to
clear the scene, and to read in content.
The constructor first wires event handlers for the Load event of the form and the FormClosing event.
In the Load event I will do a little initialization. In the FormClosing event I will be adding checks that
if the level has changed the user gets a warning that they haven't saved the level. I then set the Enabled
property of most of the main menu items to false. I do that because you don't want to add items until
there is a level to work with. I then wire the handlers for the Click event of the new level, new tileset,
and new maplayer menu items.
The event handler for the Load event for the form wires an event handler for the Idle event of the
application. The Idle event will be fired when your form isn't doing anything, or idle. When it is idle is
a good time to let the map display know that it can redraw itself. The FormClosing event handler does
nothing at the moment but will be used in the future so I added it in now. Next is the event handler for
the Idle event of the application. All it does is call the Invalidate method of the map display letting it
know it is time to redraw itself.
The next region of code is for the new menu items. The first is for the new level menu item. Inside of a
using statement I create a new instance of FormNewLevel. Inside that statement I call the ShowDialog

method. I then check the OKPressed property of the form. If it was true I set the levelData field to be
the LevelData property of FormNewLevel. I also set the Enabled property of the tileset menu item to
true so tilesets can be added.
The handler for the Click event for the new tileset menu item is similar. It creates an instance of the
form inside of a using statement. Inside that it displays the form using the ShowDialog method. It
checks to see if the OKPressed property of the form is true as well. It is I set a TilesetData local
variable to the TilesetData property. I also set the Enabled property of the map layer menu item to
true.
The event handler for the Click event for a new layer has a similar form as the others. In a using
statement I create a new FormNewLayer. I pass in the MapWidth and MapHeight from the
levelData field to the constructor. Inside the using statement I call the ShowDialog method to display
the form. I check to see if the OKPressed property of the form is true. If so, I create a MapLayerData
object. I also set the Enabled property of the other main menu items to true.
There is a region of code that is dedicated to event handlers related to the MapDisplay control. The
OnInitialize event will be triggered when the MapDisplay is first initialized. In the handler for that
event I create a new SpriteBatch object to be used in rendering the map. I then wire handlers for many
of the events related to the mouse.
The OnDraw event handler for the MapDisplay will be called when it is time to redraw the map. It
will be triggered automatically everything the Application.Idle event handler is called by calling the
Invalidate method of the MapDisplay. The method calls the Clear method of the GraphicsDevice for
the MapDisplay control and sets the background to the familiar CornflowerBlue you see in XNA. It
then calls a method Render. The Render method will be responsible for rendering the map. I also
added a method called Logic. The Logic method will be responsible for the logic of the editor. Both
methods live in a region dedicated to rendering and logic. At the moment they are method stubs that
will be filled out as things progress.
That is the basic framework for the level editor. The next step will be reading in the image for a tileset
so it can be used. To handle that I'm going to extend the event handler for the new tileset menu item.
First, you are going to want a list of images you can use for the form. Add the following field and
Change the newTilesetToolStripMenuItem_Click method to the following. I also added a new
method to the TileMap class called AddTileset that will add a new tileset to the map.
List<GDIImage> tileSetImages = new List<GDIImage>();
void newTilesetToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewTileset frmNewTileset = new FormNewTileset())
{
frmNewTileset.ShowDialog();
if (frmNewTileset.OKPressed)
{
TilesetData data = frmNewTileset.TilesetData;
try
{

GDIImage image = (GDIImage)GDIBitmap.FromFile(data.TilesetImageName);


tileSetImages.Add(image);
Stream stream = new FileStream(data.TilesetImageName, FileMode.Open,

FileAccess.Read);
Texture2D texture = Texture2D.FromStream(GraphicsDevice, stream);
Tileset tileset = new Tileset(
texture,
data.TilesWide,
data.TilesHigh,
data.TileWidthInPixels,
data.TileHeightInPixels);
tileSets.Add(tileset);
if (map != null)
map.AddTileset(tileset);
stream.Close();
stream.Dispose();

}
catch (Exception ex)
{
MessageBox.Show("Error reading file.\n" + ex.Message, "Error reading image");
return;
}
lbTileset.Items.Add(data.TilesetName);
if (lbTileset.SelectedItem == null)
lbTileset.SelectedIndex = 0;
mapLayerToolStripMenuItem.Enabled = true;
}

}
public void AddTileset(Tileset tileset)
{
tilesets.Add(tileset);
}

So, what is the new code doing. First I used a try-catch block when I try to read in the image for the
tileset. Here is a spot where an exception could be thrown if there is a problem reading the the image so
it is best to do this in a try-catch block. That way you can recover from an exception. I first try to read
in the image in a format that can be assigned to the Picture Box control that will preview the tileset, an
Image from GDI+. To do that I cast the return value of the FromFile method of the Bitmap class from
GDI+ as well. I then add the image to the list of tileset images. A change in XNA 4.0 from previous
versions of XNA is that there is no longer a FromFile method for the Texture2D class. You now have
to use the FromStream method instead. A stream is data that is sent from a source, be it a file or over
the internet, or other sources. I create a FileStream using the TilesetImageName property from the
TilesetData object for the name of the file and use FileMode.Open and FileAccess.Read to read the
file. If you don't specify you want to read the file you may get an access violation stating the file is
being used by another process. I then use the FromStream method of the Texture2D class to read in
the image. I then create a new Tileset object and add it to the list of tilesets using the Texture2D I just
read in and the fields from the TilesetData object. If that map field is not null I call the new
AddTileset method to add it to the map. I then close the stream and dispose it. I the catch for the try I
display a message box stating there was an error and the error then exit the method. I then add the
TilesetName property of the TilesetData object to lbTileset, the List Box that holds the names of the
tile sets. If the SelectedItem of lbTileset is null I set the SelectedIndex property of lbTileset to zero so
that the tileset will be selected. I also set the Enabled property of map layer menu item to true.

When the user clicks a tileset name in lbTileset you are going to want to perform a few actions. The
best way to do that is to handle the SelectedIndexChanged event of the List Box. I wired the event in
the Load event of the form and placed the handler in the same region. Change the FormMain_Load
method to the following and add this new region below the Form Event Handler Region.
void FormMain_Load(object sender, EventArgs e)
{
Application.Idle += new EventHandler(Application_Idle);
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
}
#region Tile Tab Event Handler Region
void lbTileset_SelectedIndexChanged(object sender, EventArgs e)
{
if (lbTileset.SelectedItem != null)
{
nudCurrentTile.Value = 0;
nudCurrentTile.Maximum = tileSets[lbTileset.SelectedIndex].SourceRectangles.Length - 1;
FillPreviews();
}
}
private void FillPreviews()
{
int selected = lbTileset.SelectedIndex;
int tile = (int)nudCurrentTile.Value;
GDIImage preview = (GDIImage)new GDIBitmap(pbTilePreview.Width, pbTilePreview.Height);
GDIRectangle dest = new GDIRectangle(0, 0, preview.Width, preview.Height);
GDIRectangle source = new GDIRectangle(
tileSets[selected].SourceRectangles[tile].X,
tileSets[selected].SourceRectangles[tile].Y,
tileSets[selected].SourceRectangles[tile].Width,
tileSets[selected].SourceRectangles[tile].Height);
GDIGraphics g = GDIGraphics.FromImage(preview);
g.DrawImage(tileSetImages[selected], dest, source, GDIGraphicsUnit.Pixel);
pbTilesetPreview.Image = tileSetImages[selected];
pbTilePreview.Image = preview;
}
#endregion

The new event handler for the Load event of the form wires a handler for the SelectedIndexChanged
event of lbTileset that will be fired when a different tileset is selected from the list. The new region I
added is called Tile Tab Event Handler Region and will house event handlers related to the tile tab in
the left pane. The event handler makes sure that the SelectedItem is not null. If it is not I set the Value
property of nudCurrentTile to 0, the first tile in the tile set. I also set the Maximum property to be the
number of source rectangles in the tileset minus 1 because the source rectangles are zero based so the
last rectangle is the length minus 1. I then call a method I wrote that will fill the Picture Boxes in the
tab called FillPreviews.
The FillPreviews does a little GDI+ manipulation to get the selected tile in nudCurrentTile into the
tile preview Picture Box. You first need to know what tileset is selected, the SelectedIndex property of
lbTileset, and which tile is selected, the Value property of nudCurrentTile. It needs to be cast to an
integer because it is a decimal. I first create a GDIImage that is the width and height of the preview
Picture Box for the tile. I need a destination rectangle to draw the image to. I use zero for the X and Y
coordinates and the Width and Height of the image for the width and height. I also need the source

rectangle of the tile in the tile set. I get that using the SelectedIndex and the Value properties that I
captured and the tileSets collection. I then create a Graphics object using FromImage and the preview
image that I created. I then using the DrawImage method passing in the image from tileSetImages, the
destination rectangle, source rectangle, and GDIGraphicsUnit.Pixel. I then set the Image properties of
the Picture Boxes.
You are also going to want to change the tile preview if the Value property of nudCurrentTile
changes. I will wire that in the Load event handler of the form as well. Change that method to the
following and add this handler to the Tile Tab Event Handler Region.
void FormMain_Load(object sender, EventArgs e)
{
Application.Idle += new EventHandler(Application_Idle);
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
nudCurrentTile.ValueChanged += new EventHandler(nudCurrentTile_ValueChanged);
}
void nudCurrentTile_ValueChanged(object sender, EventArgs e)
{
if (lbTileset.SelectedItem != null)
{
FillPreviews();
}
}

The new handler checks to see if the SelectedItem property is not null. If it is null you don't want a
preview. If it is not null I call the FillPreviews method to update the previews.
The next step it to handle creating a new layer. That takes place in the handler for the new layer menu
item. Change that handler to the following.
void newLayerToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewLayer frmNewLayer = new FormNewLayer(levelData.MapWidth, levelData.MapHeight))
{
frmNewLayer.ShowDialog();
if (frmNewLayer.OKPressed)
{
MapLayerData data = frmNewLayer.MapLayerData;
if (clbLayers.Items.Contains(data.MapLayerName))
{
MessageBox.Show("Layer with name " + data.MapLayerName + " exists.", "Existing
layer");

return;
}
MapLayer layer = MapLayer.FromMapLayerData(data);
clbLayers.Items.Add(data.MapLayerName, true);
clbLayers.SelectedIndex = clbLayers.Items.Count - 1;
layers.Add(layer);
if (map == null)
map = new TileMap(tileSets, layers);
charactersToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;

}
}

What the new code is doing is checking to see if an entry in clbLayers, the Checked List Box, with
the name data.MapLayername already exists. If it does I display an error message that a layer with
that name already exists and exit the method. Otherwise I use the FromMapLayerData method I
added to the MapLayer class to create a new layer and add it to the List<MapLayer>. I also add the
MapLayerName to clbLayers. When adding the item I have it set to be checked initially, and thus
drawn by default. I also set the SelectedIndex property of clbLayers to be the last layer that was
added. If the map field is null I create a new TileMap using the List<Tileset> and List<MapLayer>.
This is where you can see what the dangers of passing reference types around. The map field now has
references to the tileSets and layers fields of the form. Changing one of them changes the other. I had
originally included an else to the if statement that checked to see if map was null where I'd the new
layer to map and suddenly there were 3 layers in the layers field, not 2. So, be careful when you are
passing reference types around that this sort of thing does not happen.
The next step will be to actually draw the layers. For that we need two more things, a Camera and an
Engine. Add the following fields to the Fields region of FormMain. Also, change the Load event
handler of the form to the following to actually create the camera and the engine.
Camera camera;
Engine engine;
void FormMain_Load(object sender, EventArgs e)
{
Application.Idle += new EventHandler(Application_Idle);
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
nudCurrentTile.ValueChanged += new EventHandler(nudCurrentTile_ValueChanged);
Rectangle viewPort = new Rectangle(0, 0, mapDisplay.Width, mapDisplay.Height);
camera = new Camera(viewPort);
}

engine = new Engine(32, 32);

What I did was create a Rectangle that describes the MapDisplay control because the Camera class
needs a rectangle that describes the view port it is using. I then create an Engine instance with pixel
width and height of 32 pixels. Something I will be handling later, possibly not in this tutorial, is what to
do if the size of the MapDisplay changes and how to recover from that. I will also add in options that
you can set to change the width and height of the tiles on the screen.
So, the next step is to start drawing. That will take place in the Render method. Change the Render
method to the following.
private void Render()
{
for (int i = 0; i < layers.Count; i++)
{
spriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
camera.Transformation);
if (clbLayers.GetItemChecked(i))
layers[i].Draw(spriteBatch, camera, tileSets);

spriteBatch.End();
}

I loop through all of the layers in a for loop, not a foreach loop. I don't use a foreach loop because I will
want to check if a layer should be drawn or not controlled by clbLayers. I then call the Begin method
of SpriteBatch passing in parameters like I did in the game. There is an if statement after the call to
Begin that gets if the item at the index in clbLayers is checked. If it is I call the Draw method of the
layer in the list of layers passing in the SpriteBatch object, the Camera object and the List<Tileset>
for the tilesets. I then call the End method of SpriteBatch.
The next thing I want to do is get the map scrolling and adding in tiles with the editor. My computer
was scrolling my maps really fast so I ended up adding a Timer control to the form. Right click
FormMain in the solution explorer and select View Designer. Now drag a new Timer control onto the
form. Set the (Name) property of the timer to controlTimer. Now go back to the code for the form. I
the Load event of the form I wired a handler for the Tick event of the timer and set a couple properties.
I added the handler to the Form Event Handler Region. Change the FormMain_Load method to the
following and add this handler to the region. I also removed the handler for Application.Idle. I wall
call the Invalidate method from the handler for the timer's tick event.
void FormMain_Load(object sender, EventArgs e)
{
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
nudCurrentTile.ValueChanged += new EventHandler(nudCurrentTile_ValueChanged);
Rectangle viewPort = new Rectangle(0, 0, mapDisplay.Width, mapDisplay.Height);
camera = new Camera(viewPort);
engine = new Engine(32, 32);

controlTimer.Tick += new EventHandler(controlTimer_Tick);


controlTimer.Enabled = true;
controlTimer.Interval = 200;

void controlTimer_Tick(object sender, EventArgs e)


{
mapDisplay.Invalidate();
Logic();
}

So, all I did was wire the Tick event handler and set the Enabled property to true so that the event will
be fired and I set the Interval value to 200 milliseconds, a fifth of a second. You may want to make the
Interval smaller if your logic is sluggish or higher if it is too fast. Even where I set it my map scrolled
rather quickly. Part of the reason being that I'm scrolling the map 1 tile at a time. The handler for the
Tick event calls Invalidate on mapDisplay to redraw the display and it calls the Logic method.
For handling the logic of the editor I needed to add a few fields. A Point field for the position of the
mouse over the MapDisplay and two bool fields that determines if the left mouse button is down and
the other determines if we are interested in tracking the mouse. Add the following fields to FormMain.
Point mouse = new Point();
bool isMouseDown = false;
bool trackMouse = false;

Now I'm going to change the code for the mouse event handlers for the MapDisplay. Change them to
the following.

void mapDisplay_MouseUp(object sender, MouseEventArgs e)


{
if (e.Button == MouseButtons.Left)
isMouseDown = false;
}
void mapDisplay_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
isMouseDown = true;
}
void mapDisplay_MouseMove(object sender, MouseEventArgs e)
{
mouse.X = e.X;
mouse.Y = e.Y;
}
void mapDisplay_MouseLeave(object sender, EventArgs e)
{
trackMouse = false;
}
void mapDisplay_MouseEnter(object sender, EventArgs e)
{
trackMouse = true;
}

The handler for MouseUp checks to see if the left mouse button triggered the event. If it did I set
isMouseDown to false. The MouseDown handler works in reverse. The MouseMove handler sets the
X and Y properties of mouse to the X and Y properties of the mouse. The handler for MouseLeave sets
trackMouse to false because the mouse has moved outside of the MapDisplay and we aren't interested
in processing it. The MouseEnter handler does the reverse, sets trackMouse to true because we are
now interested in the mouse again.
Before I get to the logic of scrolling the map and drawing tiles I need to make two changes to the
Camera class. I need to make the set part of the Position property public. I also need to make the
LockCamera method public. Change the Position property and LockCamera method to the
following.
public Vector2 Position
{
get { return position; }
set { position = value; }
}
public void LockCamera()
{
position.X = MathHelper.Clamp(position.X,
0,
TileMap.WidthInPixels * zoom - viewportRectangle.Width);
position.Y = MathHelper.Clamp(position.Y,
0,
TileMap.HeightInPixels * zoom - viewportRectangle.Height);
}

The next thing to do is to update the Logic method as that is where I will be handling scrolling the map
and drawing the map. Change the Logic method to the following.
private void Logic()
{

if (layers.Count == 0)
return;
Vector2 position = camera.Position;
if (trackMouse)
{
if (mouse.X < Engine.TileWidth)
position.X -= Engine.TileWidth;
if (mouse.X > mapDisplay.Width - Engine.TileWidth)
position.X += Engine.TileWidth;
if (mouse.Y < Engine.TileHeight)
position.Y -= Engine.TileHeight;
if (mouse.Y > mapDisplay.Height - Engine.TileHeight)
position.Y += Engine.TileHeight;
camera.Position = position;
camera.LockCamera();
position.X = mouse.X + camera.Position.X;
position.Y = mouse.Y + camera.Position.Y;
Point tile = Engine.VectorToCell(position);

if (isMouseDown)
{
if (rbDraw.Checked)
{
layers[clbLayers.SelectedIndex].SetTile(
tile.X,
tile.Y,
(int)nudCurrentTile.Value,
lbTileset.SelectedIndex);
}
if (rbErase.Checked)
{
layers[clbLayers.SelectedIndex].SetTile(
tile.X,
tile.Y,
-1,
-1);
}
}

The first thing I do is check to see if the Count property of the layers field is zero. If it is you don't
want to try and edit anything so I exit the method. I then save the Position property of the camera in a
local variable position. I then check the trackMouse property. If it is true the mouse is in the map
display. I check to see if the X value of the mouse's position is less than the width of a tile on the
screen. If it is I subtract the width of a tile from the position of the camera, scrolling the map left. Then
if the X value of the mouse's position is greater than or equal to the width of the map display minus the
width of a tile I scroll the map one tile to the right. I do something similar for the Y component of the
mouse's position. If it is less than the height of tile I scroll up and if it is greater than the height of the
display minus a tile I scroll the map down. Since Position is a property in the Camera class and a
structure you can't modify its X and Y value directly so I set the Position property of the camera to the
position variable and call the LockCamera method to lock the camera. I then set the position variable
to the position of the mouse plus the position of the camera. This tells us which tile the mouse is in on
the map. I capture what tile the mouse is in using the VectorToCell method of the Engine class. I then
check if the isMouseDown field is true. If it is I check if rbDraw's Checked property is true. If it is

true you want to draw the tile. I call the SetTile method of the MapLayer class that takes the x and y
coordinates of the tile and the tile index and tileset the tile belongs to. Similarly if rbErase's Checked
property is true you want to erase the tile. That is done by setting its tile index and tileset to -1.
So we have a working basic level editor. I'm going to stop this tutorial here though as I think you've had
more than enough to digest. In a future tutorial I'm going to add writing out and reading in maps and
adding some more options into the editor. The plan for this tutorial was to get started with the level
editor and we are well under way.
I encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest
news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 24
Level Editor Continued
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In tutorial 23 I got the basics of a level editor up and running. You could create new levels that included
the map. You could add tilesets to the map as well as layers to the map. In this tutorial I'm going to do a
little more work on the editor.
The first thing I want to do is to modify the design of the Tiles tab. I want to display the tile the mouse
is over. The first step is to modify the properties of lbTileset. I want to make it shorter. Set the
following properties
lbTileset
Property

Value

Size

180, 212

Below that I dragged on a Label and a Text Box. Set these properties for those two controls
Label
Property

Value

(Name)

lblCursor

AutoSize

FALSE

Location

8, 567

Size

180, 23

Text

Map Location

TextAlign

TopCenter

Text Box
Property

Value

(Name)

tbMapLocation

Enabled

FALSE

Location

8, 590

Size

180, 22

You can now set the Text property of tbMapLocation in the Logic method. Update the Logic method
to the following.
private void Logic()
{
if (layers.Count == 0)
return;
Vector2 position = camera.Position;
if (trackMouse)
{
if (mouse.X < Engine.TileWidth)
position.X -= Engine.TileWidth;
if (mouse.X > mapDisplay.Width - Engine.TileWidth)
position.X += Engine.TileWidth;
if (mouse.Y < Engine.TileHeight)
position.Y -= Engine.TileHeight;
if (mouse.Y > mapDisplay.Height - Engine.TileHeight)
position.Y += Engine.TileHeight;
camera.Position = position;
camera.LockCamera();
position.X = mouse.X + camera.Position.X;
position.Y = mouse.Y + camera.Position.Y;
Point tile = Engine.VectorToCell(position);
tbMapLocation.Text =
"( " + tile.X.ToString() + ", " + tile.Y.ToString() + " )";
if (isMouseDown)
{
if (rbDraw.Checked)
{
layers[clbLayers.SelectedIndex].SetTile(
tile.X,
tile.Y,
(int)nudCurrentTile.Value,
lbTileset.SelectedIndex);
}
if (rbErase.Checked)
{
layers[clbLayers.SelectedIndex].SetTile(
tile.X,
tile.Y,
-1,
-1);
}
}
}
}

All I did was create a string using the X and Y properties of the Point called tile that is the current tile
the mouse is in. You could set the TextAlign property of tbMapLocation to center the text if you want.
Choosing the tile you want to draw using the Numeric Up Down can be a real pain in the you know
what. What would be nicer is if you could also click on the Picture Box that holds the tileset image and
select that tile. Part of the problem with that is that the tileset image in the Picture Box has been scaled.
Unless the tileset is the same size as the Picture Box you need to do a little math. Update the code for

the Load event of the form to wire an event handler for the MouseDown event for pbTilesetPreview
and add in this handler to the Tile Tab Event Handler. I also set the TextAlign property for
tbMapLocation to center the text. You want MouseDown because it uses MouseEventArgs that has
information about the mouse unlike the Click event.
void FormMain_Load(object sender, EventArgs e)
{
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
nudCurrentTile.ValueChanged += new EventHandler(nudCurrentTile_ValueChanged);
Rectangle viewPort = new Rectangle(0, 0, mapDisplay.Width, mapDisplay.Height);
camera = new Camera(viewPort);
engine = new Engine(32, 32);
controlTimer.Tick += new EventHandler(controlTimer_Tick);
controlTimer.Enabled = true;
controlTimer.Interval = 200;

tbMapLocation.TextAlign = HorizontalAlignment.Center;
pbTilesetPreview.MouseDown += new MouseEventHandler(pbTilesetPreview_MouseDown);

void pbTilesetPreview_MouseDown(object sender, MouseEventArgs e)


{
if (lbTileset.Items.Count == 0)
return;
if (e.Button != System.Windows.Forms.MouseButtons.Left)
return;
int index = lbTileset.SelectedIndex;
float xScale = (float)tileSetImages[index].Width /
pbTilesetPreview.Width;
float yScale = (float)tileSetImages[index].Height /
pbTilesetPreview.Height;
Point previewPoint = new Point(e.X, e.Y);
Point tilesetPoint = new Point(
(int)(previewPoint.X * xScale),
(int)(previewPoint.Y * yScale));
Point tile = new Point(
tilesetPoint.X / tileSets[index].TileWidth,
tilesetPoint.Y / tileSets[index].TileHeight);
nudCurrentTile.Value = tile.Y * tileSets[index].TilesWide + tile.X;
}

Not much new in the event handler for the form loading. I just wire the handler and set a property for
tbMapLocation. It is in the handler for MouseDown of pbTilesetPreview where the interesting code
is. First, I check to see that there is a tileset loading comparing the Count property of the Items
collection of lbTileset to zero. If there is no tileset I exit the method. Also, if the left mouse button did
not trigger the event I exit the method. I capture the SelectedIndex property of lbTileset because I
needed to use it a lot. As I mentioned if the size of the tileset image is not the size of the image of the
picture box there was scaling done. To get the scaling factor for the X coordinate, I take the Width of
the tileset, cast to a float, divided by the Width of the picture box. If you don't cast the width of the
tileset to a float first you will be doing integer division and you want the digits after the decimal place.
The scaling factor for the Y coordinate is done using the Height of the tileset and picture box. I then
get the location of the mouse as a Point using the X and Y properties of the MouseEventArgs. I then

get what that Point would be if scaled using xScale and yScale. To find which tile that point is in,
using X and Y coordinates, you do like you do in the Engine class to determine what cell a Vector2 is
in. You divide the X by the Width and the Y by the Height. Tiles are created going left to right first and
then top to bottom. To find the value of the tile in one dimension you take the Y coordinate, multiply it
by the number of tiles wide, and add the X coordinate. I set that value to the Value property of
nudCurrentTile. Since the ValueChanged event handler is wired for that, even if it is changed in the
program, the event handler will be triggered. That will fill the preview picture box with the right prieve
from the tileset.
If you start changing the size of the MapDisplay you may end up seeing the blue background, not what
is desired. You will want what is displayed to change with what is visible. To do that I'm going to
subscribe to the SizeChanged event of the MapDisplay. I will subscribe to that in the Load event of
the form. Modify the handler for that event to the following and add this handler to the Form Event
Handler region.
void FormMain_Load(object sender, EventArgs e)
{
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
nudCurrentTile.ValueChanged += new EventHandler(nudCurrentTile_ValueChanged);
Rectangle viewPort = new Rectangle(0, 0, mapDisplay.Width, mapDisplay.Height);
camera = new Camera(viewPort);
engine = new Engine(32, 32);
controlTimer.Tick += new EventHandler(controlTimer_Tick);
controlTimer.Enabled = true;
controlTimer.Interval = 200;
tbMapLocation.TextAlign = HorizontalAlignment.Center;
pbTilesetPreview.MouseDown += new MouseEventHandler(pbTilesetPreview_MouseDown);
}

mapDisplay.SizeChanged += new EventHandler(mapDisplay_SizeChanged);

void mapDisplay_SizeChanged(object sender, EventArgs e)


{
Rectangle viewPort = new Rectangle(0, 0, mapDisplay.Width, mapDisplay.Height);
Vector2 cameraPosition = camera.Position;
camera = new Camera(viewPort, cameraPosition);
camera.LockCamera();
mapDisplay.Invalidate();
}

What the event handler does is create a new camera and tell the map display to redraw itself. I create a
new rectangle the size of the map display. I then save the current position of the camera. I then create
the new camera passing in the new rectangle and the position of the old camera. I then call the
LockCamera method to lock the camera. Finally I call the Invalidate method to tell the MapDisplay
to refresh itself.
What I also want to do is add in a grid that can be toggled on and off as well as a cursor for the mouse.
If there is a map layer I will draw the currently selected tile under the image of the cursor as well. For
that you are going to require two images. One for the grid and one for the mouse cursor. You can
download them from http://xnagpa.net/xna4/downloads/mapimages.zip. Download the images and
extract them to a folder. Open that folder and drag the cursor.png and grid.png files onto the
XLevelEditorContent project. That was another reason for making a Windows Game compared to a

Windows Form Application. I had the content project available to add content to, like the image for the
grid and the image of the cursor. If you were able to create the Windows Form Application and have it
render successfully follow these steps. Right click the XLevelEditor project, select Add and then New
Folder. Name this new folder Content. Drag the two files above onto that folder. No matter which
approach you took in the properties set the Copy to Output Directory property to Copy Always.
Before I code the logic I want to add another menu item, a View menu item. Beside the &Key entry
add in an entry &View. Under that add an item &Display Grid. Set the Checked and CheckOnClick
properties for that menu item to True. I also dragged my &View menu item to be beside the &Level
item.

Before I get to drawing the grid and the cursor you need to load the images and you will need fields to
hold the images. Add these two fields and change the OnInitialize handler to the following.
Texture2D cursor;
Texture2D grid;
void mapDisplay_OnInitialize(object sender, EventArgs e)
{
spriteBatch = new SpriteBatch(GraphicsDevice);
mapDisplay.MouseEnter += new EventHandler(mapDisplay_MouseEnter);
mapDisplay.MouseLeave += new EventHandler(mapDisplay_MouseLeave);
mapDisplay.MouseMove += new MouseEventHandler(mapDisplay_MouseMove);
mapDisplay.MouseDown += new MouseEventHandler(mapDisplay_MouseDown);
mapDisplay.MouseUp += new MouseEventHandler(mapDisplay_MouseUp);
try
{
using (Stream stream = new FileStream(@"Content\grid.png", FileMode.Open,
FileAccess.Read))
{
grid = Texture2D.FromStream(GraphicsDevice, stream);
stream.Close();
}
using (Stream stream = new FileStream(@"Content\cursor.png", FileMode.Open,
FileAccess.Read))
{
cursor = Texture2D.FromStream(GraphicsDevice, stream);
stream.Close();
}
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading images");
grid = null;
cursor = null;
}
}

The code in the handler for OnInitialize is contained in a try-catch block. When you are doing file IO
that could crash your application it is always a good idea to do it in a try-catch block to recover. Inside
using statements I create a new Stream for the image files in the \Content\ folder, that is why I had
you set the Copy to Output Directory property so they will be there. Inside the using statements I use
the FromStream method of the Texture2D class to read in the files. I then close the stream. It will
automatically be disposed when the block ends. If there is an exception I display the message in a

message box with a title. I also set both of the fields to be null. I will still have the editor work with out
the images but not draw them. That is much nicer than not having the editor work at all.
Drawing the grid and the mouse cursor and preview will be done in a method that I will call from the
Render method. Change the Render method to the following and add the method DrawDisplay.
private void Render()
{
for (int i = 0; i < layers.Count; i++)
{
spriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
camera.Transformation);
if (clbLayers.GetItemChecked(i))
layers[i].Draw(spriteBatch, camera, tileSets);
spriteBatch.End();
}
}

DrawDisplay();

private void DrawDisplay()


{
if (map == null)
return;
Rectangle destination = new Rectangle(
0,
0,
Engine.TileWidth,
Engine.TileHeight);
if (displayGridToolStripMenuItem.Checked)
{
int maxX = mapDisplay.Width / Engine.TileWidth + 1;
int maxY = mapDisplay.Height / Engine.TileHeight + 1;
spriteBatch.Begin();
for (int y = 0; y < maxY; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = 0; x < maxX; x++)
{
destination.X = x * Engine.TileWidth;
spriteBatch.Draw(grid, destination, Color.White);
}

spriteBatch.End();
}
spriteBatch.Begin();
destination.X = mouse.X;
destination.Y = mouse.Y;
spriteBatch.Draw(

tileSets[lbTileset.SelectedIndex].Texture,
destination,
tileSets[lbTileset.SelectedIndex].SourceRectangles[(int)nudCurrentTile.Value],
Color.White);
spriteBatch.Draw(cursor, destination, Color.White);
}

spriteBatch.End();

The Render method just calls the DrawDisplay method after rendering the map. The DrawDisplay
method will exit if the map field is null and there is no map being drawn. It then creates a Rectangle
object, destination, that has the width and height from the Engine class. There is then an if statement
that checks the Checked property of the displayGridToolStripMenuItem. If it is I will draw the grid
over the map. I find out how many items to draw across by dividing the Width of the map display by
the Width of a tile on the screen and add 1. For the number to draw down I divide the Height of the
map display by the Height of a tile on the screen. I call Begin on spriteBatch and then there is a set of
nested loops much like when you draw a tile map. The out loop will loop through all of the rows and
the inner loop the columns. In the outer loop I set the Y property of destination and in the inner loop I
set the X property of destination just like when tiling. After the loops I call the End method of the
spriteBatch. In between calls to Begin and End I draw the preview tile and the cursor image. I set the
X and Y properties of destination to the X and Y properties of the mouse. For drawing the tile I get the
Texture2D using the tileSets field and the SelectedIndex property of lbTileset. To select the source
rectangle I again use the SelectedIndex property of lbTileset and the Value property, cast to an integer,
of nudCurrentTile. I then draw the cursor.
Bixel is a reader of my tutorials and an active member of my forum. He had a nice idea for not always
having to enter in values to the new level and new tileset forms. I'm going to take his suggestion and
add them in here. Right click FormNewLevel in the solution explorere and select View Code. Change
the constructor to the following and add the following method to the Constructor region.
public FormNewLevel()
{
InitializeComponent();
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
SetDefaultValues();
}
private void SetDefaultValues()
{
tbLevelName.Text = "Starting Level";
tbMapName.Text = "Village";
mtbWidth.Text = "100";
mtbHeight.Text = "100";
}

The new code just gives the controls on the form some starting values. I'm also going to change the and
design for FormNewTileset. You don't need to input the number of tiles wide and tiles high a tileset is.
You can calculate those values with the other values on the form. Right click FormNewTileset and
select View Designer to bring up design view of the form. While holding down <SHIFT> select the
Labels and Masked Text Boxes associated with tiles wide and tiles high and press the <Delete> key to
remove them. Move the buttons up a little and change the size of the form. My form appears on the
next page.

That is going to break the code of the btnOK_Click method. I'm also going to set some default values
for the form as well. Right click FormNewTileset in the solution explorer and select View Code.
Change the code for that form to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.WorldClasses;
namespace XLevelEditor
{
public partial class FormNewTileset : Form
{
#region Field Region
bool okPressed;
TilesetData tilesetData;
#endregion
#region Property Region
public TilesetData TilesetData
{
get { return tilesetData; }
}
public bool OKPressed
{
get { return okPressed; }
}
#endregion
#region Constructor Region
public FormNewTileset()
{
InitializeComponent();
btnSelectImage.Click += new EventHandler(btnSelectImage_Click);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);

SetDefaultValues();
}
private void SetDefaultValues()
{
tbTilesetName.Text = "Village Tileset";
mtbTileWidth.Text = "32";
mtbTileHeight.Text = "32";
}
#endregion
#region Button Event Handler Region
void btnSelectImage_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Image Files|*.BMP;*.GIF;*.JPG;*.TGA;*.PNG";
ofDialog.CheckFileExists = true;
ofDialog.CheckPathExists = true;
ofDialog.Multiselect = false;
DialogResult result = ofDialog.ShowDialog();

if (result == DialogResult.OK)
{
tbTilesetImage.Text = ofDialog.FileName;
}

void btnOK_Click(object sender, EventArgs e)


{
if (string.IsNullOrEmpty(tbTilesetName.Text))
{
MessageBox.Show("You must enter a name for the tileset.");
return;
}
if (string.IsNullOrEmpty(tbTilesetImage.Text))
{
MessageBox.Show("You must select an image for the tileset.");
return;
}
int
int
int
int

tileWidth = 0;
tileHeight = 0;
tilesWide = 0;
tilesHigh = 0;

if (!int.TryParse(mtbTileWidth.Text, out tileWidth))


{
MessageBox.Show("Tile width must be an integer value.");
return;
}
else if (tileWidth < 1)
{
MessageBox.Show("Tile width must me greater than zero.");
return;
}
if (!int.TryParse(mtbTileHeight.Text, out tileHeight))
{
MessageBox.Show("Tile height must be an integer value.");
return;
}
else if (tileHeight < 1)
{
MessageBox.Show("Tile height must be greater than zero.");
return;
}

Image tileSet = (Image)Bitmap.FromFile(tbTilesetImage.Text);


tilesWide = tileSet.Width / tileWidth;
tilesHigh = tileSet.Height / tileHeight;
tilesetData = new TilesetData();
tilesetData.TilesetName = tbTilesetName.Text;
tilesetData.TilesetImageName = tbTilesetImage.Text;
tilesetData.TileWidthInPixels = tileWidth;
tilesetData.TileHeightInPixels = tileHeight;
tilesetData.TilesWide = tilesWide;
tilesetData.TilesHigh = tilesHigh;
okPressed = true;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
okPressed = false;
this.Close();
}
}

#endregion

Like on the other form the constructor calls a method, SetDefaultValues, to set some values for the
form. That method sets the Text property of tbTilesetName to Village Tileset and the Text property of
mtbTileWidth and mtbTileHeight to 32. In the handler for the Click event of btnOK I removed the
code that checked the values of tiles wide and tiles high. After validating the form I read in the image
for the tile set using the FromFile method of the Bitmap class. I get tilesWide by dividing the Width
of the image by tileWidth. Similarly, for tilesHigh I divide the Height of the image by tileHeight. The
rest of the code is the same.
The next step will be writing out a level and reading it back in. To do that I'm going to add in a class
like I did to the other editor to serialize and deserialize the data classes. Right click the XLevelEditor
project select Add and then Class. Name this new class XnaSerializer. The code for that class follows
next.
using
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
System.IO;
System.Xml;

using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
namespace XLevelEditor
{
static class XnaSerializer
{
public static void Serialize<T>(string filename, T data)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(filename, settings))
{
IntermediateSerializer.Serialize<T>(writer, data, null);
}

}
public static T Deserialize<T>(string filename)
{
T data;
using (FileStream stream = new FileStream(filename, FileMode.Open))
{
using (XmlReader reader = XmlReader.Create(stream))
{
data = IntermediateSerializer.Deserialize<T>(reader, null);
}
}
return data;
}

The code is the same as before. There are generic methods for serializing and deserializing objects
using the IntermediateSerializer class. Since it is the same as before I'm not going to go into the code
here.
Before I get to saving maps I need to add a little logic into FormMain. I need to keep track of the file
names for the tileset images. To do that I'm going to add a List<TilesetData> that will hold the tilesets
that are added to a map. Add the following field to the Field region of FormMain and change the
handler for the Click event of the new tileset menu item to the following.
List<TilesetData> tileSetData = new List<TilesetData>();
void newTilesetToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewTileset frmNewTileset = new FormNewTileset())
{
frmNewTileset.ShowDialog();
if (frmNewTileset.OKPressed)
{
TilesetData data = frmNewTileset.TilesetData;
try
{
GDIImage image = (GDIImage)GDIBitmap.FromFile(data.TilesetImageName);
tileSetImages.Add(image);
Stream stream = new FileStream(data.TilesetImageName, FileMode.Open,
FileAccess.Read);
Texture2D texture = Texture2D.FromStream(GraphicsDevice, stream);
Tileset tileset = new Tileset(
texture,
data.TilesWide,
data.TilesHigh,
data.TileWidthInPixels,
data.TileHeightInPixels);
tileSets.Add(tileset);
tileSetData.Add(data);
if (map != null)
map.AddTileset(tileset);
stream.Close();
stream.Dispose();

}
catch (Exception ex)

{
MessageBox.Show("Error reading file.\n" + ex.Message, "Error reading image");
return;
}
lbTileset.Items.Add(data.TilesetName);
if (lbTileset.SelectedItem == null)
lbTileset.SelectedIndex = 0;
mapLayerToolStripMenuItem.Enabled = true;
}

All the new code does is create a new List<TilesetData> called tileSetData. In the handler for the
Click event of the new tileset menu item after I add the new tileset to tileSets I also add it to the new
field tileSetData.
I also want to add an overload of the SetTile method of MapLayerData that takes four parameters
instead of three. I will be passing in the X and Y coordinates of the tile as well as the index of the tile in
the tileset and the tileset. Add the following method to MapLayerData.
public void SetTile(int x, int y, int tileIndex, int tileSet)
{
Layer[y * Width + x] = new Tile(tileIndex, tileSet);
}

It works the same as the other SetTile method but just creates a new Tile rather than assigning the Tile
that was passed in. Now I can handle writing out the data for a level. In the constructor for FormMain
you want to wire a handler for the Click event for saveLevelToolStripMenuItem. I added a new
region at the bottom of FormMain for menu items related to saving called Save Menu Item Event
Handler. Change the constructor for FormMain to the following and the following code to the new
region.
public FormMain()
{
InitializeComponent();
this.Load += new EventHandler(FormMain_Load);
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
tilesetToolStripMenuItem.Enabled = false;
mapLayerToolStripMenuItem.Enabled = false;
charactersToolStripMenuItem.Enabled = false;
chestsToolStripMenuItem.Enabled = false;
keysToolStripMenuItem.Enabled = false;
newLevelToolStripMenuItem.Click += new EventHandler(newLevelToolStripMenuItem_Click);
newTilesetToolStripMenuItem.Click += new EventHandler(newTilesetToolStripMenuItem_Click);
newLayerToolStripMenuItem.Click += new EventHandler(newLayerToolStripMenuItem_Click);
saveLevelToolStripMenuItem.Click += new EventHandler(saveLevelToolStripMenuItem_Click);
mapDisplay.OnInitialize += new EventHandler(mapDisplay_OnInitialize);
mapDisplay.OnDraw += new EventHandler(mapDisplay_OnDraw);
}
#region Save Menu Item Event Handler Region
void saveLevelToolStripMenuItem_Click(object sender, EventArgs e)
{
if (map == null)
return;

List<MapLayerData> mapLayerData = new List<MapLayerData>();


for (int i = 0; i < clbLayers.Items.Count; i++)
{
MapLayerData data = new MapLayerData(
clbLayers.Items[i].ToString(),
layers[i].Width,
layers[i].Height);
for (int y = 0; y < layers[i].Height; y++)
for (int x = 0; x < layers[i].Width; x++)
data.SetTile(
x,
y,
layers[i].GetTile(x, y).TileIndex,
layers[i].GetTile(x, y).Tileset);
mapLayerData.Add(data);
}
MapData mapData = new MapData(levelData.MapName, tileSetData, mapLayerData);
FolderBrowserDialog fbDialog = new FolderBrowserDialog();
fbDialog.Description = "Select Game Folder";
fbDialog.SelectedPath = Application.StartupPath;
DialogResult result = fbDialog.ShowDialog();
if (result == DialogResult.OK)
{
if (!File.Exists(fbDialog.SelectedPath + @"\Game.xml"))
{
MessageBox.Show("Game not found", "Error");
return;
}
string LevelPath = Path.Combine(fbDialog.SelectedPath, @"Levels\");
string MapPath = Path.Combine(LevelPath, @"Maps\");
if (!Directory.Exists(LevelPath))
Directory.CreateDirectory(LevelPath);
if (!Directory.Exists(MapPath))
Directory.CreateDirectory(MapPath);
XnaSerializer.Serialize<LevelData>(LevelPath + levelData.LevelName + ".xml", levelData);
XnaSerializer.Serialize<MapData>(MapPath + mapData.MapName + ".xml", mapData);
}

#endregion

What the new method does is check to see if the field map is null. If there is no map there is nothing
really to save. I then create a List<MapLayerData> to hold the MapLayerData for each of the layers
in the map. There is then a for loop that loops through all of the items the Items of clbLayers. Inside
the loop I create a new MapLayerData object. I pass in the Items object for the associated index
converted to a string because the items are stored as objects, and the Width and Height of the
associated MapLayer. There is then a set of nest loops that will loop through all of the tiles in the layer.
I call the SetTile method the I just wrote for the MapLayerData class passing in the loop variables for
the x and y. For the TileIndex I use the TileIndex property after calling GetTile on the layer. You can
string things like this together to make life easier. For the Tileset I use the Tileset property from
GetTile on the the layer.

I then create a new instance of MapData use the MapName property of the LevelData object, the
List<TilesetData> I created earlier and the List<MapLayerData> that I just created.
To save the level I display a FolderBrowserDialog setting the Description property to Select Game
Folder and setting the SelectedPath to the start up path of the application. I capture the result of the
dialog in the variable result. If the result of the dialog was that the user pressed OK I check to see if the
Game.xml file exists. If it doesn't then I display an error message that the game wasn't found and exit
the method. I then create two paths. The first path is for the levels in the game, LevelPath, and the
second is for the maps in the game, MapPath. I then check to see if these paths exists. If they don't
exist I create them. I then call the Serialize method of the XnaSerializer class to serialize the level and
the map.
The next step is to reverse the process and read in a level and a map. In the constructor you will want to
wire an event handler for openLevelToolStripMenuItem. I added another region to deal with reading
in data. Change the constructor for FormMain to the following and add in the following region.
public FormMain()
{
InitializeComponent();
this.Load += new EventHandler(FormMain_Load);
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
tilesetToolStripMenuItem.Enabled = false;
mapLayerToolStripMenuItem.Enabled = false;
charactersToolStripMenuItem.Enabled = false;
chestsToolStripMenuItem.Enabled = false;
keysToolStripMenuItem.Enabled = false;
newLevelToolStripMenuItem.Click += new EventHandler(newLevelToolStripMenuItem_Click);
newTilesetToolStripMenuItem.Click += new EventHandler(newTilesetToolStripMenuItem_Click);
newLayerToolStripMenuItem.Click += new EventHandler(newLayerToolStripMenuItem_Click);
saveLevelToolStripMenuItem.Click += new EventHandler(saveLevelToolStripMenuItem_Click);
openLevelToolStripMenuItem.Click += new EventHandler(openLevelToolStripMenuItem_Click);
mapDisplay.OnInitialize += new EventHandler(mapDisplay_OnInitialize);
mapDisplay.OnDraw += new EventHandler(mapDisplay_OnDraw);
}
void openLevelToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Level Files (*.xml)|*.xml";
ofDialog.CheckFileExists = true;
ofDialog.CheckPathExists = true;
DialogResult result = ofDialog.ShowDialog();
if (result != DialogResult.OK)
return;
string path = Path.GetDirectoryName(ofDialog.FileName);
LevelData newLevel = null;
MapData mapData = null;
try
{

newLevel = XnaSerializer.Deserialize<LevelData>(ofDialg.FileName);
mapData = XnaSerializer.Deserialize<MapData>(path + @"\Maps\" + newLevel.MapName +
".xml");

}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading level");
return;
}
tileSetImages.Clear();
tileSetData.Clear();
tileSets.Clear();
layers.Clear();
lbTileset.Items.Clear();
clbLayers.Items.Clear();
foreach (TilesetData data in mapData.Tilesets)
{
Texture2D texture = null;
tileSetData.Add(data);
lbTileset.Items.Add(data.TilesetName);
GDIImage image = (GDIImage)GDIBitmap.FromFile(data.TilesetImageName);
tileSetImages.Add(image);
using (Stream stream = new FileStream(data.TilesetImageName, FileMode.Open,
FileAccess.Read))
{
texture = Texture2D.FromStream(GraphicsDevice, stream);
tileSets.Add(
new Tileset(
texture,
data.TilesWide,
data.TilesHigh,
data.TileWidthInPixels,
data.TileHeightInPixels));
}
}
foreach (MapLayerData data in mapData.Layers)
{
clbLayers.Items.Add(data.MapLayerName, true);
layers.Add(MapLayer.FromMapLayerData(data));
}
lbTileset.SelectedIndex = 0;
clbLayers.SelectedIndex = 0;
nudCurrentTile.Value = 0;
map = new TileMap(tileSets, layers);
tilesetToolStripMenuItem.Enabled = true;
mapLayerToolStripMenuItem.Enabled = true;
charactersToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
}

The code of interest is in the Click event handler for the level open menu item. I first create an object
of type OpenFileDialog to browse for levels. Levels were stored as .xml files so I set the Filter
property of ofDialog to display just .xml files. I set the CheckFileExists and CheckPathExists
properties to true. I then capture the result of the ShowDialog method. If the result was not the user
hitting the OK button I exit out of the method. To load all information about the level I need to capture
the directory the level sits in. Use the GetDirectoryName method of the Path class to do that. I then
create two local variables to read the data into. The variable newLevel will hold the LevelData and
mapData will hold the MapData. In a try-catch block I try and deserialize the level and the map. If an
exception was thrown I display it in a message box and exit the method. I then call the Clear method

on a lot of collections. The tileSetImages is the GDI+ images of the tilesets, tileSetData is the
TilesetData for the tilesets, tileSets is the actual tilesets, layers is the map layers, and the last two are
the List Box of tilesets and the Checked List Box for controlling map layers.
I guess I probably should have done the next part in a try-catch as well. In a foreach loop I loop through
all of the TilesetData objects in the Tilesets field of the MapData read in. Inside the loop is a
Texture2D to hold the texture for the tileset. I add the current TilesetData object to tileSetData and
the name of the tileset data to Items collection of lbTileset. This is where I probably should have added
a try-catch block. I use the FormFile method of the GDI+ Bitmap class to read in the image associated
with the TilesetData. I then add that image to tileSetImages. The next block of code is a using
statement where I create a Stream to the image for the tileset to read it in as a Texture2D. If you don't
use FileAccess.Read you can get an exception that the file is in use if you are running from the
debugger. I use the FromStream method to read the image in as a Texture2D and then add a new
Tileset to the tileSets field.
Now that I'm done with the tilesets its time to do the layers. There is a foreach loop that will loop
through all of the layers in mapData. I add the MapLayerName of the layer to the Items collection of
clbLayers passing in true as well so that it will be checked. I then use the FromMapLayerData
method of the MapLayer class to add a new layer to the layers field. I then set the SelectedIndex of
lbTileset and clbLayers to 0 as well as the Value property of nudCurrentTile. I then create a new
TileMap object using the tileSets and layers fields. The last thing I do is set the Enabled property of
the other menu items to true.
I think I'm going to end this tutorial here. I've added in more functionality to the level editor, being able
to select tiles more easily, and write and reading in maps. I encourage you to visit the news page of my
site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 25
Level Editor Continued
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
I'm going to work on the level editor again in this tutorial and add in some more features. I found a bug
while I was working on the editor though. If you open a map and try and add a new layer to it a Null
Reference exception will be thrown. I forget to set the levelData field. All I do is set the field
levelData to the newLevel that I read in. Change the event handler for opening a level to the following.
void openLevelToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Level Files (*.xml)|*.xml";
ofDialog.CheckFileExists = true;
ofDialog.CheckPathExists = true;
DialogResult result = ofDialog.ShowDialog();
if (result != DialogResult.OK)
return;
string path = Path.GetDirectoryName(ofDialog.FileName);
LevelData newLevel = null;
MapData mapData = null;
try
{
newLevel = XnaSerializer.Deserialize<LevelData>(ofDialog.FileName);
mapData = XnaSerializer.Deserialize<MapData>(path + @"\Maps\" + newLevel.MapName +
".xml");
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading level");
return;
}
tileSetImages.Clear();
tileSetData.Clear();
tileSets.Clear();
layers.Clear();
lbTileset.Items.Clear();
clbLayers.Items.Clear();
levelData = newLevel;
foreach (TilesetData data in mapData.Tilesets)
{
Texture2D texture = null;
tileSetData.Add(data);

lbTileset.Items.Add(data.TilesetName);
GDIImage image = (GDIImage)GDIBitmap.FromFile(data.TilesetImageName);
tileSetImages.Add(image);
using (Stream stream = new FileStream(data.TilesetImageName, FileMode.Open,
FileAccess.Read))
{
texture = Texture2D.FromStream(GraphicsDevice, stream);
tileSets.Add(
new Tileset(
texture,
data.TilesWide,
data.TilesHigh,
data.TileWidthInPixels,
data.TileHeightInPixels));
}
}
foreach (MapLayerData data in mapData.Layers)
{
clbLayers.Items.Add(data.MapLayerName, true);
layers.Add(MapLayer.FromMapLayerData(data));
}
lbTileset.SelectedIndex = 0;
clbLayers.SelectedIndex = 0;
nudCurrentTile.Value = 0;
map = new TileMap(tileSets, layers);

tilesetToolStripMenuItem.Enabled = true;
mapLayerToolStripMenuItem.Enabled = true;
charactersToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;

I saw on my forum that the editor was being a little sluggish. I'm going to fix that first. You can easily
change the Interval property of controlTimer so that the Tick event is triggered more often so that the
logic and drawing will be done more often. The problem with doing that is that the map will scroll
really fast. What I did to fix that was introduce a frame counter and only call the scrolling logic every
so often.
To the Field region you are going to want to add in a field to keep track of the frame count. Add in the
following field.
int frameCount = 0;

The next thing you will want to do is to change the Interval property of controlTimer so that the Tick
event will be called more often. If you set it to 17 milliseconds it will be called approximately 60 times
per second. I set the value in the FormMain_Load method so change that method to the following.
void FormMain_Load(object sender, EventArgs e)
{
lbTileset.SelectedIndexChanged += new EventHandler(lbTileset_SelectedIndexChanged);
nudCurrentTile.ValueChanged += new EventHandler(nudCurrentTile_ValueChanged);
Rectangle viewPort = new Rectangle(0, 0, mapDisplay.Width, mapDisplay.Height);
camera = new Camera(viewPort);
engine = new Engine(32, 32);

controlTimer.Tick += new EventHandler(controlTimer_Tick);


controlTimer.Enabled = true;
controlTimer.Interval = 17;
tbMapLocation.TextAlign = HorizontalAlignment.Center;
pbTilesetPreview.MouseDown += new MouseEventHandler(pbTilesetPreview_MouseDown);
}

mapDisplay.SizeChanged += new EventHandler(mapDisplay_SizeChanged);

The next step will be to update the frameCount field in the event handler for the Tick event. Change
the handler to the following.
void controlTimer_Tick(object sender, EventArgs e)
{
frameCount = ++frameCount % 6;
mapDisplay.Invalidate();
Logic();
}

What I do is pre-increment frameCount and get the remainder of dividing it by 6. So what happens is
that frameCount will have the values 0, 1, 2, 3, 4, and 5. I will check in the Logic method when the
value is 0 that the map should be scrolled. Change the Logic method to the following.
private void Logic()
{
if (layers.Count == 0)
return;
Vector2 position = camera.Position;
if (trackMouse)
{
if (frameCount == 0)
{
if (mouse.X < Engine.TileWidth)
position.X -= Engine.TileWidth;
if (mouse.X > mapDisplay.Width - Engine.TileWidth)
position.X += Engine.TileWidth;
if (mouse.Y < Engine.TileHeight)
position.Y -= Engine.TileHeight;
if (mouse.Y > mapDisplay.Height - Engine.TileHeight)
position.Y += Engine.TileHeight;

camera.Position = position;
camera.LockCamera();

position.X = mouse.X + camera.Position.X;


position.Y = mouse.Y + camera.Position.Y;
Point tile = Engine.VectorToCell(position);
tbMapLocation.Text =
"( " + tile.X.ToString() + ", " + tile.Y.ToString() + " )";
if (isMouseDown)
{
if (rbDraw.Checked)
{
layers[clbLayers.SelectedIndex].SetTile(
tile.X,
tile.Y,

(int)nudCurrentTile.Value,
lbTileset.SelectedIndex);

}
if (rbErase.Checked)
{
layers[clbLayers.SelectedIndex].SetTile(
tile.X,
tile.Y,
-1,
-1);
}
}

All I did was wrap the logic for scrolling the map in an if statement. If the frameCount field is at 0
then I want to check to see if the map should be scrolled. This will fix the problem with the editor
lagging and it still scrolling at a reasonable rate.
What I want to do now is to add in different brush sizes. What I did was add in a new menu item,
&Brushes, and under &Brushes I added: 1 x 1, 2 x 2, 4 x 4, and 8 x 8. I also set the Checked property
of 1 x 1 to True. You are going to want to subscribe to the Click event of the menu items. I did that in
the constructor. I also placed the handlers into a region of their own, add in the region below as well.
Also add in the following field to the Field region.
int brushWidth = 1;
public FormMain()
{
InitializeComponent();
this.Load += new EventHandler(FormMain_Load);
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
tilesetToolStripMenuItem.Enabled = false;
mapLayerToolStripMenuItem.Enabled = false;
charactersToolStripMenuItem.Enabled = false;
chestsToolStripMenuItem.Enabled = false;
keysToolStripMenuItem.Enabled = false;
newLevelToolStripMenuItem.Click += new EventHandler(newLevelToolStripMenuItem_Click);
newTilesetToolStripMenuItem.Click += new EventHandler(newTilesetToolStripMenuItem_Click);
newLayerToolStripMenuItem.Click += new EventHandler(newLayerToolStripMenuItem_Click);
saveLevelToolStripMenuItem.Click += new EventHandler(saveLevelToolStripMenuItem_Click);
openLevelToolStripMenuItem.Click += new EventHandler(openLevelToolStripMenuItem_Click);
mapDisplay.OnInitialize += new EventHandler(mapDisplay_OnInitialize);
mapDisplay.OnDraw += new EventHandler(mapDisplay_OnDraw);
x1ToolStripMenuItem.Click
x2ToolStripMenuItem.Click
x4ToolStripMenuItem.Click
x8ToolStripMenuItem.Click

+=
+=
+=
+=

new
new
new
new

EventHandler(x1ToolStripMenuItem_Click);
EventHandler(x2ToolStripMenuItem_Click);
EventHandler(x4ToolStripMenuItem_Click);
EventHandler(x8ToolStripMenuItem_Click);

}
#region Brush Event Handler Region
void x1ToolStripMenuItem_Click(object sender, EventArgs e)
{
x1ToolStripMenuItem.Checked = true;
x2ToolStripMenuItem.Checked = false;
x4ToolStripMenuItem.Checked = false;

x8ToolStripMenuItem.Checked = false;
brushWidth = 1;
}
void x2ToolStripMenuItem_Click(object sender, EventArgs e)
{
x1ToolStripMenuItem.Checked = false;
x2ToolStripMenuItem.Checked = true;
x4ToolStripMenuItem.Checked = false;
x8ToolStripMenuItem.Checked = false;
brushWidth = 2;
}
void x4ToolStripMenuItem_Click(object sender, EventArgs e)
{
x1ToolStripMenuItem.Checked = false;
x2ToolStripMenuItem.Checked = false;
x4ToolStripMenuItem.Checked = true;
x8ToolStripMenuItem.Checked = false;
brushWidth = 4;
}
void x8ToolStripMenuItem_Click(object sender, EventArgs e)
{
x1ToolStripMenuItem.Checked = false;
x2ToolStripMenuItem.Checked = false;
x4ToolStripMenuItem.Checked = false;
x8ToolStripMenuItem.Checked = true;
brushWidth = 8;
}
#endregion

The handlers all have the same basic form. They set the Checked property of the other menu items to
false and theirs to true. They also set the brushWidth field to be the appropriate width.
Time to update the logic a little and try drawing of the display. In the logic method I called a new
method that I wrote called SetTiles that will set a range of tiles based on the brushWidth field. Change
the Logic method to the following and add the SetTiles method below it.
private void Logic()
{
if (layers.Count == 0)
return;
Vector2 position = camera.Position;
if (trackMouse)
{
if (frameCount == 0)
{
if (mouse.X < Engine.TileWidth)
position.X -= Engine.TileWidth;
if (mouse.X > mapDisplay.Width - Engine.TileWidth)
position.X += Engine.TileWidth;
if (mouse.Y < Engine.TileHeight)
position.Y -= Engine.TileHeight;
if (mouse.Y > mapDisplay.Height - Engine.TileHeight)
position.Y += Engine.TileHeight;

camera.Position = position;
camera.LockCamera();
}
position.X = mouse.X + camera.Position.X;
position.Y = mouse.Y + camera.Position.Y;
Point tile = Engine.VectorToCell(position);
tbMapLocation.Text =
"( " + tile.X.ToString() + ", " + tile.Y.ToString() + " )";
if (isMouseDown)
{
if (rbDraw.Checked)
SetTiles(tile, (int)nudCurrentTile.Value, lbTileset.SelectedIndex);

}
}

if (rbErase.Checked)
SetTiles(tile, -1, -1);

private void SetTiles(Point tile, int tileIndex, int tileset)


{
int selected = clbLayers.SelectedIndex;
for (int y = 0; y < brushWidth; y++)
{
if (tile.Y + y >= layers[selected].Height)
break;

for (int x = 0; x < brushWidth; x++)


{
if (tile.X + x < layers[selected].Width)
layers[selected].SetTile(
tile.X + x,
tile.Y + y,
tileIndex,
tileset);
}

The Logic method will call the SetTiles method passing in the Point tile, the Value property of
nudCurrentTile, and the SelectedIndex of lbTileset if rbDraw is checked. If rbErase is checked I
pass in -1 for the tile and -1 for the tileset.
The SetTiles method first declared a local variable selected that is the SelectedIndex of clbLayers, the
current layer to be drawn to. I then have a set of nested for loops. The outer loop, y, loops from 0 to
brushWidth. There is a check to see if tile.Y + y is greater than or equal to the Height of the layer. If it
is the index is outside the height of the map and you don't want to continue drawing so I break out of
the loop. In the inner loop, x, I loop from 0 to brushWidth. If tile.X + x is less than the Width of the
layer I call the SetTile method of the layer passing in tile.X + x, tile.Y + y, tileIndex and tileset.
I want to update the DrawDisplay method. First though I want to add in a few things. In the View
menu I want to add in being able to draw the grid in different colors. I created the grid image to be all
white. This means if I use color different than Color.White as the tint color the grid will be that color.
I'm going to add a menu with a sub menu to the side with different color options. Under &Display
Grid add in &Grid Color. Now, select Grid Color and beside it add in the following items. &Black,
B&lue, R&ed, &Green, &Yellow, &White. For the White option I set Checked to true. You can add

in other colors if you want and set your default to what you want.
Now, in the constructor I want to wire some event handlers and add in a new region for the handlers. I
also want to add in a field. Add in this field, change the constructor to the following, and add in this
region.
Color gridColor = Color.White;
public FormMain()
{
InitializeComponent();
this.Load += new EventHandler(FormMain_Load);
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
tilesetToolStripMenuItem.Enabled = false;
mapLayerToolStripMenuItem.Enabled = false;
charactersToolStripMenuItem.Enabled = false;
chestsToolStripMenuItem.Enabled = false;
keysToolStripMenuItem.Enabled = false;
newLevelToolStripMenuItem.Click += new EventHandler(newLevelToolStripMenuItem_Click);
newTilesetToolStripMenuItem.Click += new EventHandler(newTilesetToolStripMenuItem_Click);
newLayerToolStripMenuItem.Click += new EventHandler(newLayerToolStripMenuItem_Click);
saveLevelToolStripMenuItem.Click += new EventHandler(saveLevelToolStripMenuItem_Click);
openLevelToolStripMenuItem.Click += new EventHandler(openLevelToolStripMenuItem_Click);
mapDisplay.OnInitialize += new EventHandler(mapDisplay_OnInitialize);
mapDisplay.OnDraw += new EventHandler(mapDisplay_OnDraw);
x1ToolStripMenuItem.Click
x2ToolStripMenuItem.Click
x4ToolStripMenuItem.Click
x8ToolStripMenuItem.Click

+=
+=
+=
+=

new
new
new
new

EventHandler(x1ToolStripMenuItem_Click);
EventHandler(x2ToolStripMenuItem_Click);
EventHandler(x4ToolStripMenuItem_Click);
EventHandler(x8ToolStripMenuItem_Click);

blackToolStripMenuItem.Click += new EventHandler(blackToolStripMenuItem_Click);


blueToolStripMenuItem.Click += new EventHandler(blueToolStripMenuItem_Click);
redToolStripMenuItem.Click += new EventHandler(redToolStripMenuItem_Click);
greenToolStripMenuItem.Click += new EventHandler(greenToolStripMenuItem_Click);
yellowToolStripMenuItem.Click += new EventHandler(yellowToolStripMenuItem_Click);
whiteToolStripMenuItem.Click += new EventHandler(whiteToolStripMenuItem_Click);
}
#region Grid Color Event Handler Region
void whiteToolStripMenuItem_Click(object sender, EventArgs e)
{
gridColor = Color.White;
blackToolStripMenuItem.Checked = false;
blueToolStripMenuItem.Checked = false;
redToolStripMenuItem.Checked = false;
greenToolStripMenuItem.Checked = false;
yellowToolStripMenuItem.Checked = false;
whiteToolStripMenuItem.Checked = true;
}
void yellowToolStripMenuItem_Click(object sender, EventArgs e)
{
gridColor = Color.Yellow;
blackToolStripMenuItem.Checked = false;
blueToolStripMenuItem.Checked = false;
redToolStripMenuItem.Checked = false;
greenToolStripMenuItem.Checked = false;
yellowToolStripMenuItem.Checked = true;
whiteToolStripMenuItem.Checked = false;

}
void greenToolStripMenuItem_Click(object sender, EventArgs e)
{
gridColor = Color.Green;
blackToolStripMenuItem.Checked = false;
blueToolStripMenuItem.Checked = false;
redToolStripMenuItem.Checked = false;
greenToolStripMenuItem.Checked = true;
yellowToolStripMenuItem.Checked = false;
whiteToolStripMenuItem.Checked = false;
}
void redToolStripMenuItem_Click(object sender, EventArgs e)
{
gridColor = Color.Red;
blackToolStripMenuItem.Checked = false;
blueToolStripMenuItem.Checked = false;
redToolStripMenuItem.Checked = true;
greenToolStripMenuItem.Checked = false;
yellowToolStripMenuItem.Checked = false;
whiteToolStripMenuItem.Checked = false;
}
void blueToolStripMenuItem_Click(object sender, EventArgs e)
{
gridColor = Color.Blue;
blackToolStripMenuItem.Checked = false;
blueToolStripMenuItem.Checked = true;
redToolStripMenuItem.Checked = false;
greenToolStripMenuItem.Checked = false;
yellowToolStripMenuItem.Checked = false;
whiteToolStripMenuItem.Checked = false;
}
void blackToolStripMenuItem_Click(object sender, EventArgs e)
{
gridColor = Color.Black;
blackToolStripMenuItem.Checked = true;
blueToolStripMenuItem.Checked = false;
redToolStripMenuItem.Checked = false;
greenToolStripMenuItem.Checked = false;
yellowToolStripMenuItem.Checked = false;
whiteToolStripMenuItem.Checked = false;
}
#endregion

The handlers should look familiar as they have the same form as those for the brush sizes. I set the
gridColor field to the appropriate color. I then set the Checked property for the appropriate item to
true and the others to false. You can now update the DrawDisplay method to the following.
private void DrawDisplay()
{
if (map == null)
return;
Rectangle destination = new Rectangle(
0,
0,
Engine.TileWidth,
Engine.TileHeight);
if (displayGridToolStripMenuItem.Checked)
{
int maxX = mapDisplay.Width / Engine.TileWidth + 1;
int maxY = mapDisplay.Height / Engine.TileHeight + 1;

spriteBatch.Begin();
for (int y = 0; y < maxY; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = 0; x < maxX; x++)
{
destination.X = x * Engine.TileWidth;
spriteBatch.Draw(grid, destination, gridColor);
}

spriteBatch.End();
}
spriteBatch.Begin();
destination.X = mouse.X;
destination.Y = mouse.Y;
if (rbDraw.Checked)
{
spriteBatch.Draw(
tileSets[lbTileset.SelectedIndex].Texture,
destination,
tileSets[lbTileset.SelectedIndex].SourceRectangles[(int)nudCurrentTile.Value],
Color.White);
}
spriteBatch.Draw(cursor, destination, Color.White);
}

spriteBatch.End();

The change is that instead of using Color.White in the call to Draw when I'm drawing the grid. I also
added in an if statement to check to see if the Checked property of rbDraw is true. If it is true I draw
the preview tile.
There is one more thing that you might want to add to the editor. You may want to have the tiles that
will be changed high lighted. The first step in doing that is to that is to have a Texture2D. In this case
I'm not going to create an image in an image editor and add it to the content. I'm going to create a
Texture2D with code. Add in these fields to the Field region and change mapDisplay_OnInitialize to
the following.
Texture2D shadow;
Vector2 shadowPosition = Vector2.Zero;
void mapDisplay_OnInitialize(object sender, EventArgs e)
{
spriteBatch = new SpriteBatch(GraphicsDevice);
shadow = new Texture2D(GraphicsDevice, 20, 20, false, SurfaceFormat.Color);
Color[] data = new Color[shadow.Width * shadow.Height];
Color tint = Color.LightSteelBlue;
tint.A = 25;
for (int i = 0; i < shadow.Width * shadow.Height; i++)
data[i] = tint;
shadow.SetData<Color>(data);
mapDisplay.MouseEnter += new EventHandler(mapDisplay_MouseEnter);

mapDisplay.MouseLeave += new EventHandler(mapDisplay_MouseLeave);


mapDisplay.MouseMove += new MouseEventHandler(mapDisplay_MouseMove);
mapDisplay.MouseDown += new MouseEventHandler(mapDisplay_MouseDown);
mapDisplay.MouseUp += new MouseEventHandler(mapDisplay_MouseUp);
try
{
using (Stream stream = new FileStream(@"Content\grid.png", FileMode.Open,
FileAccess.Read))
{
grid = Texture2D.FromStream(GraphicsDevice, stream);
stream.Close();
}
using (Stream stream = new FileStream(@"Content\cursor.png", FileMode.Open,
FileAccess.Read))
{
cursor = Texture2D.FromStream(GraphicsDevice, stream);
stream.Close();
}
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading images");
grid = null;
cursor = null;
}
}

What I did is add a Texture2D field and a Vector2 field. The Texture2D is the image for the shadow
and the Vector2 will be its position on the map, not the display, the map itself. In the event handler for
the OnInitialize method of mapDisplay I create a new Texture2D passing in the GraphicsDevice for
the map display, 20 for the width and height, and SurfaceFormat.Color. That format is a 32-bit format
with 8 bits for red, blue, green, and alpha. I then create an array the size of the width shadow times the
height of shadow. I create a color, tint, set to Blue with a low alpha channel. In a for loop I loop over
the entire array and set each item to be tint. I then call the SetData method that takes a Color and pass
in the array of color.
You will want to update the Logic method to update the shadowPosition field so that it holds the tile
that the mouse is hovering over. All I did was after finding the tile the mouse plus the camera is in is set
shadowPosition to a Vector2 with X and Y components tile.X and tile.Y. Update the Logic method to
the following.
private void Logic()
{
if (layers.Count == 0)
return;
Vector2 position = camera.Position;
if (trackMouse)
{
if (frameCount == 0)
{
if (mouse.X < Engine.TileWidth)
position.X -= Engine.TileWidth;
if (mouse.X > mapDisplay.Width - Engine.TileWidth)
position.X += Engine.TileWidth;
if (mouse.Y < Engine.TileHeight)
position.Y -= Engine.TileHeight;

if (mouse.Y > mapDisplay.Height - Engine.TileHeight)


position.Y += Engine.TileHeight;
camera.Position = position;
camera.LockCamera();
}
position.X = mouse.X + camera.Position.X;
position.Y = mouse.Y + camera.Position.Y;
Point tile = Engine.VectorToCell(position);
shadowPosition = new Vector2(tile.X, tile.Y);
tbMapLocation.Text =
"( " + tile.X.ToString() + ", " + tile.Y.ToString() + " )";
if (isMouseDown)
{
if (rbDraw.Checked)
SetTiles(tile, (int)nudCurrentTile.Value, lbTileset.SelectedIndex);
if (rbErase.Checked)
SetTiles(tile, -1, -1);
}

Now I will update the Render method. What it will do is draw the shadow texture tinted white with a
low alpha value so it is somewhat transparent.
private void Render()
{
for (int i = 0; i < layers.Count; i++)
{
spriteBatch.Begin(
SpriteSortMode.Deferred,
BlendState.AlphaBlend,
SamplerState.PointClamp,
null,
null,
null,
camera.Transformation);
if (clbLayers.GetItemChecked(i))
layers[i].Draw(spriteBatch, camera, tileSets);
Rectangle destination = new Rectangle(
(int)shadowPosition.X * Engine.TileWidth,
(int)shadowPosition.Y * Engine.TileHeight,
brushWidth * Engine.TileWidth,
brushWidth * Engine.TileHeight);
Color tint = Color.White;
tint.A = 1;
spriteBatch.Draw(shadow, destination, tint);
spriteBatch.End();
}
}

DrawDisplay();

The one thing I want to do is to add in writing out and reading in tilesets and map layers. This way you
don't have to always be creating tilesets when you are reusing the same tileset over and over again. You
can also have a stock layer filled with grass that you can add easily. As always the first step is to wire
event handlers. I went a head and wired handlers for the Click event of both the Tileset menu and the

Map Layer menu. I did that in the constructor for FormMain, of course. I added the handlers for
saving to the Save Menu Event Handler region and for opening to the Open Menu Event Handler
region. Change the constructor of FormMain to the following first.
public FormMain()
{
InitializeComponent();
this.Load += new EventHandler(FormMain_Load);
this.FormClosing += new FormClosingEventHandler(FormMain_FormClosing);
tilesetToolStripMenuItem.Enabled = false;
mapLayerToolStripMenuItem.Enabled = false;
charactersToolStripMenuItem.Enabled = false;
chestsToolStripMenuItem.Enabled = false;
keysToolStripMenuItem.Enabled = false;
newLevelToolStripMenuItem.Click += new EventHandler(newLevelToolStripMenuItem_Click);
newTilesetToolStripMenuItem.Click += new EventHandler(newTilesetToolStripMenuItem_Click);
newLayerToolStripMenuItem.Click += new EventHandler(newLayerToolStripMenuItem_Click);
saveLevelToolStripMenuItem.Click += new EventHandler(saveLevelToolStripMenuItem_Click);
openLevelToolStripMenuItem.Click += new EventHandler(openLevelToolStripMenuItem_Click);
mapDisplay.OnInitialize += new EventHandler(mapDisplay_OnInitialize);
mapDisplay.OnDraw += new EventHandler(mapDisplay_OnDraw);
x1ToolStripMenuItem.Click
x2ToolStripMenuItem.Click
x4ToolStripMenuItem.Click
x8ToolStripMenuItem.Click

+=
+=
+=
+=

new
new
new
new

EventHandler(x1ToolStripMenuItem_Click);
EventHandler(x2ToolStripMenuItem_Click);
EventHandler(x4ToolStripMenuItem_Click);
EventHandler(x8ToolStripMenuItem_Click);

blackToolStripMenuItem.Click += new EventHandler(blackToolStripMenuItem_Click);


blueToolStripMenuItem.Click += new EventHandler(blueToolStripMenuItem_Click);
redToolStripMenuItem.Click += new EventHandler(redToolStripMenuItem_Click);
greenToolStripMenuItem.Click += new EventHandler(greenToolStripMenuItem_Click);
yellowToolStripMenuItem.Click += new EventHandler(yellowToolStripMenuItem_Click);
whiteToolStripMenuItem.Click += new EventHandler(whiteToolStripMenuItem_Click);
saveTilesetToolStripMenuItem.Click += new EventHandler(saveTilesetToolStripMenuItem_Click);
saveLayerToolStripMenuItem.Click += new EventHandler(saveLayerToolStripMenuItem_Click);
openTilesetToolStripMenuItem.Click += new EventHandler(openTilesetToolStripMenuItem_Click);
openLayerToolStripMenuItem.Click += new EventHandler(openLayerToolStripMenuItem_Click);
}

That just wires the event handlers. I will start with the handlers for the save events. Add these two
methods to the Save Menu Event Handlers region.
void saveTilesetToolStripMenuItem_Click(object sender, EventArgs e)
{
if (tileSetData.Count == 0)
return;
SaveFileDialog sfDialog = new SaveFileDialog();
sfDialog.Filter = "Tileset Data (*.tdat)|*.tdat";
sfDialog.CheckPathExists = true;
sfDialog.OverwritePrompt = true;
sfDialog.ValidateNames = true;
DialogResult result = sfDialog.ShowDialog();
if (result != DialogResult.OK)
return;

try
{

XnaSerializer.Serialize<TilesetData>(
sfDialog.FileName,
tileSetData[lbTileset.SelectedIndex]);

}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error saving tileset");
}

void saveLayerToolStripMenuItem_Click(object sender, EventArgs e)


{
if (layers.Count == 0)
return;
SaveFileDialog sfDialog = new SaveFileDialog();
sfDialog.Filter = "Map Layer Data (*.mldt)|*.mldt";
sfDialog.CheckPathExists = true;
sfDialog.OverwritePrompt = true;
sfDialog.ValidateNames = true;
DialogResult result = sfDialog.ShowDialog();
if (result != DialogResult.OK)
return;
MapLayerData data = new MapLayerData(
clbLayers.SelectedItem.ToString(),
layers[clbLayers.SelectedIndex].Width,
layers[clbLayers.SelectedIndex].Height);
for (int y = 0; y < layers[clbLayers.SelectedIndex].Height; y++)
{
for (int x = 0; x < layers[clbLayers.SelectedIndex].Width; x++)
{
data.SetTile(
x,
y,
layers[clbLayers.SelectedIndex].GetTile(x, y).TileIndex,
layers[clbLayers.SelectedIndex].GetTile(x, y).Tileset);
}
}
try
{
XnaSerializer.Serialize<MapLayerData>(sfDialog.FileName, data);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error saving map layer data");
}
}

The methods are very similar. The big difference is the data that they work on. You also need to do a
little more work to save the layer data because I don't have a collection of MapLayerData to work
with. First I check to make sure there is data to write out checking the Count property of the
appropriate collection. I then create a SaveFileDialog object to display to the user. I set a few
properties for the SaveFileDialog next. The Filter property filters the files shown and is different for
TilesetData and MapLayerData. I also set a few fields to validate that a correct path is chosen and if
the file exists there will be an overwrite prompt. I capture the result of the ShowDialog method of the
SaveFileDialog. If the result was not that the OK button was pressed I exit out of the method. I then try
to save the data.

In the handler for saving a tileset I already have the collection of TilesetData objects so I don't have to
create one. In a try-catch block I call the Serialize method of XnaSerializer passing in the FileName
property of the SaveFileDialog object and the TilesetData object in tileSetData that is currently
selected in lbTileset. If there was an exception I display the exception in a message box with a title
saying there was an error saving the tileset.
In the handler for saving a map layer I first have to create a MapLayerData object to work with. I
create the object passing in the SelectedItem of clbLayers for the name of the layer and for the width
of the map I use the Width and Height properties of the layer at the SelectedIndex property of
clbLayers. There are nested for loops to loop through the entire layer. I call the SetTile method of
MapLayerData passing in x, y, GetTile(x, y).TileIndex, and GetTile(x, y).Tileset. Then there is a try
catch block where I try to serialize the object. If there is an exception I display the exception in a
message box with a title saying there was an error saving the layer.
Reading in tilesets is a little more complicated than writing out as you recall from loading a level. So I
will first cover reading in tilesets and then reading in map layers. Add the following method to the
Open Menu Event Handler region.
void openTilesetToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Tileset Data (*.tdat)|*.tdat";
ofDialog.CheckPathExists = true;
ofDialog.CheckFileExists = true;
DialogResult result = ofDialog.ShowDialog();
if (result != DialogResult.OK)
return;
TilesetData data = null;
Texture2D texture = null;
Tileset tileset = null;
GDIImage image = null;
try
{
data = XnaSerializer.Deserialize<TilesetData>(ofDialog.FileName);
using (Stream stream = new FileStream(data.TilesetImageName, FileMode.Open,
FileAccess.Read))
{
texture = Texture2D.FromStream(GraphicsDevice, stream);
stream.Close();
}
image = (GDIImage)GDIBitmap.FromFile(data.TilesetImageName);
tileset = new Tileset(
texture,
data.TilesWide,
data.TilesHigh,
data.TileWidthInPixels,
data.TileHeightInPixels);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading tileset");
return;
}
for (int i = 0; i < lbTileset.Items.Count; i++)
{

if (lbTileset.Items[i].ToString() == data.TilesetName)
{
MessageBox.Show("Level already contains a tileset with this name.", "Existing
tileset");
return;
}
}
tileSetData.Add(data);
tileSets.Add(tileset);
lbTileset.Items.Add(data.TilesetName);
pbTilesetPreview.Image = image;
tileSetImages.Add(image);
lbTileset.SelectedIndex = lbTileset.Items.Count - 1;
nudCurrentTile.Value = 0;
}

mapLayerToolStripMenuItem.Enabled = true;

I first create an object of type OpenFileDialog to browse for tileset data. Tileset data was stored with
a .tdat extension so I set the Filter property of ofDialog to display just .tdat files. I set the
CheckFileExists and CheckPathExists properties to true. I then capture the result of the ShowDialog
method. If the result was not the user hitting the OK button I exit out of the method. I then have four
local variables needed to create a tileset. The variable data will hold the TilesetData for the tileset
being read in. The texture variable will hold the Texture2D associated with the tile set. I also have a
Tileset variable called tileset to hold the tileset created and the variable image holds the image to
display in pbTilesetPreview and add to tileSetImages.
In a try-catch block I try to deserialize the tileset data. I then try to read in the Texture2D for the tileset
like I have before. I create a Stream to the image location and use the FromStream method to try and
read the image into a Texture2D. I also try and read in the image as a GDI+ image. If an exception was
thrown I display it in a message box and exit the method. Finally I assign tileset to be a new Tileset
using the information read in. If any exceptions were thrown I display them in a message box and then
exit the method.
There is next a for loop that will loop through all of the items in the Items collection of lbTileset to
check to see if a tileset with the name of the loaded tileset exists already. If it does I display an error
message in a message box and then exit the method.
I then add the data variable to tileSetData and tileset to tilesets. I also add the TilesetName of the
data variable to lbTileset. I set the Image property of pbTilesetPreview to the image variable and add
it to the tileSetImages collection. I also set the SelectedIndex property of lbTileset to be the Count
property of the Items collection minus 1 and set the Value property of nudCurrentTile to 0 as well.
The last step is to set the Enabled property of mapLayerToolStripMenuItem to true.
The last thing that I'm going to cover in this tutorial is opening a saved map layer. Add the following
method to the Open Menu Item Handler region.
void openLayerToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Map Layer Data (*.mldt)|*.mldt";
ofDialog.CheckPathExists = true;
ofDialog.CheckFileExists = true;

DialogResult result = ofDialog.ShowDialog();


if (result != DialogResult.OK)
return;
MapLayerData data = null;
try
{

data = XnaSerializer.Deserialize<MapLayerData>(ofDialog.FileName);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading map layer");
return;
}
for (int i = 0; i < clbLayers.Items.Count; i++)
{
if (clbLayers.Items[i].ToString() == data.MapLayerName)
{
MessageBox.Show("Layer by that name already exists.", "Existing layer");
return;
}
}
clbLayers.Items.Add(data.MapLayerName, true);
layers.Add(MapLayer.FromMapLayerData(data));
if (map == null)
map = new TileMap(tileSets, layers);
}

This method has the same basic layout as the last. I create an OpenFileDialog object to select the
MapLayerData to be opened. I set the Filter property so that it will only display MapLayerData files
with the extension mldt. I set the CheckPathExists and CheckFileExists properties to true. I display
the dialog using the ShowDialog method and capture the result. If the result was not the OK button I
exit the method.
There is then a local variable that will hold a MapLayerData object. I try and deserialize the file in a
try-catch block. If there was an error deserializing the object I display an error message and exit the
method. A for loop follows next that checks to see if there is already a map layer with the same name as
the map layer just read in. If there is I display an message box and exit the method. I then add the
MapLayerName property of the layer to the items of clbLayers passing in true so that the item will be
checked. I then add a new MapLayer using the FromMapLayerData method and the data variable
that was just read in. If the map field is null I create a new TileMap.
I think I'm going to end this tutorial here. I've added in even more functionality to the level editor. I
think the next tutorial will be on the game instead of editors to give you a break from editors. I
encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest
news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 26
More On Skills
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to work on the game to give you a break from the editors that I think you
deserve. What I want to do first is to make a change to the way the screen manager works. Instead of
switching directly to a new screen I want to add a transition period. This transition period will give the
state a chance to finish off a few tasks that will leave it in a better state and help prevent cascading. It
has to do with the control manager and the fact that I'm using events. The control manager should
really be threaded but I don't want to go into that in these tutorials. Having a transition state will allow
it to finish off tasks before switching to a new state.
First, make the EyesOfTheDragon project the start up project by right clicking it in the solution
explorer and selecting Set As StartUp Project. The first thing that I did was add an enumeration to the
GameStateManager class at the namespace level. Doing things makes it accessible with out having to
reference a class name. Add this enumeration just above the class declaration for GameStateManager.
public enum ChangeType { Change, Pop, Push }

The enumeration has values for each of the three types of state changes. You can change states, pop the
current state off the stack, and push a new state on top of the stack. It will be used to determine how to
respond to switching states.
I added changing states to the BaseGameState class. The changes are rather extensive so I will give
you the code for the entire class. Change the BaseGameState to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using XRpgLibrary;
using XRpgLibrary.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace EyesOfTheDragon.GameScreens
{
public abstract partial class BaseGameState : GameState
{
#region Fields region
protected Game1 GameRef;

protected ControlManager ControlManager;


protected PlayerIndex playerIndexInControl;
protected BaseGameState TransitionTo;
protected bool Transitioning;
protected ChangeType changeType;
protected TimeSpan transitionTimer;
protected TimeSpan transitionInterval = TimeSpan.FromSeconds(0.5);
#endregion
#region Properties region
#endregion
#region Constructor Region
public BaseGameState(Game game, GameStateManager manager)
: base(game, manager)
{
GameRef = (Game1)game;
playerIndexInControl = PlayerIndex.One;
}
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
ContentManager Content = Game.Content;
SpriteFont menuFont = Content.Load<SpriteFont>(@"Fonts\ControlFont");
ControlManager = new ControlManager(menuFont);
}

base.LoadContent();

public override void Update(GameTime gameTime)


{
if (Transitioning)
{
transitionTimer += gameTime.ElapsedGameTime;
if (transitionTimer >= transitionInterval)
{
Transitioning = false;
switch (changeType)
{
case ChangeType.Change:
StateManager.ChangeState(TransitionTo);
break;
case ChangeType.Pop:
StateManager.PopState();
break;
case ChangeType.Push:
StateManager.PushState(TransitionTo);
break;
}
}

}
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
}
#endregion
#region Method Region
public virtual void Transition(ChangeType change, BaseGameState gameState)
{
Transitioning = true;
changeType = change;
TransitionTo = gameState;
transitionTimer = TimeSpan.Zero;
}
#endregion
}

What I did was add a few fields to BaseGameState. The first is of type BaseGameState and is the
state that you are transitioning to. It can be set to null if you are popping a state. It isn't set directly
though, I set it with a new virtual method that I created that I will get to in a bit. There is also a bool
field, Transitioning, that determines if the screen is transitioning or not. The changeType field will
hold what the change is. The transitionTimer field will be used to tell when the transition period is
over and is a TimeSpan. The last field, transitionInterval, holds how long until the transition
happens. I set it to be half a second initially.
In the Update method is where I handle the transition logic. I check to see if the Transitioning field is
true. If it is I increment the transitionTimer field and then check to see if it is great than or equal to the
transitionInterval field. If it greater than the transition interval I set the Transitioning field to be
false. There is then a switch statement on the changeType field. For each of the cases I call an
appropriate method of the GameStateManager to change the state.
The new method is the Transition method and it takes two parameters. The first is a ChangeType
parameter and the second is a BaseGameState parameter. If the ChangeType is Pop you can pass in
null for the BaseGameState parameter. I set the Transitioning field to true, changeType to the value
passed in, transitionTo to the value passed in, and set transitionTimer to TimeSpan.Zero.
Implementing this in the game will require changing the calls that change the state to call the new
Transition method. I will start with the TitleScreen class. Change the startLabel_Selected method to
the following.
private void startLabel_Selected(object sender, EventArgs e)
{
Transition(ChangeType.Push, GameRef.StartMenuScreen);
}

As you can see before I was pushing the StartMenuScreen onto the stack using the game state
manager. Instead I call the Transition method with ChangeType.Push and the StartMenuScreen
from the Game1 class.

In the StartMenuScreen class you need to change the menuItem_Selected method. It will be called
when a menu item is selected. Change that method to the following.
private void menuItem_Selected(object sender, EventArgs e)
{
if (sender == startGame)
Transition(ChangeType.Push, GameRef.CharacterGeneratorScreen);
if (sender == loadGame)
Transition(ChangeType.Push, GameRef.LoadGameScreen);

if (sender == exitGame)
GameRef.Exit();

There is only one change in the CharacterGeneratorScreen and that is in the linkLabel1_Selected
method. Change that method to the following.
void linkLabel1_Selected(object sender, EventArgs e)
{
InputHandler.Flush();
CreatePlayer();
CreateWorld();

GameRef.SkillScreen.SkillPoints = 25;
Transition(ChangeType.Change, GameRef.SkillScreen);

In the SkillScreen class you need to change the acceptLabel_Selected method. Change that method to
the following.
void acceptLabel_Selected(object sender, EventArgs e)
{
undoSkill.Clear();
}

Transition(ChangeType.Change, GameRef.GamePlayScreen);

That leaves the LoadGameScreen. In that class there are two methods that need to be updated. They
are the exitLinkLabel_Selected and loadListBox_Selected method. Change those methods to the
following.
void exitLinkLabel_Selected(object sender, EventArgs e)
{
Transition(ChangeType.Pop, null);
}
void loadListBox_Selected(object sender, EventArgs e)
{
loadLinkLabel.HasFocus = true;
ControlManager.AcceptInput = true;
Transition(ChangeType.Change, GameRef.GamePlayScreen);
CreatePlayer();
CreateWorld();
}

I'm now going to revisit skills for the rest of this tutorial. After looking at the numbers I was using it
would be too easy for a character to get a 100 ranking in a skill. What I intend to do is instead of

starting with 25 skill points and 10 every level drop that down to 10 skill points to start with and 5
more every level. I've also been thinking about adding a configuration file that you can create to set
values like this in an editor and read them in at run time. It will make personalizing things a little easier.
To get started I'm going to modify the Modifier structure. I'm going to make it a class and add in a
string field that will tell what is being modified. I was going to place a Modifier field in each of the
Skill, Talent, and Spell classes. Instead in the Entity class I will have a fields that contains all of the
modifiers for one type. There will be a List<Modifier> for the skills, talents, and spells an entity has
learned. Change the code of the Modifier structure to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary
{
public class Modifier
{
#region Field Region
public
public
public
public

string Modifying;
int Amount;
int Duration;
TimeSpan TimeLeft;

#endregion
#region Constructor Region
public Modifier(string modifying, int amount)
{
Modifying = modifying;
Amount = amount;
Duration = -1;
TimeLeft = TimeSpan.Zero;
}
public Modifier(string modifying, int amount, int duration)
{
Modifying = modifying;
Amount = amount;
Duration = duration;
TimeLeft = TimeSpan.FromSeconds(duration);
}
#endregion
#region Method Region
public void Update(TimeSpan elapsedTime)
{
if (Duration == -1)
return;
TimeLeft -= elapsedTime;
if (TimeLeft.TotalMilliseconds < 0)
{
TimeLeft = TimeSpan.Zero;
Amount = 0;
}
}
#endregion
#region Virtual Method Region

#endregion
}

I just added in a new field, Modifying, that says what is being modified. I change the two constructors
to take a string parameter for what is being modified and set the field to the value passed in.
The next thing to do is to flesh out the Skill class. Update the Skill class to the following.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.CharacterClasses;

namespace RpgLibrary.SkillClasses
{
public enum DifficultyLevel
{
Master = -25,
Expert = -10,
Improved = -5,
Normal = 0,
Easy = 10,
}
public class Skill
{
#region Field Region
string skillName;
string primaryAttribute;
readonly Dictionary<string, int> classModifiers;
int skillValue;
#endregion
#region Property Region
public string SkillName
{
get { return skillName; }
}
public int SkillValue
{
get { return skillValue; }
}
public string PrimaryAttribute
{
get { return primaryAttribute; }
}
public Dictionary<string, int> ClassModifiers
{
get { return classModifiers; }
}
#endregion
#region Constructor Region
private Skill()
{
skillName = "";

primaryAttribute = "";
classModifiers = new Dictionary<string, int>();
skillValue = 0;
}
#endregion
#region Method Region
public void IncreaseSkill(int value)
{
skillValue += value;
if (skillValue > 100)
skillValue = 100;
}
public void DecreaseSkill(int value)
{
skillValue -= value;
if (skillValue < 0)
skillValue = 0;
}
public static Skill FromSkillData(SkillData data)
{
Skill skill = new Skill();
skill.skillName = data.Name;
skill.skillValue = 0;
foreach (string s in data.ClassModifiers.Keys)
{
skill.classModifiers.Add(s, data.ClassModifiers[s]);
}
}

return skill;

public static int AttributeModifier(int attribute)


{
int result = 0;
if (attribute < 25)
result = 1;
else if (attribute <
result = 2;
else if (attribute <
result = 3;
else if (attribute <
result = 4;
else if (attribute <
result = 5;
else
result = 10;
}

50)
75)
90)
95)

return result;

#endregion

#region Virtual Method region


#endregion

The first thing I did was tweak the numbers for the DifficultyLevel enum. With the values I had it
could be impossible for any character to perform a skill that was ranked at Master level. I added in

four fields. Two string fields, one for the name of the skill and one for the primary attribute associated
with the skill. I also added in a Dictionary<string, int> that holds the modifiers for the different
classes and the skill. The last is an integer field for the value of the skill. There a public read only, or
get only, properties to expose the values of the fields.
There is a private constructor for this class. It initializes the values of the fields to known states. Instead
of a public constructor I will create Skill objects using a public static method and the SkillData class.
I included two public methods, IncreaseSkill and DecreaseSkill, that increase and decrease the value
of a skill respectively. The IncreaseSkill method makes sure that the base value of a skill never goes
above 100. Similarly, the DecreaseSkill method makes sure that the base value of a skill never goes
below 0.
The next method is the static method, FromSkillData, that takes a SkillData object. This method
creates a new Skill object. It sets the skillName field to the Name of the SkillData object. I initialize
the skillValue field to 0. In a foreach loop I loop through all of the keys in the ClassModifiers
dictionary. I add each entry to the Dictionary<string, int> in the Skill class. I finally return the Skill
object.
The last method that I added is also a public static method, AttributeModifier, that takes an integer
value. This method returns a modifier for a skill based on a character's attribute. I have a local variable,
result, that is initially assigned to 0. Then there is a set of chained if-else-if statements that will set the
result local variable. The values will need to be tweaked more than likely to make the game balanced.
At the end of the method I return the result variable.
I add a static method to the Mechanics class to determine if using a skill is successful or not. The way I
evaluate it is like this. You take the base value of the skill, add in the difficulty associated with trying to
use the skill, and add any modifiers to the attempt. If the roll of a d100 is less than or equal to that
number the attempt succeeds. Add the following method to the Mechanics class.
public static bool UseSkill(Skill skill, Entity entity, DifficultyLevel difficulty)
{
bool result = false;
int target = skill.SkillValue + (int)difficulty;
foreach (string s in skill.ClassModifiers.Keys)
if (s == entity.EntityClass)
target += skill.ClassModifiers[s];
foreach (Modifier m in entity.SkillModifiers)
{
if (m.Modifying == skill.SkillName)
{
target += m.Amount;
}
}
string lower = skill.PrimaryAttribute.ToLower();
switch (lower)
{
case "strength":
target += Skill.AttributeModifier(entity.Strength);
break;
case "dexterity":
target += Skill.AttributeModifier(entity.Dexterity);

break;
case "cunning":
target += Skill.AttributeModifier(entity.Cunning);
break;
case "willpower":
target += Skill.AttributeModifier(entity.Willpower);
break;
case "magic":
target += Skill.AttributeModifier(entity.Magic);
break;
case "constitution":
target += Skill.AttributeModifier(entity.Constitution);
break;
}
if (Mechanics.RollDie(DieType.D100) <= target)
result = true;
return result;
}

The method takes as parameters the Skill being used, the Entity using the skill, and a DifficultyLevel
for the attempt. The first thing I do is declare a local variable result set to false that determines if the
attempt was successful or not. I then declare a local variable target that will be the target number of the
attempt. It is initially set to the SkillValue property of the skill plus the DifficultyLevel passed in.
There is then a foreach loop that loops through the Keys of the ClassModifiers dictionary of the skill.
If the Entity's class is one of the keys I add the value of the ClassModifiers entry in the dictionary.
There is a second foreach loop that loops through all of the modifiers in the SkillModifiers collection
of the entity. If the Modifying property of the Modifier is the SkillName property of the skill I add the
Amount property of the modifier to target. I then get the PrimaryAttribute of the skill as a lower
case string. In a switch I add the AttributeModifier result of the Skill class to target based on the
attribute of the Entity. I use the RollDie method of the Mechanics class to roll a D100 and compare it
to target. If it is less than or equal to target then result is set to true. Since result was initially set to
false there is no need to include an else clause to set result to false.
The last thing I'm going to do in this tutorial is update the SkillScreen class for assigning points to a
skill. I first want to update the CreatePlayer method of the CharacterGeneratorScreen. What I want
to do is add skills to the entity. Update the CreatePlayer method to the following. Also add a using
statement for the RpgLibrary.Skills classes.
using RpgLibrary.SkillClasses;
private void CreatePlayer()
{
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>();
Animation animation = new Animation(3, 32, 32, 0, 0);
animations.Add(AnimationKey.Down, animation);
animation = new Animation(3, 32, 32, 0, 32);
animations.Add(AnimationKey.Left, animation);
animation = new Animation(3, 32, 32, 0, 64);
animations.Add(AnimationKey.Right, animation);
animation = new Animation(3, 32, 32, 0, 96);
animations.Add(AnimationKey.Up, animation);
AnimatedSprite sprite = new AnimatedSprite(
characterImages[genderSelector.SelectedIndex, classSelector.SelectedIndex],
animations);

EntityGender gender = EntityGender.Male;


if (genderSelector.SelectedIndex == 1)
gender = EntityGender.Female;
Entity entity = new Entity(
"Pat",
DataManager.EntityData[classSelector.SelectedItem],
gender,
EntityType.Character);
foreach (string s in DataManager.SkillData.Keys)
{
Skill skill = Skill.FromSkillData(DataManager.SkillData[s]);
entity.Skills.Add(s, skill);
}
Character character = new Character(entity, sprite);
}

GamePlayScreen.Player = new Player(GameRef, character);

What I added was a foreach loop that loops through all the keys in the SkillData dictionary in the
DataManager component. I create a new Skill using the FromSkillData method that I created earlier
in the tutorial. I then add the skill to the Skills dictionary of the entity.
There were a lot of changes to the SkillScreen so I'm going to give you the code for the entire class.
Update the SkillScreen to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using
using
using
using

Microsoft.Xna.Framework;
Microsoft.Xna.Framework.Graphics;
Microsoft.Xna.Framework.Input;
Microsoft.Xna.Framework.Content;

using XRpgLibrary;
using XRpgLibrary.Controls;
using XRpgLibrary.CharacterClasses;
using RpgLibrary.SkillClasses;
using EyesOfTheDragon.Components;
namespace EyesOfTheDragon.GameScreens
{
internal class SkillLabelSet
{
internal Label Label;
internal Label SkillLabel;
internal LinkLabel LinkLabel;
internal int SkillValue;
internal SkillLabelSet(Label label, Label skillLabel, LinkLabel linkLabel)
{
Label = label;
SkillLabel = skillLabel;
LinkLabel = linkLabel;
SkillValue = 0;
}
}
public class SkillScreen : BaseGameState

{
#region Field Region
int skillPoints;
int unassignedPoints;
Character target;
PictureBox backgroundImage;
Label pointsRemaining;
List<SkillLabelSet> skillLabels = new List<SkillLabelSet>();
Stack<string> undoSkill = new Stack<string>();
EventHandler linkLabelHandler;
#endregion
#region Property Region
public int SkillPoints
{
get { return skillPoints; }
set
{
skillPoints = value;
unassignedPoints = value;
}
}
#endregion
#region Constructor Region
public SkillScreen(Game game, GameStateManager manager)
: base(game, manager)
{
linkLabelHandler = new EventHandler(addSkillLabel_Selected);
}
#endregion
#region Method Region
public void SetTarget(Character character)
{
target = character;
foreach (SkillLabelSet set in skillLabels)
{
set.SkillValue = character.Entity.Skills[set.Label.Text].SkillValue;
set.SkillLabel.Text = set.SkillValue.ToString();
}
}
#endregion
#region Virtual Method region
#endregion
#region XNA Method Region
public override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
base.LoadContent();
ContentManager Content = GameRef.Content;

CreateControls(Content);

private void CreateControls(ContentManager Content)


{
backgroundImage = new PictureBox(
Game.Content.Load<Texture2D>(@"Backgrounds\titlescreen"),
GameRef.ScreenRectangle);
ControlManager.Add(backgroundImage);
Vector2 nextControlPosition = new Vector2(300, 150);
pointsRemaining = new Label();
pointsRemaining.Text = "Skill Points: " + unassignedPoints.ToString();
pointsRemaining.Position = nextControlPosition;
nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;
ControlManager.Add(pointsRemaining);
foreach (string s in DataManager.SkillData.Keys)
{
SkillData data = DataManager.SkillData[s];
Label label = new Label();
label.Text = data.Name;
label.Type = data.Name;
label.Position = nextControlPosition;
Label sLabel = new Label();
sLabel.Text = "0";
sLabel.Position = new Vector2(
nextControlPosition.X + 300,
nextControlPosition.Y);
LinkLabel linkLabel = new LinkLabel();
linkLabel.TabStop = true;
linkLabel.Text = "Add";
linkLabel.Type = data.Name;
linkLabel.Position = new Vector2(
nextControlPosition.X + 390,
nextControlPosition.Y);
nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;
linkLabel.Selected += addSkillLabel_Selected;
ControlManager.Add(label);
ControlManager.Add(sLabel);
ControlManager.Add(linkLabel);
}

skillLabels.Add(new SkillLabelSet(label, sLabel, linkLabel));

nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;


LinkLabel undoLabel = new LinkLabel();
undoLabel.Text = "Undo";
undoLabel.Position = nextControlPosition;
undoLabel.TabStop = true;
undoLabel.Selected += new EventHandler(undoLabel_Selected);
nextControlPosition.Y += ControlManager.SpriteFont.LineSpacing + 10f;
ControlManager.Add(undoLabel);
LinkLabel acceptLabel = new LinkLabel();

acceptLabel.Text = "Accept Changes";


acceptLabel.Position = nextControlPosition;
acceptLabel.TabStop = true;
acceptLabel.Selected += new EventHandler(acceptLabel_Selected);
ControlManager.Add(acceptLabel);
ControlManager.NextControl();
}
void acceptLabel_Selected(object sender, EventArgs e)
{
undoSkill.Clear();
}

Transition(ChangeType.Change, GameRef.GamePlayScreen);

void undoLabel_Selected(object sender, EventArgs e)


{
if (unassignedPoints == skillPoints)
return;
string skillName = undoSkill.Peek();
undoSkill.Pop();
unassignedPoints++;
foreach (SkillLabelSet set in skillLabels)
{
if (set.LinkLabel.Type == skillName)
{
set.SkillValue--;
set.SkillLabel.Text = set.SkillValue.ToString();
target.Entity.Skills[skillName].DecreaseSkill(1);
}
}
}

pointsRemaining.Text = "Skill Points: " + unassignedPoints.ToString();

void addSkillLabel_Selected(object sender, EventArgs e)


{
if (unassignedPoints <= 0)
return;
string skillName = ((LinkLabel)sender).Type;
undoSkill.Push(skillName);
unassignedPoints--;
// Update the skill points for the appropriate skill
foreach (SkillLabelSet set in skillLabels)
{
if (set.LinkLabel.Type == skillName)
{
set.SkillValue++;
set.SkillLabel.Text = set.SkillValue.ToString();
target.Entity.Skills[skillName].IncreaseSkill(1);
}
}
pointsRemaining.Text = "Skill Points: " + unassignedPoints.ToString();
}
public override void Update(GameTime gameTime)
{
ControlManager.Update(gameTime, PlayerIndex.One);
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GameRef.SpriteBatch.Begin();

base.Draw(gameTime);
ControlManager.Draw(GameRef.SpriteBatch);
}

GameRef.SpriteBatch.End();

#endregion
}

I removed the using statement for the System.IO name space as I no longer read in the file names for
the skills. I instead use the DataManager that I added to get the skill names. I did add a using
statement for the EyesOfTheDragon.Components and XRpgLibrary name spaces.
I updated the SkillLabelSet class. I added another Label to the class that is used to draw the points the
character has allocated. I also added an integer to hold the skill points that are allocated to a skill. I
modified the constructor to take another Label. The constructor assigns fields to values passed in and
sets SkillValue to 0.
I added on new field to the SkillScreen class, target, which is a Character. You will set this field with
the target character that you want to assign skill points to. At the moment there is just the one character
but eventually I will be moving to a party system where other characters may join the player character.
When one of those characters levels up you will be able to assign skill points to them.
I added a public method, SetTarget, that is used to set the target field. I first set the target field to the
value passed in. In a foreach loop I loop over all of the SkillLabelSet items. I set the SkillValue to the
SkillValue of the appropriate skill of the target passed in. I then update the Text property of SkillLabel
to be the SkillValue passed in.
There were many changes to the CreateControls method. I removed the code that loaded in the skills
using the Content Pipeline. I instead get the skills from the DataManager. It still creates a background
image and a Label that displays the unassigned points. The biggest difference is in the foreach loop. It
loops through all of the keys in the SkillData dictionary of the DataManager. There is a variable to
hold the appropriate SkillData called data. I create a new Label and set its Text and Type property to
be the Name of the SkillData item. I position it like before. I then create another Label and set its Text
property to 0 initially. I position it 300 pixels to the right of the first label. I then create a LinkLabel
like before but position it 390 pixels to the right of the first label. I subscribe to the Selected property
of the LinkLabel as well. I then add all three controls to the ControlManager. I also add an item to the
SkillLabelSet collection. The rest of the method continues on as before.
I also updated the undoLabel_Selected and addSkillLabel_Selected methods. Before there was a
comment in each method that you needed to update the skill points of the target. I now actually update
the skill points assigned to a skill.
In the undoLabel_Selected method I handle removing a previously assigned skill point. The new code
is a foreach loop that loops through all of the SkillLabelSet items in the collection. If the Type
property of the LinkLabel in the set is equal to the skillName that I got off the undoSkill stack I
reduce the SkillValue of the set item by 1. I update the Text property of the SkillLabel to show the
change. I also call the DecreaseSkill method of the appropriate skill of the target field.

The addSkillLabel_Selected method works the same way. I just increment instead of decrement. There
is a foreach loop that loops through all of the SkillLabelSet items in the collection. If the Type
property of the LinkLabel of the set is equal to the skillName variable, that I got from the LinkLabel
triggered the event, I increment SkillValue by 1, update the Text property, and call the IncreaseSkill
method passing in 1. Ideally you would want to add a check here that SkillValue doesn't go over the
maximum of 100. I will also mention that we are dealing with raw values. The character can assign up
to 100 raw points to a skill. Any modifiers for their class or primary attribute are extras and are not
applied in the raw skill points.
I will mention that the target field is a reference to the actual character. Any changes you do the the
target field will apply to the actual character. You really need to be careful when you are passing
references around. There is the potential for unwanted side effects. When you assign a different
character to the target field you are not working with the old value you are working with the new
value.
The last thing to do is to update the linkLabel1_Selected method in the CharacterGeneratorScreen
to pass in the character to the SkillScreen. Change that method to the following.
void linkLabel1_Selected(object sender, EventArgs e)
{
InputHandler.Flush();
CreatePlayer();
CreateWorld();

GameRef.SkillScreen.SkillPoints = 10;
Transition(ChangeType.Change, GameRef.SkillScreen);
GameRef.SkillScreen.SetTarget(GamePlayScreen.Player.Character);

I also updated the method to have 10 skill points to start with rather than 25. After calling Transition I
call the SetTarget method of the SkillScreen passing in GamePlayScreen.Player.Character, the
player's character.
I think I'm going to end this tutorial here. I wanted to update the state manager and add more on skills
to the game. Thinks are starting to take form but there is a long way to go yet. I encourage you to visit
the news page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 27
Updating Components and Talents
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
The first thing that I want to update is the Player component. Right now the sprite moves at a constant
speed. If you were to run your game on a slower computer than yours the sprite will move slower. It is
also possible that the sprite will move faster on a faster computer. This is a little more unlikely because
an XNA game will not, by default, run at more than 60 frames per second. If the game was only
running at 30 frames per second the slow down would be much more noticeable. Luckily this is an easy
fix. You determine how many units you want the sprite to travel in 1 second and you the multiply that
by the elapsed time in each frame there is movement. The first step is to update the Update method of
the Player component. Modify that method as follows.
public void Update(GameTime gameTime)
{
camera.Update(gameTime);
Sprite.Update(gameTime);
if (InputHandler.KeyReleased(Keys.PageUp) ||
InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One))
{
camera.ZoomIn();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);
}
else if (InputHandler.KeyReleased(Keys.PageDown) ||
InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One))
{
camera.ZoomOut();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);
}
Vector2 motion = new Vector2();
if (InputHandler.KeyDown(Keys.W) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Up;
motion.Y = -1;
}
else if (InputHandler.KeyDown(Keys.S) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Down;
motion.Y = 1;
}
if (InputHandler.KeyDown(Keys.A) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One))

{
Sprite.CurrentAnimation = AnimationKey.Left;
motion.X = -1;
}
else if (InputHandler.KeyDown(Keys.D) ||
InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One))
{
Sprite.CurrentAnimation = AnimationKey.Right;
motion.X = 1;
}
if (motion != Vector2.Zero)
{
Sprite.IsAnimating = true;
motion.Normalize();
Sprite.Position += motion * Sprite.Speed * (float)gameTime.ElapsedGameTime.TotalSeconds;
Sprite.LockToMap();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);
}
else
{
Sprite.IsAnimating = false;
}
if (InputHandler.KeyReleased(Keys.F) ||
InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One))
{
camera.ToggleCameraMode();
if (camera.CameraMode == CameraMode.Follow)
camera.LockToSprite(Sprite);
}
if (camera.CameraMode != CameraMode.Follow)
{
if (InputHandler.KeyReleased(Keys.C) ||
InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One))
{
camera.LockToSprite(Sprite);
}
}
}

The change is in the if statement where I check to see if the motion is not the zero vector. I cast the
TotalSeconds property of gameTime.ElapsedGameTime to a float and multiply the Speed property
of Sprite by it. If you were to run the game now the sprite would really crawl. You need to update the
Speed property of the sprite to an appropriate value. I did that in the AnimatedSprite class. What I did
was increase the speed field and the Speed property. Change the speed field and Speed property to the
following.
float speed = 200.0f;
public float Speed
{
get { return speed; }
set { speed = MathHelper.Clamp(speed, 1.0f, 400.0f); }
}

I decided on those values after trying different values. At 200 units per second the sprite moves at a
good pace and seems appropriate for the game. At 400 units per second the sprite was really moving.
You probably don't want to go much higher than that. Testing that the sprite is moving at a constant rate
can be achieved easily. What you can do is set the IsFixedTimeStep property of the Game1 class to

false and the SynchronizeWithVertivalRetrace property of the GraphicsDevice to false. Change the
constructor of the Game1 class to the following.
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = screenWidth;
graphics.PreferredBackBufferHeight = screenHeight;
ScreenRectangle = new Rectangle(
0,
0,
screenWidth,
screenHeight);
Content.RootDirectory = "Content";
Components.Add(new InputHandler(this));
stateManager = new GameStateManager(this);
Components.Add(stateManager);
TitleScreen = new TitleScreen(this, stateManager);
StartMenuScreen = new StartMenuScreen(this, stateManager);
GamePlayScreen = new GamePlayScreen(this, stateManager);
CharacterGeneratorScreen = new CharacterGeneratorScreen(this, stateManager);
LoadGameScreen = new LoadGameScreen(this, stateManager);
SkillScreen = new GameScreens.SkillScreen(this, stateManager);
stateManager.ChangeState(TitleScreen);
this.IsFixedTimeStep = false;
graphics.SynchronizeWithVerticalRetrace = false;
}

Now if you build and run the game the sprite moves at the same rate as before the change. It is good
practice to try and have your game run at the same speed across all computers. You may find if you
create an XBOX 360 project from a Windows game that the game is very sluggish. That is because
your computer is usually much faster than an XBOX 360, especially if you have a newer computer. I
would suggest when you are creating a game to use this. It is a good way to have your game run the
same across all computers.
I've been considering the next change for a while now. I've been considering moving the classes in the
RpgLibrary into the XRpgLibrary project. The reason I didn't want to is that there would be a lot of
classes in one project and organization would suffer. The reason I wanted to was to reduce the amount
of dependencies. In the end I think that reducing the dependencies is worth the hit in organization.
The first step is to copy things from the RpgLibrary to the XRpgLibrary. Right click the
ConversationClasses folder in the RpgLibary and select Copy. Right click the XRpgLibrary and
select Paste. Repeat those steps with the QuestClasses, SkillClasses, SpellClasses, TalentClasses and
TrapClasses folders. Now repeat the process with the CharacterClasses, ItemClasses, and
WorldClasses folders. In the pop up box that appears for these select Yes. Select the Mechanic.cs,
Modifier.cs, and RolePlayingGame.cs files in the RpgLibrary and copy them to the XRpgLibrary
project as well.
Now expand the References node in the RpgEditor project and delete the RpgLibrary reference.Do
the same for the References node in the EyesOfTheDragonContent project. Right click the

References node in the EyesOfTheDragonContent project and select Add Reference. From the
Project tab select the XRpgLibrary. The last step is to right click the RpgLibrary project and select
Remove to remove the project.
What I'm going to add next is add some code to count the frames per second. This will give you an idea
of how changes you make to the game affect performance. If the frames per second drop to a really low
value you will know that a change you made has made a performance hit. The first step is to add a new
field region with the other field regions in the Game1 class.
#region Frames Per Second Field Region
private
private
private
private

float
float
float
float

fps;
updateInterval = 1.0f;
timeSinceLastUpdate = 0.0f;
frameCount = 0;

#endregion

You measure frames per second in the Draw method. Change the Draw method of the Game1 class to
the following.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
frameCount++;
timeSinceLastUpdate += elapsed;
if (timeSinceLastUpdate > updateInterval)
{
fps = frameCount / timeSinceLastUpdate;
#if XBOX360
System.Diagnostics.Debug.WriteLine("FPS: " + fps.ToString());
#else
this.Window.Title = "FPS: " + fps.ToString();
#endif
frameCount = 0;
timeSinceLastUpdate -= updateInterval;
}
}

The preprocessor directives may not line up like they are in the code that I pasted. The first thing the
new code does is get the amount of time that has elapsed in seconds. This is going to be a small value
that will eventually add up to approximately 1. I then increase the frameCount variable that holds the
number of frames that have elapsed. The timeSinceLastUpdate holds the amount of time since the last
update of the frames per second. The if statement check if timeSinceLastUpdate is greater than the
updateInterval field, which is set to 1.0f. If it is I set the fps field to frameCount divided by
timeSinceLastUpdate. Then if the target platform is the Xbox 360 I write the fps value using the
System.Diagnostics.Debug.WriteLine method. This will write the fps value to the output window in
Visual Studio. If it is not the Xbox 360 I set the Title property of the window to be the fps value. The
last step is to set frameCount back to 0 and subtract updateInterval from timeSinceLastUpdate.
The last thing I'm going to work on in this tutorial is flesh out the talents a little. There will be three
types of talents. Talents will be passive, sustained, or activated. A passive talent is a talent that requires
no stamina to be available and is always active. A sustained talent is a talent that requires stamina to

activate and once activated is active until it is deactivated. An activated talent costs a certain amount of
stamina to activate produces a specific effect. Spells will work in a similar way except they require
mana instead of stamina.
For this we are going to need some new classes for effects. There will be different types of effects. So,
what types of effects are there. An effect may modify a primary attribute or a secondary attribute.
Secondary attributes are attributes that are derived from the primary attributes like hit points. Besides
affecting attributes spells and talents may change the characters status like paralyzing them for a while.
Right click the XRpgLibrary projects, select Add and then New Folder. Name this new folder
EffectClasses. To this folder you are going to add three class. Right click the EffectClasses folder,
select Add and then Class. Name this class BaseEffect. Repeat the process and name the classes
BaseEffectData and BaseEffectDataManager. For now these are template classes for now and the
code for them follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public enum EffectType { Damage, Heal }
public class BaseEffect
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method Region
#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class BaseEffectData
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method Region
#endregion

}
}
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class BaseEffectDataManager
{
#region Field Region
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method Region


#endregion

I used BaseEffect rather than Effect because XNA already contains a classed called Effect that is used
in 3D rendering. I just wanted to avoid confusion with that class. You will notice that I changed the
namespace from XRpgLibrary to just RpgLibrary. I did that to try and keep the XNA part and the
mechanics part separate. I added an enumeration to the BaseEffect class called EffectType. This is also
a place holder but I added two values to it: Damage and Heal. There purpose I think is obvious.
Damage will cause damage to a target and Heal will heal a target. I will be adding in more down the
road.
The next class that I'm going to work on is the TalentData class. Change the TalentData class to the
following. Update the code for the TalentData class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.TalentClasses
{
public enum TalentType { Passive, Sustained, Activated }
public class TalentData
{
#region Field Region
public
public
public
public
public
public
public
public

string Name;
string[] AllowedClasses;
Dictionary<string, int> AttributeRequirements;
string[] TalentPrerequisites;
int LevelRequirement;
TalentType TalentType;
int ActivationCost;
string[] Effects;

#endregion
#region Property Region

#endregion
#region Constructor Region
public TalentData()
{
AttributeRequirements = new Dictionary<string, int>();
}
#endregion
#region Method Region
#endregion
#region Virtual Method region
public override string ToString()
{
string toString = Name;
foreach (string s in AllowedClasses)
toString += ", " + s;
foreach (string s in AttributeRequirements.Keys)
toString += ", " + s + "+" + AttributeRequirements[s].ToString();
foreach (string s in TalentPrerequisites)
toString += ", " + s;
toString += ", " + LevelRequirement.ToString();
toString += ", " + TalentType.ToString();
toString += ", " + ActivationCost.ToString();
foreach (string s in Effects)
toString += ", " + s;
}

return toString;

#endregion
}

There are several fields associated with the TalentData class. The first is Name, the name of the talent.
Next is an array of strings, AllowedClasses, that holds the classes that can learn the talent. I did this
because a rogue or a fighter can, for instance, learn archery talents. Next there is a dictionary with
string keys and integer values, AttributeRequirements, that holds any attribute values that a character
must have to learn the talent. There is then an array of strings, TalentPrerequisites, that will hold any
talents that must be learned before this talent can be learned. The next field, LevelRequirement, will
hold what level a character must be to learn the talent. In this way a low level character can't learn a
very powerful talent that will unbalance your game, whereas a high level character may need that talent
against the stronger foes. The field TalentType is the type of talent, whether it is passive, sustained, or
activated. The next one, ActivationCost, is the cost required to activate the talent. The last, Effects, is
an array of strings that holds the effects that the talent may cause.
The constructor just creates a new Dictionary<string, int> for the AttributeRequirements field. The
one method, ToString, just creates a string that represents a TalentData object and returns it.
Before I get to the Talent class I want to add a static method to the Mechanics class. This method will
return an attribute from an entity based on the name of the attribute. This will be helpful in many
places. Add the following static method to the Mechanics class.

public static int GetAttributeByString(Entity entity, string attribute)


{
int value = 0;
switch (attribute.ToLower())
{
case "strength":
value = entity.Strength;
break;
case "dexterity":
value = entity.Dexterity;
break;
case "cunning":
value = entity.Cunning;
break;
case "willpower":
value = entity.Willpower;
break;
case "magic":
value = entity.Magic;
break;
case "constitution":
value = entity.Constitution;
break;
}
return value;
}

It is a rather simple method. I set the value variable to be 0. There is then a switch statement on the
value of the attribute parameter converted to a lower case string. There is then a case for each of the
primary attributes. The case sets the value variable to be the value of the appropriate attribute. Finally I
return the value.
Now I'm going to add a few things to the Talent class. Change the code for the Talent class to the
following.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.CharacterClasses;

namespace RpgLibrary.TalentClasses
{
public class Talent
{
#region Field Region
string name;
List<string> allowedClasses;
Dictionary<string, int> attributeRequirements;
List<string> talentPrerequisites;
int levelRequirement;
TalentType talentType;
int activationCost;
List<string> effects;
#endregion
#region Property Region
public string Name
{
get { return name; }

}
public List<string> AllowedClasses
{
get { return allowedClasses; }
}
public Dictionary<string, int> AttributeRequirements
{
get { return attributeRequirements; }
}
public List<string> TalentPrerequisites
{
get { return talentPrerequisites; }
}
public int LevelRequirement
{
get { return levelRequirement; }
}
public TalentType TalentType
{
get { return talentType; }
}
public int ActivationCost
{
get { return activationCost; }
}
public List<string> Effects
{
get { return effects; }
}
#endregion
#region Constructor Region
private Talent()
{
allowedClasses = new List<string>();
attributeRequirements = new Dictionary<string, int>();
talentPrerequisites = new List<string>();
effects = new List<string>();
}
#endregion
#region Method Region
public static Talent FromTalentData(TalentData data)
{
Talent talent = new Talent();
talent.name = data.Name;
foreach (string s in data.AllowedClasses)
talent.allowedClasses.Add(s.ToLower());
foreach (string s in data.AttributeRequirements.Keys)
talent.attributeRequirements.Add(
s.ToLower(),
data.AttributeRequirements[s]);
foreach (string s in data.TalentPrerequisites)

talent.talentPrerequisites.Add(s);
talent.levelRequirement = data.LevelRequirement;
talent.talentType = data.TalentType;
talent.activationCost = data.ActivationCost;
foreach (string s in data.Effects)
talent.Effects.Add(s);
}

return talent;

public static bool CanLearn(Entity entity, Talent talent)


{
bool canLearn = true;
if (entity.Level < talent.LevelRequirement)
canLearn = false;
string entityClass = entity.EntityClass.ToLower();
if (!talent.AllowedClasses.Contains(entityClass))
canLearn = false;
foreach (string s in talent.AttributeRequirements.Keys)
{
if (Mechanics.GetAttributeByString(entity, s) < talent.AttributeRequirements[s])
{
canLearn = false;
break;
}
}
foreach (string s in talent.TalentPrerequisites)
{
if (!entity.Talents.ContainsKey(s))
{
canLearn = false;
break;
}
}
return canLearn;
}
#endregion
#region Virtual Method Region
#endregion
}

First thing that I did was include a using statement to bring the Entity class into scope in this class.
There are fields that match the fields from the TalentData class as well as get only properties to expose
their values. Instead of arrays I use the generic List<T> collection to hold the values. There is a private
constructor that takes no parameters. It creates new collections for the fields are collections. You don't
use a constructor to create Talent objects you instead use the static method FromTalentData.
The FromTalentData method takes a TalentData parameter called data. The first step is to create a
new Talent object using the private constructor. In a foreach loop I then loop through all of the entries
in the AllowedClasses field of the TalentData and add each item converted to a lower case string to
the allowedClasses field of the Talent object. I loop over the keys in the AttributeRequirements in
data and add the key converted to a lower case string with the value to attributeRequirements in

talent. I do the same with TalentPrerequisites and talentPrerequisites for data and talent. There are
then three straight assignments from data to talent. There is one last loop that assigns values from the
Effects in data to effects in talent. I then return the talent variable.
The last method, CanLearn, is also a static method that takes an Entity that is trying to learn the talent
and a Talent that represents the talent to be learned. There is a local variable canLearn that is set to
true initially. There are then a number of checks to see if all of the prerequisites for the talent have been
learned. If one of the checks fails canLearn will be set to false. The first check makes sure that the
level of the entity passed in is not less than the required level. I then get the name of the class for the
entity and convert it to a lower case string. I then use the Contains method of List<T> to see if the
class is not in the list of allowed classes. There is next a foreach loop that loops through all of the keys
in AttributeRequirements. I then use the GetAttributeByString method passing in the entity and the
key to get the value of the attribute of the entity and compare it to the value in the dictionary for the
key. If it is less than the minimum level I set can learn to false and break out of the loop. The last step is
to loop through all of the prerequisites for the talent. If the talent is not I set canLearn to false and
break out of the loop.
I think I'm going to end this tutorial here. I wanted to update a few things and add more on talents to
the game. Things are starting to take form but there is a long way to go yet. I encourage you to visit the
news page of my site, XNA Game Programming Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 28
Spells and Effects
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to add some meat to the classes related to spells and effects. Spells are similar
to talents. I probably could have combined them into a single set of classes rather than dividing them
into two classes. I did it because I see talents as different than spells. I don't like the idea of a warrior
casting a spell. I do like the idea of a warrior having a special attack where he bashes an enemy with
their shield.
First, let's update the SpellData class. Change the code for the SpellData tclass o the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.SpellClasses
{
public enum SpellType { Passive, Sustained, Activated }
public class SpellData
{
#region Field Region
public
public
public
public
public
public
public
public

string Name;
string[] AllowedClasses;
Dictionary<string, int> AttributeRequirements;
string[] SpellPrerequisites;
int LevelRequirement;
SpellType SpellType;
int ActivationCost;
string[] Effects;

#endregion
#region Property Region
#endregion
#region Constructor Region
public SpellData()
{
AttributeRequirements = new Dictionary<string, int>();
}
#endregion
#region Method Region
#endregion

#region Virtual Method region


public override string ToString()
{
string toString = Name;
foreach (string s in AllowedClasses)
toString += ", " + s;
foreach (string s in AttributeRequirements.Keys)
toString += ", " + s + "+" + AttributeRequirements[s].ToString();
foreach (string s in SpellPrerequisites)
toString += ", " + s;
toString += ", " + LevelRequirement.ToString();
toString += ", " + SpellType.ToString();
toString += ", " + ActivationCost.ToString();
foreach (string s in Effects)
toString += ", " + s;
return toString;
}
}

#endregion

Everything there should look familiar from that last tutorial. There are several fields in the SpellData
class. The first is Name, the name of the spell. Next is an array of strings, AllowedClasses, that holds
the classes that can learn the spell. I decided to go this route in the case where more than one class may
learn a specific spell rather than have two spells that achieve the same result. Next there is a dictionary
with string keys and integer values, AttributeRequirements, that holds any attribute values that a
character must have to learn the spell. There is then an array of strings, SpellPrerequisites, that will
hold any spells that must be learned before the spell can be learned. The next field, LevelRequirement,
will hold what level a character must be to learn the spell. In this way a low level character can't learn a
very powerful spell that will unbalance your game, whereas a high level character may need that spell
against the stronger foes. The field SpellType is the type of talent, whether it is passive, sustained, or
activated. I decided to use a passive spell what really isn't a spell. A wizard may, for example, learn a
spell that increases the damage they do. The next field, ActivationCost, is the cost required to activate
the spell. The last, Effects, is an array of strings that holds the effects that the spell may cause. Spells
will have a variety of effects like causing damage, healing, and a variety of other effects.
The constructor just creates a new Dictionary<string, int> for the AttributeRequirements field. The
one method, ToString, just creates a string that represents a SpellData object and returns it.
Now for the Spell class. Change the Spell class to the following.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.CharacterClasses;

namespace RpgLibrary.SpellClasses
{
public class Spell
{

#region Field Region


string name;
List<string> allowedClasses;
Dictionary<string, int> attributeRequirements;
List<string> spellPrerequisites;
int levelRequirement;
SpellType spellType;
int activationCost;
List<string> effects;
#endregion
#region Property Region
public string Name
{
get { return name; }
}
public List<string> AllowedClasses
{
get { return allowedClasses; }
}
public Dictionary<string, int> AttributeRequirements
{
get { return attributeRequirements; }
}
public List<string> SpellPrerequisites
{
get { return spellPrerequisites; }
}
public int LevelRequirement
{
get { return levelRequirement; }
}
public SpellType SpellType
{
get { return spellType; }
}
public int ActivationCost
{
get { return activationCost; }
}
public List<string> Effects
{
get { return effects; }
}
#endregion
#region Constructor Region
private Spell()
{
allowedClasses = new List<string>();
attributeRequirements = new Dictionary<string, int>();
spellPrerequisites = new List<string>();
effects = new List<string>();
}
#endregion
#region Method Region

public static Spell FromSpellData(SpellData data)


{
Spell spell = new Spell();
spell.name = data.Name;
foreach (string s in data.AllowedClasses)
spell.allowedClasses.Add(s.ToLower());
foreach (string s in data.AttributeRequirements.Keys)
spell.attributeRequirements.Add(
s.ToLower(),
data.AttributeRequirements[s]);
foreach (string s in data.SpellPrerequisites)
spell.SpellPrerequisites.Add(s);
spell.levelRequirement = data.LevelRequirement;
spell.spellType = data.SpellType;
spell.activationCost = data.ActivationCost;
foreach (string s in data.Effects)
spell.Effects.Add(s);
}

return spell;

public static bool CanLearn(Entity entity, Spell spell)


{
bool canLearn = true;
if (entity.Level < spell.LevelRequirement)
canLearn = false;
string entityClass = entity.EntityClass.ToLower();
if (!spell.AllowedClasses.Contains(entityClass))
canLearn = false;
foreach (string s in spell.AttributeRequirements.Keys)
{
if (Mechanics.GetAttributeByString(entity, s) < spell.AttributeRequirements[s])
{
canLearn = false;
break;
}
}
foreach (string s in spell.SpellPrerequisites)
{
if (!entity.Spells.ContainsKey(s))
{
canLearn = false;
break;
}
}
return canLearn;
}
#endregion
#region Virtual Method Region
#endregion
}

Again that should look very familiar because it is basically the same code as the Talent class. First
thing that I did was include a using statement to bring the Entity class into scope in this class. There
are fields that match the fields from the SpellData class as well as get only properties to expose their
values. Instead of arrays I use the generic List<T> collection to hold the values. There is a private
constructor that takes no parameters. It creates new collections for the fields that are collections. You
don't use a constructor to create Spell objects, instead you use the static method FromSpellData.
The FromSpellData method takes a SpellData parameter called data. The first step is to create a new
Spell object using the private constructor. In a foreach loop I then loop through all of the entries in the
AllowedClasses field of the SpellData and add each item converted to a lower case string to the
allowedClasses field of the Spell object. I loop over the keys in the AttributeRequirements in data
and add the key converted to a lower case string with the value to attributeRequirements in spell. I do
the same with SpellPrerequisites and spellPrerequisites for data and spell. There are then three
straight assignments from data to spell. There is one last loop that assigns values from the Effects in
data to effects in spell. I then return the spell variable.
The last method, CanLearn, is also a static method that takes an Entity that is trying to learn a spell
and a Spell that represents the spell to be learned. There is a local variable canLearn that is set to true
initially. There are then a number of checks to see if all of the prerequisites for the talent have been
learned. If one of the checks fails canLearn will be set to false. The first check makes sure that the
level of the entity passed in is not less than the required level. I then get the name of the class for the
entity and convert it to a lower case string. I then use the Contains method of List<T> to see if the
class is not in the list of allowed classes. There is next a foreach loop that loops through all of the keys
in AttributeRequirements. I then use the GetAttributeByString method passing in the entity and the
key to get the value of the attribute of the entity and compare it to the value in the dictionary for the
key. If it is less than the minimum level I set can learn to false and break out of the loop. The last step is
to loop through all of the prerequisite spells for the spell. If a spell in the preequisites is not in the
player's spell book I set canLearn to false and break out of the loop.
I'm now going work on classes related to effects. I created classes called BaseEffect, BaseEffectData,
and BaseEffectDataManager. I had added an enumeration called EffectType but instead of using an
enumeration for the different types of effects I'm going to create classes that inherit from BaseEffect
that represent the different types of effects so I'm going to make BaseEffect an abstract class. First,
change the code for the BaseEffectDataManager to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class BaseEffectDataManager
{
#region Field Region
readonly Dictionary<string, BaseEffectData> effectData;
#endregion
#region Property Region
public Dictionary<string, BaseEffectData> EffectData
{
get { return effectData; }

}
#endregion
#region Constructor Region
public BaseEffectDataManager()
{
effectData = new Dictionary<string, BaseEffectData>();
}
#endregion
#region Method Region
#endregion

#region Virtual Method Region


#endregion

Fairly straight forward like the other manager classes. There is read only field, effectData, that is a
Dictionary<string, BaseEffectData> that will hold all of the BaseEffectData objects. There is a
public property, EffectData, that exposes the effectData field and is a get only property. The
constructor just creates a new Dictionary<string, BaseEffectData>.
Next is the BaseEffectData class. This class is going to be really simple, just a single string field
Name and a protected constructor. Change BaseEffectData to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class BaseEffectData
{
#region Field Region
public string Name;
#endregion
#region Property Region
#endregion
#region Constructor Region
protected BaseEffectData()
{
}
#endregion
#region Method Region
#endregion
#region Virtual Method Region
#endregion
}

The BaseEffect class will be similar. It will be an abstract class though and have an abstract method as
well. Change the BaseEffect class to the following.

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using RpgLibrary.CharacterClasses;
namespace RpgLibrary.EffectClasses
{
public abstract class BaseEffect
{
#region Field Region
protected string name;
#endregion
#region Property Region
public string Name
{
get { return name; }
protected set { name = value; }
}
#endregion
#region Constructor Region
protected BaseEffect()
{
}
#endregion
#region Method Region
#endregion
#region Virtual Method Region
public abstract void Apply(Entity entity);
#endregion
}

The first thing is I included a using statement to bring the Enity class in CharacterClasses into scope
for the abstract method. There is one protected field, name, that is the name of the effect. There is a
public property to expose the value with a protected set. I included a protected constructor for the class.
I added an abstract method, Apply, that takes an Enity as a parameter. This method will be
implemented in classes that inherit from BaseEffect so their effects can be applied to an entity.
I'm going to add in two effects that inherit from BaseEffect: HealEffect and DamageEffect. I will be
adding data classes as well. Right click EffectClasses in the XRpgLibrary project, select Add and
then Class. Name this new class HealEffect. Repeat that process three more times and call the classes
DamageEffect, HealEffectData and DamageEffectData.
Change the code for the DamageEffectData and HealEffectData classes to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public enum DamageType { Crushing, Slashing, Piercing, Poison, Disease, Fire, Ice, Lightning,
Earth }
public enum AttackType { Health, Mana, Stamina }
public class DamageEffectData : BaseEffectData
{
#region Field Region
public
public
public
public
public

DamageType DamageType;
AttackType AttackType;
DieType DieType;
int NumberOfDice;
int Modifier;

#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method Region
#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public enum HealType { Health, Mana, Stamina }
public class HealEffectData : BaseEffectData
{
#region Field Region
public
public
public
public

HealType HealType;
DieType DieType;
int NumberOfDice;
int Modifier;

#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method Region


#endregion

I changed the base namespace of the classes from XRpgLibrary to RpgLibrary. To HealEffectData I
added in an enumeration for the types of healing. Health, Mana, and Stamina can all be healed, though

not necessarily with spells. You could have potions, and we will, that can restore lost mana, stamina
and health. I inherit HealEffectData from BaseEffectData. I added four fields to HealEffectData:
HealType, DieType, NumberOfDice, and Modifier. The first, HealType, tells what the effect is
healing. The others are used to determine how much is healed. You take a certain number of dice, roll
them, and add in a modifier. I will work it so that if NumberOfDice is 0 then just Modifier will heal,
for effects that heal a specific amount.
To DamageEffectData I added in two enumerations: AttackType and DamageType. DamageType is
the type of damage being done and AttackType is what is being attacked. I will eventually be adding in
resistance to the game to specific types of damage so I included DamageType. I will also be adding in
more effects to characters like stunned, asleep, petrified, etc. Those will be specific types of effects and
the effects will be curable. There will also be the ability to resist those types of effects. I will get to that
in a later tutorial. I inherit DamageEffectData from BaseEffectData as well. There are five fields in
this class: DamageType, AttackType, DieType, NumberOfDice, and Modifier. The last three work
the same as healing effects. DamageType is the type of damage being inflicted and AttackType is the
type of damage being inflicted.
I will deal with HealEffect next. This is the code for the HealEffect class.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using RpgLibrary.CharacterClasses;
namespace RpgLibrary.EffectClasses
{
public class HealEffect : BaseEffect
{
#region Field Region
HealType healType;
DieType dieType;
int numberOfDice;
int modifier;
#endregion
#region Property Region
#endregion
#region Constructor Region
private HealEffect()
{
}
#endregion
#region Method Region
public static HealEffect FromHealEffectData(HealEffectData data)
{
HealEffect effect = new HealEffect();
effect.healType = data.HealType;
effect.dieType = data.DieType;
effect.numberOfDice = data.NumberOfDice;
effect.modifier = data.Modifier;
return effect;

}
#endregion
#region Virtual Method Region
public override void Apply(Entity entity)
{
int amount = modifier;
for (int i = 0; i < numberOfDice; i++)
amount += Mechanics.RollDie(dieType);
if (amount < 1)
amount = 1;
switch (healType)
{
case HealType.Health:
entity.Health.Heal((ushort)amount);
break;
case HealType.Mana:
entity.Mana.Heal((ushort)amount);
break;
case HealType.Stamina:
entity.Stamina.Heal((ushort)amount);
break;
}
}
}

#endregion

First, there is a using statement to bring CharacterClasses into scope, for Entity. There are fields that
match the fields in the HealEffectData class but they start with a lower case letter rather than an upper
case letter. This is how I generally write my code. Private fields generally start with a lower case letter
while public start with upper case letters. I included a private constructor because again I'll be using a
static method to create instance of HealEffect.
The FromHealEffectData method takes a HealEffectData object and returns a HealEffect object. It
consists of creating an object, assigning values, and returning the object. The other method is the
override of the Apply method. First there is a local variable, amount, that holds the amount that will be
healed set to the modifier field. Then there is a for loop that loops numberOfDice times. Each pass
through the loop adds the result of rolling dieType. An effect will always heal at least 1 point so if the
amount is less than 1 I set the amount to 1. If you specify numberOfDice to be zero then modifer will
be healed allowing a set value to be healed. Finally there is a switch statement on healType. If
healType is Health then I call the Heal method of Health for the entity passed in. I do similar for the
mana and stamina cases. If you recall, from way back when, the Heal method will add the value passed
in to the current value and if the current value is larger than the maximum it will set the current value to
the maximum.
DamageEffect is similar to HealEffect but works in reverse. It damages an entity rather than healing
it. The code for DamageEffect follows next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using RpgLibrary.CharacterClasses;
namespace RpgLibrary.EffectClasses
{
public class DamageEffect : BaseEffect
{
#region Field Region
DamageType damageType;
AttackType attackType;
DieType dieType;
int numberOfDice;
int modifier;
#endregion
#region Property Region
#endregion
#region Constructor Region
private DamageEffect()
{
}
#endregion
#region Method Region
public static DamageEffect FromDamageEffectData(DamageEffectData data)
{
DamageEffect effect = new DamageEffect();
effect.damageType = data.DamageType;
effect.attackType = data.AttackType;
effect.dieType = data.DieType;
effect.numberOfDice = data.NumberOfDice;
effect.modifier = data.Modifier;
return effect;
}
#endregion
#region Virtual Method Region
public override void Apply(Entity entity)
{
int amount = modifier;
for (int i = 0; i < numberOfDice; i++)
amount += Mechanics.RollDie(dieType);
if (amount < 1)
amount = 1;
switch (attackType)
{
case AttackType.Health:
entity.Health.Damage((ushort)amount);
break;
case AttackType.Mana:
entity.Mana.Damage((ushort)amount);
break;
case AttackType.Stamina:
entity.Stamina.Damage((ushort)amount);
break;
}
}

#endregion

There is a using statement to bring CharacterClasses into scope, for Entity. There are fields that
match the fields in the DamageEffectData class but they start with a lower case letter rather than an
upper case letter. I included a private constructor because again I'll be using a static method to create
instance of DamageEffect.
The FromDamageEffectData method takes a DamageEffectData object and returns a DamageEffect
object. It consists of creating an object, assigning values, and returning the object. The other method is
the override of the Apply method. First there is a local variable, amount, that holds the amount of
damage that will be inflicted set to the modifier field. Then there is a for loop that loops
numberOfDice times. Each pass through the loop adds the result of rolling dieType. An effect will
always do at least at least 1 point so if the amount is less than 1 I set the amount to 1. If you specify
numberOfDice to be zero then modifer will be inflicted allowing a specific amount of damage to be
inflicted. Finally there is a switch statement on attackType. If healType is Health then I call the
Damage method of Health for the entity passed in. I do similar for the mana and stamina cases. If you
recall, from way back when, the Damage method will subtract the value passed in from the current
value and if the value is negative the current value will be set to 0.
I think I'm going to end this tutorial here. I wanted to flesh out classes related to spells and start on the
classes related to effects. Things are starting to take form but there is a long way to go yet. I encourage
you to visit the news page of my site, XNA Game Programming Adventures, for the latest news on my
tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 29
Resistances and Weaknesses
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
This is going to be a rather short tutorial where I cover adding in resistances and weaknesses to
different types of damage. The first thing I did was update the DamageType enumeration in the
DamageEffectData class. Update the DamageType enumeration to the following.
public enum DamageType { Weapon, Poison, Disease, Fire, Earth, Water, Air }

A bit of a change. I replaced Crushing, Piercing and Slashing with one value, Weapon, that
represents damage done with a weapon. I did that because I'd have to go back and make a lot of
modifications to the item classes and editors for weapons and armor to have different resistances and
weaknesses. I just didn't want to make that many changes. You could modify weapons to do different
types of damage and armors have different strengths and weaknesses against different damage types. I
also renamed Ice to Water and Lightning to Air just to be a little more generic.
I also want to add in four classes. Two classes are for the weaknesses that a character has and the other
two for the resistances that a character has. Right click the EffectClasses folder, select Add and then
Class. Name the new class WeaknessData. Repeat that procedure three more times and name the new
classes Weakness, ResistanceData and Resistance. The code for those classes is next.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class WeaknessData
{
#region Field Region
public DamageType WeaknessType;
public int Amount;
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion

#region Virtual Method Region


#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class Weakness
{
#region Field Region
DamageType weakness;
int amount;
#endregion
#region Property Region
public DamageType WeaknessType
{
get { return weakness; }
private set { weakness = value; }
}
public int Amount
{
get { return amount; }
private set
{
if (value < 0)
amount = 0;
else if (value > 100)
amount = 100;
else
amount = value;
}
}
#endregion
#region Constructor Region
public Weakness(WeaknessData data)
{
WeaknessType = data.WeaknessType;
Amount = data.Amount;
}
#endregion
#region Method Region
public int Apply(int damage)
{
return (damage + damage * amount / 100);
}
#endregion

}
}

#region Virtual Method Region


#endregion

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class ResistanceData
{
#region Field Region
public DamageType ResistanceType;
public int Amount;
#endregion
#region Property Region
#endregion
#region Constructor Region
#endregion
#region Method Region
#endregion
#region Virtual Method Region
#endregion
}

using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

namespace RpgLibrary.EffectClasses
{
public class Resistance
{
#region Field Region
DamageType resistance;
int amount;
#endregion
#region Property Region
public DamageType ResistanceType
{
get { return resistance; }
private set { resistance = value; }
}
public int Amount
{
get { return amount; }
private set
{
if (value < 0)
amount = 0;
else if (value > 100)
amount = 100;
else
amount = value;
}
}
#endregion

#region Constructor Region


public Resistance(ResistanceData data)
{
ResistanceType = data.ResistanceType;
Amount = data.Amount;
}
#endregion
#region Method Region
public int Apply(int damage)
{
return (damage - damage * amount / 100);
}
#endregion

#region Virtual Method Region


#endregion

First important note is that I change the namespace from XRpgLibrary to RpgLibrary. The data
classes are basically the same other than the names of the first field. The WeaknessData class has
WeaknessType and ResistanceData has ResistanceType. Those fields are what the weakness or
resistance is applied to. If it is set to DamageType.Fire for example the entity is either weak against
fire or strong against fire. I had thought of making it so that if a resistance is greater than 100% that it
would heal the target. That would be something you could do but I ended up deciding against it. The
other field in the data classes is Amount which is the amount of the resistance or weakness.
The Weakness class has two fields weakness and amount. The weakness field is the type of weakness
and the amount field is the percentage of the weakness. An entity will have a percentage that they are
weak to. A fire type entity may have a high weakness to water where they will take 25% more damage
from a water attack. A resistance works in reverse. A fire type entity will be strong against a fire based
attack taking 75% less damage from a fire based attack. There are get and set accessors to assign and
get the values of the weakness and amount fields, WeaknessType and Amount respectively. The set
part for both is private because you don't want the values to change after initially set.
The constructor takes a WeaknessData parameter that is the weakness to be created. It just sets the
fields of the class using the values of the parameter passed in.
There is also a method in the class called Apply that will apply a weakness to the damage passed in. A
weakness to an attack type will increase the damage caused by that attack type. To calculate the
damage done after the weakness is applied you take the original damage and add the damage times the
amount divided by 100. That is just a basic percentage formula.
The Resistance class has two fields as well that are similar to the Weakness class resistance and
amount. The resistance field is the type of resistance and the amount field is the percentage of the
resistance like in the Weakness class. There are get and set accessors to assign and get the values of the
resistance and amount fields, ResistanceType and Amount respectively. The set part for both is
private because you don't want the values to change after they are set initially.
The constructor takes a ResistanceData parameter that describes the resistance to be created. It then
sets the fields for the class using the accessors and the values passed in.

There is an Apply method in the Resistance class as well that takes the damage that the resistance is
applied to. To apply the resistance you use a similar formula as the Apply method of the Weakness
class. You take the original damage and subtract the damage times the amount divided by 100. That
will reduce the damage by the percentage of the resistance.
You will need a way to track the weaknesses and resistances of the entities in the game. The best place
to do that would be in the Entity class. I'm going to add a new region to the Entity class. You will also
want to add in a using statement for the RpgLibrary.EffectClasses namespace to the Entity class. I
added my region after the Calculated Attribute Field and Property Region. Add the following to the
Entity class.
using RpgLibrary.EffectClasses;
#region Resistance and Weakness Field and Property Region
private readonly List<Resistance> resistances;
public List<Resistance> Resistances
{
get { return resistances; }
}
private readonly List<Weakness> weaknesses;
public List<Weakness> Weaknesses
{
get { return weaknesses; }
}
#endregion

The fields that I added are List<T> for both resistances and weaknesses that are readonly. There are
also public properties to expose the fields that are get only. The field resistances and property
Resistances are for resistances and weaknesses and Weaknesses are for weaknesses. What you need to
do now is modify the private constructor of the Entity class to initialize these new fields. Modify the
private constructor of the Entity class to the following.
private Entity()
{
Strength = 10;
Dexterity = 10;
Cunning = 10;
Willpower = 10;
Magic = 10;
Constitution = 10;
health = new AttributePair(0);
stamina = new AttributePair(0);
mana = new AttributePair(0);
skills = new Dictionary<string, Skill>();
spells = new Dictionary<string, Spell>();
talents = new Dictionary<string, Talent>();
skillModifiers = new List<Modifier>();
spellModifiers = new List<Modifier>();
talentModifiers = new List<Modifier>();

resistances = new List<Resistance>();


weaknesses = new List<Weakness>();

There is one more thing to do with weaknesses and resistances. You need to update the Apply method
of DamageEffect to call the Apply method of any resistances and weaknesses. You would do that after
rolling the damage but before checking to see if the damage is less than one. The order that I will apply
them is first weaknesses and then resistances. It would be possible for an entity to have items with
resistances and weaknesses that counter act each other. Both still should be applied though. There is the
possibility that a lot of resistances will negate all damage and multiple weaknesses would greatly
increase the damage being done. When you are designing weaknesses and resistances for your game
you will have to be careful to balance them out so they don't make the game too hard or too easy when
an entity has the appropriate items. Change the Apply method of the DamageEffect class to the
following.
public override void Apply(Entity entity)
{
int amount = modifier;
for (int i = 0; i < numberOfDice; i++)
amount += Mechanics.RollDie(dieType);
foreach (Weakness weakness in entity.Weaknesses)
if (weakness.WeaknessType == damageType)
amount = weakness.Apply(amount);
foreach (Resistance resistance in entity.Resistances)
if (resistance.ResistanceType == damageType)
amount = resistance.Apply(amount);
if (amount < 1)
amount = 1;

switch (attackType)
{
case AttackType.Health:
entity.Health.Damage((ushort)amount);
break;
case AttackType.Mana:
entity.Mana.Damage((ushort)amount);
break;
case AttackType.Stamina:
entity.Stamina.Damage((ushort)amount);
break;
}

There are two foreach loops. The first loops through all of the weaknesses in the Weakness property of
the entity. If the WeaknessType property of the weakness matches the damageType field then the
amount variable is set to the return value of the Apply method for the weakness. The foreach loop for
resistances works the same way.
I'm going to end this tutorial here. I just wanted to add in resistances and weaknesses to the different
types of effects. Things are really starting to take form and hopefully soon I will get to actually
clobbering some mobs. I encourage you to visit the news page of my site, XNA Game Programming
Adventures, for the latest news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 30
Updating Weapons
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
I wasn't going to modify weapons to use effects but in the end it makes sense to update them. It is going
to break a few things so this is going to be a rather tedious tutorial. In the end it will be well worth the
effort though.
The first step is to add in an override of the ToString method of DamageEffectData. Add this override
of the ToString method to the Virtual Method Region of DamageEffectData.
public override string ToString()
{
string toString = Name + ", " + DamageType.ToString() + ", ";
toString += AttackType.ToString() + ", ";
toString += DieType.ToString() + ", ";
toString += NumberOfDice.ToString() + ", ";
toString += Modifier.ToString();
return toString;
}

Nothing hard there. I just create a local variable and build a string that represents the instance of
DamageEffectData and then return it. The next step is to update the WeaponData class. You want to
replace the old field related to damage with a DamageEffectData field. Then update the ToString
method to use DamageEffectData now. Update the WeaponData class to the following.
Weapons will have a DamageEffectData associated with them because they cause damage. You will
need to update the WeaponData and Weapon classes. I will start with the WeaponData class. Change
that class to the following.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.EffectClasses;

namespace RpgLibrary.ItemClasses
{
public class WeaponData
{
public string Name;
public string Type;
public int Price;
public float Weight;
public bool Equipped;
public Hands NumberHands;

public
public
public
public

int AttackValue;
int AttackModifier;
DamageEffectData DamageEffectData;
string[] AllowableClasses;

public WeaponData()
{
DamageEffectData = new DamageEffectData();
}
public override string ToString()
{
string toString = Name + ", ";
toString += Type + ", ";
toString += Price.ToString() + ", ";
toString += Weight.ToString() + ", ";
toString += NumberHands.ToString() + ", ";
toString += AttackValue.ToString() + ", ";
toString += AttackModifier.ToString() + ", ";
toString += DamageEffectData.ToString();
foreach (string s in AllowableClasses)
toString += ", " + s;
return toString;
}

I replaced the DamageValue and DamageModifier fields with a DamageEffectData field that
represents the damage that the weapon does. I also update the ToString method to write out the
DamageEffectData field. I also added in a using statement for EffectClasses. The Weapon class took
a little more work. Update the Weapon class to the following.
using
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;
RpgLibrary.EffectClasses;

namespace RpgLibrary.ItemClasses
{
public class Weapon : BaseItem
{
#region Field Region
Hands hands;
int attackValue;
int attackModifier;
DamageEffectData damageEffectData;
#endregion
#region Property Region
public Hands NumberHands
{
get { return hands; }
protected set { hands = value; }
}
public int AttackValue
{
get { return attackValue; }
protected set { attackValue = value; }
}
public int AttackModifier

{
get { return attackModifier; }
protected set { attackModifier = value; }
}
public DamageEffectData DamageEffect
{
get { return damageEffectData; }
protected set { damageEffectData = value; }
}
#endregion
#region Constructor Region
public Weapon(
string weaponName,
string weaponType,
int price,
float weight,
Hands hands,
int attackValue,
int attackModifier,
DamageEffectData damageEffectData,
params string[] allowableClasses)
: base(weaponName, weaponType, price, weight, allowableClasses)
{
NumberHands = hands;
AttackValue = attackValue;
AttackModifier = attackModifier;
DamageEffect = damageEffectData;
}
#endregion
#region Abstract Method Region
public override object Clone()
{
string[] allowedClasses = new string[allowableClasses.Count];
for (int i = 0; i < allowableClasses.Count; i++)
allowedClasses[i] = allowableClasses[i];
Weapon weapon = new Weapon(
Name,
Type,
Price,
Weight,
NumberHands,
AttackValue,
AttackModifier,
DamageEffect,
allowedClasses);
}

return weapon;

public override string ToString()


{
string weaponString = base.ToString() + ", ";
weaponString += NumberHands.ToString() + ", ";
weaponString += AttackValue.ToString() + ", ";
weaponString += AttackModifier.ToString() + ", ";
weaponString += DamageEffect.ToString();
foreach (string s in allowableClasses)
weaponString += ", " + s;
return weaponString();

}
#endregion
}

Again there is a using statement for the EffectClasses namespace. I replaced the damageValue and
damageModifier fields with a DamageEffectData field. I updated the properties for the new field as
well. The constructor now takes a DamageEffectData field for damage instead of the two integer
fields. It sets the fields with the values passed in. I updated the Clone and ToString methods as well to
use the new field.
That ends up breaking a lot of things. It breaks the item editor and the weapons that were created. The
best solution that I could come up with is to delete the weapons that were added and update the editor.
Browse to the Weapon folder in the EyesOfTheDragonContent project. Select all of the entries, right
click on them and select Delete. Next, right click the RpgEditor project and select Set as StartUp
Project. You will have four errors, all of them in FormWeaponDetails. The first step is to redesign
the form. I'm going to make our life a little easier though. A weapon only causes Weapon damage. The
will also only attack Health. That leaves having to change the form so you can select the DieType,
number of dice, and modifier. The finished form in the designer appears below.

First, make your form bigger to allow for the new controls. I deleted the Damage Value: label and the
mtbDamageValue masked text box. I deleted the Damage Modifier: label and mtbDamageModifier
as well. Under the Attack Modifier: label I dragged a new label and set its Text property to Die Type:.
Beside that I dragged a combo box. Under the Die Type: label I dragged on and positioned a label and
set its Text property to Number Of Dice. I dragged a numeric up down control. Under those I dragged
a label and set its Text property to Damage Modifier:. I dragged a masked text box beside that label
and under the numeric up down. I then resized the form back to the desired size. I also moved the OK
and Cancel buttons closer to the bottom of the form and resized the list boxes. The properties I set for
the new controls are in the following tables.

Combo Box
Property

Value

(Name)

cboDieType

DropDownStyle

DropDownList

Location

115, 194

Size

100, 21

TabIndex

17

Numeric Up Down
Property

Value

(Name)

nudDice

Location

115, 221

Minimum

Size

100, 20

TabIndex

18

Masked Text Box


Property

Value

(Name)

mtbDamageModifier

Location

115, 247

Mask

Size

100, 20

TabIndex

19

The next step will be to add the code for the form. Bring up the code for FormWeaponDetails and
update it to the following.
using
using
using
using
using
using
using
using

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
System.Drawing;
System.Linq;
System.Text;
System.Windows.Forms;

using RpgLibrary.ItemClasses;
using RpgLibrary;
namespace RpgEditor
{
public partial class FormWeaponDetails : Form
{
#region Field Region

WeaponData weapon = null;


#endregion
#region Property Region
public WeaponData Weapon
{
get { return weapon; }
set { weapon = value; }
}
#endregion
#region Constructor Region
public FormWeaponDetails()
{
InitializeComponent();
this.Load += new EventHandler(FormWeaponDetails_Load);
this.FormClosing += new FormClosingEventHandler(FormWeaponDetails_FormClosing);
btnMoveAllowed.Click += new EventHandler(btnMoveAllowed_Click);
btnRemoveAllowed.Click += new EventHandler(btnRemoveAllowed_Click);
btnOK.Click += new EventHandler(btnOK_Click);
btnCancel.Click += new EventHandler(btnCancel_Click);
}
#endregion
#region Event Handler Region
void FormWeaponDetails_Load(object sender, EventArgs e)
{
foreach (string s in FormDetails.EntityDataManager.EntityData.Keys)
lbClasses.Items.Add(s);
foreach (Hands location in Enum.GetValues(typeof(Hands)))
cboHands.Items.Add(location);
foreach (DieType die in Enum.GetValues(typeof(DieType)))
cboDieType.Items.Add(die);
cboHands.SelectedIndex = 0;
cboDieType.SelectedIndex = 0;
cboDieType.SelectedValue = Enum.GetName(typeof(DieType), DieType.D4);
if (weapon != null)
{
tbName.Text = weapon.Name;
tbType.Text = weapon.Type;
mtbPrice.Text = weapon.Price.ToString();
nudWeight.Value = (decimal)weapon.Weight;
cboHands.SelectedIndex = (int)weapon.NumberHands;
mtbAttackValue.Text = weapon.AttackValue.ToString();
mtbAttackModifier.Text = weapon.AttackModifier.ToString();
for (int i = 0; i < cboDieType.Items.Count; i++)
{
if (cboDieType.Items[i].ToString() ==
weapon.DamageEffectData.DieType.ToString())
{
cboDieType.SelectedIndex = i;
cboDieType.SelectedValue = cboDieType.Items[i];
break;
}
}
nudDice.Value = weapon.DamageEffectData.NumberOfDice;

mtbDamageModifier.Text = weapon.DamageEffectData.Modifier.ToString();
foreach (string s in weapon.AllowableClasses)
{
if (lbClasses.Items.Contains(s))
lbClasses.Items.Remove(s);
}
}

lbAllowedClasses.Items.Add(s);

void FormWeaponDetails_FormClosing(object sender, FormClosingEventArgs e)


{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
}
}
void btnMoveAllowed_Click(object sender, EventArgs e)
{
if (lbClasses.SelectedItem != null)
{
lbAllowedClasses.Items.Add(lbClasses.SelectedItem);
lbClasses.Items.RemoveAt(lbClasses.SelectedIndex);
}
}
void btnRemoveAllowed_Click(object sender, EventArgs e)
{
if (lbAllowedClasses.SelectedItem != null)
{
lbClasses.Items.Add(lbAllowedClasses.SelectedItem);
lbAllowedClasses.Items.RemoveAt(lbAllowedClasses.SelectedIndex);
}
}
void btnOK_Click(object sender, EventArgs e)
{
int price = 0;
float weight = 0f;
int attVal = 0;
int attMod = 0;
int damMod = 0;
if (string.IsNullOrEmpty(tbName.Text))
{
MessageBox.Show("You must enter a name for the item.");
return;
}
if (!int.TryParse(mtbPrice.Text, out price))
{
MessageBox.Show("Price must be an integer value.");
return;
}
weight = (float)nudWeight.Value;
if (!int.TryParse(mtbAttackValue.Text, out attVal))
{
MessageBox.Show("Attack value must be an interger value.");
return;
}
if (!int.TryParse(mtbAttackModifier.Text, out attMod))
{
MessageBox.Show("Attack modifier must be an interger value.");
return;

}
if (!int.TryParse(mtbDamageModifier.Text, out damMod))
{
MessageBox.Show("Damage modifier must be an integer value.");
return;
}
List<string> allowedClasses = new List<string>();
foreach (object o in lbAllowedClasses.Items)
allowedClasses.Add(o.ToString());
weapon = new WeaponData();
weapon.Name = tbName.Text;
weapon.Type = tbType.Text;
weapon.Price = price;
weapon.Weight = weight;
weapon.NumberHands = (Hands)cboHands.SelectedIndex;
weapon.AttackValue = attVal;
weapon.AttackModifier = attMod;
weapon.AllowableClasses = allowedClasses.ToArray();
weapon.DamageEffectData.Name = tbName.Text;
weapon.DamageEffectData.AttackType = RpgLibrary.EffectClasses.AttackType.Health;
weapon.DamageEffectData.DamageType = RpgLibrary.EffectClasses.DamageType.Weapon;
weapon.DamageEffectData.DieType = (DieType)Enum.Parse(
typeof(DieType),
cboDieType.Items[cboDieType.SelectedIndex].ToString());
weapon.DamageEffectData.NumberOfDice = (int)nudDice.Value;
weapon.DamageEffectData.Modifier = damMod;
this.FormClosing -= FormWeaponDetails_FormClosing;
this.Close();
}
void btnCancel_Click(object sender, EventArgs e)
{
weapon = null;
this.FormClosing -= FormWeaponDetails_FormClosing;
this.Close();
}
#endregion
}

Most of the code is the same but enough of it changed that I wanted to give you the code for it all. The
first changes were in the FormWeaponDetails_Load method. The first new code is that I add all of the
values in the DieType enumeration to cboDieType. What is different is that you can't just cast DieType
to an integer for the SelectedIndex property of cboDieType because DieType has values associated
with each member of the enumeration. For example, 4 is associated with D4 instead of 0. For that
reason I set the SelectedValue property of cboDieType to the name of DieType.D4. I get the name
using the GetName method passing in the type and the value.
The next change is in the if statement where I check to see if weapon is not null. The first change is a
for loop that loops through all of the items in cboDieType. I check to see if the ToString value of the
item is equal to the ToString value of the DieType field of DamageEffectData of weapon. I set
SelectedIndex to i and SelectedValue to cboDieType.Items[i]. I also break out of the loop. I then set
the Value property of nudDice to the NumberOfDice field of DamageEffectData. I also set the Text
property of mtbDamageModifier to the Modifier field.

I also had to modify btnOK_Click because WeaponData changed as well as the controls on the form.
I of course removed everything that had to do with damage to use DamageEffectData. The verifying
of values on controls is like before. The new part is creating a new WeaponData object. You have to
set the fields of DamageEffectData. I set the Name to be the Text property of tbName as weapons
will be unique so the DamageEffectData associated with weapons will also be unique by name. For
now I'm assuming that the AttackType will be AttackType.Health and that the DamageType will be
DamageType.Weapon. You could easily have controls on the form to select these. The hard part was
setting the DieType field. I used the Enum.Parse method which parses a string to be the associated
value of an enumeration. For the type you pass in typeof(DieType) and for the string I passed in the
item at SelectedIndex and the ToString method. I then set the NumberOfDice and Modifier fields
using the Value property of nudDice and damMod variable respectively.
You could have different types of weapon damages like piercing, slashing and crushing and different
armors have different resistances to the types of damages. A lot of pen and paper RPGs follow this
route and I've seen computer RPGs follow that route as well. I'm not going to go into that depth in the
tutorials but it is certainly possible to do it. What I've done doesn't allow for enchanted weapons and
armor, like a flaming sword. I may in another tutorial add in enchanted weapons and armor. I would be
doing that by creating classes that inherit from Weapon and Armor rather than changing the existing
classes.
If you build and run the editor now things will work. You will be able to create weapons, save them and
read them back in like before. I think the last thing I'm going to cover in this tutorial is adding in
updated data for weapons. I will use a table like I did before.

Weapons
Name

Type

Price

Weight

Hands

Attack
Value

Attack
Modifier

Die
Type

#
of Dice

Damage
Modifier

Allowed Classes

Club

Crushing

10

One

Fighter
Rogue
Priest

Mace

Crushing

16

12

One

Fighter
Rogue
Priest

Flail

Crushing

20

14

One

10

Fighter
Priest

Apprentice Staff

Magic

20

Two

Wizard

Acolyte Staff

Magic

40

Two

Wizard

Dagger

Piercing

10

One

Fighter
Rogue

Short Sword

Piercing

20

10

One

Fighter
Rogue

Long Sword

Slashing

40

15

One

10

10

Fighter
Rogue

Broad Sword

Slashing

60

18

One

12

12

Fighter
Rogue

Great Sword

Slashing

80

25

Two

12

Fighter

Halberd

Slashing

100

30

Two

16

20

Fighter

War Axe

Slashing

20

15

One

10

10

Fighter
Rogue

Battle Axe

Slashing

50

25

Two

12

Fighter

The last thing that you want to do for this tutorial is to add the new data to the content project for the
game. Open up the directory where your Game folder is in Windows Explorer. Now drag the Game
folder from Windows Explorer onto the EyesOfTheDragonContent project to update the content for
the game. If you drill down to the Weapon folder from the root folder Game you will find all of the
weapons. As well, right click the EyesOfTheDragon project and select Set as StartUp Project.
I think I'm going to end this tutorial here. I wanted to expand weapons to use the new DamageEffect
and DamageEffectData classes. Things are starting to take form but there is a long way to go yet. I
encourage you to visit the news page of my site, XNA Game Programming Adventures, for the latest
news on my tutorials.
Good luck in your game programming adventures!
Jamie McMahon

XNA 4.0 RPG Tutorials


Part 31
Tile Engine Update
I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they
are read in order. You can find the list of tutorials on the XNA 4.0 RPG tutorials page of my web site. I
will be making my version of the project available for download at the end of each tutorial. It will be
included on the page that links to the tutorials.
In this tutorial I'm going to make an update to the tile engine and in doing so the levels. I started doing
something in my tile engines that I really liked. I created an abstract base that represents a layer of the
map. I also have a class that inherits from this class that represents a layer of tiles. I also have other
layers like an item layer and a sprite layer. On the item layer I have things like chests that the player
interacts with. On the sprite layer you can have animated tiles that will be updated each frame of the
game. You are unlimited in the types of layers you can have. All of the layers can still be managed by
the map class. Instead of using the abstract class I'm going to use an interface instead.
To get started you will want to add a class that represents a layer. Right click the TileEngine folder in
the XRpgLibrary folder, select Add and then Code File. Name this new file ILayer. The code for that
file follows next.
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.TileEngine
{
public interface ILayer
{
void Update(GameTime gameTime);
void Draw(SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets);
}
}

A simple interface that has two methods associated with it. There are also some using statements to
bring classes into scope. The first method is an Update method that allows a layer to update itself,
useful for animated tiles and sprites. The second is a Draw method that allows a layer to draw itself.
For a layer to draw itself it needs a SpriteBatch, Camera, and a List<Tileset>.
The next step is to update the MapLayer class to implement the ILayer interface. Update the
MapLayer class to the following.
using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RpgLibrary.WorldClasses;
namespace XRpgLibrary.TileEngine
{
public class MapLayer : ILayer
{
#region Field Region
Tile[,] layer;
#endregion
#region Property Region
public int Width
{
get { return layer.GetLength(1); }
}
public int Height
{
get { return layer.GetLength(0); }
}
#endregion
#region Constructor Region
public MapLayer(Tile[,] map)
{
this.layer = (Tile[,])map.Clone();
}
public MapLayer(int width, int height)
{
layer = new Tile[height, width];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
layer[y, x] = new Tile(0, 0);
}
}
}
#endregion
#region Method Region
public Tile GetTile(int x, int y)
{
return layer[y, x];
}
public void SetTile(int x, int y, Tile tile)
{
layer[y, x] = tile;
}
public void SetTile(int x, int y, int tileIndex, int tileset)
{
layer[y, x] = new Tile(tileIndex, tileset);
}
public void Update(GameTime gameTime)
{
}

public void Draw(SpriteBatch spriteBatch, Camera camera, List<Tileset> tilesets)


{
Point cameraPoint = Engine.VectorToCell(camera.Position * (1 / camera.Zoom));
Point viewPoint = Engine.VectorToCell(
new Vector2(
(camera.Position.X + camera.ViewportRectangle.Width) * (1 / camera.Zoom),
(camera.Position.Y + camera.ViewportRectangle.Height) * (1 / camera.Zoom)));
Point min = new Point();
Point max = new Point();
min.X
min.Y
max.X
max.Y

=
=
=
=

Math.Max(0, cameraPoint.X
Math.Max(0, cameraPoint.Y
Math.Min(viewPoint.X + 1,
Math.Min(viewPoint.Y + 1,

- 1);
- 1);
Width);
Height);

Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);


Tile tile;
for (int y = min.Y; y < max.Y; y++)
{
destination.Y = y * Engine.TileHeight;
for (int x = min.X; x < max.X; x++)
{
tile = GetTile(x, y);
if (tile.TileIndex == -1 || tile.Tileset == -1)
continue;
destination.X = x * Engine.TileWidth;
spriteBatch.Draw(
tilesets[tile.Tileset].Texture,
destination,
tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
Color.White);
}

}
public static MapLayer FromMapLayerData(MapLayerData data)
{
MapLayer layer = new MapLayer(data.Width, data.Height);
for (int y = 0; y < data.Height; y++)
for (int x = 0; x < data.Width; x++)
{
layer.SetTile(
x,
y,
data.GetTile(x, y).TileIndex,
data.GetTile(x, y).TileSetIndex);
}
}

return layer;

#endregion
}

Not many changes here. The first is MapLayer now implements the ILayer interface. The only other
change is that I included a blank Update method, to complete implementing the interface as there was
already a Draw method that matches the interface.
Next I will update the TileMap class to use the interface. There were a number of changes to the

TileMap class. The new TileMap class follows next.


using
using
using
using

System;
System.Collections.Generic;
System.Linq;
System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace XRpgLibrary.TileEngine
{
public class TileMap
{
#region Field Region
List<Tileset> tilesets;
List<ILayer> mapLayers;
static int mapWidth;
static int mapHeight;
#endregion
#region Property Region
public static int WidthInPixels
{
get { return mapWidth * Engine.TileWidth; }
}
public static int HeightInPixels
{
get { return mapHeight * Engine.TileHeight; }
}
#endregion
#region Constructor Region
public TileMap(List<Tileset> tilesets, MapLayer baseLayer, MapLayer buildingLayer,
MapLayer splatterLayer)
{
this.tilesets = tilesets;
this.mapLayers = new List<ILayer>();
mapLayers.Add(baseLayer);
AddLayer(buildingLayer);
AddLayer(splatterLayer);
mapWidth = baseLayer.Width;
mapHeight = baseLayer.Height;
}
public TileMap(Tileset tileset, MapLayer baseLayer)
{
tilesets = new List<Tileset>();
tilesets.Add(tileset);
mapLayers = new List<ILayer>();
mapLayers.Add(baseLayer);
mapWidth = baseLayer.Width;
mapHeight = baseLayer.Height;
}
#endregion

#region Method Region


public void AddLayer(ILayer layer)
{
if (layer is MapLayer)
{
if (!(((MapLayer)layer).Width == mapWidth && ((MapLayer)layer).Height ==
mapHeight))
throw new Exception("Map layer size exception");
}
}

mapLayers.Add(layer);

public void AddTileset(Tileset tileset)


{
tilesets.Add(tileset);
}
public void Update(GameTime gameTime)
{
foreach (ILayer layer in mapLayers)
{
layer.Update(gameTime);
}
}
public void Draw(SpriteBatch spriteBatch, Camera camera)
{
foreach (MapLayer layer in mapLayers)
{
layer.Draw(spriteBatch, camera, tilesets);
}
}
}

#endregion

The first change is that instead of having a List<MapLayer> I now have a List<ILayer> for the layers
in the map. The next change is in the constructors. Instead of taking a List<MapLayer> the first
constructor now takes three MapLayer objects. Eventually I will expand it to take more objects but it
works for now. The constructor assigns the tilesets field and creates a new List<ILayer> for the layers.
I then add the baseLayer parameter to the List<ILayer>. To add the other layers I use the AddLayer
method to confirm that layers are the same size. I then set the mapWidth and mapHeight fields using
the baseLayer.Width and baseLayer.Height respectively.
The next constructor still takes a Tileset and MapLayer object as arguments. The only real changes are
that I renamed the MapLayer parameter to baseLayer and instead of creating a List<MapLayer> I
create a List<ILayer>.
I also modified the AddLayer method. It now takes an ILayer parameter rather than a MapLayer
parameter. Since both constructors require a MapLayer as a parameter I check to see if the type of
layer being added is a MapLayer. There is then an if statement that checks if the width and height of
the layer are not the height and width of the map. MapLayers must be the same width as the base layer
so if the size of both are not the size of the base I raise an exception. I then add the layer to the list of
layers.
That broke the LoadGameScreen and CharacterGeneratorScreen classes because we were using the
constructor that passes in a lists of tile sets and map layers in the CreateWorld methods. First, change

the CreateWorld method of LoadGameScreen to the following.


private void CreateWorld()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

MapLayer splatter = new MapLayer(100, 100);


Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);

Tile tile = new Tile(index, 0);


splatter.SetTile(x, y, tile);

splatter.SetTile(1, 0, new Tile(0, 1));


splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
TileMap map = new TileMap(tileset1, layer);
map.AddTileset(tileset2);
map.AddLayer(splatter);
Level level = new Level(map);
World world = new World(GameRef, GameRef.ScreenRectangle);
world.Levels.Add(level);
world.CurrentLevel = 0;
GamePlayScreen.World = world;
}

The flow is basically the same. I create the two tile sets and the two map layers. I just removed the two
List<T> variables as they aren't needed. I then use the second constructor, the one that takes a Tileset
and ILayer arguments. To create the TileMap object. I then use the AddTileset and AddLayer
methods of TileMap to add the second tile set and layer.
The changes to the CreateWorld method of CharacterGeneratorScreen were the same as in the
LoadGameScreen. You can change that method to the following.
private void CreateWorld()
{
Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);

tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);
MapLayer layer = new MapLayer(100, 100);
for (int y = 0; y < layer.Height; y++)
{
for (int x = 0; x < layer.Width; x++)
{
Tile tile = new Tile(0, 0);
layer.SetTile(x, y, tile);
}

MapLayer splatter = new MapLayer(100, 100);


Random random = new Random();
for (int i = 0; i < 100; i++)
{
int x = random.Next(0, 100);
int y = random.Next(0, 100);
int index = random.Next(2, 14);

Tile tile = new Tile(index, 0);


splatter.SetTile(x, y, tile);

splatter.SetTile(1, 0, new Tile(0, 1));


splatter.SetTile(2, 0, new Tile(2, 1));
splatter.SetTile(3, 0, new Tile(0, 1));
TileMap map = new TileMap(tileset1, layer);
map.AddTileset(tileset2);
map.AddLayer(splatter);
Level level = new Level(map);
ChestData chestData = Game.Content.Load<ChestData>(@"Game\Chests\Plain Chest");
Chest chest = new Chest(chestData);
BaseSprite chestSprite = new BaseSprite(
containers,
new Rectangle(0, 0, 32, 32),
new Point(10, 10));
ItemSprite itemSprite = new ItemSprite(
chest,
chestSprite);
level.Chests.Add(itemSprite);
World world = new World(GameRef, GameRef.ScreenRectangle);
world.Levels.Add(level);
world.CurrentLevel = 0;
GamePlayScreen.World = world;
}

The other thing that was broken was FormMain in the XLevelEditor project. The first thing to change
however is the layers field. Instead of a List<MapLayer> I use a List<ILayer>. Change the layers
field to the this.
List<ILayer> layers = new List<ILayer>();

The first thing that was broken was the newLayerToolStripMenuItem_Click method. It was where I
created a map if the map field was null. I had to create the map using the second constructor that takes
a Tileset and a MapLayer. Update the newLayerToolStringMenuItem_Click method.
void newLayerToolStripMenuItem_Click(object sender, EventArgs e)
{
using (FormNewLayer frmNewLayer = new FormNewLayer(levelData.MapWidth, levelData.MapHeight))
{
frmNewLayer.ShowDialog();
if (frmNewLayer.OKPressed)
{
MapLayerData data = frmNewLayer.MapLayerData;
if (clbLayers.Items.Contains(data.MapLayerName))
{
MessageBox.Show("Layer with name " + data.MapLayerName + " exists.", "Existing
layer");

return;
}
MapLayer layer = MapLayer.FromMapLayerData(data);
clbLayers.Items.Add(data.MapLayerName, true);
clbLayers.SelectedIndex = clbLayers.Items.Count - 1;
layers.Add(layer);
if (map == null)
{
map = new TileMap(tileSets[0], (MapLayer)layers[0]);
for (int i = 1; i < tileSets.Count; i++)
map.AddTileset(tileSets[1]);

for (int i = 1; i < layers.Count; i++)


map.AddLayer(layers[1]);

charactersToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
}

So, what changed here is the code for the if statement that checks if the map field is null. I use the
constructor that takes a single TileSet and a ILayer. There is then a two for loops. They start from 1
instead of 0 because the first object is already in the map and you down want it added again. The first
loop adds in tile sets and the second adds in layers.
The next thing that was broken is the SetTiles method. The layers are no longer just MapLayers so I
can't use the methods and properties associated with MapLayers. Change the SetTiles method to the
follow.
private void SetTiles(Point tile, int tileIndex, int tileset)
{
int selected = clbLayers.SelectedIndex;
if (layers[selected] is MapLayer)
{
for (int y = 0; y < brushWidth; y++)
{
if (tile.Y + y >= ((MapLayer)layers[selected]).Height)
break;

}
}

for (int x = 0; x < brushWidth; x++)


{
if (tile.X + x < ((MapLayer)layers[selected]).Width)
((MapLayer)layers[selected]).SetTile(
tile.X + x,
tile.Y + y,
tileIndex,
tileset);
}

What the method does is check to see if the selected layer is a MapLayer. If it is a MapLayer I cast
the ILayer to a MapLayer so I can use the methods and properties of the MapLayer class.
The saveLevelToolStripMenuItem_Click was broken as well. Again it was because I'm using ILayer
instead of MapLayer to hold information. What I did was in the for loop that loops through the layers
is check to see if the layer is a MapLayer. If it is a MapLayer I cast the layer to be a MapLayer.
Change the saveLevelToolStripMenuItem_Click to the following.
void saveLevelToolStripMenuItem_Click(object sender, EventArgs e)
{
if (map == null)
return;
List<MapLayerData> mapLayerData = new List<MapLayerData>();
for (int i = 0; i < clbLayers.Items.Count; i++)
{
if (layers[i] is MapLayer)
{
MapLayerData data = new MapLayerData(
clbLayers.Items[i].ToString(),
((MapLayer)layers[i]).Width,
((MapLayer)layers[i]).Height);
for (int y = 0; y < ((MapLayer)layers[i]).Height; y++)
for (int x = 0; x < ((MapLayer)layers[i]).Width; x++)
data.SetTile(
x,
y,
((MapLayer)layers[i]).GetTile(x, y).TileIndex,
((MapLayer)layers[i]).GetTile(x, y).Tileset);
}

mapLayerData.Add(data);

}
MapData mapData = new MapData(levelData.MapName, tileSetData, mapLayerData);
FolderBrowserDialog fbDialog = new FolderBrowserDialog();
fbDialog.Description = "Select Game Folder";
fbDialog.SelectedPath = Application.StartupPath;
DialogResult result = fbDialog.ShowDialog();
if (result == DialogResult.OK)
{
if (!File.Exists(fbDialog.SelectedPath + @"\Game.xml"))
{
MessageBox.Show("Game not found", "Error");
return;
}

string LevelPath = Path.Combine(fbDialog.SelectedPath, @"Levels\");


string MapPath = Path.Combine(LevelPath, @"Maps\");
if (!Directory.Exists(LevelPath))
Directory.CreateDirectory(LevelPath);
if (!Directory.Exists(MapPath))
Directory.CreateDirectory(MapPath);

XnaSerializer.Serialize<LevelData>(LevelPath + levelData.LevelName + ".xml", levelData);


XnaSerializer.Serialize<MapData>(MapPath + mapData.MapName + ".xml", mapData);

The same problem is in the saveLayerToolStripMenuItem_Click method. You can change that
method to the following.
void saveLayerToolStripMenuItem_Click(object sender, EventArgs e)
{
if (layers.Count == 0)
return;
if (layers[clbLayers.SelectedIndex] is MapLayer)
{
SaveFileDialog sfDialog = new SaveFileDialog();
sfDialog.Filter = "Map Layer Data (*.mldt)|*.mldt";
sfDialog.CheckPathExists = true;
sfDialog.OverwritePrompt = true;
sfDialog.ValidateNames = true;
DialogResult result = sfDialog.ShowDialog();
if (result != DialogResult.OK)
return;
MapLayerData data = new MapLayerData(
clbLayers.SelectedItem.ToString(),
((MapLayer)layers[clbLayers.SelectedIndex]).Width,
((MapLayer)layers[clbLayers.SelectedIndex]).Height);
for (int y = 0; y < ((MapLayer)layers[clbLayers.SelectedIndex]).Height; y++)
{
for (int x = 0; x < ((MapLayer)layers[clbLayers.SelectedIndex]).Width; x++)
{
data.SetTile(
x,
y,
((MapLayer)layers[clbLayers.SelectedIndex]).GetTile(x, y).TileIndex,
((MapLayer)layers[clbLayers.SelectedIndex]).GetTile(x, y).Tileset);
}
}
try
{
XnaSerializer.Serialize<MapLayerData>(sfDialog.FileName, data);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error saving map layer data");
}
}

Opening levels and layers is also broken. For now I'm going to make the assumption that all levels
being loaded are made of MapLayers. When I add other layer types I will have to update the saving

and loading code. It is unfortunate that so much got broken but it will be well worth the headache later
on. First the openLevelToolStripMenuItem_Click method. You can change that method to the
following.
void openLevelToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Level Files (*.xml)|*.xml";
ofDialog.CheckFileExists = true;
ofDialog.CheckPathExists = true;
DialogResult result = ofDialog.ShowDialog();
if (result != DialogResult.OK)
return;
string path = Path.GetDirectoryName(ofDialog.FileName);
LevelData newLevel = null;
MapData mapData = null;
try
{
newLevel = XnaSerializer.Deserialize<LevelData>(ofDialog.FileName);
mapData = XnaSerializer.Deserialize<MapData>(path + @"\Maps\" + newLevel.MapName +
".xml");
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading level");
return;
}
tileSetImages.Clear();
tileSetData.Clear();
tileSets.Clear();
layers.Clear();
lbTileset.Items.Clear();
clbLayers.Items.Clear();
levelData = newLevel;
foreach (TilesetData data in mapData.Tilesets)
{
Texture2D texture = null;
tileSetData.Add(data);
lbTileset.Items.Add(data.TilesetName);
GDIImage image = (GDIImage)GDIBitmap.FromFile(data.TilesetImageName);
tileSetImages.Add(image);
using (Stream stream = new FileStream(data.TilesetImageName, FileMode.Open,
FileAccess.Read))
{
texture = Texture2D.FromStream(GraphicsDevice, stream);
tileSets.Add(
new Tileset(
texture,
data.TilesWide,
data.TilesHigh,
data.TileWidthInPixels,
data.TileHeightInPixels));
}
}
foreach (MapLayerData data in mapData.Layers)
{

clbLayers.Items.Add(data.MapLayerName, true);
layers.Add(MapLayer.FromMapLayerData(data));

lbTileset.SelectedIndex = 0;
clbLayers.SelectedIndex = 0;
nudCurrentTile.Value = 0;
map = new TileMap(tileSets[0], (MapLayer)layers[0]);
for (int i = 1; i < tileSets.Count; i++)
map.AddTileset(tileSets[i]);
for (int i = 1; i < layers.Count; i++)
map.AddLayer(layers[i]);
tilesetToolStripMenuItem.Enabled = true;
mapLayerToolStripMenuItem.Enabled = true;
charactersToolStripMenuItem.Enabled = true;
chestsToolStripMenuItem.Enabled = true;
keysToolStripMenuItem.Enabled = true;
}

The change here is where I create the map. I did it as before, after creating all of the map layers and tile
sets. I create a TileMap using the constructor that takes a single tile set and map layer. I then loop
through the remaining tile sets and add them using the AddTileset method. I then do the same for the
layers using the AddLayer method.
That just leaves the openLayerToolStripMenuItem_Click method. You can change that method to the
following.
void openLayerToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofDialog = new OpenFileDialog();
ofDialog.Filter = "Map Layer Data (*.mldt)|*.mldt";
ofDialog.CheckPathExists = true;
ofDialog.CheckFileExists = true;
DialogResult result = ofDialog.ShowDialog();
if (result != DialogResult.OK)
return;
MapLayerData data = null;
try
{

data = XnaSerializer.Deserialize<MapLayerData>(ofDialog.FileName);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error reading map layer");
return;
}
for (int i = 0; i < clbLayers.Items.Count; i++)
{
if (clbLayers.Items[i].ToString() == data.MapLayerName)
{
MessageBox.Show("Layer by that name already exists.", "Existing layer");
return;
}
}
clbLayers.Items.Add(data.MapLayerName, true);

layers.Add(MapLayer.FromMapLayerData(data));
if (map == null)
{
map = new TileMap(tileSets[0], (MapLayer)layers[0]);

for (int i = 1; i < tileSets.Count; i++)


map.AddTileset(tileSets[i]);

The change again is in creating a new map if the map field is null. I create a map using the constructor
that takes a tile set and a map layer. I then loop through any other tile sets and add them to the map.
I think I'm going to end this tutorial here. I wanted to flesh out classes related to spells and start on the
classes related to effects. Things are starting to take form but there is a long way to go yet. I encourage
you to visit the news page of my site, XNA Game Programming Adventures, for the latest news on my
tutorials.
Good luck in your game programming adventures!
Jamie McMahon

You might also like