Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /nfs/c03/h07/mnt/48256/domains/blog.sokay.net/html/wp-includes/pomo/plural-forms.php on line 210

Warning: Cannot modify header information - headers already sent by (output started at /nfs/c03/h07/mnt/48256/domains/blog.sokay.net/html/wp-includes/pomo/plural-forms.php:210) in /nfs/c03/h07/mnt/48256/domains/blog.sokay.net/html/wp-content/plugins/bad-behavior/bad-behavior/screener.inc.php on line 9

Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /nfs/c03/h07/mnt/48256/domains/blog.sokay.net/html/wp-content/plugins/jetpack/_inc/lib/class.media-summary.php on line 77

Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /nfs/c03/h07/mnt/48256/domains/blog.sokay.net/html/wp-content/plugins/jetpack/_inc/lib/class.media-summary.php on line 87
2D Sprite Animation in Unity - blog.sokay.net



 

We recently released Donut Get! on iPhone and Android. It was originally developed in Flash and we made the mobile ports in Unity. One of the challenges of porting was figuring out how to bring the game’s Flash animation into Unity. Here’s the “quick ‘n dirty” solution I came up with.

Exporting Animation Spritesheet from Flash

The first step was figuring out a good way to export the character animation from Flash. I needed a program that could export a sheet at a uniform size that would be in power of 2’s so it’ll work as a texture (256×256, 512×512, 1024×1024, etc).

I ended up using Keith Peters’ SWFSheet, it loads a swf and allows you to an export a spritesheet from it. I created an FLA for each animation with the dimensions of 256×256. I placed the cop Movieclip in the center, extended the timeline to the appropriate amount of frame and let ‘er rip!

The result is a sprite sheet image that looks like this. I exported it at 2048×2048 but dropped it down to 1024×1024 within Unity for its release.

 

The Fuzzy Textures Edges Removal

You want to import your textures into Unity as PSDs, especially if there are alpha channels. Once imported, Unity can convert it to whatever type of texture you need with the texture properties panel. One problem I encountered early one was fuzzy white edges around the black outlines of the characters and backgrounds. I found a good trick to get around the problem on Unity Answers.

  • Download and install the Flaming Pear “Free Plugins” pack (near the bottom of that list)
  • Open your PNG in photoshop.
  • Go to Select -> Load Selection and click OK.
  • Go to Select -> Save Selection and click OK. This will create a new alpha channel.
  • Now Deselect all (Ctrl-D or Cmd-D)
  • Select Filter -> Flaming Pear -> Solidify B

I jacked this directly from the thread here. Also in my PSD I made a copy of the original, hid it, and applied the filter to the copy just in case I needed to revert back to the original. I did encounter issues with the alpha channel not working correctly sometimes, you may also have to delete the “Alpha” channel in the Photoshop Channels window to try again.

 

The Plane

Unity is a 3d game engine. As such, I needed to create a 3d world for my 2d artwork. I used the textures on planes, like billboards. The problem with Unity’s built in plane is that it has too many subdivisions so it uses a lot of unnecessary polys. Fortunately on the Unity forums, I found a CreatePlane.cs script that created a plane models for me. I tried to match up the proportions (width vs height) to a similar ratio as the images, so I didn’t have to scale the models for them to look correct in game. The result was something like this:

Yeah, it looks 2D but it’s all 3D mang!

 

The 2D Shader

For DONUT GET! I needed a simple shader that didn’t have any lighting and also had transparency. I found some Unlit/Transparent shaders that worked well but I needed something that allowed double-sided polygons. I also needed a Color channel to use to apply tints and alpha fades. Here’ what I came up with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Shader "Sokay/Simple Shader" {
Properties {
_Color ("Tint (A = Opacity)", Color) = (1,1,1,1)
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
}

SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
LOD 100

ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha

// BACKFACING PASS
Pass {
Lighting Off
Cull Front
ColorMaterial AmbientAndDiffuse
SetTexture [_MainTex] {
ConstantColor [_Color]
combine texture * constant
}

SetTexture [_MainTex] {
combine previous * primary
}
}

// FRONT PASS
Pass {
Lighting Off
Cull Back
ColorMaterial AmbientAndDiffuse
SetTexture [_MainTex] {
ConstantColor [_Color]
combine texture * constant
}

SetTexture [_MainTex] {
combine previous * primary
}

}
}

}

I wasn’t able to find a way to combine the front and back pass or use a single SetTexture command, although I believe there may be a way, possibly without using Shader language.

 

 

The 2D Sprite Class

Here is the class I created to run through the animation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
using UnityEngine;
using System.Collections;

public class SpriteAnimation : MonoBehaviour
{
private bool initialized = true; // don't run anything until initalized
private bool active = false; // wait till active to do anything
private bool startedRoutine;

public string name = "Sprite Animation Name!";

public Material sheetMaterial;
public Texture sheetTexture;

public bool isPlaying;
public bool loop = true;
public string completeFunction;
private bool animationComplete = false; // am I done playing yet?

public int _totalFrames;
public int _currentFrame = 1;

public float sheetWidth = 8f;
public float sheetHeight = 8f;

public int tileWidth; // width in pixels of individual tiles
public int tileHeight; // height in pixels of individual tiles

public float updateRate = 1f; // update rate in seconds or something

public delegate void OnComplete(); // need to figure this delegate shiet out!

public void Initialize( Material _material , Texture _texture, int _totalAniFrames, int _tileWidth, int _tileHeight, int sheetWidth, int sheetHeight) {

initialized = true; // get the party started!

sheetMaterial = _material;

sheetTexture = _texture;

_totalFrames = _totalAniFrames;

sheetWidth = sheetWidth;
sheetHeight = sheetHeight;

tileWidth = _tileWidth;
tileHeight = _tileHeight;

}

// play an animation!

public void Play() {

//Debug.Log("Play: " + name);

if (!active) {
active = true;

if (!isPlaying) {

isPlaying = true;

animationComplete = false;

sheetMaterial.mainTexture = sheetTexture;

StartCoroutine(Draw());

if (!startedRoutine) {

startedRoutine = true;
}

}

}

}

// play an animation!

public void Stop() {

//Debug.Log("Stop: " + name);

isPlaying = false;

active = false;

_currentFrame = 1;
}

// draw function

public IEnumerator Draw() {

while(isPlaying) {

//Debug.Log("Draw: " + name);

if (initialized) {

if (active) {

if (_currentFrame > _totalFrames) _currentFrame -= _totalFrames;
if (_currentFrame < 1) _currentFrame += _totalFrames; int _offsetX = (_currentFrame - 1) % (int) sheetWidth; int _offsetY = (_currentFrame - 1) / (int) sheetWidth; //Set the texture to the indicated offset sheetMaterial.mainTextureOffset = new Vector2 (_offsetX / sheetWidth, 1f - ((_offsetY + 1) / sheetHeight)); //Change the scale of the texture sheetMaterial.mainTextureScale = new Vector2 ( 1f / sheetWidth, 1f / sheetHeight); } } // next steps! _currentFrame++; if (_currentFrame > _totalFrames) {
if (loop) {
_currentFrame = 1;
} else {
_currentFrame = _totalFrames; // stop at the last frame

animationComplete = true;

if (!string.IsNullOrEmpty(completeFunction)) CallCompleteFunction();

break;
}
}

// run this again!
if (isPlaying) {
yield return new WaitForSeconds(updateRate);
} else {
yield return null;
break;
}

}

}

void Awake() {
sheetMaterial = renderer.material; // modify this instance of the material
//sheetMaterial = renderer.sharedMaterial; // modify all materials of this type
}

// Looks within this gameobject for a function to call

void CallCompleteFunction () {
//Debug.Log("CallCompleteFunction >>> name: " + name + " , fn: " + completeFunction);
gameObject.SendMessage(completeFunction, gameObject, SendMessageOptions.DontRequireReceiver);
}

public bool GetAnimationComplete() {
return animationComplete;
}

}

And I used code like this within the Player class component to control the different SpriteAnimation components.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void PlayAnimation( string _name ) {

if (_name != currentAnimationName) {

// find animation with the matching name!
SpriteAnimation[] sprites = gameObject.GetComponents();

// stop currently playing animation!
if (currentAnimation) currentAnimation.Stop();

// save a reference
foreach (SpriteAnimation sprite in sprites) {
if (sprite.name == _name) {
currentAnimation = sprite;
break;
}
}

// play that shiet homie!
currentAnimation.Play();
currentAnimationName = _name;

}

}

The basic ideas I used to write this, I learned from this tutorial. I had used that idea of scaling the texture size to work out a system for creating tile-based stages with spritesheets. I adapted that class to make an animation class, and learn a bit about coroutines in Unity.

The Warnings

This may not be the perfect solution but its a start. This was all I needed for Donut Get! but there were some issues by using this exact same code.

This totally wastes texture space. Many animations in the game were only using 50-70% of the texture sheet size. These texture sheets can eat up a lot of mobile memory quickly, and that’ll crash older phones and tablets easily, so you only want to use as much as you need. Ideally, this code would allow multiple animations per sheet, allowing you to select which tile will be the start of the animation.

There may be other bugs or errors I didn’t encounter for this game, if you’ve got suggestions I’m down to listen! 😉

 

Play DONUT GET!

Now that you’ve seen the shader, play the game!


About the author:
Bryson Whiteman (http://www.sonofbryce.com)
Bryson is the guy behind all of the Sokay creations. Heading artwork and development, he's determined to make sure each game has a "distinctively Sokay" quality to them. He's always looking forward for a chance to experiment with new technologies to explore exciting ways to achieve fun.