Get the FULL version

Unity: Animated texture from image sequence – Part 1

Unity: Animated texture from image sequence – Part 1 thumbnail

This is the first tutorial of a series that explains how to create an animated texture from a sequence of images in the Unity engine. Before going into details on the script or the requirements necessary in order to make the animation work, please bear in mind that there are other alternatives available which will achieve the same results. For any animation with a small number of frames and/or small frame sizes, it’s recommended to join then into a single sprite sheet image and use this script. For video playback, please use the MovieTexture (only available at Unity Pro). However, if the animation doesn’t fit a 4096×4096 px image file, the script in this tutorial might be the solution.

That said, the first thing to do is to have the animation exported as a sequence of images. You can export animation frames as image files from After Effects, Flash, Blender, 3DStudioMax and even from the latest versions of Photoshop Extended, just to name a few. For this and the next tutorial, the following animated clock will be used as an example:

After obtaining the image sequence, open Unity and create a folder named ‘Resources’ if it isn’t already there. To do so, right click anywhere inside the Project tab and select Create->Folder. It has to be named ‘Resources’ or else it won’t work. Repeat these steps to create another folder inside Resources with the name ‘Sequence’. Place the exported animation images inside Sequence. At this point, the Project tab should look like this:

Sequence folder

This is what the 'Resource' folder should look like after creating the 'Sequence' folder and adding the images to it.

Now, the only thing that is let to do is to create a script that read these images and progressively change them as a animated texture. Here’s the code:

using UnityEngine;
using System.Collections;

public class ImageSequenceTextureArray : MonoBehaviour
{
	//An array of Objects that stores the results of the Resources.LoadAll() method
	private Object[] objects;
	//Each returned object is converted to a Texture and stored in this array
	private Texture[] textures;
	//With this Material object, a reference to the game object Material can be stored
	private Material goMaterial;
	//An integer to advance frames
	private int frameCounter = 0;	

	void Awake()
	{
		//Get a reference to the Material of the game object this script is attached to
		this.goMaterial = this.renderer.material;
	}

	void Start ()
	{
		//Load all textures found on the Sequence folder, that is placed inside the resources folder
		this.objects = Resources.LoadAll("Sequence", typeof(Texture));

		//Initialize the array of textures with the same size as the objects array
		this.textures = new Texture[objects.Length];

		//Cast each Object to Texture and store the result inside the Textures array
		for(int i=0; i < objects.Length;i++)
		{
			this.textures[i] = (Texture)this.objects[i];
		}
	}

	void Update ()
	{
		//Call the 'PlayLoop' method as a coroutine with a 0.04 delay
        StartCoroutine("PlayLoop",0.04f);
		//Set the material's texture to the current value of the frameCounter variable
		goMaterial.mainTexture = textures[frameCounter];

	}

    //The following methods return a IEnumerator so they can be yielded:
	//A method to play the animation in a loop
    IEnumerator PlayLoop(float delay)
    {
        //Wait for the time defined at the delay parameter
        yield return new WaitForSeconds(delay);  

		//Advance one frame
		frameCounter = (++frameCounter)%textures.Length;

        //Stop this coroutine
        StopCoroutine("PlayLoop");
    }  

	//A method to play the animation just once
    IEnumerator Play(float delay)
    {
        //Wait for the time defined at the delay parameter
        yield return new WaitForSeconds(delay);  

		//If the frame counter isn't at the last frame
		if(frameCounter < textures.Length-1)
		{
			//Advance one frame
			++frameCounter;
		}

        //Stop this coroutine
        StopCoroutine("PlayLoop");
    } 

}

At the beginning of this script, four member variables are being declared: an Object array, a Texture array, a Material and an integer (lines 7 through 13). The first two arrays are going to store all images at the ‘Sequence’ folder. The Material will store a reference of the game object’s material in which the script is attached to. The integer is just a counter that gets incremented, to advance the animation. Then, inside the Awake() method, the Material is being initialized (lines 15 through 19).

The Start() method populates both objects and textures arrays. First, at line 24, the objects array is filled with the results of the Resources.LoadAll() method call. The parameter it takes is a string, with the name of the folder or the path of a single file to be loaded. The string “Sequence” has been passed since all images on this folder are going to be loaded. The second parameter takes the type of the object to load, filtering other files that aren’t textures. The textures array is initialized with the same length as the objects array (line 27). Then, all elements on the objects array are being cast to a Texture object and stored at the textures array at the for loop (lines 30 through 33).

At the Update() method, a coroutine is being start. It calls the PlayLoop() IEnumerator method, every 0.04 seconds, making the transitions between frames (line 39). The 0.04 delay in the second parameter is the time each frame of the animation remains active and this value has been obtained by dividing the desired number of frames per second on this animation by one (1/25 = 0.04) .

The PlayLoop() IEnumerator is being defined right below the Update() method (lines 47 through 57) and works like this: the code execution is yielded the amount of time passed at the delay parameter. After the delay, the frameCounter variable is incremented the rest of its division by the length of the textures array is stored inside itself (line 53). That way, the frameCounter will always remain between zero and number of image files minus one. The last thing this IEnumerator method does is to stop the coroutine, by calling the StopCoroutine() method (line 56).

Back to the Update() method, the next line sets the current game object material texture to the corresponding frameCounter element of the textures array, therefore animating the frames (line 41). That’s it!

In the script above, there’s another IEnumerator named Play(). It works exactly as PlayLoop() except it doesn’t loop the animation after it reaches the last frame. This is achieved by incrementing the frameCounter variable only if its value is smaller then the length of the textures array minus one (lines 60 through 74).

Final Thoughts

The most noticeable disadvantage of using this script is the amount of memory it needs, since there are basically two large arrays that holds the same data. If the animation being loaded has a lot of frames, this can be a issue. The advantage is that, since every frame is loaded into the memory, it’s more likely that the it will play at a constant rate, with little to no lag between frames. This is useful for animations that have a small number of frames with each frame having big dimensions (in pixels).

The next post of the series will show how to make the same script much more memory efficient and what are the disadvantages of using it. Plus, an example project with the code featured in the post series.

EDIT: Second part is out! Read it here: Unity: Animated texture from image sequence – Part 2!

6 Comments to “Unity: Animated texture from image sequence – Part 1”

  1. Me says:

    Great! when will the second part be published?

  2. Chad says:

    Is there a way to cue and stop the animation?

  3. jwolf says:

    Is this JavaScript or C#?

  4. Pff says:

    ?? How this should work ??

    What is goMaterial ?
    StopCoroutine(“PlayLoop”); in loop?

    You should correct this script!

Leave a Reply to Me

Post Comments RSS