Monday 30 April 2012

A simple State based level manager in XNA

This post is to explain a simple level manager that will handle the transition between levels of a simple sprite based game based on simple state management of a sequence of levels. It assumes linear progression from one level to the next

A level consists of a set of items (Sprite based items in this case) that you want to interact with or get them to interact independently, during game play. The key to level management is to have a parent class for each level that you want in your game and that will contain references to collections that you may have in each level of your game.

The level manager manages the transition between the levels based on the outcome of a particular level. Failure and re-doing a level is not dealt with here but could be easily accommodated by extending the state model for the Level Class and coding the Level Manager accordingly.

To keep it simple here and concentrate on the Level class and the Level Manager class I will create two levels of a game that will just transit through states based on a Timer. These two classes will be level1 and level2 respectively. I will also only have static (non-animated and non interactive) content in the Level class which can then be filled in the two sub classes (level1 and level2).

The level Manager is created, updated and drawn in the Game1 class. The Draw is implemented just to call the Draw methods of the level1 and level2 classes which in turn pass the draw message on to the components that they contain.

All the classes here only implement or override  a common Update or Draw method, which are most fundamental in Game programming.

So the Game creates the Game Manager.

image

The Level manager creates the Game Levels.

image

The Levels create instances of the level contents Sprites, sound, textures.

There are a couple of Key issues about the structure and control of the levels.

  1. As Level1 and Level2 inherit from Level they can be treated as being the general Level class in some instances. Such as iteration over the collection of Level Objects.
  2. The Sate of a level is kept in the parent class Level and is chosen from the enumerated type LEVELSTATE

image

 

So to the actual State Management and transition of the individual levels. The Level Manager maintains a collection of levels that are created and assigned in the constructor.

class LevelManager
{
// Indicate the last level is complete
bool gameOver = false;

public bool GameOver
{
get { return gameOver; }
set { gameOver = value; }
}
// Counter for the current level
int CurrentLevel = 0;
// Maximum amount of levels

const int MAXLEVEL = 2;

// Collection of levels which are created as subclasses of Level
Level[] Levels;


public LevelManager(Game g)
{
Levels = new Level[MAXLEVEL];
Levels[0] = new Level1(g);
Levels[1] = new Level2(g);

Levels[0].LevelState = LEVELSTATE.PLAYING;

}


Level1 is put into playing mode. It is up to Level1 to transit itself into Finished Mode when it’s end condition is met. In this case a timer is used to time down the fairly static levels. The levels here just show a collection of Chasing Sprites in a location and a player in Level1 that the Chasing sprites will chase.



The transition from playing for a Level is monitored in the Update method of the Level Manager class



 



public void Update(GameTime t)
{
if (!gameOver)
{
foreach (Level l in Levels)
{
if (l != null && l.LevelState == LEVELSTATE.PLAYING)
{ // Update the current playing level
l.Update(t);
// if the current level has finished
if (l.LevelState == LEVELSTATE.FINISHED)
{ // Get rid of the level should
Levels[CurrentLevel] = null;
// and if the not the last level finished
if (++CurrentLevel < MAXLEVEL)
// then play the next level
Levels[CurrentLevel].LevelState = LEVELSTATE.PLAYING;
//Or else we are finished
else gameOver = true;
}
}
}
}

}


The key thing here is that although level1 and level2 are objects in their own right they have a LevelState variable that can be monitored. The Level class definition is shown below



class Level
{

private LEVELSTATE levelState;
public LEVELSTATE LevelState
{
get { return levelState; }
set { levelState = value; }
}
protected ChasingEnemy[] enemies;
protected Sprite[] collectables;
protected Sprite BackGround;
protected Player player;

public virtual void Update(GameTime t)
{

}

public virtual void Draw(SpriteBatch sp)
{

}


The virtual methods are provided in case the methods need to be overridden in the future. Any collections of object that you want to have in levels are added as class variables here and can be populated in the constructor for the sub class for example here is the constructor for the Level1 class



class Level1 : Level
{
float Timer = 5000; // milliseconds

public Level1(Game g)
{
player = new Player(g, g.Content.Load<Texture2D>("FullTank"), new Vector2(400, 600), 1);
enemies = new ChasingEnemy[3];
enemies[0] = new ChasingEnemy(g, g.Content.Load<Texture2D>("EnemyDot"), new Vector2(100, 200), 1);
enemies[1] = new ChasingEnemy(g, g.Content.Load<Texture2D>("EnemyDot"), new Vector2(300, 200), 1);
enemies[2] = new ChasingEnemy(g, g.Content.Load<Texture2D>("EnemyDot"), new Vector2(300, 400), 1);
LevelState = LEVELSTATE.CREATED;
}


NOTE the sub-classing of the Level class is Key as it contains the exposed LevelState and allows all levels to iterated over using for each and treating them all as Level objects for the purposes of this iteration



Here is the state transition in the Level1 update method



public override void Update(GameTime t)
{
if (Timer > 0)
Timer -= t.ElapsedGameTime.Milliseconds;
else
LevelState = LEVELSTATE.FINISHED;

player.Update(t);

foreach (ChasingEnemy c in enemies)
c.follow(player);

foreach (ChasingEnemy c in enemies)
c.Update(t);

}

the full code listing can be found here

2 comments:

  1. Great post, very clear, thank you very much, keep up the good work

    ReplyDelete