Archive for April, 2013

Simple platformer game in XNA tutorial – part six "jumping and stopping"

Sunday, April 28th, 2013

image

Previously on xnafan.net...

We looked at how to improve collision detection, and use recursion to have a method call itself.

In this final installment we will

  • enable Jumper to Jump
  • stop movement when blocked
  • refactor our collision code to increase readability
  • make a few minor improvements

So let's get to it Smiley

Enabling Jumper to jump

First of all, we only want Jumper to be able to jump when he has a solid surface under his feet. We can test this by checking if a Rectangle one pixel lower than Jumper's bounding rectangle would be blocked. If that is the case, we let him jump.

So go ahead and introduce this method in the Jumper class. Notice the "!" in front of the last line. That means not. Which means that if HasRoomForRectangle is true (Jumper is floating) then we return NOT true (we're not on firm ground) and vice versa.

public bool IsOnFirmGround()
{
        Rectangle onePixelLower = Bounds;
        onePixelLower.Offset(0, 1);
        return !Board.CurrentBoard.HasRoomForRectangle(onePixelLower);
}

Rectangle.Offset() moves a Rectangle in the direction given in the x and y parameters

Change the Up key's code

...so we only allow jumps if Jumper is on firm ground. And then boost the upward movement with a value you like for his movement.

if (keyboardState.IsKeyDown(Keys.Up) && IsOnFirmGround())
{
    Movement = -Vector2.UnitY * 25;
}

Important!

Notice that I've changed the Movement -= Vector2.UnitY * 25;

to Movement = -Vector2.UnitY * 25;

because no matter whether we were falling fast when we hit the floor, we don't want our upward momentum to have any memory of our downward speed (which would be the result of using the -= operator), so we just set it to a firm negative 25 upward.

Go ahead and fiddle around with the values in AffectWithGravity and SimulateFriction until you are satisfied with how Jumper moves.

Updating debug information

Go ahead and add some additional debug information to the game, so you can see whether Jumper is on firm ground.

And since our Draw method is getting pretty long, refactor the it, so you extract the writing of debug information in a separate method.

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

private void WriteDebugInformation()
{
    string positionInText = string.Format("Position of Jumper: ({0:0.0}, {1:0.0})", _jumper.Position.X, _jumper.Position.Y);
    string movementInText = string.Format("Current movement: ({0:0.0}, {1:0.0})", _jumper.Movement.X, _jumper.Movement.Y);
    string isOnFirmGroundText = string.Format("On firm ground? : {0}", _jumper.IsOnFirmGround());

    _spriteBatch.DrawString(_debugFont, positionInText, new Vector2(10, 0), Color.White);
    _spriteBatch.DrawString(_debugFont, movementInText, new Vector2(10, 20), Color.White);
    _spriteBatch.DrawString(_debugFont, isOnFirmGroundText, new Vector2(10, 40), Color.White);
}

Stop movement when blocked

This is as simple as looking at what WhereCanIGetTo returns, and ensure that movement on either axis is set to zero if that direction was blocked.

To be able to find out whether we were able to move in this update, we need access to the previous position of Jumper, so add a private member variable to Jumper

private Vector2 oldPosition;

and a method:

private void StopMovingIfBlocked()
{
    Vector2 lastMovement = Position - oldPosition;
    if (lastMovement.X == 0) { Movement *= Vector2.UnitY; }
    if (lastMovement.Y == 0) { Movement *= Vector2.UnitX; }
}

What happens in StopMovingIfBlocked is that we get the last movement by subtracting where Jumper was before he moved from where he ended up. If there was no movement on an axis, we set that part of the current movement to zero.

We will then call StopMovingIfBlocked as the last line of Jumper's Update():

public void Update(GameTime gameTime)
{
    CheckKeyboardAndUpdateMovement();
    AffectWithGravity();
    SimulateFriction();
    MoveAsFarAsPossible(gameTime);
    StopMovingIfBlocked();
}

This is my final Jumper code

public class Jumper : Sprite
{
    public Vector2 Movement { get; set; }
    private Vector2 oldPosition;
    public Jumper(Texture2D texture, Vector2 position, SpriteBatch spritebatch)
        : base(texture, position, spritebatch)
    { }

    public void Update(GameTime gameTime)
    {
        CheckKeyboardAndUpdateMovement();
        AffectWithGravity();
        SimulateFriction();
        MoveAsFarAsPossible(gameTime);
        StopMovingIfBlocked();
    }

    private void CheckKeyboardAndUpdateMovement()
    {
        KeyboardState keyboardState = Keyboard.GetState();

        if (keyboardState.IsKeyDown(Keys.Left)) { Movement -= Vector2.UnitX; }
        if (keyboardState.IsKeyDown(Keys.Right)) { Movement += Vector2.UnitX; }
        if (keyboardState.IsKeyDown(Keys.Up) && IsOnFirmGround())
        {
            Movement = -Vector2.UnitY * 20; 
        }
    }

    private void AffectWithGravity()
    {
        Movement += Vector2.UnitY * .65f;
    }

    private void SimulateFriction()
    {
        Movement -= Movement * Vector2.One * .03f;
    }

    private void MoveAsFarAsPossible(GameTime gameTime)
    {
        oldPosition = Position;
        UpdatePositionBasedOnMovement(gameTime);
        Position = Board.CurrentBoard.WhereCanIGetTo(oldPosition, Position, Bounds);
    }

    private void UpdatePositionBasedOnMovement(GameTime gameTime)
    {
        Position += Movement * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 15;
    }

    public bool IsOnFirmGround()
    {
        Rectangle onePixelLower = Bounds;
        onePixelLower.Offset(0, 1);
        return !Board.CurrentBoard.HasRoomForRectangle(onePixelLower);
    }

    private void StopMovingIfBlocked()
    {
        Vector2 lastMovement = Position - oldPosition;
        if (lastMovement.X == 0) { Movement *= Vector2.UnitY; }
        if (lastMovement.Y == 0) { Movement *= Vector2.UnitX; }
    }
}

Refactor collision code to improve readability

If you go back and look at Board's WhereCanIGetTo method, you'll see that it is over thirty lines long:

public Vector2 WhereCanIGetTo(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle)
{

    Vector2 movementToTry = destination - originalPosition;
    Vector2 furthestAvailableLocationSoFar = originalPosition;
    int numberOfStepsToBreakMovementInto = (int)(movementToTry.Length() * 2) + 1;
    Vector2 oneStep = movementToTry / numberOfStepsToBreakMovementInto;

    for (int i = 1; i <= numberOfStepsToBreakMovementInto; i++)
    {
        Vector2 positionToTry = originalPosition + oneStep * i;
        Rectangle newBoundary = CreateRectangleAtPosition(positionToTry, boundingRectangle.Width, boundingRectangle.Height);
        if (HasRoomForRectangle(newBoundary)) { furthestAvailableLocationSoFar = positionToTry; }
        else
        {
            bool isDiagonalMove = movementToTry.X != 0 && movementToTry.Y != 0;
            if (isDiagonalMove)
            {
                int stepsLeft = numberOfStepsToBreakMovementInto - (i - 1);

                Vector2 remainingHorizontalMovement = oneStep.X * Vector2.UnitX * stepsLeft;
                Vector2 finalPositionIfMovingHorizontally = furthestAvailableLocationSoFar + remainingHorizontalMovement;
                furthestAvailableLocationSoFar =
                    WhereCanIGetTo(furthestAvailableLocationSoFar, finalPositionIfMovingHorizontally, boundingRectangle);

                Vector2 remainingVerticalMovement = oneStep.Y * Vector2.UnitY * stepsLeft;
                Vector2 finalPositionIfMovingVertically = furthestAvailableLocationSoFar + remainingVerticalMovement;
                furthestAvailableLocationSoFar =
                    WhereCanIGetTo(furthestAvailableLocationSoFar, finalPositionIfMovingVertically, boundingRectangle);
            }
            break;
        }
    }
    return furthestAvailableLocationSoFar;
}

    If we could get that down to about ten lines we might increase readability. So see what I do here, and if you like it, copy it. If you don't - then don't Smiley, der blinker.

Wrapping multiple variables and related functionality into a wrapper

First of all we have four lines which are just initialization. We could wrap all those variables and the math up into a structure, and initialize it with the same data that WhereCanIGetTo receives. Add a new class to your SimplePlatformerGame project, name the file MovementWrapper.cs and move all this code

public struct MovementWrapper
{
    public Vector2 MovementToTry { get; private set; }
    public Vector2 FurthestAvailableLocationSoFar { get; set; }
    public int NumberOfStepsToBreakMovementInto { get; private set; }
    public bool IsDiagonalMove { get; private set; }
    public Vector2 OneStep { get; private set; }
    public Rectangle BoundingRectangle { get; set; }

    public MovementWrapper(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle) : this()
    {
        MovementToTry = destination - originalPosition;
        FurthestAvailableLocationSoFar = originalPosition;
        NumberOfStepsToBreakMovementInto = (int)(MovementToTry.Length() * 2) + 1;
        IsDiagonalMove = MovementToTry.X != 0 && MovementToTry.Y != 0;
        OneStep = MovementToTry / NumberOfStepsToBreakMovementInto;
        BoundingRectangle = boundingRectangle;
    }
}

We use a struct instead of a class, since objects (instantiated from classes) are only cleaned out (garbage collected) at irregular intervals, which might make our game stutter. Structs (like int, float, Vector2, etc.) are garbage collected the moment you leave the "{...}" in which they were created.

The most important gotcha to remember when working with structs is that you always get a copy of it when you pass it to a method, so you can't just pass it to a method, change some values on the struct in the method, and then expect to see the changes reflected in the reference to the struct that you had outside the method (see example 2 for more info on this).

That not only made four lines into one, it also collected all the information about the move we're about to perform into one neat little package, which makes it easier to refactor further, and extract methods from whatever else we're performing, just wait and see Smiley, der blinker. Now see what our WhereCanIGetTo method looks like

public Vector2 WhereCanIGetTo(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle)
{
    MovementWrapper move = new MovementWrapper(originalPosition, destination);

    for (int i = 1; i <= move.NumberOfStepsToBreakMovementInto; i++)
    {
        Vector2 positionToTry = originalPosition + move.OneStep * i;
        Rectangle newBoundary = CreateRectangleAtPosition(positionToTry, boundingRectangle.Width, boundingRectangle.Height);
        if (HasRoomForRectangle(newBoundary)) {move.FurthestAvailableLocationSoFar = positionToTry; }
        else
        {
            if (move.IsDiagonalMove)
            {
                int stepsLeft = move.NumberOfStepsToBreakMovementInto - (i - 1);

                Vector2 remainingHorizontalMovement = move.OneStep.X * Vector2.UnitX * stepsLeft;
                Vector2 finalPositionIfMovingHorizontally = move.FurthestAvailableLocationSoFar + remainingHorizontalMovement;
                move.FurthestAvailableLocationSoFar =
                    WhereCanIGetTo(move.FurthestAvailableLocationSoFar, finalPositionIfMovingHorizontally, boundingRectangle);

                Vector2 remainingVerticalMovement = move.OneStep.Y * Vector2.UnitY * stepsLeft;
                Vector2 finalPositionIfMovingVertically = move.FurthestAvailableLocationSoFar + remainingVerticalMovement;
                move.FurthestAvailableLocationSoFar =
                    WhereCanIGetTo(move.FurthestAvailableLocationSoFar, finalPositionIfMovingVertically, boundingRectangle);
            }
            break;
        }
    }
    return move.FurthestAvailableLocationSoFar;
}

Basically, we just updated the references to the four variables to use the variables contained in MovementWrapper instead.

Refactor nondiagonal movement out into a separate method

Since we have all the variables for moving in the MovementWrapper struct, we can easily refactor the nondiagonal part of the movement out in a separate method. First create the method CheckPossibleNonDiagonalMovement, and move the code there.

private Vector2 CheckPossibleNonDiagonalMovement(MovementWrapper wrapper, int i)
{
    if (wrapper.IsDiagonalMove)
    {
        int stepsLeft = wrapper.NumberOfStepsToBreakMovementInto - (i - 1);

        Vector2 remainingHorizontalMovement = wrapper.OneStep.X * Vector2.UnitX * stepsLeft;
        wrapper.FurthestAvailableLocationSoFar = 
            WhereCanIGetTo(wrapper.FurthestAvailableLocationSoFar, wrapper.FurthestAvailableLocationSoFar + remainingHorizontalMovement, wrapper.BoundingRectangle);

        Vector2 remainingVerticalMovement = wrapper.OneStep.Y * Vector2.UnitY * stepsLeft;
        wrapper.FurthestAvailableLocationSoFar = 
            WhereCanIGetTo(wrapper.FurthestAvailableLocationSoFar, wrapper.FurthestAvailableLocationSoFar + remainingVerticalMovement, wrapper.BoundingRectangle);
    }

    return wrapper.FurthestAvailableLocationSoFar;
}

...then go back to WhereCanIGetTo and use our new method

public Vector2 WhereCanIGetTo(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle)
{
    MovementWrapper move = new MovementWrapper(originalPosition, destination, boundingRectangle);

    for (int i = 1; i <= move.NumberOfStepsToBreakMovementInto; i++)
    {
        Vector2 positionToTry = originalPosition + move.OneStep * i;
        Rectangle newBoundary = 
            CreateRectangleAtPosition(positionToTry, boundingRectangle.Width, boundingRectangle.Height);
        if (HasRoomForRectangle(newBoundary)) {move.FurthestAvailableLocationSoFar = positionToTry; }
        else
        {
            if (move.IsDiagonalMove)
            {
                move.FurthestAvailableLocationSoFar = CheckPossibleNonDiagonalMovement(move, i);
            }
            break;
        }
    }
    return move.FurthestAvailableLocationSoFar;
}

That's much better! Smiley med åben mund

Now I'm satisfied with our codebase - I hope you are too Smiley.

Make a few minor improvements

First of all, let's implement some code to restart with a random level whenever we want to.

So go ahead and move the initialization code of the Board constructor into a separate method like this

public Board(SpriteBatch spritebatch, Texture2D tileTexture, int columns, int rows)
{
    Columns = columns;
    Rows = rows;
    TileTexture = tileTexture;
    SpriteBatch = spritebatch;
    Tiles = new Tile[Columns, Rows];
    CreateNewBoard();
    Board.CurrentBoard = this;
}

public void CreateNewBoard()
{
    InitializeAllTilesAndBlockSomeRandomly();
    SetAllBorderTilesBlocked();
    SetTopLeftTileUnblocked();
}

And add a new method call CheckKeyboardAndReact to listen for keyboard input in SimplePlatformerGame.Update. In This method will react to someone pressing F5, and call a new method RestartGame() which creates a new Board and puts Jumper back in the top left corner:

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

private void CheckKeyboardAndReact()
{
    KeyboardState state = Keyboard.GetState();
    if (state.IsKeyDown(Keys.F5)) { RestartGame(); }
}

private void RestartGame()
{
    Board.CurrentBoard.CreateNewBoard();
    PutJumperInTopLeftCorner();
}

private void PutJumperInTopLeftCorner()
{
    _jumper.Position = Vector2.One * 80;
    _jumper.Movement = Vector2.Zero;
}

Add some code to let the player exit game when ESC is pressed:

private void CheckKeyboardAndReact()
{
    KeyboardState state = Keyboard.GetState();
    if (state.IsKeyDown(Keys.F5)) { RestartGame(); }
    if (state.IsKeyDown(Keys.Escape)) { Exit(); }
}

More friction on ground

If you want to you could also add extra friction when Jumper is gliding on a surface

private void SimulateFriction()
{
    if (IsOnFirmGround()) { Movement -= Movement * Vector2.One * .08f; }
    else { Movement -= Movement * Vector2.One * .02f; }
}

There are many small changes you can make. I added some functionality which adds a shadow under our debug text, and changed the jump key to SPACE instead of UP. You go ahead and do whatever you wish - it's your code now Smiley med åben mund

Here's the final result

Play around with it, expand it and have fun!

If you need help with anything in particular, or you want to give feedback on improving my tutorial feel free to leave a comment Smiley

The final code

Is here.

/Jake "xnafan"

Simple platformer game in XNA tutorial – part five "Improved collision detection"

Saturday, April 27th, 2013

image

Previously on xnafan.net...

We looked at implementing simple collision detection, but still had a way to go before we're satisfied with the result.

Agenda

In this part of the tutorial we will

  • add gravity
  • add some debug info to the game
  • add more realistic collision and movement to the Jumper

Adding gravity

Go ahead and add a new line AffectWithGravity() to Jumper's Update():

public void Update(GameTime gameTime)
{
    CheckKeyboardAndUpdateMovement();
    AffectWithGravity();
    SimulateFriction();
    MoveIfPossible(gameTime);
}

Add the AffectWithGravity() method as well

private void AffectWithGravity()
{
    Movement += Vector2.UnitY * .5f;
}

This line adds half a pixel of vertical movement (downward) using the constant UnitY

(which is (0,1) you may recall :)). We may have to adjust this later, but for now we have something to work with ;).

I'm stuck!!

Now you can see that Jumper will fall whenever he is unsupported, but we also get some unwanted sideeffects, the most annoying being - we can't move once we've hit the floor :(, and sometimes we don't even hit the floor:

image

In situations like this one, it is a good idea to add some debug info to your game so you can see what is going on.

Adding debug info to the game

To write something onscreen you will need a SpriteFont in your content project. So go ahead and add a SpriteFont to your contentproject:

image

and name it "DebugFont"

image

Add a SpriteFont membervariable to the SimplePlatformerGame class

private SpriteFont _debugFont;

and load the font in the LoadContent method of SimplePlatformerGame

_debugFont = Content.Load<SpriteFont>("DebugFont");

Update Draw to show debug info

Then add some lines to the SimplePlatformerGame's Draw method to show where Jumper is, and where he is headed:

protected override void Draw(GameTime gameTime)
{
    string positionInText =
        string.Format("Position of Jumper: ({0:0.0}, {1:0.0})", _jumper.Position.X, _jumper.Position.Y);
    string movementInText =
        string.Format("Current movement: ({0:0.0}, {1:0.0})", _jumper.Movement.X, _jumper.Movement.Y);

    GraphicsDevice.Clear(Color.WhiteSmoke);
    _spriteBatch.Begin();
    base.Draw(gameTime);
    _board.Draw();
    _spriteBatch.DrawString(_debugFont, positionInText, new Vector2(10, 0), Color.White);
    _spriteBatch.DrawString(_debugFont, movementInText, new Vector2(10, 20), Color.White);
    _jumper.Draw();
    _spriteBatch.End();
}

If you are unfamiliar with String.Format(), have a look here. Basically you pass it a string with placeholders named "{0}", "{1}", etc. for all the variables you want inserted into it. This makes it easier to read the format of the string, since formatting and data are kept separate.

You can add conversion information inside the brackets, using a separating colon.

E.g. {0:c} for currency or {0:0.0} for one decimal.

When .NET renders the result on screen you will see that the decimal separator on my screendumps is a comma, not a period, as my PC uses european (da-DK) culture :).

 

Now we can see what is going on

Jumper still has a desired vertical downward movement of 4.5, which makes him want to bury himself.

image

Q: Why is Jumper sometimes floating?

A: Same reason why we couldn't get close to the wall when moving sideways until we let go of the left/right key: the movement we're attempting would make Jumper end up inside a blocked Tile, and the Board object's HasRoomForRectangle() won't allow that move.

Q: Why can't we move sideways?

A: We haven't stopped Jumper's downward movement when he landed on the ground, which means that even if we try to move sideways, Jumper would still try to move downward and sideways.

Let's fix the floating first by improving how we handle leftover movement for Jumper.

Improving Jumper's movement

Here you can see what we want to accomplish.

image

In the illustration above, Jumper is moving diagonally down and left. The current Movement for Jumper wants to move him to the end of the diagonal red arrow in this Update().

We want that movement to be stopped right when he hits the horizontal row of blocks, and have the leftover motion carry over into horizontal movement, which only terminates when he hits the vertical wall of blocks a bit later.

Algorithm

What we're going to implement is a function WhereCanIGetTo on the Board class which

  • gets the origin and destination of a Rectangle
  • breaks down that movement into a number of half pixel steps
  • checks for every step whether it is blocked
  • if it is blocked, tries to carry over any diagonal movement into vertical or horizontal movement
  • returns where it was possible to move the Rectangle to

Creating the WhereCanIGetTo() method

Go ahead and add a method WhereCanIGetTo() to the Board class:

public Vector2 WhereCanIGetTo(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle)
{
}

The reason we don't send the bounding rectangle of Jumper to the method, is that Rectangle uses ints for positioning, and we need more finegrained movement here.

We're going to do some vector math here, so if you're not used to that, have a look at this :).

Before we begin stepping along the path from origin to destination, we need some variables. These variables will be used to store

  • the complete movement we want to try (the distance from origin to destination)
  • the furthest available location we've found so far
  • the direction only (without distance)
  • the direction and length of one step
  • the number of steps we want to break movement into

Add some code to calculate these values. First calculate the movement from origin to destination:

Vector2 movementToTry = destination - originalPosition;

This means that if Jumper was at  wants to go to (80, 120) and starts out at (100,100), the movement he wants to carry out is (80 - 100, 120 - 100) = (-20, 20), or in other words, -20 on the x-axis (meaning left) and 20 on the y-axis meaning down.

we assume that the originalPosition is in a nonblocked area, so we use the original position as the furthest possible location we have found along the path we want to travel

Vector2 furthestAvailableLocationSoFar = originalPosition;

to figure out how many steps we want to break the movement into, we multiply the length of the movement by 2 (so we approximately try once per half pixel), and add one, to make sure we at least try one step for very small movements

int numberOfStepsToBreakMovementInto = (int)(movementToTry.Length() * 2) + 1;

In the example mentioned above ((-20,20).Length() being approximately 28.3) this would work out at

(28.3 * 2) ≈ 57 + 1 = 58 small steps

And finally figure out how far one step is by dividing the entire move by the number of steps

Vector2 oneStep = movementToTry / numberOfStepsToBreakMovementInto;

Each step would be (-20,20) / 58 ≈ (-0.34, 0.34)

One small step at a time

Now that we have these values we can make a loop where we:

  • keep trying the next step along the movementToTry and see if we can go there. We do this by creating a Rectangle at that position and asking HasRoomForRectangle whether it is blocked
  • if that move was unblocked, we store that position in furthestAvailableLocationSoFar and continue
  • If that place is blocked, we exit the loop and return furthestAvailableLocation

Before we begin coding that, we need functionality to create a new Rectangle at a given position, so we have something to test each step with. So add a new function to the Board class which receives a Vector2 and a width and height, and creates a Rectangle at that position.

private Rectangle CreateRectangleAtPosition(Vector2 positionToTry, int width, int height)
{
    return new Rectangle((int)positionToTry.X, (int)positionToTry.Y, width, height);
}

We will lose some precision in converting the floats from positionToTry to ints, but we will finetune that later.

Now we have the basic skeleton of our improved collision detection up and running. So update the WhereCanIGetTo method. I suggest you don't copy and paste the code, but code it while making sure you understand every step of it, but suit yourself Smiley.

public Vector2 WhereCanIGetTo(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle)
{
    Vector2 movementToTry = destination - originalPosition;
    Vector2 furthestAvailableLocationSoFar = originalPosition;
    int numberOfStepsToBreakMovementInto = (int)(movementToTry.Length() * 2) + 1;
    Vector2 oneStep = movementToTry / numberOfStepsToBreakMovementInto;

    for (int i = 1; i <= numberOfStepsToBreakMovementInto; i++)
    {
        Vector2 positionToTry = originalPosition + oneStep * i;
        Rectangle newBoundary = 
            CreateRectangleAtPosition(positionToTry, boundingRectangle.Width, boundingRectangle.Height);
        if (HasRoomForRectangle(newBoundary)) { furthestAvailableLocationSoFar = positionToTry; }
        else { break; }
    }
    return furthestAvailableLocationSoFar;
}

Q: Why do you iterate from "1 to i <= numberOfSteps..." instead of "0 to i < numberOfSteps..." ?

A: Because it wouldn't make much sense to take a "zero" step which was right on top of the original position.

If we did, positionToTry would be originalPosition + (oneStep * 0) on the first step, and then we would end up one step from the finishing point instead of on it.

Figure it this way: We want to move ten meters, do we do this in ten steps: 0 meters, then 1 meters ... up to 9 meters, or would it make more sense to move 1 meter, then 2 meters ... up to 10 meters?

Finally, change the MoveIfPossible() method on Jumper. Delete the last line where we let any movement depend on whether the final destination was unblocked:

if (!Board.CurrentBoard.HasRoomForRectangle(Bounds)) { Position = oldPosition; }

and change it to:

Position = Board.CurrentBoard.WhereCanIGetTo(oldPosition, Position, Bounds);

...so we move as far as possible along the wanted route instead Smiley

Also update the MoveIfPossible name to MoveAsFarAsPossible, to better reflect that it is no longer a do/don't move decision, but a movement within the confines of the possible. Now your MoveIfPossible method should look like this:

private void MoveAsFarAsPossible(GameTime gameTime)
{
    Vector2 oldPosition = Position;
    UpdatePositionBasedOnMovement(gameTime);
    Position = Board.CurrentBoard.WhereCanIGetTo(oldPosition, Position, Bounds);
}

Press F5 to run the program and try moving around. It is better, but not perfect. Especially because we can "stick to walls" :).

image

Here's a little status quo explanation:

If you start out stuck...

Sometimes your little Jumper will start out stuck in a Tile like this:

image

To make sure that he is born a free little Sprite, you can set that tile's IsBlocked property to false when you construct a board object.

Create a well named method like this:

private void SetTopLeftTileUnblocked()
{
    Tiles[1, 1].IsBlocked = false;
}

And then call SetTopLeftTileUnblocked() in Board's constructor:

public Board(SpriteBatch spritebatch, Texture2D tileTexture, int columns, int rows)
{
    Columns = columns;
    Rows = rows;
    TileTexture = tileTexture;
    SpriteBatch = spritebatch;
    Tiles = new Tile[Columns, Rows];
    InitializeAllTilesAndBlockSomeRandomly();
    SetAllBorderTilesBlocked();
    SetTopLeftTileUnblocked();
    Board.CurrentBoard = this;
}

Recycle leftover diagonal movement as horizontal or vertical movement

Right now our movement ends as soon as we take a step which ends in a blocked position.

What we want to do is:

If we get blocked before finishing a move:

  • find out if we we're moving diagonally when we got blocked, and if we were:
  • try to move as far horizontally and/or vertically as possible and return the farthest possible location

We can illustrate it like this: Here we hit a blocked Tile about halfway into the move the Jumper is trying to make. When that happens we check whether it is a diagonal move (neither of the X and Y part of the movement vector is zero), and then we test movement using the remaining movement along the X and Y axis.

image

Remember you can turn a movement vector into horizontal movement by multiplying it by Vector2.UnitX (thereby setting its movement on the y-axis to zero) and into vertical movement by multiplying it by Vector2.UnitY (setting its x-axis movement to zero).

"If we get blocked before finishing a move"

    The place to add code for this case is inside the else part of the loop in the WhereCanIGetTo() method

    if (HasRoomForRectangle(newBoundary)) { furthestAvailableLocationSoFar = positionToTry; }
    else { 
    //here! :)
    break; 
    }
    

    "If it is a diagonal move"

    Create a boolean variable to store whether it is a diagonal move, and add it to the beginning of the else in WhereCanIGetTo(). As you can see, we store a true if neither the x- nor the y-movement is zero (think about it! :)).

    Add an if where we can add code to handle remaining diagonal movement

    bool isDiagonalMove = movementToTry.X != 0 && movementToTry.Y != 0;
    if (isDiagonalMove)
    {
        
    }
    

    To calculate the steps left, we have to subtract the step we just tried, as that moved us into a blocked area (if HasRoomForRectangle() returned true we wouldn't be down here handling all the messy details :)), and subtract the result from however many steps we were supposed to take on the entire path.

    int stepsLeft = numberOfStepsToBreakMovementInto - (i - 1);
    

    Example: We want to move 10 steps in this complete Update(). When testing step 7, we find it to be blocked, so we subtract 1 from 7 to find the last valid position (7-1 = 6), and then subtract that step from the entire trip (10 - 6 = 4) to find out how many steps we still need to try.

    "try to move as far horizontally and/or vertically as possible"

    We're almost there now - we can see the finishing line ... so let's perform the final sprint!

    As mentioned earlier, to get only the horizontal/vertical movement part of a vector, we multiply by Vector2.UnitX or Vector2.UnitY respectively. So for each type of movement, we calculate the remaining movement in that direction, find out where we want to end up of we completed that movement by adding the remaining movement to furthestAvailableLocationSoFar.

    Now we have the position to start from, and where we want to end up ... if only there were some way of finding out how far little Jumper could get to along that path...? 😉

    "But there IS!" (I hear you cry!)

    "Just feed those two positions right back into WhereCanIGetTo(), and it'll tell you!".

    Right you are - so here is the final part of our if (isDiagonalMove) {... }

    Vector2 remainingHorizontalMovement = oneStep.X * Vector2.UnitX * stepsLeft;
    Vector2 finalPositionIfMovingHorizontally = furthestAvailableLocationSoFar + remainingHorizontalMovement;
    furthestAvailableLocationSoFar =
        WhereCanIGetTo(furthestAvailableLocationSoFar, finalPositionIfMovingHorizontally, boundingRectangle);
    
    Vector2 remainingVerticalMovement = oneStep.Y * Vector2.UnitY * stepsLeft;
    Vector2 finalPositionIfMovingVertically = furthestAvailableLocationSoFar + remainingVerticalMovement;
    furthestAvailableLocationSoFar =
        WhereCanIGetTo(furthestAvailableLocationSoFar, finalPositionIfMovingVertically, boundingRectangle);
    

    The calling of a function from itself is called recursion. In a lot of cases the calling can be nested many times deep, but we only ever call two layers deep. When calling functions recursively it is very important to have a criteria for when to stop, otherwise the program enters an infinite loop. The reason our calling stops is that the recursion is only performed when movement is diagonal, and the parameters to the second call to WhereCanIGetTo is never diagonal.

    Go ahead and try it out, you will see that little Jumper no longer sticks to walls or floors, and slides right along - YAY! Go celebrate with a cup of coffee/cola/juice/water... 😀

    The final version of WhereCanIGetTo

    Read it through and see if there is something you still don't understand. If there is, now is the time to scroll back up and read the explanation again :)

    public Vector2 WhereCanIGetTo(Vector2 originalPosition, Vector2 destination, Rectangle boundingRectangle)
    {
    
        Vector2 movementToTry = destination - originalPosition;
        Vector2 furthestAvailableLocationSoFar = originalPosition;
        int numberOfStepsToBreakMovementInto = (int)(movementToTry.Length() * 2) + 1;
        Vector2 oneStep = movementToTry / numberOfStepsToBreakMovementInto;
    
        for (int i = 1; i <= numberOfStepsToBreakMovementInto; i++)
        {
            Vector2 positionToTry = originalPosition + oneStep * i;
            Rectangle newBoundary = CreateRectangleAtPosition(positionToTry, boundingRectangle.Width, boundingRectangle.Height);
            if (HasRoomForRectangle(newBoundary)) { furthestAvailableLocationSoFar = positionToTry; }
            else
            {
                bool isDiagonalMove = movementToTry.X != 0 && movementToTry.Y != 0;
                if (isDiagonalMove)
                {
                    int stepsLeft = numberOfStepsToBreakMovementInto - (i - 1);
    
                    Vector2 remainingHorizontalMovement = oneStep.X * Vector2.UnitX * stepsLeft;
                    Vector2 finalPositionIfMovingHorizontally = furthestAvailableLocationSoFar + remainingHorizontalMovement;
                    furthestAvailableLocationSoFar =
                        WhereCanIGetTo(furthestAvailableLocationSoFar, finalPositionIfMovingHorizontally, boundingRectangle);
    
                    Vector2 remainingVerticalMovement = oneStep.Y * Vector2.UnitY * stepsLeft;
                    Vector2 finalPositionIfMovingVertically = furthestAvailableLocationSoFar + remainingVerticalMovement;
                    furthestAvailableLocationSoFar =
                        WhereCanIGetTo(furthestAvailableLocationSoFar, finalPositionIfMovingVertically, boundingRectangle);
                }
                break;
            }
        }
        return furthestAvailableLocationSoFar;
    }
    

    This method is too long for my tastes. But simplifying it would mean having to put some of the code into other methods and passing along a lot of parameters (which is also not optimal for readability), or encapsulating the functionality in a small class. At the end of the next and final part of this tutorial, I will show you how that can be achieved.

    What we've covered

    After this part of the tutorial you should have learnt that

    • Gravity can be implemented simply by adding a downward momentum in every Update
    • You can benefit from adding debug information in your games to let you know why something is happening
    • You can reuse a method inside itself using recursion, and you should always ensure you have a stopping condition, so the program doesn't enter an infinite loop

    The code so far

    Is here Smiley

    Next up...

    In the final part of this tutorial we will have a look at how to make Jumper jump, and how to stop his Movement when he hits something.

    Simple platformer game in XNA tutorial – part four “Simple collision detection”

    Saturday, April 27th, 2013

    image

    Previously on xnafan.net

    In the last part we looked at movement, and how to respond to keyboard input.

    The current codebase lets the Jumper move all over the screen without any constraints at all - not exactly what we had in mind. This must be stopped! And it will Smiley, der blinker...

    The agenda

    • First we will take a look at a very simple implementation of how to detect collisions in XNA.
    • Then we will add a boundingrectangle to our Sprite class, so everything we draw has knowledge of its outer bounds
    • We will add code to test for collisions and stop the Jumper's movement if it hits something

    How simple collision detection works

    XNA offers us a very simple way of testing whether two items overlap. We define a Rectangle for each of two items, where the rectangles define the outer boundary of the items. Then we send one of the rectangles to the Intersects() method on the other, and it returns a bool telling us whether they intersect.

    Example

    We create three rectangles using the following code:

    Rectangle _rectangle1 = new Rectangle(5, 5, 25, 20);
    Rectangle _rectangle2 = new Rectangle(25, 10, 15, 10);
    Rectangle _rectangle3 = new Rectangle(35, 25, 10, 5);
    
    

    The parameters to the constructor are:

    X and Y of upper left corner

    width and height

    Here I've illustrated where these three rectangles appear, and whether Intersects() returns true or false.

    image

    Adding a bounding rectangle to our Sprite class

    Since a Rectangle can be constructed using the coordinates of the upper lefthand corner, plus width and height, it's easy to to create a bounding rectangle, because that's the information we've got in the Position property on the Sprite and the Width and Height of the Texture :).

    The easy way of exposing a Rectangle property to the world is to implement a read-only property (a property with no set part) which, when accessed from outside, creates a new Rectangle based on the Position and Texture of the Sprite. Since we want to keep things simple in this tutorial, that's what we'll do! 😀

    Add the following code to the Sprite class:

    public Rectangle Bounds
    {
        get { return new Rectangle((int)Position.X, (int)Position.Y, 
                        Texture.Width, Texture.Height); }
    }
    

    We need to cast the Position.X and Position.Y to int, since they are of type float, and will lose precision (the decimal part) when stored in an int. A lot of these operations which can introduce subtle, implicit errors are automatically checked by the compiler for us. By writing (int) in front of the value we want to convert, we can silence those warnings.

    It's like saying to the compiler: "It's alright, I know what's going on and I explicitly allow it" 😉

    Now whenever anybody wants to get the outer boundaries of our Sprite, they can retrieve the current position and size of our Sprite as a Rectangle through the Bounds property.

    And remember that since Jumper and Tile inherit Sprite, they now also have a Bounds property.

    Simple collision detection

    Now, whenever we want to move something in our game, we have to test that object's bounding rectangle against the bounding rectangle of everything else, to see whether we hit something. In a large level we would have to cut the level up into smaller quadrants and store the objects in those quadrants somewhere for fast lookups. If we didn't, our game might slow down due to the large amounts of collision checks performed each Update(). Our gameboard is 15 * 10 tiles, and about one quarter of those will be blocked. This will give us about 15 * 10 /4 = 37.5 collisiondetections per movement we want to perform. That's not even close to being a problem :). When we refine our collision detection, we will get quite a few more checks per Update(), but still nothing to worry about.

    Letting the Board tell whether is has room for something

    Now we have to figure out where to put the code for detecting collisions.

    We could put it in Jumper, but this would mean that we could only reuse that code in classes which inherit Jumper. We could also put it in Sprite, so all other subclasses of Sprite had access to it, but some of those classes might not need it. And the functionality is very closely related to the Board. So we'll add a method to Board to check for a given Rectangle whether there is room for it:

    public bool HasRoomForRectangle(Rectangle rectangleToCheck)
    {
        foreach (var tile in Tiles)
        {
            if (tile.IsBlocked && tile.Bounds.Intersects(rectangleToCheck))
            {
                return false;
            }
        }
        return true;
    }
    

    Q: Why do I first check for IsBlocked, and then check Intersects afterwards, and not the other way around?

    A: Because the first check takes less computations (a simple true/false lookup) than the other (mathematical comparisons with position, width, height). This way we don't calculate if there is no need :)

     

    Letting Jumper access the Board object through a static property

    To give Jumper access to the Board object and all its tiles, we could just add a Board property to Jumper (or Sprite) and store a reference to the Board object here. This would enable Jumper to detect collisions. But I am going to show you a technique that requires less coding and enables any code in our project to access the Board.

    In case you're still a bit wobbly on the concepts of class and object, think of the Class as the definition (the mold) for objects. There is only one class, but from that class you can instantiate ("new up") many objects, each with their own data.

    Even though there may be many objects of any given class, there is only one class of that type. So we will create a property on the Board class instead of on the objects which we've done so far. This property will store a reference to the current Board object.

    Since we can write "Board" anywhere, and gain access to the class, we will automatically have access to any public properties on the class.

    Adding a static property to a class

    To tell the compiler that we want a property (or method) stored on the class instead of on the objects created from the class, we add the keyword "static" to the property.

    Go ahead and add a static Board property "CurrentBoard" to the Board class:

    public static Board CurrentBoard { get; private set; }
    

    We mark the property's setter private, so nobody can accidentally set a new Board outside the Board class. Right now we only want one board instance (object).

    In the constructor of the Board class we add a final line to store the newly created Board object in the CurrentBoard property.

    Board.CurrentBoard = this;
    

    What's "this"?

    "this" is a special word meaning "the object I am currently in. So what we're doing here is getting a reference to the Board object we're constructing through this and storing it on the class. Now we can access the Board object from Jumper :).

    IMPORTANT!  With the approach we've chosen here, the latest created Board object will always overwrite the previous (if any), so we should only construct the board once.

    Now your Board class should look 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; }
        private SpriteBatch SpriteBatch { get; set; }
        private Random _rnd = new Random();
        public static Board CurrentBoard { get; private set; }
    
        public Board(SpriteBatch spritebatch, Texture2D tileTexture, int columns, int rows)
        {
            Columns = columns;
            Rows = rows;
            TileTexture = tileTexture;
            SpriteBatch = spritebatch;
            Tiles = new Tile[Columns, Rows];
            InitializeAllTilesAndBlockSomeRandomly();
            SetAllBorderTilesBlocked();
            Board.CurrentBoard = this;
        }
    
        private void InitializeAllTilesAndBlockSomeRandomly()
        {
            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(); }
        }
    }
    

    Updating Jumper's Update()

    Now we will add code to Jumper's Update() method to

    • store where we were before we move
    • move (already implemented)
    • check whether we've collided
    • if we collided, move back to where we came from

    Our Update looks like this now:

    public void Update(GameTime gameTime)
    {
        CheckKeyboardAndUpdateMovement();
        SimulateFriction();
        UpdatePositionBasedOnMovement(gameTime);
    }
    

    but we no longer want to unconditionally UpdatePositionBasedOnMovement(). We want to Move If Possible :).

    So cut the the last line of Update() into the clipboard, and instead write:

    MoveIfPossible(gameTime);
    

    Position your cursor inside the MoveIfPossible methodname and press ALT + SHIFT + F10, and you'll see this.

    image

    Click the "Generate method stub..." menuitem and you'll get a method like this:

     

    private void MoveIfPossible(GameTime gameTime)
    {
    }
    

    ...where you can paste your UpdatePositionBasedOnMovement(gameTime); methodcall.

    This is my preferred method of coding: Write it like you would like to read it, and then have Visual Studio implement methods which support exactly that.

    Now go ahead and

    • store the position of Jumper before updating the Position
    • check the board to see whether Jumper's new position is blocked
    • restore Position to the previous position if Jumper can't go there

    When you're done, check to see whether you have something along these lines:

    private void MoveIfPossible(GameTime gameTime)
    {
        Vector2 oldPosition = Position;
        UpdatePositionBasedOnMovement(gameTime);
        if (!Board.CurrentBoard.HasRoomForRectangle(Bounds)) { Position = oldPosition; }
    }
    

    Try it out - and don't be too sad that it isn't perfect ..yet!

    Okay - now go ahead and change the LoadContent() method of SimplePlatformerGame to move the starting point for _jumper to 80 * 80, so he doesn't start out inside the border.

    Here I use the shorthand notation for new Vector2(80,80) by multiplying a vector of (1,1) by 80:

    _jumper = new Jumper(_jumperTexture, Vector2.One * 80, _spriteBatch);
    

    Okay - now run the game, and check that the code works, but probably not exactly what you had hoped for.

    Here's an explanation of what's happening.

    The short of it is that because we're still "teleporting" from one position to the next, we are stopped too far out. Look at this illustration:

    image

    Jumper wants to update his position from an unblocked tile to a blocked tile.  Since that isn't possible, Jumper doesn't move any closer to the wall. Jumper doesn't move till we release the arrow key, and speed decreases to the point where possible movement occurs in Update().

    So we need to find out how far we can get in the direction we want. And that's what we'll do in part five of this tutorial

    The updated class diagram

    Here you can see that the Bounds property has been added to the Sprite class, and the HasRoomForRectangle method to the Board class:

    image

    What we've covered

    You should take the following away from this chapter of the tutorial:

    • You can store a reference to an object on the class using the static keyword. This enables code from anywhere to gain access to that object.
    • You should think about which class you choose to add functionality to. Which class should have the responsibility. Think of the task of finding out whether there is room for a rectangle on the Board.
    • Simple collision detection can be implemented using Rectangles.Intersects()
    • Simple collision detection isn't perfect 😉

    The code for the project so far

    ...is here :).

    Next up...

    In the next part of this tutorial, we will improve the collision detection to let us know exactly how far we can get before we must stop completely.

    Simple platformer game in XNA tutorial – part three “Movement”

    Tuesday, April 23rd, 2013

    image

    Previously …on xnafan.net Smiley

    In the previous parts of this tutorial we looked at

    • how to create a basic Sprite class (part one)
    • how to specialize that class into a Tile class and create a Board of Tiles (part two)

    In this part of the tutorial we will create a specialization of the Sprite class for the little blue guy

    jumper

    and enable him to be moved around. We will do this by storing all the necessary functionality in the Jumper class, including the math for movement, and the Keyboard input.

    We will do this by

    • extending the Sprite class
    • add an Update() method to Jumper, so we can get keyboard input and update Movement
    • add a Vector2 Movement variable to update the Position with in every Update()

    The Jumper class

    First I want you to go ahead and create a new class Jumper which inherits Sprite, like we did with Tile in part two of the tutorial.

    Try performing the following steps without peeking at part two, so you practice recalling how to create subclasses, sending parameters to the superclass’ constructor, etc.

    Add an Update method with the same signature (name, return type and parameterlist) as the Update() in the Game class. Later I will tell you why we need the GameTime parameter in Jumper, and how it helps us create smooth movement.

    Add an automatic property Vector2 Movement to the class.

    Add a constructor which accepts all the parameters that Sprite needs, and passes them on to the base class (Sprite).

    When you’re done, take a look at the code below and make sure you have something similar Smiley

    public class Jumper : Sprite
    {
        public Vector2 Movement { get; set; }
    
        public Jumper(Texture2D texture, Vector2 position, SpriteBatch spritebatch)
            : base(texture, position, spritebatch)
        {
        }
    
        public void Update(GameTime gameTime)
        {
        }
    }
    

    Movement in computergames

    It is beneficial to think of movement in computergames as an illusion created by teleporting between locations. We don’t actually move anything. Instead we show an image in a new position 30-60 times per second and our brain just fills in the blanks to maintain the illusion of smooth movement. In fact our Sprite hasn’t been to more than a couple of the positions inbetween the origin and endpoint.

    This is important to remember because we may be “moving” so fast that in an Update() we may teleport our object from one side of an obstacle to another and miss the collision. Here that situation is illustrated by an acceleration while falling, where the first call to Jumper.Update() moves him 50 pixels down from top to middle position and second call is moving even faster moving him 70 pixels down and across the 64 pixel tall Tile. We never detect the collision Trist smiley.

    image

    Therefore we either:

    have to make sure we move slowly enough that each Update() only tries to teleport us a distance shorter than the smallest obstacle on the Board

    or

    we have to split every Update’s “teleport” into smaller parts and test for obstacles at every little step

    We are going to go with the second approach Smiley - but more about collisions in next part of this tutorial. Let’s just get something moving already!

    Keyboard input

    In XNA, keyboard input is obtained through the Keyboard.GetState() method. The GetState() method returns a KeyboardState which is a snapshot of the keyboard which allows you to test which keys were pressed and which weren’t at the time of the snapshot.

    Every Update() we will:

    • get the snapshot
    • inspect the Left, Right, Up, Down keys
    • change the amount to move (Movement)
    • change the Position by the amount in Movement

    In case you need to understand a bit more about using Vector2 for movement have a look at this tutorial (you can stop at “How to point a Texture2D in the correct direction”, since our Jumper won’t rotate Smiley).

    Updating the Update() method

    First – we will get the snapshot of the keyboard state.

    KeyboardState keyboardState = Keyboard.GetState();
    

    Converting input to movement – a naïve approach

    In a naïve approach we could then change the Movement property like this (NOT the way to to it!)

    if (keyboardState.IsKeyDown(Keys.Left)) { Movement =  new Vector2(-1,0); }
    if (keyboardState.IsKeyDown(Keys.Right)) { Movement = new Vector2(1, 0); }
    if (keyboardState.IsKeyDown(Keys.Up)) { Movement = new Vector2(0, -1); }
    

    Q: Can you figure out what is wrong with the approach above?

    A: Since all the if checks are performed in order, in case you had Right and Up pressed at the same time, Movement would first be set to +1 on the X-axis and 0 on the Y axis, and then the next if would set Movement to 0 on the x-axis and –1 on the Y axis (up). The player would not get any reaction from his right key.

    Converting input to movement – a better approach

    What we want to do instead of replacing the Movement is adjust it like this:

    if (keyboardState.IsKeyDown(Keys.Left)) { Movement +=  new Vector2(-1,0); }
    if (keyboardState.IsKeyDown(Keys.Right)) { Movement += new Vector2(1, 0); }
    if (keyboardState.IsKeyDown(Keys.Up)) { Movement += new Vector2(0, -1); }
    

    The += operator adds the value to the to the right of the operator to the variable on the left. This operator also exists in a lot of other variants, to subtract, multiply and divide (–=, *=, /=) …and more Smiley.

    BTW: I haven’t added Keys.Down, since gravity will be performing that movement for us later Smiley, der blinker.

    This will give us the benefit of gradual acceleration/deceleration, since a fast movement left, say 5 pixels per update, wouldn’t be replaced by a sudden reversal to the right when the right key is pressed. Instead Jumper decelerates to 4 pixels/update, 3 pixels/update, etc. until he stands still and then accelerates slowly right. Right now we don’t know if a whole pixel is too much or not enough, but we can fiddle around with it later till it seems right Smiley.

    In case you haven’t tried Vector math before, basically all operations are performed on both the X and the Y.

    Vector2 aPosition = new Vector2(6,12);  //X is set to 6 and Y to 12
    aPosition /= 2; //results in X=3 (6 divided by 2) and Y=6 (12 divided by 2)
    aPosition += new Vector2(4, 5.3f); //results in X=7 (3 plus 4) and Y=11.3 (6 plus 5.3)
    

    The f after the 5.3 in the third line above is necessary because decimal numbers in C# are doubles by default (larger variables than floats which Vector2 use) so we explicitly declare the 5.3 a float with the “f” suffix.

    Updating Position based on Movement

    To update Jumper’s Position based on the Movement variable we just add Movement to Position.

    call Jumper’s Update() in every call

    Changing the _jumper Sprite to a Jumper

    On the SimplePlatformerGame class we had a membervariable _jumper of type Sprite:

    private Sprite _jumper;
    

    go ahead and change its type to Jumper

    private Jumper  _jumper;
    

    …change the instantiation in LoadContent(), so we instantiate a Jumper instead of a Sprite:

    _jumper = new Jumper(_jumperTexture, new Vector2(50, 50), _spriteBatch);
    

    and call _jumper.Update from SimplePlatformerGame.Update

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

    Now run the program and try moving Jumper around Smiley

    Not what you expected? Smiley, der blinker

    Your Jumper will fly off screen pretty fast, because we haven’t got any collision detection in place, nor any automatic slowing down. Collision detection is next tutorial, so let’s make sure we slow Jumper down at the end of every Update().

    Constraining speed of movement

    If we coded a real physics engine we would have an amount of drag (wind-resistance, ground-friction, etc.) which would increase with our speed and at some point we would automatically reach a maximum speed. That would require us to introduce more math though, and the result might not be discernible to the user anyway.

    And the basic rule of creating games states that:

     

    “Don’t make it if you can fake it!”

     

    …which we will Smiley, der blinker.

    So go ahead and add code to Jumper’s Update to decrease the speed by a tenth every update (right before you add Movement to Position):

    Movement -= Movement * new Vector2(.1f, .1f);
    

    Read this as “Multiply both the X and Y values of Movement by .1, subtract the result of that from Movement and store it in Movement again”.

    This could also have been written as

    Movement *= new Vector2(.9f, .9f);
    

    …but I like to be able to read what I use to change something with (a tenth of the speed), as opposed to what remains (nine tenths of the motion). Suit yourself, just make sure you understand what you’re doing Smiley.

    Your Update should look like this now:

    public void Update(GameTime gameTime)
    {
        KeyboardState keyboardState = Keyboard.GetState();
        if (keyboardState.IsKeyDown(Keys.Left)) { Movement += new Vector2(-1, 0); }
        if (keyboardState.IsKeyDown(Keys.Right)) { Movement += new Vector2(1, 0); }
        if (keyboardState.IsKeyDown(Keys.Up)) { Movement += new Vector2(0, -1); }
    
        Movement *= Movement * new Vector2(.1f, .1f);
        Position += Movement;
    }
    

    Hit F5 and try it again Smiley  … muuuuch better! (I hope? …otherwise go over your code again. If something is not working, set a breakpoint and look at the math for each Update() to see what is happening).

    Smoothing movement

    Though you may not notice it right now your Update is called 60 times a second. In a perfect world this would be every 1/60th of a second. But (and I hate to break it to you) it’s not a perfect world! Grædende ansigt Smiley, der blinker

    We may have 3/60ths of a second between two Updates and then two Updates right after each other with close to a millisecond interval to catch up. So what do we do? We compensate! Smiley med åben mund

    We just add the elapsed time into our math, so an Update with 1/60th uses 1/60th of the movement for a complete second, and an Update which takes three times that amount of time also performs a three times longer move (it’s a “teleport” – remember? Smiley, der blinker).

    We can get the elapsed time since last Update() in the gameTime parameter in the Update method. So let’s add that to our equation. Change the last line of Update() to:

    Position += Movement * (float)gameTime.ElapsedGameTime.TotalMilliseconds;
    

    the (float) is necessary because TotalMilliseconds is a double, which we can’t multiply with a Vector2. So we cast it (change it) to a float before doing the math.

    Try it again – and you’ll see that movement gets waaayy to fast (for me at least Smiley, der blinker). So divide those TotalMilliseconds with something until you’re happy. I ended up with this which suited me fine:

    Position += Movement *(float)gameTime.ElapsedGameTime.TotalMilliseconds/15;
    

    Cleaning your code

    Our Update is already six lines of code and growing, let’s prune it back by using some Clean Code.

    If you were a regular Joe coding along, you would add comments to your Update method and it would end up like this:

    public void Update(GameTime gameTime)
    {
        //get the keyboard state
        KeyboardState keyboardState = Keyboard.GetState();
                
        //change movement to reflect the keypresses
        if (keyboardState.IsKeyDown(Keys.Left)) { Movement += new Vector2(-1, 0); }
        if (keyboardState.IsKeyDown(Keys.Right)) { Movement += new Vector2(1, 0); }
        if (keyboardState.IsKeyDown(Keys.Up)) { Movement += new Vector2(0, -1); }
                
        //simulate friction
        Movement -= Movement * new Vector2(.1f, .1f);
    
        //update position based on movement
        Position += Movement * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 15;
    }
    

    BUT … since you’re NOT a regular Joe, you have realized through years and years of practical experience that:

    • Comments lie!  Not always, but they get out of sync with the code as we update it (which we invariably will) and forget to modify the comments to reflect the changes
    • When we return to our own code (or read other people’s code) we don’t want to sift through lines and lines to find what we need, to be able to correct an error or modify functionality

    So you quickly move the code from Update() into three methods

    public void Update(GameTime gameTime)
    {
        CheckKeyboardAndUpdateMovement();
        SimulateFriction();
        UpdatePositionBasedOnMovement(gameTime);
    }
    
    private void CheckKeyboardAndUpdateMovement()
    {
        KeyboardState keyboardState = Keyboard.GetState();
    
        if (keyboardState.IsKeyDown(Keys.Left)) { Movement += new Vector2(-1, 0); }
        if (keyboardState.IsKeyDown(Keys.Right)) { Movement += new Vector2(1, 0); }
        if (keyboardState.IsKeyDown(Keys.Up)) { Movement += new Vector2(0, -1); }
    }
    
    private void SimulateFriction()
    {
        Movement -= Movement * new Vector2(.1f, .1f);
    }
    
    private void UpdatePositionBasedOnMovement(GameTime gameTime)
    {
        Position += Movement * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 15;
    }
    

    The benefits of this approach are

    • You don’t need to keep comments in sync, and code never lies – it does what it says Smiley
    • You already have a very readable description of what Update() does inside Update(), just by reading the code.
    • You have a structure in Update() which makes it easy to build upon it when things get more complicated (and they will Smiley, der blinker)
    • You don’t need to read every line to understand what it does, if you need to change something, go to that method
    • You don’t need to spend time writing comments – just stay productive and CODE! Smiley med åben mund

    A few pointers on Vector2

    There are a few constants on the Vector2 struct which are nice to know

    • Vector2.One is equivalent to new Vector2(1,1)
    • Vector2.Zero is equivalent to new Vector2(0,0)
    • Vector2.UnitX is equivalent to new Vector2(1,0)
    • Vector2.UnitY is equivalent to new Vector2(0,1)

    These are nice to know because they increase readability (once you know what they are), and they are easy to modify movement with.

    Stop movement:

    Movement = Vector2.Zero;
    

    Constrain to horizontal movement by multiplying X with one and Y with zero:

    Movement *= Vector2.UnitX;
    

    Constrain to vertical movement by multiplying X with zero and Y with one:

    Movement *= Vector2.UnitX;
    

    What we’ve covered

    Now hopefully you’ve store inside that pretty little noggin’ of yours, that

    • Movement in computergames is “teleportation”, not real movement
    • To best let the user change movement, we get the state of the controller (Keyboard in this case), we modify the movement we want to perform (accelerate, brake, block, slow, etc.) and then we update the position based on the movement
    • We use the elapsed time in our movement math, to compensate for computers doing other things while we’re playing our game
    • We can make our code self commenting by using the Clean Code principles.

    The code for the project so far

    Is available here.

    Next up: Collision detection

    In the fourth part, we will look at how to stop our Jumper when he collides with something.

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

    Sunday, April 21st, 2013

    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.

    Simple platformer game in XNA tutorial – part one “The basic Sprite”

    Wednesday, April 17th, 2013

    This tutorial covers all the steps in coding your own platformer for use in your games. We will cover a bit of the principles in Object Oriented Design and Clean Code along the way Smiley.

    What we want to end up making is something like this:

    If you just want to try it out and play around with it, you can download the complete source code here.

    The agenda

    Here’s the agenda.

    • Setting up
    • The Game class
    • The Sprite class
    • The Tile and Board class
    • The Jumper class
    • Introducing simple collision detection
    • Improving our collision detection
    • Adding gravity and jump ability
    • Refactoring code for readability

    In this first part of the tutorial I will cover the first three bulletpoints Smiley

    Qualifications needed to understand this tutorial

    I expect you to know C#, some Object Oriented programming principles (like how to create a class, and add properties to it), and have a basic understanding of how XNA works (LoadContent/Update/Draw).

    If you aren’t at this level – then you’re welcome to try and keep up anyway, and ask in the comments if there’s something you want to understand and aren’t able to figure out by searching the web Smiley.

    Setting up

    Create a new solution with a XNA game project and a Content project. I have named my project “SimplePlatformer”. You may want to follow my naming to stay in sync Smiley.

    If you want to, you can create your own graphics, or you can download the tile.png (64x64 pixels) and jumper.png (40 x 40 pixels) textures here:

    jumper  tile

    right click –> save as… and save them somewhere where you can find them again. Then add them to the content project by right clicking the project - like this:

    image

    So your content project looks like this:

    image

    The Game class

    Next we’ll clean up the Game1.cs file. Rename it to SimplePlatformerGame.cs, and you will be asked

    image

    say yes, so the class name is also updated everywhere in the solution.

    Now clean away all unnecessary code in the SimplePlatformerGame class so you only have the following methods left:

    The constructor “SimplePlatFormerGame()”
    LoadContent()
    Update()
    Draw()

    Compile to check that everything is working.

    Your SimplePlatformerGame class should look like this
    (“usings” and namespace declaration omitted for brevity in most samples in this tutorial)

    public class SimplePlatformerGame : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
    
        public SimplePlatformerGame()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
    
        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);
        }
    
        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }
    
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }
    }
    

    You can see that I’ve renamed the graphics to _graphics and spriteBatch to _spriteBatch. I do this so that I will always know when using a variable somewhere in my code whether this is a local variable or a member variable, which a lot of other methods are using.

    The Sprite class

    The Sprite class is basically just a help for combining a texture and a position. It will make it easier for us to place all the elements which we will need to create a complete game.

    Create a new classfile Sprite.cs in your project class and the variables to store a Texture2D with a Vector2 for position:

    public class Sprite
    {
        public Vector2 Position { get; set; }
        public Texture2D Texture { set; get; }
    }
    

    Are we satisfied?

    Now while this would be enough if we implemented the code for setting Texture and Position outside the Sprite, and had the drawing code outside (e.g. in the SimplePlatformerGame class) we would have a satisfactory class.

    We could initialize it like this in the SimplePlatformerGame class’ LoadContent() method:

    Sprite mySprite =
     new Sprite {Texture = Content.Load("tile"), Position = new Vector2(100,50)};
    

    and draw it like this in the SimplePlatformerGame class’ Draw() method:

    SpriteBatch.Draw(mySprite.Texture, mySprite.Position, Color.White);
    

    We can do better…

    And we will, because we want each tile to be able to draw itself and work as a self-contained unit with data and functionality in one neat package. So we will also add

    • a SpriteBatch variable which the Sprite will use to draw itself.
    • a constructor to ensure that a Sprite object can not be created from the Sprite class without the necessary data for the Sprite to function (which is a main task for a constructor)
    • a Draw method which uses the provided SpriteBatch to draw to the graphics memory (see code sample below)
    public class Sprite
    {
        public Vector2 Position { get; set; }
        public Texture2D Texture { set; get; }
        public SpriteBatch SpriteBatch { get; set; }
    
        public Sprite(Texture2D texture, Vector2 position, SpriteBatch batch)
        {
            Texture = texture;
            Position = position;
            SpriteBatch = batch;
        }
    
        public void Draw()
        {
            SpriteBatch.Draw(Texture, Position, Color.White);
        }
    }
    

    Now it’s easy peasy

    …to initialize, and use the Sprite class from our SimplePlatformerGame:

    We can create a new Sprite object like this:

    Sprite mySprite = new Sprite(_tileTexture, new Vector2(40,40), _spriteBatch);
    

    (provided we have a texture stored in the variable _tileTexture, and a reference to the SimplePlatformerGame object’s SpriteBatch in the variable _spriteBatch)

    In case you didn’t catch the difference between the creation of the Sprites in the two sections above: In the first case I used an object initializer, where

    Sprite mySprite = new Sprite {Texture = _aTexture, Position = _aPosition};
    

    is just shorthand for

    Sprite mySprite = new Sprite(); 
    mySprite.Texture = _aTexture; 
    mySprite.Position = _aPosition;
    

    in the other sample I used a constructor with parameters which ensures that it is no longer possible to instantiate the object like this:

    Sprite mySprite = new Sprite();
    

    because the parameterless constructor (or “default constructor”) is removed when you explicitly create one yourself. The logic of the compiler being “oh – the programmer wants to control object-creation, I better mind my own business then Smiley).

    Now, whenever we need the Sprite drawn, we just call the Sprite’s Draw() method from the SimplePlatformerGame class’ Draw method:

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

    The call to base.Draw(gameTime) is a best practice when we are overriding a method (notice the override void Draw(…)). Since our SimplePlatformerGame class inherits Microsofts Game class, we actually don’t know what is going on inside the SimplePlatformerGame.Draw method. To ensure we don’t break any internal mechanics, we always call the overriden method’s base implementation (unless we really know what we’re doing Smiley, der blinker).

    Let’s test it

    Add two Texture2D member variables to the top of the SimplePlatformerGame class, where the _graphics and _spriteBatch are declared, and a Sprite (just for testing, that variable will be deleted shortly):

    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    private Texture2D _tileTexture, _jumperTexture;
    private Sprite _aSpriteForTesting
    

    Add code to LoadContent() to load and store the two textures:

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        _tileTexture = Content.Load("tile");
        _jumperTexture = Content.Load("jumper");
        _aSpriteForTesting = new Sprite(_jumperTexture, new Vector2(50,50), _spriteBatch);
    }
    

    Finally call the Sprite’s Draw() from the SimplePlatformerGame’s Draw():

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

    Run the game (F5) and you should see something like this:

    image

    Yay! We’ve got something on the screen Smiley

    Assignment for the reader

    To ensure you’ve understood what’s going on – try adding another Sprite variable and load a Sprite with the Tile texture underneath and to the right.

    image

    If you get stuck – the solution code is here:

    public class SimplePlatformerGame : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        private Texture2D _tileTexture, _jumperTexture;
        private Sprite _aSpriteForTesting, _anotherSpriteForTesting;
    
        public SimplePlatformerGame()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
    
        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);
            _tileTexture = Content.Load("tile");
            _jumperTexture = Content.Load("jumper");
    
            _aSpriteForTesting = 
                new Sprite(_jumperTexture, new Vector2(50, 50), _spriteBatch);
            _anotherSpriteForTesting = 
                new Sprite(_tileTexture, new Vector2(90, 120), _spriteBatch);
        }
    
        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }
    
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.WhiteSmoke);
            _spriteBatch.Begin();
            base.Draw(gameTime);
            _aSpriteForTesting.Draw();
            _anotherSpriteForTesting.Draw();
            _spriteBatch.End();
        }
    }
    

    What we’ve covered

    After reading this part one of the tutorial, you should have learnt that

    • a class is a nifty way to combine data which should be logically paired, such as the Texture and Position in the Sprite class
    • a class can be used to encapsulate both data (image, position, spritebatch) and functionality (draw, constructor) in a little package which can then be interacted with using very little code (the Sprite.Draw() method)
    • you can use a constructor to ensure that an object can only be constructed from a class if all the parameters are passed along (though null values can be substituted)

    Solution so far

    Here’s the code at the stage we’re at now, with the extra Sprite variable removed and the remaining sprite variable renamed _jumper

    Class diagram

    image

    Here we see that a Sprite has three properties: Position, SpriteBatch and Texture, and implements a Draw method and a Sprite constructor.

    It doesn’t show that Sprite implicitly inherits Object, but if a class doesn’t explicitly inherit another class, it is a subclass of Object.

    Next up: the Tile and Board

    In the next part we will look at the Tile and Board class…