Friday, August 14, 2015

Reusing your Unity 2D in-game animations for UI/Canvas

Finally Dolguth has been approved on Steam Greenlight, so we started pushing things up to begin a Kickstarter campaign and to release the first beta demo.

In the last sprint we started redesigning the choose-your-mech menu. Now we have a fully responsive interface that dinamically adapts itself based on the number of players (and obviously by the resolution). The screenshot below shows a 4-player interface (Dolguth currently supports up to 6 player)


During the test of the new interface we realized that animating the pilot (and the mech after the player confirmation) could have been a great addition.

Well, we already have our "idle" animations, so it should be easy to show them in our Unity UI/Canvas-based scene. Wrong.

We suddenly realized that there is no fast way to run an animation controller over an Image UI object. Our first (horrible) choice was to assign to each player interface (that is a prefab) a list of frames taken by the "idle" animation clip. Finally, in the Update() cycle we dynamically change the sprite attribute of the Image UI component based on the fps. It worked. But come on, it sucks.

We want to reuse our animation clip without compromises !

Well, after some head-smashing we came out with a really funny (and working) solution.

In our 2D environment, each animation clip keyframe modifies the sprite field of the SpriteRenderer component attached to the object. Accessing this field during the animation (in the Update() phase) is extremely easy:

Sprite image = GetComponent<SpriteRenderer> ().sprite;

If we add an Animator component to the object with the UI/Image and we assign a controller to it (one of those already defined in our game) it will not work, as it expects to modify the sprite field of a SpriteRenderer.

The trick here is adding a disabled SpriteRenderer component to the object to allow the AnimatorController to do its magic.

Finally In the Update() function we simply read the value of the sprite field of the SpriteRenderer (remember: it is disabled, so it is not visible) and assign it to the sprite field on the UI/Image component. We can do all of this at runtime:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class ImageCanvasAnimator : MonoBehaviour
{
    
    // set the controller you want to use in the inspector
    public RuntimeAnimatorController controller;
    
    // the UI/Image component
    Image imageCanvas;
    // the fake SpriteRenderer
    SpriteRenderer fakeRenderer;
    // the Animator
    Animator animator;
    
    
    void Start ()
    {
        imageCanvas = GetComponent<Image>();
        fakeRenderer = gameObject.AddComponent<SpriteRenderer>();
        // avoid the SpriteRenderer to be rendered
        fakeRenderer.enabled = false;
        animator = gameObject.AddComponent<Animator>();
        
        // set the controller
        animator.runtimeAnimatorController = controller;
    }
    
    void Update () {
        // if a controller is runningset the sprite
        if (animator.runtimeAnimatorController) {
            imageCanvas.sprite = fakeRenderer.sprite;
        }
    }
    
}


Easy and effective :)

Now when you want to animate a UI/Image component just add the previous script to the object and assign an AnimatorController to controller field in the inspector. The default state of the controller will be executed.

Some note:


  • You can obviously set AnimatorController parameters in your code, just get a reference to the Animator
  • You can create animation clips with a curve that directly modifies the UI/Image component instead of the SpriteRenderer. This is obviously way more elegant and efficient than the solution we used, but who wants to define the same animation twice?

6 comments:

  1. You deserve a medal. This was such a frustrating problem to run into. Thank you for being so creative.

    ReplyDelete
  2. nice tutorial...but i am having one problem relates to sprite animation means i want to create loading screen having one animated character so i achieve this using sprite animation technique but am having canvas and contains some text element so how could i add this sprite object in to canvas so that it would be change according to screen resolution..plz help me to sort out the problem

    ReplyDelete
  3. Should I create an empty UI/Image in the Canvas and attach the script to it and then select the animation controller ? For some reason it does not work for me :(

    ReplyDelete
  4. This is a clever workaround! Thanks, this saved me a fair bit of time.

    ReplyDelete
  5. Thanks you guys for the tutorial we had similar problem and this was very clever solution.

    ReplyDelete
  6. Thanks a bunch, Its surprising how little documentation there is to do something like this. I had an Idea of how to go about this but this pointed me in the right direction keep it up!

    ReplyDelete