Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Changing sprites in animation

Discussion in '2D' started by Jinxology, Apr 29, 2018.

  1. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    The following worked in UnityScript, and has since stopped working, and/or works unpredictably, when I ported all my code to C#.

    I have a prefab "figureBones" that is structured like the image below that has an Animator on the "body" component:


    There are 9 character classes in my game, all with different heads, hair, body, left hand, right hand and extra (eyepatch, beard, etc):


    I have a single animator controller, and I created 3 animation clips for each class - run, stand and attack - that swaps in the class-specific sprites (ie. leather armor on body for rogue and two daggers in hands).

    I call the following code to create all the characters:
    Main Code
    Code (CSharp):
    1.     public GameObject CreateFigureGraphic(Being being, GameObject targetSprite)
    2.     {
    3.  
    4.         GameObject sprite;
    5.         if (targetSprite == null)
    6.             sprite = Instantiate(Resources.Load("figureBones")) as GameObject;
    7.         else
    8.             sprite = targetSprite;
    9.  
    10.         int bodyID = 0;
    11.         int leftID = 0;
    12.         int rightID = 0;
    13.         int bodySpriteID = 0;
    14.  
    15.         if (being.classID == CharClass.WARRIOR) {
    16.             bodyID = 0;
    17.             bodySpriteID = 0;
    18.             leftID = 0;
    19.             rightID = 1;
    20.         }
    21.         else if (being.classID == CharClass.ROGUE) {
    22.             bodyID = 1;
    23.             bodySpriteID = 7;
    24.             leftID = 2;
    25.             rightID = 2;
    26.         }
    27.         else if (being.classID == CharClass.MAGE) {
    28.             bodyID = 2;
    29.             bodySpriteID = 14;
    30.             leftID = 5;
    31.             rightID = 3;
    32.         }
    33.         else if (being.classID == CharClass.PALADIN) {
    34.             bodyID = 3;
    35.             bodySpriteID = 21;
    36.             leftID = 3;
    37.             rightID = 4;
    38.         }
    39.         else if (being.classID == CharClass.DRUID) {
    40.             bodyID = 4;
    41.             bodySpriteID = 28;
    42.             leftID = 5;
    43.             rightID = 5;
    44.         }
    45.         else if (being.classID == CharClass.SHAMAN) {
    46.             bodyID = 5;
    47.             bodySpriteID = 35;
    48.             leftID = 5;
    49.             rightID = 6;
    50.         }
    51.         else if (being.classID == CharClass.PRIEST) {
    52.             bodyID = 6;
    53.             bodySpriteID = 42;
    54.             leftID = 5;
    55.             rightID = 7;
    56.         }
    57.         else if (being.classID == CharClass.WARLOCK) {
    58.             bodyID = 7;
    59.             bodySpriteID = 49;
    60.             leftID = 4;
    61.             rightID = 8;
    62.         }
    63.         else if (being.classID == CharClass.RANGER) {
    64.             bodyID = 8;
    65.             bodySpriteID = 56;
    66.             leftID = 5;
    67.             rightID = 0;
    68.         }
    69.         else if (being.classID == CharClass.MONK) {
    70.             bodyID = 9;
    71.             bodySpriteID = 63;
    72.             leftID = 6;
    73.             rightID = 9;
    74.         }
    75.  
    76.         sprite.GetComponent<BeingSprite>().ChangeClip("figureAttack0", "figureAttack" + bodyID);
    77.         sprite.GetComponent<BeingSprite>().ChangeClip("figureStand0", "figureStand" + bodyID);
    78.         sprite.GetComponent<BeingSprite>().ChangeClip("figureRun0", "figureRun" + bodyID);
    79.  
    80.  
    81.         sprite.GetComponent<BeingSprite>().ChangeSprite("body", bodySpriteID);
    82.         sprite.GetComponent<BeingSprite>().ChangeSprite("rightHand", rightID);
    83.         sprite.GetComponent<BeingSprite>().ChangeSprite("leftHand", leftID);
    84.  
    85.         sprite.GetComponent<BeingSprite>().ChangeSprite("head", being.headGraphicID);
    86.         sprite.GetComponent<BeingSprite>().ChangeSprite("extra", being.extraGraphicID);
    87.         if (being.extraGraphicID == 3)
    88.             sprite.GetComponent<BeingSprite>().ChangeSprite("hair", 0);
    89.         else
    90.             sprite.GetComponent<BeingSprite>().ChangeSprite("hair", being.hairGraphicID);      
    91.  
    92.  
    93.         return sprite;
    94.     }
    Code in the BeingSprite component on each character called above:
    Code (CSharp):
    1.     public void ChangeSprite(string layerName, int spriteNumber)
    2.     {
    3.         string spritePath = "";
    4.         Sprite[] sprites = null;
    5.  
    6.         //transform.Find("body").GetComponent<Animator>().enabled = false;
    7.  
    8.         if (layerName == "head") {
    9.             spritePath = "body/head";
    10.             sprites = Resources.LoadAll<Sprite>("Bodyparts/head");
    11.         }
    12.         else if (layerName == "body") {
    13.             spritePath = "body";
    14.             sprites = Resources.LoadAll<Sprite>("Bodyparts/body_sheet");
    15.         }
    16.         else if (layerName == "leftHand") {
    17.             spritePath = "body/leftHand";
    18.             sprites = Resources.LoadAll<Sprite>("Bodyparts/leftHand");
    19.             //Debug.Log (spriteNumber);
    20.         }
    21.         else if (layerName == "rightHand") {
    22.             spritePath = "body/rightHand";
    23.             sprites = Resources.LoadAll<Sprite>("Bodyparts/rightHand");
    24.         }
    25.         else if (layerName == "hair") {
    26.             spritePath = "body/head/hair";
    27.             sprites = Resources.LoadAll<Sprite>("Bodyparts/hair");
    28.         }
    29.         else if (layerName == "extra") {
    30.             spritePath = "body/head/extra";
    31.             sprites = Resources.LoadAll<Sprite>("Bodyparts/extra");
    32.  
    33.             //        if (spriteNumber == 3) {
    34.             //            ChangeSprite ("hair", 0);
    35.             //            ChangeSprite ("head", 0);
    36.             //        }
    37.         }
    38.  
    39.         transform.Find(spritePath).GetComponent<Image>().sprite = sprites[spriteNumber];
    40.         //Debug.Log("Changing " + spritePath + " : " + spriteNumber.ToString());
    41.  
    42.         if (layerName == "extra") {
    43.             if (spriteNumber == 3) {
    44.                 transform.Find("body/head/hair").GetComponent<Image>().enabled = false;
    45.                 transform.Find("body/head").GetComponent<Image>().enabled = false;
    46.             }
    47.             else {
    48.                 transform.Find("body/head/hair").GetComponent<Image>().enabled = true;
    49.                 transform.Find("body/head").GetComponent<Image>().enabled = true;
    50.             }
    51.         }
    52.  
    53.         //transform.Find("body").GetComponent<Animator>().enabled = true;
    54.     }
    55.  
    56.     public void ChangeClip(string oldClipName, string newClipName)
    57.     {
    58.         AnimationClip clip = Resources.Load("Bodyparts/" + newClipName) as AnimationClip;
    59.         if (clip == null) {
    60.             Debug.Log("Failed to lookup clip: " + newClipName);
    61.         }
    62.  
    63.         Animator anim = transform.Find("body").GetComponent<Animator>();
    64.         if (anim == null) {
    65.             Debug.Log("Failed to find Animator component");
    66.         }
    67.  
    68.         //AnimatorStateInfo[] layerInfo = new AnimatorStateInfo[anim.layerCount];
    69.         //for (int i = 0; i < anim.layerCount; i++)
    70.         //{
    71.         //    layerInfo[i] = anim.GetCurrentAnimatorStateInfo(i);
    72.         //}
    73.  
    74.         AnimatorOverrideController overrideController;
    75.         if (anim.runtimeAnimatorController.GetType() == typeof(AnimatorOverrideController)) {  //I changed this after the port to C#.
    76.             overrideController = anim.runtimeAnimatorController as AnimatorOverrideController;
    77.             overrideController[oldClipName] = clip;
    78.             anim.runtimeAnimatorController = overrideController;
    79.         }
    80.         else {
    81.             overrideController = new AnimatorOverrideController();
    82.  
    83.             overrideController.runtimeAnimatorController = anim.runtimeAnimatorController;
    84.             overrideController[oldClipName] = clip;
    85.             anim.runtimeAnimatorController = overrideController;
    86.         }
    87.  
    88.         // Force an update
    89.         anim.Update(0.0f);
    90.  
    91.         //for (int i = 0; i < anim.layerCount; i++)
    92.         //{
    93.         //    anim.Play(layerInfo[i].fullPathHash, i, layerInfo[i].normalizedTime);
    94.         //}
    95.     }
    96.  
    Again, this used to work until I ported to C#. Now, when animated, the changes to the sprite in the right hand work, but the sprite change in the left hand reverts back to the original sprite (the shield) for all characters once the animator is enabled. I'm seeing that the Animator causes this when it is activated, but why does it work for some sprite changes and not others? Both the right hand and the left hand sprites are referenced in the animation clips.
     
  2. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    Figured it out (FINALLY!). Despite the fact that every example I found online sets the .sprite property, I had to set the .overrideSprite property.

    Code (CSharp):
    1. transform.Find(spritePath).GetComponent<Image>().overrideSprite = sprites[spriteNumber];
     
  3. LogicSeed

    LogicSeed

    Joined:
    Mar 21, 2014
    Posts:
    23
    I know no one likes to hear this sort of thing, but most of this code seems to be recreating the functionality you get from using Animator Override Controllers in the inspector.
     
    whimsicalchainsaw likes this.
  4. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    I'm using the animator override controllers.
     
  5. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    not really

    https://answers.unity.com/questions/676812/unity-2d-can-you-change-a-2d-animation-clips-sprit.html
    https://forum.unity.com/threads/changing-animation-sprites.213431/

    you have to text search and replace all SpriteRenderers each frame and repace the sprite
    you have to put them in the Resources folder (or subfolder Mob/ in my case) for this to work
    what a nightmare
    if there is a better way i would like to know
    if this is a better way?

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. [ExecuteInEditMode]
    5. [RequireComponent(typeof(Image))]
    6. [RequireComponent(typeof(Animator))]
    7. public class Mob : MonoBehaviour
    8. {
    9.     public string Skin = "A";
    10.     Sprite[] _sprites;
    11.     Image _image;
    12.     Animator _animator;
    13.     string _path;
    14.     void Awake()
    15.     {
    16.         _image = GetComponent<Image>();
    17.         _animator = GetComponent<Animator>();
    18.         Load();
    19.     }
    20.     void Load()
    21.     {
    22.         var path = "Mob/" + _animator.runtimeAnimatorController.name + Skin;
    23.         if (!path.Equals(_path))
    24.         {
    25.             _path = path;
    26.             _sprites = Resources.LoadAll<Sprite>(_path);
    27.         }
    28.     }
    29.     void LateUpdate()
    30.     {
    31.         if (_image == null || _image.sprite == null)
    32.             return;
    33.         Load();
    34.         var name = _image.sprite.name;
    35.         var sprite = Array.Find(_sprites, item => item.name == name);
    36.         if (sprite)
    37.             _image.sprite = sprite;
    38.     }
    39. }
    40.  
     
  6. LogicSeed

    LogicSeed

    Joined:
    Mar 21, 2014
    Posts:
    23
    Obviously you're more familiar with your design than I am. :) I only said that because it looked like you were changing the clips of an AOC based on character class at runtime.
    Code (CSharp):
    1. sprite.GetComponent<BeingSprite>().ChangeClip("figureAttack0", "figureAttack" + bodyID);
    2. sprite.GetComponent<BeingSprite>().ChangeClip("figureStand0", "figureStand" + bodyID);
    3. sprite.GetComponent<BeingSprite>().ChangeClip("figureRun0", "figureRun" + bodyID);
    Upon closer examination it looks like you may have multiple animator controllers per character.
     
  7. LogicSeed

    LogicSeed

    Joined:
    Mar 21, 2014
    Posts:
    23
    This does sound like a nightmare, and is likely the fault of the design.
     
  8. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    I don't do it that way. I honestly think my way is cleaner and I think would perform much better since I only change my sprites once when then object is first generated.

    Goal: I have 9 different characters. Each can have various weapons held and different head/hair based on player preferences. All characters can attack, run, or stand still (3 animation clips). I'm also using pixel art with frames, so I couldn't animate movement/attacking with a skeleton system.

    Solution: I create a run, attack and stand animation clip for each class since they vary slightly in how they animate. Then, during runtime, when a character is generated, I override all the animation controllers with the appropriate 3 clips for that class, then I override the head, hair and weapon sprites with the options the player has chosen. Voila.
     
    rakkarage likes this.
  9. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    are you implying i did it wrong?
    this is unity design... from this unity video... @20minutes
     
  10. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    I think you were talking to LogicSeed, but this video is from 2014. I think they were using workarounds back then, and my solution may just be newer.
     
  11. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    Oh... You have multiple animations... I am trying to reuse the same animation and just change the sprites because I have many identical skins for each animation.

    Thanks.
     
  12. Jinxology

    Jinxology

    Joined:
    Jul 13, 2013
    Posts:
    95
    Sure, my code works for that too, if you have one animation and you just want to swap out all the sprites used in that animation.
     
    daoviettuan2002 likes this.
  13. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
    thanks a lot.
    i will try it your way asap
     
  14. mashiro11

    mashiro11

    Joined:
    Dec 2, 2017
    Posts:
    7
    Saved my life. In my case, used animator + animation just to fadeOut an image (color alpha to zero), but the object should allow Sprite changes. If changed Sprite with image.sprite = newSprite, animations SEEMED to run, but image was still there. Using overrideSprite fixed it.
     
    IggyZuk likes this.
  15. daoviettuan2002

    daoviettuan2002

    Joined:
    Nov 5, 2018
    Posts:
    9
    @Jinxology
    I meet problem like you. I have multi spriterender in one character, so I would like ask you how is the best way to solve it?
    I have 1 character with 7 spriteRenders for Head, hand, weapon, horse and I want change sprites for 1 of them.
     
  16. Da_Elf

    Da_Elf

    Joined:
    Jan 21, 2017
    Posts:
    15
    my sprites are in a sprite sheet with multiple frames so the animation is keyframes of each sprite. Not rotational keyframes how would i go about changing each keyframe for a new sprite at runtime?
     
  17. Ted_Wikman

    Ted_Wikman

    Unity Technologies

    Joined:
    Oct 7, 2019
    Posts:
    906
    Hello @Da_Elf
    First off, do note that the last message in this thread was sent 2 years ago. It is better to start a new thread than re-open an old one.

    Secondly, I think the feature you are looking for is Sprite Swap. Have a look at our 2D Animation documentation for more details, and check out our samples to see it in action.