Get the FULL version

Unity: Animated texture from image sequence – Part 2

Unity: Animated texture from image sequence – Part 2 thumbnail

The second and last tutorial of a series that explains how to create an animated texture from a sequence of images in the Unity engine. This is a direct follow up of the previous post, so if you’ve missed it, please read it here. As the first post, the same disclaimer applies: for animating a texture with a small number of frames and/or small frame sizes, it’s better to join them into a single sprite sheet image and use this script. For video playback, use the MovieTexture (only available at Unity Pro).

Just as a reminder, this series is about creating an animated texture from multiple image files. In the previous post, a script loaded all image files into an array and then changed the texture of the game object it was attached to. By doing so, a lot of memory was being used in the process. This post shows how to achieve the same results using much less memory but more processing power. To create animated texture in Unity like the one below:

It’s necessary to do the same as the previous post: create a folder named ‘Resources’ by right clicking anywhere inside the Project tab and selecting Create->Folder. Then, create a folder inside it with the name ‘Sequence’ and place all image files inside it, like this:

Sequence folder

The 'Sequence' folder, after adding the images to it. Watch out for the numbering and the base file name.

This time around, since the script doesn’t load all the images at the same time from the ‘Sequence’ folder, the following information must be written down to be used later on the script:

  • The name of the subfolder created inside the ‘Resources’ folder, in this case ‘Sequence’.
  • The base name of all image files. As it can be seen at the previous image taken from the example project, the base name is ‘Clock_’.
  • The number of image files in the folder, which is 50.
  • The number of digits added to the animation sequence name, which is 5.

Except for the last information, all the other ones are going to be used to fill the required fields on the Hierarchy tab when the script gets attached to a game object. Therefore, the code that loads an image sequence as an animated texture using just a single Texture object is going to be this:

using UnityEngine;
using System.Collections;

public class ImageSequenceSingleTexture : MonoBehaviour
{
	//A texture object that will output the animation
	private Texture texture;
	//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;

	//A string that holds the name of the folder which contains the image sequence
	public string folderName;
	//The name of the image sequence
	public string imageSequenceName;
	//The number of frames the animation has
	public int numberOfFrames;

	//The base name of the files of the sequence
	private string baseName;

	void Awake()
	{
		//Get a reference to the Material of the game object this script is attached to
		this.goMaterial = this.renderer.material;
		//With the folder name and the sequence name, get the full path of the images (without the numbers)
		this.baseName = this.folderName + "/" + this.imageSequenceName;
	}

	void Start ()
	{
		//set the initial frame as the first texture. Load it from the first image on the folder
		texture = (Texture)Resources.Load(baseName + "00000", typeof(Texture));
	}

	void Update ()
	{
		//Start 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 = this.texture;
	}

	//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)%numberOfFrames;

		//load the current frame
		this.texture = (Texture)Resources.Load(baseName + frameCounter.ToString("D5"), typeof(Texture));

        //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 it isn't the last frame
		if(frameCounter < numberOfFrames-1)
		{
			//Advance one frame
			++frameCounter;

			//load the current frame
			this.texture = (Texture)Resources.Load(baseName + frameCounter.ToString("D5"), typeof(Texture));
		}

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

At the beginning of the above script, seven member variables are being declared. All the ones declared as public are self explanatory and are required to be initialized by filling their respective fields (in the Hierarchy tab) when the script is attached to a game object. These variables are the ones that are going to be filled with the information that was written down. They are all strings. (lines 14, 16 and 18).

The private variables consist of a Texture, a Material, an integer and a string. The Texture object is the one that will output the animation; the Material will act as a handle to the game object’s material the script is attached to and the integer is a counter that gets incremented to advance the animation frames (lines 7 through 11 and line 21).

At the Awake() method, the Material object is being initialized (lines 23 through 29), and, as previously mentioned, will act as a handle to the current game object Material. Also, this is where the baseName string is initialized. It holds the result of the concatenation of two strings: folderName and ImageSequenceName. These two must be passed at the Inspector tab when the script is attached to a game object. For the example project the resulting string that baseName will hold is “Sequence/Clock_“.

Next, the Start() method is being declared and it takes the first file of the ‘Sequence’ folder and sets it as the texture of the game object (lines 31 through 35). Then, the Update() method does exactly the same as the one presented at the previous post: the code execution is yielded the amount of time passed at the delay parameter and changes the current texture with the corresponding frame after the delay. The delay is also achieved through the use of IEnumerators (lines 37 through 43).

The PlayLoop() IEnumerator method is the one that causes the frames to change after the amount of seconds defined by the delay parameter. The code that gets yielded increments frameCounter and stores the rest of it’s division by numberOfFrames inside itself. Line 56 is the one that makes this method different from the one presented at the previous post. It loads the right image from from the ‘Sequence’ folder based on the frameCounter value. Note that the frameCounter value won’t contain trailing zeros when converted to a string. Since all the names of the images inside the sequence folder have at least 5 numbers, when frameCounter is converted to a string the “D5” parameter is passed, adding the correct number of zeros. The last line inside this method cancels any coroutine started at the Update() method which gets the texture animated (lines 47 through 60).

Lastly, the Play() method is being defined and works the same way as PlayLoop() except it only increments frameCounter if it’s value is smaller than the number of frames minus one. It also loads the correct image based on frameCounter (lines 63 through 80).

For this example, after attaching the script to a game object the ImageSequenceSingleTexture script parameters are going to be filled with the information we have wrote down before:

Required fields of the 'ImageSequenceSingleTexture' Script

This screenshot shows the 'ImageSequenceSingleTexture' script attached to a game object with all it's parameters filled with the folder name, name of the images files (without the numbers) and the number of frames.

Final Thoughts

This script will somehow impact performance, because the animation frames are being loaded when they are required, which might cause some lag between the frames. Since it only uses a single Texture object for the animation, there is a lot less use of memory compared to the script presented on the first part of the series. However, it’s computationally heavy to load resources like this at execution time, and there’s a great chance the animation won’t play smoothly. So, it’s probably best to use this script when the animation contains o a lot of frames, but each frame is a small image (in pixels).

Therefore, something has got to give, either the memory or the processing — pick your poison. As stated on the beginning of the two posts, this should never be used for video playback. For that, use the MovieTexture component.

As promised, here’s the example project:

Downloads

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

  1. Marko says:

    Why does my game show empty plane object before i start the scene with animation on it? It looks like a bug before my animation starts….

  2. Marko says:

    Fixed that..thanks anyway..but i would like to know how to change frame rate of video please… :)

    • DimasTheDriver says:

      To change the animation frame rate, just pass a different delay to the Play() and PlayLoop() methods when calling them as coroutines.

  3. Marko says:

    Thanks :) Worked!

  4. George says:

    Thanks for both of the tutorials :)

  5. Elay says:

    Hi
    sorry i am a newbe, can you say me how to load and connect your Script to a Sphere with the animated texture ?

    thanks in advance
    Elay

Leave a Comment

Post Comments RSS