Platform Game Sample (Animations through Sprite Sheets)

Ready, steady…

  • The entire assets set is provided by us. You can download it here.
  • You should have a basic knowledge of C# programming language. You can find a lot of documentation about C# on the Internet.
  • Braid’s art copyright from their original owners. Sprites from Cyrus Annihilator, background from David Hellman’s web. We just love this game and wanted to make a small tribute within this sample :-)

Goal

At the end of this guide you’ll have a small playable game which allows to move a character along the floor, playing different animations while idle and running. You’ll learn how to generate sprite sheets with TexturePacker, and consume those within Wave.

Generating the sprite sheet

If you’ve previously played Braid, you’ll surely have noticed Tim main character’s gorgeous animations. Such beauty is paired with the large amount of frames each animation has, where details can be observed from one frame to another.

For this sample we’ll make use of just two animations of Tim: running and idle (breathing). Each one of those is composed of multiple frames: 27 and 22 respectively. For instance, the first frame of running animation looks like this:

First frame Tim running

, while the same for idle one:

First frame Tim idle

Currently –as you can appreciate within the textures set-, we have 27+22=49 different images. Instead of consuming each one of those directly from the game, and with the aim to improve performance, all these are “packed” within a single image –known as sprite sheet-, next to a XML file which basically tells where each frame can be found within the sheet.

Sprite sheets can be generated manually with your favourite image editing software, but this task can be so tedious and thankfully exist a large bunch of programs which can do this automatically for us. Here we’ll make use of TexturePacker –which can be downloaded here.

The first thing is to open TexturePacker and drag the entire frames set to the right side Sprites tab, which by default will draw an initial sprite sheet in the center main viewport:

TexturePacker screen shot

At the TextureSettings panel, at the left side, the first thing is to change, within Output chapter, the Data Format to Generic XML.

TexturePacker TextureSettings screen shot

As you can appreciate, frame from both running and idle animations have been mixed up within the result, and also some frames are turned 90º. We don’t want this, so we’ll tell TexturePacker to not allow rotations –unchecking Allow rotation at Layout- and change the Algorithm to Basic.

TexturePacker Layout screen shot

The resulting preview should look similar to this one:

TexturePacker screen shot 2

Finally, just specify the Data and Texture files within Output and click on Publish button.

TexturePacker Publish screen shot

The output sprite sheet won’t differ so much from this one:

TimSpriteSheet

, while the XML should begin with something similar to:

<?xml version="1.0" encoding="UTF-8"?>
<TextureAtlas imagePath="TimSpriteSheet.png" width="1024" height="1024">
    <sprite n="slice01_01.png" x="2" y="2" w="81" h="137"/>
    <sprite n="slice02_02.png" x="85" y="2" w="81" h="137"/>
    <sprite n="slice03_03.png" x="168" y="2" w="81" h="137"/>
    […]

Exporting the assets

Apart from the sprite sheet of Tim, we’ll also make use of two more pictures as background. Indeed, the reason why the background is made of two different images is that we want the floor to be drawn on top of everything, as Tim should stay behind a few stones that appear on the image.

Background

Having the three assets in mind (TimSpriteSheet.png, Sky.jpg and Floor.png), simply export them to Wave format following, for example, the Dinosaur Sample entry’s Exporting the models and textures section.

Creating the game logic. Playing animations

Once you have a new Game Project created on Visual Studio with all the assets generated previously added (a comprehensive step-by-step guide can be found at the Dinosaur Sample entry’s Creating a new game project and Adding the models and textures section –please note here just applies the textures, not the models), it’s time to add the main components to the scene.

We’ll begin by adding the sky and floor ones. Everything within MyScene.CreateScene() method. As both are simple static sprites, the same source code applies for both, except the references to the sprite it-self:

var sky = new Entity("Sky")
    .AddComponent(new Sprite("Content/Sky.wpk"))
    .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
    .AddComponent(new Transform2D()
    {
        Origin = new Vector2(0.5f, 1),
        X = WaveServices.Platform.ScreenWidth / 2,
        Y = WaveServices.Platform.ScreenHeight
    });
var floor = new Entity("Floor")
    .AddComponent(new Sprite("Content/Floor.wpk"))
    .AddComponent(new SpriteRenderer(DefaultLayers.Alpha))
    .AddComponent(new Transform2D()
    {
        Origin = new Vector2(0.5f, 1),
        X = WaveServices.Platform.ScreenWidth / 2,
        Y = WaveServices.Platform.ScreenHeight
    });

Please note that both entities are placed on center bottom, with their origin right there, which allows to always center the background independently on the final screen size. In other words, you’ll always see the background door centered at the bottom. We haven’t still added those entities to the scene as we’ll do this later to remark the drawing order to get the desired effect of Tim running behind the stones.

The next and last entity to add is Tim it-self. The difference with previous entities is that here we’ll introduce the Animation2D component, which is in charge of everything related to the sprite sheet animations. Also, Animation2D has its own AnimatedSpriteRenderer. The code looks like this:

var tim = new Entity("Tim")
    .AddComponent(new Transform2D()
    {
        X = WaveServices.Platform.ScreenWidth / 2,
        Y = WaveServices.Platform.ScreenHeight - 46,
        Origin = new Vector2(0.5f, 1)
    })
    .AddComponent(new Sprite("Content/TimSpriteSheet.wpk"))
    .AddComponent(Animation2D.Create<TexturePackerGenericXml>("Content/TimSpriteSheet.xml")
        .Add("Idle", new SpriteSheetAnimationSequence() { First = 1, Length = 22, FramesPerSecond = 11 })
        .Add("Running", new SpriteSheetAnimationSequence() { First = 23, Length = 27, FramesPerSecond = 27 }))
    .AddComponent(new AnimatedSpriteRenderer(DefaultLayers.Alpha)));

Here the origin is also the same as with background ones, but the Y position is displaced a little bit upside so Tim’s feet perfectly fit with the end of the floor.

Apart from that, the rest applies to the animation it-self. The first thing to note is that, due to we’ll load a XML with each frame location, Animation2D provides a static method which creates an instance of it-self based on the strategy specified through generics. Such strategy  is basically the way the XML must be interpreted. Indeed, it can differ from a XML structure: it can be a TXT for instance, a CSV, or even a binary format; it doesn’t matter, always that an ISpriteSheetLoader strategy is supplied, and you can create new ones by demand!

As Animation2D.Create<T>() returns a new instance, we can take this advantage to directly call Add() to supply the different animation sequences the sprite sheet comprises. It’s easy: Add() receives the name which we’ll later use to refer to such animation, and a SpriteSheetAnimationSequence which, through its public properties, lets us configure the First frame of the animation within the XML (1-index based), the Length of such animation (the amount of frames sequenced one after the other) and its FramesPerSecond.

We’re done regarding creating entities for our game. Let’s continue by drawing them. Simply add those to the EntityManager in reverse order of desired draw:

EntityManager.Add(floor);
EntityManager.Add(tim);
EntityManager.Add(sky);

If you currently run the game, you’ll notice everything is well positioned, but a little bit static.

Game screen shot

It’s time to add some animations. The first thing is to make Tim breathe as soon as the game starts (remember breathing animation was named “Idle” in the source code). As this doesn’t require any input, nor a specific state nor similar, this can be achieved by accessing the Animation2D component of Tim entity and calling Play() –please note we’re not specifying previously the animation to play, as by default the first added is the one selected:

var anim2D = tim.FindComponent<Animation2D>();
anim2D.Play(true);

Play() has various overloads. The one used here simply tells to loop the animation when it reaches the last frame. So if you run the game now Tim will breath once and again.

The last step is to add an horizontal movement behaviour, to take part of the running animation. This Behavior will handle inputs and accordingly will move Tim to left or right with its corresponding animation. Probably you’ll be asking your-self how will Tim run to the left side since there’s no frame looking at this one (everything on the sprite sheet points to the right). There’s actually no need to add twice each frame horizontally flipped, but Transform2D.Effect already handles all this stuff. Simply changing its value to SpriteEffects.FlipHorizontally Tim will be looking to the left side.

So to accomplish this create a new class –we’ve named it TimBehavior-, and add this piece of code as the overridden Update() method:

protected override void Update(TimeSpan gameTime)
{
    currentState = AnimState.Idle;

    // touch panel
    var touches = WaveServices.Input.TouchPanelState;
    if (touches.Count > 0)
    {
        var firstTouch = touches[0];
        if (firstTouch.Position.X > WaveServices.Platform.ScreenWidth / 2)
        {
            currentState = AnimState.Right;
        }
        else
        {
            currentState = AnimState.Left;
        }
    }

    // Keyboard
    var keyboard = WaveServices.Input.KeyboardState;
    if (keyboard.Right == ButtonState.Pressed)
    {
        currentState = AnimState.Right;
    }
    else if (keyboard.Left == ButtonState.Pressed)
    {
        currentState = AnimState.Left;
    }

    // Set current animation if that one is diferent
    if (currentState != lastState)
    {
        switch (currentState)
        {
            case AnimState.Idle:
                anim2D.CurrentAnimation = "Idle";
                anim2D.Play(true);
                direction = NONE;
                break;
            case AnimState.Right:
                anim2D.CurrentAnimation = "Running";
                trans2D.Effect = SpriteEffects.None;
                anim2D.Play(true);
                direction = RIGHT;
                break;
            case AnimState.Left:
                anim2D.CurrentAnimation = "Running";
                trans2D.Effect = SpriteEffects.FlipHorizontally;
                anim2D.Play(true);
                direction = LEFT;
                break;
        }
    }

    lastState = currentState;

    // Move sprite
    trans2D.X += direction * SPEED * (gameTime.Milliseconds / 10);

    // Check borders
    if (trans2D.X < BORDER_OFFSET)
    {
        trans2D.X = BORDER_OFFSET;
    }
    else if (trans2D.X > WaveServices.Platform.ScreenWidth - BORDER_OFFSET)
    {
        trans2D.X = WaveServices.Platform.ScreenWidth - BORDER_OFFSET;
    }
}

This body can be split into four logical pieces: the touch input, the keyboard one, the animation machine state and the sprite movement.

The touch input piece simply hears for the first touch input, checking whether it was done at the left or right side of the screen, and updates the machine state accordingly. This’ particularly useful for mouse inputs, and even on mobile devices where touches are the only available option:

    // touch panel
    var touches = WaveServices.Input.TouchPanelState;
    if (touches.Count > 0)
    {
        var firstTouch = touches[0];
        if (firstTouch.Position.X > WaveServices.Platform.ScreenWidth / 2)
        {
            currentState = AnimState.Right;
        }
        else
        {
            currentState = AnimState.Left;
        }
    }

The keyboard handling acts in the same way as the touch one but making use of the keyboard’s left and right keys, nothing else:

    // Keyboard
    var keyboard = WaveServices.Input.KeyboardState;
    if (keyboard.Right == ButtonState.Pressed)
    {
        currentState = AnimState.Right;
    }
    else if (keyboard.Left == ButtonState.Pressed)
    {
        currentState = AnimState.Left;
    }

The animation machine state is where the animation it-self takes place. Depending on the value of currentState an animation is selected and consequently the Play() is called –please note the Transform2D.Effect change we mentioned before!:

    // Set current animation if that one is diferent
    if (currentState != lastState)
    {
        switch (currentState)
        {
            case AnimState.Idle:
                anim2D.CurrentAnimation = "Idle";
                anim2D.Play(true);
                direction = NONE;
                break;
            case AnimState.Right:
                anim2D.CurrentAnimation = "Running";
                trans2D.Effect = SpriteEffects.None;
                anim2D.Play(true);
                direction = RIGHT;
                break;
            case AnimState.Left:
                anim2D.CurrentAnimation = "Running";
                trans2D.Effect = SpriteEffects.FlipHorizontally;
                anim2D.Play(true);
                direction = LEFT;
                break;
        }
    }

    lastState = currentState;

And finally, the sprite movement section simply acts on Transform2D.X to actually move the sprite to left or right:

    // Move sprite
    trans2D.X += direction * SPEED * (gameTime.Milliseconds / 10);

    // Check borders
    if (trans2D.X < BORDER_OFFSET)
    {
        trans2D.X = BORDER_OFFSET;
    }
    else if (trans2D.X > WaveServices.Platform.ScreenWidth - BORDER_OFFSET)
    {
        trans2D.X = WaveServices.Platform.ScreenWidth - BORDER_OFFSET;
    }

Although the behaviour would be done, it won’t be taken into account until we add it to Tim’s entity, back on CreateScene()’s body:

.AddComponent(new TimBehavior())

Now you have a super-small platform game where its character moves along the X axe, everything supported by two different animations based on a single sprite sheet.

Game screen shot 2

Full source code

You can download the entire solution here.

4 responses to “Platform Game Sample (Animations through Sprite Sheets)

  1. Luis Sandoval

    How can I add sound?

  2. Pingback: [Wave Engine] Animando a Mai - Burbujas en .NET

  3. I’d like an example how I can draw a tile map.
    For example if my map is an array like this:
    A, A, A, B
    A, A, B, B
    A, B, B, B
    B, B, B, B
    Where A is air and B is block. Also how could I make my character collide with blocks? Also if I had all tiles in one image, how could I get the correct portion of the image for each tile?

    I’m coming from XNA so Wave Engine is very new to me :P

  4. Pingback: [WaveEngine] Medidor de fps - Burbujas en .NET