Using SpriteLibrary and SpriteResolver to reduce redundant work
February 23, 2021
In the spirit of trying to disentangle incremental releases for engine features from large point version releases of the engine itself, Unity went on an absolute tear a few years ago and stuck a lot of their engine features into standalone packages. These packages get incrementally upgraded separate of the engine itself, meaning, in theory, you get Updates That Matter, faster. At least that’s the dream.
The reality is that packages have effectively split Unity into a million different versions of itself, and packages themselves all have different versions of tagging themselves in terms of their own “production-readiness”. And things are fully certified production-ready…. usually at about the same cadence (if not longer) of regular engine point releases.
All this to say that it’s easy to just ignore the package system and not spend too much time thinking about it unless you are working on something specific. To their credit, packages allow Unity to de-bloat any specific install of Unity, and it has allowed you to get access to newer features soon, at the expense of knowing when something is waves hands “ready”.
Additionally, packages also often propose different, more up-to-date models for idomatic Unity work, but often don’t present themselves as the default fast enough to not be subsumed by yet another new default way. I’m looking at you, Resources/AssetBundles/Addressables nonsense. This is why it’s mentally easier to not attach yourself to one package too hard, because it’s possible it reaches the chopping block before it reaches production-ready.
In spite of this, and obligatory Unity gripes aside, I wanted to highlight a package that is mostly ready, and one that pretty significantly changes the 2D animation workflow in a way that makes things a lot easier to reckon with and manage, but doesn’t seem to be causing the (positive) uproar that it should, as I believe it fundamentally changes how to do 2D in Unity for the better. And that package is the 2D Animation package.
If you’ve done 2D animation in Unity before you’ll be familiar with this workflow:
Import and slice a spritesheet
Select the sprites from that sheet that form an animation, drag them into the Scene window to create a new animation.
Rinse and repeat step 2 for each animation for that spritesheet.
If you’re using animation controllers and this spritesheet shares the same states as others, you’ll make a new override controller for this tilesheet specfically, and link up all these animations.
This means that every single new 2D object in your game needs unique animations and a unique controller, even if the sheets are the “same”. This is because the Sprites are hard-referenced in the Animation, where the Animation essentially plays back a series of a direct references to a Sprite asset. So you can’t share animations between objects, because the animations themsevles point to specific sprites. You’re essentially starting over the process from scratch for each object.
This is a pain in the ass. It means that if you have even just 5 tilesheets that have 5 animations each, you’re looking at 25 additional assets in your project that need to be unique, and require lots of manual setting up, but that are fundamentally all doing the exact same thing: playing back sprites in tilesheet locations. Doing things this way makes sense when you consider that the animation system expects uniqueness-per-animation, which is often the case with 3D rigs. But for 2D, why isn’t there just a way to play animations by reference to a tilesheet index? Why can’t you just say “this animation plays tilesheet sprites 0-4” and share that across sprites?
Well I have great news! You can now effectively do that! Unity’s new 2D Animation package provides two major tools that more or less replace the functionality of the above workflow. The package sounds like it’s meant primarily for bone-based animation (which I suspect is why more people aren’t talking about it), but it has tools in it that work for generic 2D Animation as well.
One of the core things the package introduces is the concept of a SpriteLibrary. As the name may suggest, this is a collection of sprites. It’s almost directly a sort of C# dictionary, where sprites are indexed first by Category, then by a given sprite’s Label (set by you in the library, not the generated sprite name that comes from slicing a tilesheet). Categories can be roughly whatever you want, and are used a way to group sprites that are used together or are semantically similar. You could have a category, as ther documentation suggests, that defines all the sprites used for a given character. Or, as we’ll talk about more below, you could have a cateogry that defines the frames of an animation, with each entry in a category being a specific frame.
What this allows you to do is address sprites not by direct reference anymore, but by a collection that can be generically referenced. The SpriteLibrary API, for example, allows you to do this:
public Sprite GetSprite(string category, string label)
Note here that neither the caller nor the function itself needs to know what’s in the library. A version of the function:
var sprite = MySpriteLibrary.GetSprite("Character", "Face");
Could be called on any library. Obviously if the library doesn’t have a cooreponding category and label you’ll have a null sprite, but the point here is that SpriteLibrary has given us a resuable way to reference sprites. Contrast the above with the code with the standard ways to get a single Sprite from a tilesheet:
// From Resouces Folder
public Texture2D texture;
int spriteIndex = 0;
var sprite = Resources.LoadAll<Sprite>(texture.name)[spriteIndex];
//Via Atlas
public SpriteAtlas atlas;
var sprite = atlas.GetSprite("child_sprite_name");
// (Atlases are also un-ordered, so you can't just called GetSprites()[0])
Both of these (and many other options) require you to know the exact sprite you’re looking for, either by its index or its name. SpriteLibraries give us ways to structure sprite data in a way that can be easily referenced, and, importantly, reusable. The same code path can be used for any libraries that share the same structure, whereas the above samples all need to be edited to account for the specifics of the tilesheet or atlas they are working with.
Now where this gets interesting isn’t just through the use of a SpriteLibrary. A sprite library would be worthless as an asset alone. The 2D Animation package also gives us access to another new component, the SpriteResolver.
A SpriteResolver, when placed on an object that also has a SpriteLibrary component and linked in Sprite Library, allows you to dynamically assign the given sprite of an attached SpriteRenderer component by changing the values on the resolver. These values map directly to the values you set on the linked in SpriteLibrary.
So, the following code:
GetComponent<SpriteResolver>().SetCategoryAndLabel("Character", "Face");
Tells the resolver to try and grab the cooresponding sprite from the SpriteLibrary that it has access to. If this code succeeds, the sprite will update in the SpriteRenderer! Below is a screenshot of what would happen if you called the following code on this game object.
GetComponent<SpriteResolver>().SetCategoryAndLabel("Idle", "3");
In the library asset itself, you can see that the category Idle, at sprite label 3, points to the Scavengers_SpriteSheet_2 sprite we see linked above in the SpriteRenderer component.
It may be hard to grasp the philosophical change in this, but to support you in having the same revelation I had, Unity provides some pretty good samples in the 2D Animation Package (Download by clicking “Download Samples” From Window->Package Manager and selecting 2D Animation). What I just discussed is demonstrated in their SpriteSwap samples.
So providing a nicer way to set single sprites on an object is great, and maybe worth the package alone, but the beatuy of SpriteResolver isn’t just that it exposes an API to set sprites this way, but that the Category and Label fields of the Resolver can be animated.
So if you take a generic way to address sprites, and add in a genric way to change those addresses…. you get a generic sprite animation system! This is huge. It’s made adding new content to Cantata trivial. Here’s my process:
I slice up the new spritesheet into sprites. I do this at runtime because Cantata is data-driven, but you could just as easily do this at edit time via the import settings.
I create a copy of Cantata’s default “template” SpriteLibrary, and assign the sprite references in that library to my newly sliced sprites. The template library in Cantata has categories that define animations. So a Category may be “AttackNorth”, and contains four sprites, with labels 0,1,2, and 3.
Because all the Cantata objects (“Interactables” in Cantata lingo) share the same spritesheet layout, and all of them have their animations in the same place, I always know that, say, that sprite on the tilesheet at index 13 always maps to MoveEast sprite 3. If you were importing arbitrarily arranged sheets, you’d need to do a little extra here to make sure the sprites go to the right places.
On the core “Interactable” prefab, I swap in the SpriteLibrary I just made with the prefab’s value (the template).
That’s it! The set animations on the animation controller all animate the same label values, so I can use the prefab animation controller as is, with no need to create a new controller, or even an override controller.
For new interactables, I don’t need to create new animations, new controllers, nothing. I only need a SpriteLibrary, which can be easily created and edited at edit-time or runtime. I wasn’t even done adding in all the animations for Cantata, but using this system I was able to swap out ~60 animations (out of a total projected of ~700) for a single set of animations and a single controller.
I’m also thinking of creating a dynamic effects loading system that functions off a similar concept, and this has paved the way as well for me to add in new unit types and sizes that would have been harder in the old system. It’sa serious workflow game-changer.
And importantly, it just works. Creating new assets in Cantata before wasn’t exactly hard, but it was tedious and easy to missclick when creating animations and forgetting to link the proper animation to certain place or whatever. Now I never have to worry about it!
I want to take a small step back here as well and talk about how to actually animate the labels, because it’s a bit more frustrating that it may seem. A big shoutout here to this blog post as well that seems to be the only other place on the internet that describes how to actually set these values, and who taught me how to do it.
Annoyingly, you can’t just create an animation and easily address the Labels on the SpriteResolver by name. Instead they need to be a “LabelHash”. This is a number that is effectively impossible to set manually.
This means to set up your animations, you need to create an animation, target the GameObject you want to animate, and select the SpriteResolver property in the animation editor and add in fields for LabelHash and CategoryHash (both or either, depending on what you want to animate). Your animator should look roughly like this (minus the keyframes):
To add in animations, you can’t just set keyframes and the target label/category name. Instead you have to use their hash. Like I said above as well, these hashes cannot be set directly.
Instead, you need to record the animation yourself. Click the red button to start the animation recording system, and then move the animation playhead to the desired loaction and then change the category or click the desired sprite in the target SpriteResolver component to make a keyframe of those values at the playhead location. I know.
Additionally, and this is very imporant, you need to make sure the keyframes are set to “Broken”, and “Both Tangents” set to “Constant”. You can do this by right-clicking the keyframe. Because the hashes are discreet values, attempting to tween between them will result in invalid values. By using these settings you’re effectively making sure the values are set through the duration of their playback.
After setting the values this way, you should be good to go. For Cantata I play these animations back with an animation controller, but you could just as easily just have an animator playing back a single set of animations. I’m also not sure how the label hashes are calculated, but it seems like, as long as the library you swap in for another one has the same names/categories for a given sprite reference, the hashes are interchangeable, i.e. you dont need to make new animations for every new SpriteLibrary.
Hopefully with this post you’ve got a bit of a handle on what the new 2D Animation project offers to people doing more “traditional” 2D animation inside of Unity. For me, this is now the defacto way I’ll handle 2D animation if I’m doing animation in Unity, as it allows me to cut out any redundant work and also allows me to create animations and controllers that are reusable!
If you have any questions, feel free to reach out to me via email, or via @kkukshtel on Twitter. Thanks for reading!
If you’re trying to work with animation controllers and playing back both old-style animations and label-based animations, there is a known bug where they will conflict and not playback properly. For now, try to not mix the two.
Published on February 23, 2021.