Simple platformer game in XNA tutorial – part two “Tile and Board”

In part one of this tutorial we covered how to get something drawn onto the screen.

In this tutorial we will cover how to

  • create a Tile class which is a Sprite with an IsBlocked property
  • create a Board class which can create, store and draw a number of Tiles in rows and columns

Demo of platformer

The Tile class

A tile is basically just an image at a specified position which can be blocked or unblocked. You may have already recognized that this description is an awful lot like the description of our Sprite class:
basically just a help for combining a texture and a position”.

In object-oriented programming we can take the properties and functionality of a class and add to it by inheriting one class in another. So that’s the plan:

Take the Sprite class, add an IsBlocked property and call it a Tile Smiley.

Go ahead, right click the SimplePlatformer project, and choose “Add > Class…”

image

Call it “Tile”, and you will get something like this:

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

namespace SimplePlatformer
{
    class Tile
    {
    }
}

Inheriting Sprite

To make Tile inherit Sprite we change

class Tile { }

into

class Tile : Sprite { }

like this:

image

And see that Visual Studio rewards our work with a red squiggle under the “Tile” declaration.

Hover your mouse over it, and you will get an explanation:

“Base class ‘SimplePlatformer.Sprite’ doesn’t contain parameterless constructor”:

image

Explanation for missing parameterless constructor

When subclassing another class which doesn't have a default constructor (also known as  an "empty constructor" or a "parameterless constructor"), we *have* to explicitly pass constructor parameters from the subclass (inheriting class) to the superclass (base class or parent class).

This is the equivalent of the compiler sitting in a corner, sulking and muttering:

“oh – the programmer wants to control object-creation, I better mind my own business then!!” Smiley

In this situation we often choose to pass the parameters for the superclass' constructor to the subclass' constructor, so the subclass’ constructor can pass the parameters on to the superclass. This means that our class will not compile as it is right now, because we can't create a Tile without creating a Sprite. And the Sprite wants a Texture2D, a Vector2 and a SpriteBatch in its constructor.

Making the Tile constructor work

We add a constructor to our Tile class which takes the necessary three parameter we mentioned above. Those we pass on to Sprite’s constructor. We will also add a boolean parameter isBlocked to the constructor’s parameters, and store it in the Tile object:

class Tile : Sprite
{
    public bool IsBlocked { get; set; }

    public Tile(Texture2D texture, Vector2 position, SpriteBatch batch, bool isBlocked)
        : base(texture, position, batch)
    {
        IsBlocked = isBlocked;
    }
}

As you can see above, the constructor on Tile gets four parameters, and calls the base class (Sprite) with three of them, while storing the last one in a public property IsBlocked.

You can’t see a Draw() method nor a Position or Texture property on the Tile class

… but they are there Smiley, der blinker. This is the beauty of object oriented programming - that we get what we need through inheritance.

Q: How do object-oriented programmers get rich?

A: They inherit! (insert canned laughter)

Making nonblocked tiles invisible

The final part we want to modify on the Tile class, before making a whole bunch of’em into a Board, is to have non-blocked tiles not show up. This is easily done, because right now both a Sprite and a Tile draw their Texture at their Position through the Draw() method in Sprite:

public void Draw()
{
    SpriteBatch.Draw(Texture, Position, Color.White);
}

So basically we just need to change the drawing of a Tile to *not* use this Draw functionality if the IsBlocked property is false. Piece of cake – let’s get to it Smiley

OOP INFO

In Object Oriented Programming, we always build on top of classes that other developers have developed. At the very top of the hierarchy is System.Object, which we subclass if we don’t specifically choose otherwise. Because we always inherit existing code, a very helpful principle has been implemented, to make sure we don’t change functionality by accident in a subclass, by implementing a new method with the same name as one in a superclass. This is two-step security which demands thata superclass must explicitly declare that a given method can be overridden in a subclass, by adding the virtual keyword to the method declaration

a subclass must explicitly declare that a given method is overriding a method from a superclass by adding the override keyword to the method declaration

So go ahead make the Draw() method in the Sprite class virtual

public virtual void Draw()
{
    SpriteBatch.Draw(Texture, Position, Color.White);
}

and code the Draw() method in Tile class with the override keyword

public override void Draw()
{
    if (IsBlocked) { base.Draw(); }           
}

..so it only draws the Tile graphics if it is blocked.

Try it out

Go ahead, try to figure out how to add two Tile objects to your game class, and make one of them blocked (visible) and the other passable (invisible) and run your Game to see only one.

image

If you get stuck – get the sample code here:

public class SimplePlatformerGame : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    private Texture2D _tileTexture, _jumperTexture;
    private Sprite _jumper;

    private Tile _blockedTile, _nonBlockedTile;

    public SimplePlatformerGame()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        _tileTexture = Content.Load<Texture2D>("tile");
        _jumperTexture = Content.Load<Texture2D>("jumper");
        _jumper = new Sprite(_jumperTexture, new Vector2(50, 50), _spriteBatch);
        _blockedTile = new Tile(_tileTexture, Vector2.One * 100, _spriteBatch, true);
        _nonBlockedTile = new Tile(_tileTexture, Vector2.One * 170, _spriteBatch, false);
    }

    protected override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.WhiteSmoke);
        _spriteBatch.Begin();
        base.Draw(gameTime);
        _jumper.Draw();
        _nonBlockedTile.Draw();
        _blockedTile.Draw();
        _spriteBatch.End();
    }
}

Finally, clean up by removing the _blockedTile and _nonBlockedTile variables.

Now while that is fine, we don’t want to have to keep track of hundreds of tiles in our SimplePlatformerGame class, so let’s encapsulate them in:

The Board class

The goal here is again: to encapsulate as much functionality as possible, so it is fast and simple to instantiate a Board full of tiles, and have it do its own drawing.

Before looking at the code beneath, try to implement this:

Add a new class Board to the project

Add a property Tiles of type Tile[,] (a double-array of tiles) to the Board class

Add two int properties Columns and Rows to the Board class

Add a Texture2D TileTexture property

Add a SpriteBatch property

Add a constructor which takes a SpriteBatch, a Texture2D and the two ints columns and rows

…go ahead – you’ll learn more if you try it out yourself first – believe me! Smiley, der blinker

When you’re done, you should have something like this:

public class Board
{
    public Tile[,] Tiles { get; set; }
    public int Columns { get; set; }
    public int Rows { get; set; }
    public Texture2D TileTexture { get; set; }
    public SpriteBatch SpriteBatch {get; set; }

    public Board(SpriteBatch spritebatch, Texture2D tileTexture, int columns, int rows)
    {
    }
}

We need the SpriteBatch and the Texture2D for instantiating all the tiles we will have in the Board, and the colums and rows for the size of the Board.

Important when using Arrays as properties!

Whenever you have a property of type array, or multidimensional array like our Tiles [,] array, you have to remember to instantiate it before using it.

This often means that you have to perform instantiation in the constructor. In the Board class we also want to instantiate all the Tile objects and add them to the doublearray. So let’s do that Smiley:

Instantiating the Board

We will perform three things in the constructor:

  • Save the parameters for future use
  • Instantiate the double array to a number of columns and rows as specified by the parameters to the constructor
  • Instantiate a Tile object for each arrayindex in the Tiles[,] doublearray, which is at the correct position
    public Board(SpriteBatch spritebatch, Texture2D tileTexture, int columns, int rows)
    {
        Columns = columns;
        Rows = rows;
        TileTexture = tileTexture;
        SpriteBatch = spritebatch;
        Tiles = new Tile[Columns, Rows];
        for (int x = 0; x < Columns; x++)
        {
            for (int y = 0; y < Rows; y++)
            {
                Vector2 tilePosition = 
                    new Vector2(x * tileTexture.Width, y * tileTexture.Height);
                Tiles[x, y] = 
                    new Tile(tileTexture, tilePosition, spritebatch, true);
            }
        }
    }
    

Read through the code, and make sure you understand what is going on. If you don’t, set a breakpoint in the loops (F9) and run the program to inspect the calculated values. What we’re doing is multiplying the column and rows with the width and height of the texture we’re using, to position each Tile correctly.

Notice that we store the variables columns and rows in the properties first and then use the properties from then on, not the variables. The point being – if we want to ensure that column/row could never be a negative number or exceeed the window size, etc. we could change the automatic properties to properties with a backing variable and only store values we approve of.

Here is a sample of properties which don’t store negative values:

private int _columns, _rows;
public int Columns 
{
    set { if (value >= 0) {_columns = value;}}
    get {return _columns;}
}
public int Rows 
{
    set { if (value >= 0) {_rows = value;}}
    get {return _rows;}
}

By storing the parameters sent to the constructor in properties and from then on always referring to the properties and not the parameters, we retain this possibility of using the properties as a “filter”. To keep our code simple in this tutorial, I will not implement value validation, but feel free to add it if you want Smiley

Drawing the Board

Now we’ve got all the data we need, to start drawing the Board. So add a Draw method, where you iterate over the rows and columns and call the Draw() method of each tile.

public void Draw()
{
    for (int x = 0; x < Columns; x++)
    {
        for (int y = 0; y < Rows; y++)
        {
            Tiles[x, y].Draw();
        }
    }
}

Simplifying the Draw method

Since we don’t care in which order the tiles are drawn, since none of them overlap each other, we can just use a foreach loop to iterate over the Tiles.

public void Draw()
{
    foreach (var tile in Tiles)
    {
        tile.Draw();
    }
}

If you prefer the doble for-loop stick with it, but since I am using the principles of Clean Code, I try to keep my code as succinct as possible. The advantage of the second implementation is that it is very fast to read what is going on: “we’re drawing all tiles”. In the first implementation it is necessary to both look at the values of the two variables we’re using to iterate with and how we’re indexing into the array, to figure out exactly what is going on. It is a very small distinction, but I hope you get the idea Smiley.

Q: “Why didn’t we do the same in the double for-loop where we instantiate the tiles?”

A: Because we needed the x and y counter variables to calculate the Position property of the Tile objects.

Using the Board in the game

Finally – let’s create a Board in the SimplePlatformerGame class. Try to figure out what steps you have to perform in order to

  • instantiate a Board (15x10 tiles)
  • store it in a member variable so all methods in the SimplePlatformerGame can access it
  • draw the board

Try implementing it first, and use the following guide and code if you get stuck.

Go ahead and create a Board _board member variable on the SimplePlatformerGame class, and instantiate it in the LoadContent() method.

In the Draw() method of SimplePlatformerGame add a _board.Draw() call.

When you are done, your code should look something like this:

public class SimplePlatformerGame : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    private Texture2D _tileTexture, _jumperTexture;
    private Sprite _jumper;
    private Board _board;

    public SimplePlatformerGame()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        _tileTexture = Content.Load<Texture2D>("tile");
        _jumperTexture = Content.Load<Texture2D>("jumper");
        _jumper = new Sprite(_jumperTexture, new Vector2(50, 50), _spriteBatch);
        _board = new Board(_spriteBatch, _tileTexture, 15, 10);
    }

    protected override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.WhiteSmoke);
        _spriteBatch.Begin();
        base.Draw(gameTime);
        _board.Draw();
        _jumper.Draw();
        _spriteBatch.End();
    }
}

If you got it right, you will get something like this:

image

If you can’t see the _jumper, then you might have drawn it first and the board on top. In that case, switch the draw order Smiley

Enlarging the Window

We can’t see the entire board, because the default size of our Game window isn’t big enough. So calculate the window size needed for 15 columns and 10 rows each 64x64 pixels, and set the GraphicsDeviceManager’s PreferredBackBufferWidth and –Height in your constructor.

_graphics.PreferredBackBufferWidth = 960;
_graphics.PreferredBackBufferHeight = 640;

That’s better:

image

Creating randomly blocked Tiles and a border

Finally we will set randomly blocked tiles on the Board and then make sure that all bordertiles are blocked.

Randomly blocked Tiles

We instantiate our Tile objects in the Board constructor in this line

Tiles[x, y] = new Tile(TileTexture, tilePosition, spritebatch, true);

The true (fourth) parameter to the Tile constructor makes all Tiles blocked, so we need this to be randomly true or false.

Add a Random member variable to your Board class and instantiate it in the declaring line.

private Random _rnd = new Random();

Now, whenever we create a Tile we can “roll the dice” and set the tile blocked if it is zero. This way we can define what percentage of the tiles should be blocked by rolling a smaller or larger random value. If we roll _rnd.Next(2), then half the time we will get a zero and half the time a one. If we want only 10 % blocked tiles, we can roll _rnd.Next(10) and set the Tile blocked if it is zero, which will only be a tenth of the time.

Go ahead and change the true parameter of the tile instantiation in the constructor of the Board class, so 20 % of the tiles are blocked:

Tiles[x, y] = new Tile(tileTexture, tilePosition, spritebatch, _rnd.Next(5) == 0);

You should get something like this

image

Adding border tiles to the Board

For the final step in this part of the tutorial, we want to add borders all around, by setting all the outer tiles to IsBlocked = true.

So in the constructor of the Board class add a line right after the double loop which instantiates the Tiles.

SetAllBorderTilesBlocked();

Put your cursor inside the method name and press ALT+SHIFT+F10 and select “Generate method stub…”

image

This is the Clean Code way of coding. Express what your code performs via methodnames and keep methods short. A definite advantage of this is that it is easy to see what is going on in a method if it has few lines and most of the code is calls to methods whose names sum up what they perform.

In the SetAllBorderTilesBlocked, create a double loop, and if the x counter is a border column (0 or 14) or the y counter is a border row (0 or 9) then set the tile blocked.

Is this the way to do it?

Q: can we improve on this code?

private void SetAllBorderTilesBlocked()
{
    for (int x = 0; x < 15; x++)
    {
        for (int y = 0; y < 10; y++)
        {
            if (x == 0 || x == 14 || y == 0 || y == 9)
            {
                Tiles[x, y].IsBlocked = true;    
            }
                    
        }
    }
}

A: Yes - the control statement of the two for loops should not have hardcoded values, in case we want to change the size of the Board from the SimplePlatformerGame class. Same thing with the hardcoded values 14 and 9 for border tiles.

So try and improve the code by using variables instead Smiley

Solution here below:

private void SetAllBorderTilesBlocked()
{
    for (int x = 0; x < Columns; x++)
    {
        for (int y = 0; y < Rows; y++)
        {
            if (x == 0 || x == Columns - 1 || y == 0 || y == Rows - 1)
            { Tiles[x, y].IsBlocked = true; }
        }
    }
}

Our game now

image

As you can see we get a random board with borders all around. Go ahead and fix the position of the Jumper if you want to so he isn’t embedded in the border. We will add code to move him in the next part of the tutorial anyway Smiley.

Clean Code mini-assignment

Now you’ve seen how to move code into a method which summarizes what it performs in a meaningful name. Try to move the Tiles[,] initialization and the first two loops of the Board initialization into a separate method as well. This should leave our constructor with only

  • four lines for storing columns, rows, tileTexture and spritebatch
  • a method call for instantiating the Tiles[,] and tiles within it
  • the call to SetAllBorderTilesBlocked()

My suggestion for the final code of the Board class:

public class Board
{
    public Tile[,] Tiles { get; set; }
    public int Columns { get; set; }
    public int Rows { get; set; }
    public Texture2D TileTexture { get; set; }
    private SpriteBatch SpriteBatch { get; set; }
    private Random _rnd = new Random();

    public Board(SpriteBatch spritebatch, Texture2D tileTexture, int columns, int rows)
    {
        Columns = columns;
        Rows = rows;
        TileTexture = tileTexture;
        SpriteBatch = spritebatch;
        InitializeAllTilesAndBlockSomeRandomly();
        SetAllBorderTilesBlocked();
    }

    private void InitializeAllTilesAndBlockSomeRandomly()
    {
        Tiles = new Tile[Columns, Rows];
        for (int x = 0; x < Columns; x++)
        {
            for (int y = 0; y < Rows; y++)
            {
                Vector2 tilePosition =
                    new Vector2(x * TileTexture.Width, y * TileTexture.Height);
                Tiles[x, y] = 
                    new Tile(TileTexture, tilePosition, SpriteBatch, _rnd.Next(5) == 0);
            }
        }
    }

    private void SetAllBorderTilesBlocked()
    {
        for (int x = 0; x < Columns; x++)
        {
            for (int y = 0; y < Rows; y++)
            {
                if (x == 0 || x == Columns - 1 || y == 0 || y == Rows - 1)
                { Tiles[x, y].IsBlocked = true; }
            }
        }
    }

    public void Draw()
    {
        foreach (var tile in Tiles)
        {
            tile.Draw();
        }
    }
}

The Class diagram

Here  you can see how our Tile class inherits the Sprite class. It also shows that Tile adds an IsBlocked property, a new Draw method and a Tile constructor to the definition of Sprite. The Board has a collection (arrows with double heads) of Tiles.


image

 

What we’ve covered

In this tutorial you’ve seen how to

  • subclass another class (Tile from Sprite)
  • pass parameters from a subclass’ constructor to the superclass’ constructor
  • use variables instead of constants in your code (the for loops)
  • simplify code according to Clean Code principles of using meaningful names

The code

And here is the code so far – have fun! Smiley

In the third part of this tutorial series, we will look at moving the Jumper.

15 Responses to “Simple platformer game in XNA tutorial – part two “Tile and Board””

  1. Jonas Says:

    Great looking forward to the rest, keep up the good work..

  2. admin Says:

    Thanks :)
    I am hoping to finish part three by the weekend.

  3. Royston Says:

    Hello, just going though this,

    Is it me or is there a slight mistake on the section headed "Instantiating the Board"

    You have the line:

    Tiles[x, y] = new Tile(tileTexture, tilePosition, spritebatch, _rnd.Next(5) == 0);

    but at that point of the tutorial you haven't declared private Randon _rnd = new Random();

    Shouldn't the line read:

    Tiles[x, y] = new Tile(tileTexture, tilePosition, spritebatch, true);

    P.S.

    I love this tutorial, especially the "Try it yourself first"

  4. admin Says:

    Yup - good catch, thank you very much for pointing that out! :)
    Glad you like the format of the tutorials 😀

    Kind regards - Jakob

  5. Lawrence Says:

    Hello,

    Great Tutorial so far, really enjoying them. ever thought about doing a 2D sandbox tutorial?

  6. admin Says:

    Hi Lawrence :)

    Glad you like it!
    I didn't know what a "2D sandbox" game meant before reading your post and Googling it.
    Could you be more specific as to what you would like to see?
    The way I understand it, most of the games are like Terraria, Starbound and/or Windforge.
    Is that what you're looking for?
    In that case, I'm guessing that a lot of the terrain could be generated using the Tile engine technique.
    So what else do you need? AI? Random terrain generation? 😀

  7. Lawrence Says:

    First, thank you for the quick response. Yes games like Terraria and how to do 2D random terrarin generation would be very much appreciated..lol

  8. admin Says:

    I've just been reading a couple of articles on Perlin Noise (creating pseudorandom values, which have gradual, natural changes), which is a great way of generating random landscapes. It is used in Minecraft, so you can build a complex world from a single seed.
    I will see if I get the time and inspiration in the next couple of months to show how to get from the noisebuilding algorithms to a discoverable world, no promises though 😉
    Thanks a lot for your suggestion Lawrence!

  9. Frendy Says:

    Hai, thank you for this great tutorial.. i have been looking for a days how to make a platformer game in XNA.. Really thanks a lot sir ^^

    And, if you don't mind,t i have a question regarding the map.. is it possible to make a map constant and not randomly?
    So, it is like (example): 0 is for unblocked tiles, and 1 is for blocked tiles. and i have an array like this:

    private var Tiles:Array = new Array();

    Tiles = [0,0,0,1,1,1,1,1,0,1], <-- this is for row 1
    [0,0,0,0,0,0,0,1,1,1] <-- this is for row 2

    And that Tiles Array will be for our map, and 0 for unblocked tiles, and 1 for blocked tiles.
    Could you please teach us including me how to do it sir? So, basically the map is not random, but we already define it. Thank you

  10. admin Says:

    Hi Frendy :)

    Yup - that is very much possible.
    Did you solve it when looking at the "saving and loading data" tutorial?

    Otherwise I can make you a codesample :)

    Kind regards - Jakob

  11. Frendy Says:

    No, i haven't solve it sir. Do u mind to sent me the sample code regarding to create a map not just randomly setup, but we define it which position of x and y have to be blocked and have to be unblocked.

    And if you don't mind, i would like to know how to change the graphics in the tiles, but in the same array. Or how to make the tiles that we just array it and visible to make it invisible, but still known to player in the game, but not visible while user sees it. So, basically invisible tiles, but still known by the character in the game, only invisible to the human eyes.

    Example: The enemy is on tile [3,2], and in the [5,2] there is a tile (grass graphic), the enemy move from the [3,2] to [5,2], but the human eyes still see the tile on [5,2] for prevent the enemy to move ahead to [6,2]. What i mean is, the tile on [5,2] is invisible to the human eyes.

    Is it possible? And if possible, i would like to know how to do it in XNA.

    Thank you very much sir.

    Best Regards,

    Frendy

  12. Frendy Says:

    I mean change the graphics but in the same array, is the example: tile[1,1] is the grass tile, but on the tile [1,2] is the stone tile.

    How do u do that sir? Thank you

  13. admin Says:

    Hi Frendy :)

    I don't understand what you mean by making the tiles "invisible, but still known to the character in the game, only invisible to the human eye" ?
    If you want something to be invisible, just don't draw it in the Draw() method. I will need a bit further clarification on what you want.
    Do you mean that the player can only see a few tiles around him, and the rest is in a sort of a "fog of war" ?

    Regarding your other request: if you take a look at my "map generation" tutorial
    http://xnafan.net/2013/01/2d-map-generation-for-xna/
    at the bottom you can find an extended version which has code to save and load maps.
    This will let you have different values for different types of tile (e.g. 1 for grass, 2 for dirt, etc.) and then when you build your map, you create the tile objects with the graphics they need to draw themselves (a Texture2D) and information about whether the tile is blocked for character movement (a bool).
    The best tile-engine tutorial I've found on the web is this one (by Kurt Jaegers):
    http://www.xnaresources.com/default.asp?page=Tutorial:TileEngineSeries:1

    Kind regards - Jakob

  14. Frendy Says:

    That's not it sir.. Sorry because i am confusing you, what i mean to be invisible is like transparant to the human eye sir. Let's say alpha on the tiles in the game is 1, what i want is set the alpha to be 0, so the human cannot detect any object on the screen, but still known on the AI. The player character can go through the transparant tiles, even though the the alpha is 0. But, the AI character cannot go through the transparant tiles, and for them the transparant tiles is actually the tile that not transparant.

    Thank you sir

  15. admin Says:

    Hi Frendy :)

    In my code I've made a simple assumption, that if a tile is blocked then it's blocked for everybody, and I draw that tile. If it isn't blocked, it's passable for anybody, and I don't draw the tile.
    This is a very simple way of doing this, and from what I can understand, you will need to draw the tile no matter what it is, but then keep track of whether it is passable for A) players and B) enemies as two separate properties.
    In that case I suggest you make a Tile class, which in the Draw method just uses its Texture2D for drawing no matter what. Then you could have two boolean properties on the Tile class, e.g. PlayerPassable and EnemyPassable, where you keep track of whether the player or enemies can pass that tile.

    Does that help? :)

    Kind regards - Jakob

Leave a Reply