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!


  • Miladsalehi

    Hi,

    So thanks for sharing.very useful tips.
    Milad.

  • Pingback: Making a Game with CSS3 3D | blog.sokay.net | flash game development discussion

  • Brown Is

    Let me ask, how did you handle animation on character movement? I’ve been able to implement general animation but having animation for character movement “right”, “left”, and so forth is presenting its challenges. Can you shed any light on this for me?

    • http://www.sokay.net Bryson Whiteman

      Hey Brown Is,

      Sorry, I forgot to put in more details about how to actually implement this. Let me recall how it works…

      In the case of separate animations, let’s say you have a “stand” and “walk” animation. You will drop 2 SpriteAnimation classes onto your GameObject.

      To setup each SpriteAnimation you have to give it a name, using the public “name” variable in the editor. That’s the name that’s used with the PlayAnimation() method ( i.e. PlayAnimation(“walk”);)

      Next, you need to know which spritesheet to use for that animation. Keep in mind that this is a ghetto way of doing it, only 1 animation per sheet. So for the SpriteAnimation to know which sprite image it’s using, drop the corresponding image onto the public “sheetTexture” variable in the editor.

      Then setup the number of frames, how many rows and columns of frames their are in the image, in my case I used 8×8 images. The size of each individual frame (256×256 in my case).

      Update rate is how many milliseconds it updates a frame. I used something like 0.01, which is about 60fps. This is ghetto, you can rewrite it to take in a number which represents the framerate instead.

      Also, for the left/right animations. I did all animations facing right, and I just flipped the rotation on the Y axis to 180 when I wanted it to face left. This way you only need half the animation. For this reason I needed to make a special shader that was double-sided. Because default shaders will only display the normal side.

      Honestly, this script was a good learning experience. But looking into tools like ex2D ( http://www.ex-dev.com/ex2d/ ) it might be more worth it to just buy a tool that does everything you might to need. I haven’t used it yet, but I’m going to try it out since it does everything this script does and a lot more.

  • beez

    Hi! Can you explain the last function? And wich is the type identifier for this words (currentAnimationName, currentAnimation). And in GetComponets(); what is the component? I try the script component but im not sure. Thanks!   

    • http://www.sokay.net Bryson Whiteman

      Hey beez, here are the variable definitions I left out from the PlayerClass.cs:

      ————–

      // ANIMATIONS
      private string currentAnimationName;
      private SpriteAnimation currentAnimation;

      public SpriteAnimation aniStand;
      public SpriteAnimation aniWalk;
      public SpriteAnimation aniJumpUp;
      public SpriteAnimation aniJumpDown;

      ————–

      So on the PlayerClass.cs GameObject, I dragged a SpriteAnimation.cs component onto the game object for each animation. One for “stand”, “walk”, “jumpUp”, “jumpDown”, etc. With the SpriteAnimation component on the GameObject, you drag the spritesheet into the public sheetTexture variable in the editor. Then give it the name (like “stand”), then set up the sheetWidth/sheetHeight variables, framerate, etc.

      In PlayAnimation(), it searches for all SpriteAnimation components and stores them in an array. Then it loops through the array to find the ‘name’ that matches the _name parameter passed into PlayAnimation(). I keep track of the currentAnimationName so that it doesn’t play an animation that’s already playing.

      I hope that helps!

      • beez

         Yes! I do exactly this but i dont understand why nothing is happen. My player class:

        ————————————————————
        public class Ani_Girl_Controller : MonoBehaviour
        {
            public float speed = 5.0f;
           
            public Vector3 moveDirection = Vector3.zero;
           
            public bool isGrounded = false;
           
            // ANIMATIONS
           
            private string currentAnimationName;
            private Ani_Girl_Animation currentAnimation;
           
            public Ani_Girl_Animation ani_idle;
            public Ani_Girl_Animation ani_walk;
           
            void FixedUpdate ()
            {
                //Movement ();
                //PlayAnimation(“idle”);
            }
           
            void Movement ()
            {
               
                moveDirection = new Vector3 (Input.GetAxisRaw (“Horizontal”), moveDirection.y, moveDirection.z);
           
                if (isGrounded == false)
                {
                    this.transform.Translate((moveDirection.normalized * speed) * Time.deltaTime);
                }
            }
           
            public void PlayAnimation( string _name )
            {

                if (_name != currentAnimationName)
                {
               
                    // find animation with the matching name!
                    Ani_Girl_Animation[] sprites = gameObject.GetComponents();
                   
                    // stop currently playing animation!
                    if (currentAnimation) currentAnimation.Stop();
                   
                    // save a reference
                    foreach (Ani_Girl_Animation sprite in sprites)
                    {
                        if (sprite.name == _name)
                        {
                            currentAnimation = sprite;
                            break;
                        }
                    }
                }
               
                // play that shiet homie!
                currentAnimation.Play();
                currentAnimationName = _name;
               
            }
        }
        ————————————————————-
         My unity setup:

        https://dl.dropbox.com/u/54869313/unity_print.png

        Sorry! And thanks for understand!
        Thanks!

        • http://www.sokay.net Bryson Whiteman

          Hey beez, thanks for posting the image. That helps a lot. It seems like you have things mostly setup correctly here here’s what you need to change.

          – Sheet Width = 4, there are 4 columns of frames going from left to right.

          – Sheet Height = 2, there are 2 rows of frames going from top to bottom.

          – Tile Width & Tile Height = the size of an individual frame. It looks like your character is taller that it is wider, so I think it would be something like 128×256 for example.

          – Update Rate, this should actually be something like 0.03. This is not a framerate but how fast it should be updating. So if you want 30fps, it should equal 1/30. Sorry, this is unintuitive. Since you’ve set it to 10, it’ll only change frames every 10 seconds.

          In your Start() function just use PlayAnimation(“idle”) and it SHOULD work. I hope it works! haha

          • beez

             I setup the values like you said now and when i click in play button unity crash. I understood something better now, but it doesn’t work. Sorry for all this situation and thanks for the support.

          • http://www.sokay.net Bryson Whiteman

            I’m sorry it’s crashing now. I can’t really say what the problem is without seeing how the project is setup. If you’re still having problems I’d suggest reading through this tutorial that gives a better explanation for how the sprite animation is handled, http://www.rebelplanetcreations.com/downloads/Other/Tutorials/HowToMakeAGameInUnity3D.pdf

  • Ewaughok

    Since you developed the game in Flash, why not use adobe AIR and publish it to both iOS and Android from Flash? It’s the obvious method. To duplicate your game programming (and more) in Unity seems like a waste of man-hours to me. If you had developed this game in Unity first, then your effort makes sense …

    • http://www.sokay.net Bryson Whiteman

      I don’t know if you have experience with the AIR Exporter, but it’s pretty garbage. It’s improved a ton in all these years, but it’s still just barely usable.

      Donut Get! wasn’t the most efficiently coded game as I spent a great deal of focus on the visuals. I used a lot of “quick & dirty” Flash techniques to get it to look the way it does. For mobile w/Flash, I’d probly have to blit the screen as bitmaps and it’d just crash the device due to the sheer amount of unique animation and art in any of its 3-mini games. It didn’t look too promising given that it was already bringing desktop machines to its knees. ;)

      It’s also kind of a mess because it’s essentially 4 or 5 different swfs merged together, which the AIR exporter didn’t seem to like.

      AIR works well if you’re making simple games with AIR export in mind as you develop it. For the kinds of games I make — usually action driven with lots of character animation — it doesn’t work too well. I wish this wasn’t the case, but I’ve gave up on Adobe a long time ago.

      Porting to Unity was actually very useful to me. Since I didn’t have to worry about creating the assets, I could focus on figuring out how to bring my Flash animation and artwork into Unity, something I’ve been intending to do for years. It’s easier than starting on a new platform from scratch.

      Thanks for the comment, yo!

      • Ewaughok

        Okay, that makes sense. I’ve worked with AIR for about 4 years now and have had good results, but given the facts about your development approach, I can understand your shift to Unity.

        My own experience with Unity on mobile is that it is molasses. Glad you like it.

        • http://www.sokay.net Bryson Whiteman

          From my perspective, any improvements to Flash have been like putting a band-aid on a gunshot wound. Yeah, it’s a little better but it’s not really good enough. The biggest problem with Flash for me has been speed, and I loved Unity from the get go because of the hardware acceleration across the board.

          Adobe’s added hardware acceleration to Flash but making it require Stage3D sort of defeats the purpose to me, you don’t really get any of the great animation timeline tools of Flash or the awesome 3D tools of Unity. If I have to jump through hoops, I’d rather jump through Unity hoops, as evidenced by this blog post! :)

          I’d really love for AIR to work for me but I’ve been focused on moving past Flash, although I’m still trying to keep it as part of my pipeline. It’s still an outstanding UI and animation tool. I would have liked to use AIR to port my older games, but it doesn’t even support AS1/2. Typical Adobe disappointment.

          I’m glad you’ve been successful with AIR, that’s really dope. I’ve partly just been anxious to move onto something different because I’ve been so deep in Flash for such a long time.

          Peace!

  • pradeepk

    How are u maintaining resolution for all android devices?
    Are u changing Camera view port or Scaling model.?
    Do u have any Camera Adjuster class?

  • emazesol

    Journey To Pluto Free

    https://play.google.com/store/apps/details?id=com.emazesol.journeytoplutofree

    A little Pluto inhabitant crashed in Earth. Now it’s his epic journey to
    climb to Pluto again. On his way to Pluto he has to face different
    adventure like jumping, climbing, flying spaceship, and fighting with
    snake, leopard, mountain goat and aliens. This free version contains
    first jungle level.

  • Pingback: Flash Animation in Unity with LWF | blog.sokay.net | Sokay Game Development

  • Pingback: Flash Games » Blog Archive » Blog.Sokay.net: Flash Animation in Unity with LWF

  • Dr_batata

    I am experimenting with making 2D games on Unity. How do I go about designing the 2D characters to start with? where can I find people that do that? Thank you.