• Register

Wave Engine was born on February 21, 2013 with the exciting mission of developing an engine to help the mobile game developers’ community.

Post tutorial Report RSS Platform Game Sample (Animations through Sprite Sheets)

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.

Posted by on - Basic Client Side Coding

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

<!--more-->, 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 selet "WaveEngine" as project type:

Open project Wave Engine xml


Then 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:

Main window with sprites

As you can appreciate, frame from both running and idle animations have been mixed up within the result, remember and change the Algorithm to Basic. You must show advanced options if you can't see this fields.

Detail layout

There is another property that is not avaible in the Essential (lite) version of TexturePacker. You can disable this within Sprites section. Setting the Trim mode to None.

detail sprites


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

exportDetail


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

, while the XML should begin with something similar to:

xml code:




[...]

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.

skyflor

Having the four assets in mind (TimSpriteSheet.png, TimSpriteSheet.xml, Sky.jpg and Floor.png), simply add them Resorces.weproj of the project following, for example, the Pong Sample entry’s Adding resources section.

Creating the game logic. Playing animations

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:

Please note that both entities are placed on center bottom, with their origin right there, which

csharp code: 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
});

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:

csharp code: // Tim
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());

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() 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:

csharp code: 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:

csharp code: 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:

csharp code: 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:

csharp code: // 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:

csharp code: // 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!:

csharp code: // 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:

csharp code: // 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:

csharp code: .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.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: