Close search results
Close search results

Unity3D: Replace Sprite Programmatically in Animation

Luigi says: Don't repeat yourself

Just starting out creating a 2D game using Unity3D, I came across a simple problem: There seems to be no easy way to swap out the sprite in an animation (think: Luigi using the same animations as Mario). It may seem simple to just create a new animation, but with many different characters (sprites) using the same animation it gets old fast. Read on for a short explanation and some code.

Note: A solution based on the same approach is also described in this video starting at 20:00. The link to the source code is broken, however.

It is assumed that you have already done the following:

  1. Imported the sprite sheets and split them up in sprites accordingly, making sure corresponding sprites have the same name
  2. Created one or more animations from the sprites
Important: In the sprite editor, name each sprite (frame) consistently across all sprite sheets. The name is used as key for the mapping between sprite sheets.
Important: In the sprite editor, name each sprite (frame) consistently across all sprite sheets. The name is used as key for the mapping between sprite sheets.
The sprites to swap between are located in a 'Resources' sub folder - 'player' and 'player2'.
The sprites to swap between are located in a 'Resources' sub folder - 'player' and 'player2'.
A simple walk animation.
A simple walk animation.

Now, in order to swap out the sprite programmatically, we create the following script and add it to the game object:


using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class SpriteSwapDemo : MonoBehaviour
{
    // The name of the sprite sheet to use
    public string SpriteSheetName;

    // The name of the currently loaded sprite sheet
    private string LoadedSpriteSheetName;

    // The dictionary containing all the sliced up sprites in the sprite sheet
    private Dictionary<string, Sprite> spriteSheet;

    // The Unity sprite renderer so that we don't have to get it multiple times
    private SpriteRenderer spriteRenderer;

    // Use this for initialization
    private void Start()
    {
        // Get and cache the sprite renderer for this game object
        this.spriteRenderer = GetComponent<SpriteRenderer>();

        this.LoadSpriteSheet();
    }

    // Runs after the animation has done its work
    private void LateUpdate()
    {
        // Check if the sprite sheet name has changed (possibly manually in the inspector)
        if (this.LoadedSpriteSheetName != this.SpriteSheetName)
        {
            // Load the new sprite sheet
            this.LoadSpriteSheet();
        }

        // Swap out the sprite to be rendered by its name
        // Important: The name of the sprite must be the same!
        this.spriteRenderer.sprite = this.spriteSheet[this.spriteRenderer.sprite.name];
    }

    // Loads the sprites from a sprite sheet
    private void LoadSpriteSheet()
    {
        // Load the sprites from a sprite sheet file (png). 
        // Note: The file specified must exist in a folder named Resources
        var sprites = Resources.LoadAll<Sprite>(this.SpriteSheetName);
        this.spriteSheet = sprites.ToDictionary(x => x.name, x => x);

        // Remember the name of the sprite sheet in case it is changed later
        this.LoadedSpriteSheetName = this.SpriteSheetName;
    }
}

By changing the parameter "Sprite Sheet Name", we can apply a different sprite sheet at runtime.

The script 'Sprite Swap Demo' was created and added to the game object. The parameter 'Sprite Sheet Name' is set to 'player', which is the name of the PNG file of the player. We can start the game and change it to another value on the fly.
The script 'Sprite Swap Demo' was created and added to the game object. The parameter 'Sprite Sheet Name' is set to 'player', which is the name of the PNG file of the player. We can start the game and change it to another value on the fly.
The player with the default sprite is seen at the bottom.
The player with the default sprite is seen at the bottom.
The name of the sprite is changed to 'player2'.
The name of the sprite is changed to 'player2'.
By typing 'player2' in the sprite sheet name text box, the sprite is swapped out.
By typing 'player2' in the sprite sheet name text box, the sprite is swapped out.

I hope you don't mind my terrible artwork. Have fun creating awesome games!

Update:

It happened - the game is released!

#1
jam

Awesome! Worked right away, thanks for this!

#2
Erik

Hi jam,

glad to be of help! It's funny - I was too busy and sort of gave up on this game quite some time ago, just to pick up the ball again only a few days ago. Coming to a store near you in... 2035??

#3
Dorian

Hi, thank you so much for your code. I really search for that... but i have a problem... I recieve this : "KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[System.String,UnityEngine.Sprite].get_Item (System.String key) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:150)
SpriteSwapDemo.LateUpdate () (at Assets/Scripts/SpriteSwapDemo.cs:40)
"

What is the problem ? I don't understand the reaction of Unity.

#4
Dorian

^^' I have found myself (I forgot to rename each sliced image with the same name)

Your code just work perfectly :) thank you so much for your help and time :)

#5
Erik

Hi Dorian,

glad you got it figured out. Good luck with your game!

#6
Raitan Biz Rigon

Thank you, it worked fine!

#7
Dajka Ferenc

Hi!
Could you share the source please? I can't reproduce this in my project, maybe the Animator has different settings.

#8
Erik

Hi Dajka Ferenc,

maybe there is a simple solution to it. At which step are you having trouble?

#9
Lucas

Thank you so much! It works perfect! You saved me so much time.

#10
Rafael

The code works perfectly, but the console keep showing this every frame:

ArgumentException: An element with the same key already exists in the dictionary.
System.Collections.Generic.Dictionary`2[System.String,UnityEngine.Sprite].Add (System.String key, UnityEngine.Sprite value) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:404)
System.Linq.Enumerable.ToDictionary[Sprite,String,Sprite] (IEnumerable`1 source, System.Func`2 keySelector, System.Func`2 elementSelector, IEqualityComparer`1 comparer)
System.Linq.Enumerable.ToDictionary[Sprite,String,Sprite] (IEnumerable`1 source, System.Func`2 keySelector, System.Func`2 elementSelector)
SpriteSwapDemo.LoadSpriteSheet () (at Assets/Scripts/SpriteSwapDemo.cs:49)
SpriteSwapDemo.LateUpdate () (at Assets/Scripts/SpriteSwapDemo.cs:35)

#11
Erik

Hi Rafael,

you probably have to or more sprites with the same name in the same sprite sheet. Take a look at the first image in this guide. Click each sprite in the sheet and make sure the name is unique across the sprites in the sheet, such as "walk0", "walk1" etc. Then also make sure to name each sprite in the second sheet with the same name as in the first one. Hope this helps!

#12
Obsession

Life saver.

#13
Martin

Erik, thanks a lot for this, it is simply awesome! You helped me a lot.

#14
Xenonq

Does the sheet need to look exactly like yours. Sprite next to sprite?
[][][][][] < your sheet
[] []
[] [] < my sheet
can I have it like this?

#15
Xenon

I did exactly what you showed and this came up KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[System.String,UnityEngine.Sprite].get_Item (System.String key) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:150)
SkinSwapper.LateUpdate () (at Assets/SkinSwapper.cs:41)
I don't know what to do pls help :(

#16
Erik

Hi Xenon,

please check the answer above, Dorian had the same issue.

#17
Shamahan

Holy hell thank you so much for this piece of code, you wouldn't believe how long I searched for something like this, it is exactly what I needed!

One question - is there some way to make the sprite sheet change only once at the press of a button or does it have to constantly be in the LateUpdate function?

#18
Erik

Hi Shamahan,

that's great! And with the above code, the sprite sheet should only reload once if changed on the fly. Let me know if you have found that it doesn't.

#19
John

Ok so I followed the instructions item by item, and have named each frame of both my sprite sheets individual cells to match each other, I am getting the same error as above:

KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <7d97106330684add86d080ecf65bfe69>:0)
Crowd_Sprite_Sheet_Swapper.LateUpdate () (at Assets/Crowd_Sprite_Sheet_Swapper.cs:40)

What's up fam?

#20
John

Just to update - got it working - thank you! But I'm getting spammed hard in the console:

ArgumentException: An item with the same key has already been added. Key: Crowd_Generic_0
System.Collections.Generic.Dictionary`2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) (at <7d97106330684add86d080ecf65bfe69>:0)

Any idea why?

#21
Erik

Hi John,

probably, two sprites in the sprite sheet have the same name. Make sure each name only appears once ("walk1", "walk2"...). If you can't find the problem, try setting a debugging breakpoint in your code in visual studio. Then click "attach to unity" and start your game. Hopefully, stepping through the code will make the problem obvious. Hope this helps!

#22
John

Hey thanks for getting back to me so quickly. I can confirm that within the sheet, no sliced sprites are named the same. Each has a unique name (Crowd_Generic_0, Crowd_Generic_1, etc...)

So I am using the sprite swapping because I am emulating a large crowd (50+ NPCS). A crowd NPC is to use the same animation set, but I am swapping their sheets in order to avoid having to create unique animations over and over again for each NPC.

So essentially 50 NPCs are in the scene - all at once - all referencing one "leader" NPC's animations.

I appreciate the help!

#23
Tutmo

Hey John,

I ran into the exact same issue - all my Sprite names were unique per Sprite Sheet, but still getting the exception error.

After debugging in circles, I realized the issue (for me anyways) was that my initial Sprite in the Sprite Renderer was set to 'None'. I just needed to set the initial Sprite to any one of the Sprites in my Resource Sprite Sheets and the error went away. Hope that helps.

Thanks for the original post Erik! This was way clearer than anything else I've found after hours of Googling!!

#24
Erik

Hi Tutmo,

that's great to know, let's hope it helps John - or someone else for that matter.

And happy to hear you found the post useful!

#25
Tuan

Hi Erik,
I have 1 character with 7 spriteRenders for Head, hand, weapon, horse .... and I want change sprites for 1 or more of them, maybe change all of them.
I learn from your youtube , but when change sprite in Head and Body Sprite Render, it have problem is run change 1st sprite and not others.
With multi spriteRenderer in one character, so I would like ask how is the best way to solve it?

#26
Marcel

You're awesome thanks

#27
Maurilio Alves (@realeirosplay)

Men, you save my project! kkkkkk
Thank you very much!

#28
Hank

Saved me tons of time!
THX so much

#29
Farn

This saved my life and probably hours and hours of work by not having to deal with animation overrides.

#30
Jeff

Anyone can help? I can swap the sprite just fine, but I spammed with this line

KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <eae584ce26bc40229c1b1aa476bfa589>:0)
SpriteSwap.LateUpdate () (at Assets/Farming RPG/Scripts/Misc/SpriteSwap.cs:41)

Line 41 is:
spriteRenderer.sprite = spriteSheet[spriteRenderer.sprite.name];

Anyone can help me?

#31
Jeff

And yes, I'm not "falsely" naming my sprite, because everything works. Just spammed with those line

#32
Erik

Hi Jeff,

strange if each slice is named correctly, but I think it still sounds like the problem that Dorian had above, check https://www.erikmoberg.net/article/unity3d-replace-sprite-programmatically-in-animation#comment-2

Hope this helps!

#33
Jeff

Hi Erik thanks for your reply.

Well it's weird, I even test it by renaming each slice only with numbers (1 to 10) to make sure I'm not having a typo.

The error is still there from the moment I hit the play button (still not interacting with anything in my game).
I'm not using "this" in my code, don't know if it affects anything

#34
Erik

Hi Jeff,

no, "this" should not be required, it's mostly a convention.

Just make sure that each slice is named the same in every image - so that each slice for player 1 has the same name as for player 2 and so on.

Usually when I'm having trouble I just set a breakpoint on the offending line and start the debugger, inspecting the variables should definitely help in your case as well.

#35
Jeff

So my solution is I'm following the exact code from the youtube link you give.
It works fine too just like your code, except it does not give me any error warning.

Though I prefer your code, maybe for now I'll stick with the one on the video.

Thank you!

#36
Erik

Sure Jeff, whatever floats you boat! Glad you got it working.

#37
Camillo

'Sprite []' does not contain a definition of 'ToDictionary' and no accessible extension method 'ToDictionary' was found that accepts a first argument of type 'Sprite []'. Probably missing a directive using or a reference to the assembly. [Assembly-CSharp]

#38
David from Ukraine

Hello Camillo!
Try turn on the Linq (Language Integrated Query) feature in the header of your script.
*top of the script file*
using System.Linq; <---
*script*

Hello Erik!
I'm really thankful to you for this great tutorial.
Thank you so much! :)
Have a success in your job and hobbies!

Hello from Ukraine! =)

#39
Tymofii

OMG, thanks a lot for this post, you really helped a lot.

Leave a comment





This will just take a second.

Submitting your comment...
Page Theme: Dark / Light
Erik Moberg  2024