Search Unity

Mini tutorial on changing sprite on runtime

Discussion in '2D' started by amjadyahya, Nov 21, 2013.

Thread Status:
Not open for further replies.
  1. amjadyahya

    amjadyahya

    Joined:
    Nov 16, 2013
    Posts:
    11
    Hello,

    I have been searching in this forum on how to change a sprite image during runtime but could not find a complete answer to address this issue. Can someone just post a short (but complete) c# code that would show us how to do this simple task giving that a certain sprite was created in the editor named "fruits" and was sliced to be "fruits_0, fruits_1 ..etc." also an object was created in the editor named "fruitObject", what code should I use to assign different frames to "fruitObject"? And please don't tell me to use prefabs because I have a lot of frames and it seems it's not the right way to do this using them.

    Thanks in advance.
     
  2. sotirosn

    sotirosn

    Joined:
    Jan 5, 2013
    Posts:
    24
    There does not seem to be an API reference to a sprite sheet, UnityEngine.Sprite only gives parameters of a single sprite within a sheet. Maybe you could make an animation that has one frame per sprite and sample from it?
     
  3. amjadyahya

    amjadyahya

    Joined:
    Nov 16, 2013
    Posts:
    11
    No, you got me wrong, I want to access just one single sprite within a sheet, not the whole sheet of course! how could it be done? any one?
     
  4. El_Danimal

    El_Danimal

    Joined:
    Nov 17, 2013
    Posts:
    3
    I'm pretty sure the API doesn't support that. I posted a question on Answers and someone offered a decent alternative. I think the "proper" way to do sprite animations is the process shown by Max in this video.
     
  5. amjadyahya

    amjadyahya

    Joined:
    Nov 16, 2013
    Posts:
    11
    Finally I have figured it out, here is how I have accomplished it:
    1- First, I created a Resources folder inside my assets folder and put my texture "fruits.png" inside it.
    2- In the Project pane I clicked on the fruits assets that I have created in step (1) then in the inspector I changed the Sprite Mode from Single to Multiple, using the sprite editor I created my sprite sheet with 60 frames "fruits_0, fruits_1 ... fruits_59".
    3- Now it's time for the code

    Code (csharp):
    1. public Sprite[] fruitSprites;
    2.  
    3. void Awake()
    4.     {
    5.         // load all frames in fruitsSprites array
    6.         fruitSprites = Resources.LoadAll<Sprite>("fruits");
    7.     }
    8.  
    9. void Start ()
    10.     {
    11.         // create the object
    12.         GameObject fruit = new GameObject();
    13.         // add a "SpriteRenderer" component to the newly created object
    14.         fruit.AddComponent<SpriteRenderer>();
    15.         // assign "fruit_9" sprite to it
    16.         fruit.GetComponent<SpriteRenderer>().sprite = fruitSprites[9];
    17.         // to assign the 5th frame
    18.         fruit.GetComponent<SpriteRenderer>().sprite = fruitSprites[5];
    19.     }
    So basically we just loaded our sprites into an array of sprites and used them as a reference.

    It took me some time to figure it out because someone in a another post has stated that sprites cannot be auto-atlased when put in Resources folder, that statement has discouraged me from using the Resources.LoadAll for a while until I decided to try it.
     
  6. ZayLong

    ZayLong

    Joined:
    Jun 2, 2013
    Posts:
    33
    Thats really cool. I was wondering how this could be achieved myself. I'll be sure to try out your script.

    Ultimately I just ended up using the Animator and setting up different states. I think it's very useful for organization purposes.

    But this method looks good too
     
  7. PerntNo

    PerntNo

    Joined:
    Nov 13, 2013
    Posts:
    8
    Thanks from me too. Makes sense when I see the solution :)
     
  8. dimitroff

    dimitroff

    Joined:
    Apr 3, 2013
    Posts:
    131
    Thanks that solutions saved me a lot of time!
     
  9. LogicaLInsanity

    LogicaLInsanity

    Joined:
    Nov 29, 2012
    Posts:
    3
    Interesting. I did this without knowing about "Resources", which may have been much easier. But I did.

    Code (csharp):
    1. public Sprite[] sprites;
    In the script on my sprite.

    I then set the created Size property in the inspector to 13 for the 13 different images I needed (this number could be whatever for anyone else), and clicked and dragged each individual image I wanted to change, into elements 0-12 in the inspector.

    Then in code, I did something similar to you.

    Code (csharp):
    1. void Start() {
    2. spriterenderer.sprite = sprites[0]
    3. }
    And just changed the element in the array for whichever sprite image I wanted at any given time.
     
  10. Srbhunter

    Srbhunter

    Joined:
    Feb 25, 2014
    Posts:
    3
    Thank you, it really helped me!
     
  11. nkhajanchi.mg14

    nkhajanchi.mg14

    Joined:
    Mar 6, 2014
    Posts:
    13
    I would really like to know how to do this on spite atlas loaded at runtime via web call? Suppose I load a sprite sheet / atlas in runtime and I like to show all the images that are in that atlas and perform mouse click / touch event on all of them.

    Also what if the sprite sheet that is loaded at runtime having different sized images (not fixed size sheet) ? Where to store the information of x/y/width/height ? Like we used to do in a XML file in actionscript3/flash or cocos2d.

     
  12. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Trying to solve a similar problem, thot I'd post an alternative solution in case people are interested. Check out the solution breakdown on my site. Code below. Cheers!

    Code (csharp):
    1.  
    2. // new stuff
    3. using System.Linq;
    4. using System;
    5. using UnityEditor;
    6.  
    7. // standard
    8. using UnityEngine;
    9. using System.Collections;
    10.  
    11. public class SkinController : MonoBehaviour {
    12.  
    13.     // drop all the "skins" you want to be swappable here, these are the multi-sprite textures in your Assets
    14.     public Texture2D[] skins;
    15.    
    16.     // the skin you want to use, you could add a public accessor so this can be changed at runtime.
    17.     public int activeSkin;
    18.  
    19.     // Use this for initialization
    20.     void Awake ()
    21.     {
    22.         // default loaded sprites
    23.         SpriteRenderer[] loadedRenderers = GetComponentsInChildren <SpriteRenderer>(true);
    24.  
    25.         // sprites we want
    26.         Sprite[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath("Assets/Sprites/" + skins[activeSkin].name + ".png").OfType<Sprite>().ToArray();
    27.  
    28.         for (int i = 0; i < sprites.Length; i++)
    29.         {
    30.             replaceMatchingSprite (loadedRenderers, sprites[i]);
    31.         }
    32.     }
    33.  
    34.     void replaceMatchingSprite (SpriteRenderer[] loaded, Sprite newSprite)
    35.     {
    36.         for (int i = 0; i < loaded.Length; i++)
    37.         {
    38.             try
    39.             {
    40.                 if (loaded[i].sprite.rect.x == newSprite.rect.x  loaded[i].sprite.rect.y == newSprite.rect.y)
    41.                 {
    42.                     // we found a match, replaced loaded with new, we can have multiple matches
    43.                     loaded[i].sprite = newSprite;
    44.                 }
    45.             }
    46.             catch (Exception e)
    47.             {
    48.                 // do nothing, we seem to get a few null values when looking through the loaded sprites, but seems we can safely ignore them :).
    49.             }
    50.         }
    51.     }
    52. }
    53.  
     
  13. SNSD

    SNSD

    Joined:
    Jun 17, 2014
    Posts:
    5
    Which of those method are best for memory optimazation ?

    I am thinking changing the state of a static object for example from deactivated to activate,
    by only changing the Sprite Renderer "sprite" would save memory than adding an animation.

    What do you think ?
     
  14. Valette

    Valette

    Joined:
    Jun 9, 2014
    Posts:
    68
    Just getting back from a major hard drive failure otherwise I would have replied to this sooner.

    I came across this method which I've found really helpful on the game (my first) I'm currently making. It allows you to replace a sprite sheet on a prefabbed model, effectively allowing you to skin and reuse one model for a variety of character.

    There's probably a better way of doing it (I'm a three month beginner) but I made a video about my walk cycle a few weeks ago but it has the skinning in there if anybody is interested in seeing the results.



    This is the code I'm using. Not sure it's the most efficient but it seems to do the job.

    Code (CSharp):
    1. Texture2D sprite_sheet = Resources.Load<Texture2D> ("Character" + character_skin.ToString ());
    2.  
    3. Component[] parts;
    4.  
    5. parts = gameObject.GetComponentsInChildren<SpriteRenderer> ();
    6.  
    7. foreach (SpriteRenderer sr in parts) {
    8.  
    9. MaterialPropertyBlock myblock = new MaterialPropertyBlock ();
    10. myblock.AddTexture ("_MainTex", sprite_sheet);
    11. sr.SetPropertyBlock (myblock);
    12.  
    13. }
    14.  
    15. }
     
  15. Sam-K

    Sam-K

    Joined:
    Mar 23, 2013
    Posts:
    27
    Code (CSharp):
    1. IEnumerator Start () {
    2.         GameObject test = new GameObject ("SampleBundle");
    3.         test.AddComponent (typeof(SpriteRenderer));
    4.  
    5.         yield return StartCoroutine (DownloadManager.Instance.WaitDownload ("MyBundle.assetBundle"));
    6.  
    7.         WWW www = DownloadManager.Instance.GetWWW("MyBundle.assetBundle");  
    8.         AssetBundle bundle = www.assetBundle;
    9.         Texture2D tex = bundle.Load (assetName) as Texture2D;
    10.         Sprite sprite = GameObject.Find ("New Sprite").GetComponent<SpriteRenderer> ().sprite;
    11.         Debug.Log (sprite.bounds);
    12.         test.GetComponent<SpriteRenderer> ().sprite = Sprite.Create(tex, sprite.rect, new Vector2(0.5f,0.5f));
    13.         Debug.Log (test.GetComponent<SpriteRenderer> ().bounds);
    14.         Debug.Log (test.GetComponent<SpriteRenderer> ().sprite.bounds);
    15.         //        test.GetComponent<SpriteRenderer> ().sprite = Resources.Load <Sprite>(assetName);
    16.     }
    Im facing a problem I have sprite in scene. When i load a sprite from resources its exactly the same as the one in the scene. But when i Load it from AssetBundle the bounds of the sprite change and if i do ti like
    Code (CSharp):
    1. Sprite.Create(tex, sprite.rect, new Vector2(0.5f,0.5f), 80);
    Then it works but its not the correct way.


    Output:
    Center: (0.0, 0.0, 0.0), Extents: (6.4, 4.0, 0.1)
    Center: (0.0, 0.0, 0.0), Extents: (5.1, 3.2, 0.1)
    Screenshot mages is attached for referece.
     

    Attached Files:

  16. Jeeses

    Jeeses

    Joined:
    Aug 30, 2013
    Posts:
    3
    Hey Guys,
    I was really happy to find the code "amjadyahya" posted. I tried to use it for a code in my project (to create a SpriteFont) but It's not working..

    Code (JavaScript):
    1.  
    2. public var font : Sprite[];
    3.  
    4. function Awake(){
    5.  
    6.      // the File is a PNG in "Assets/Resources/Font/SpriteFont.png" sliced into 101 pieces
    7.     font = Resources.LoadAll("Font/SpriteFont", Sprite) as Sprite[];
    8.  
    9.   if (font != null){
    10.  
    11.               Char = new GameObject();
    12.               Char.AddComponent(SpriteRenderer);
    13.               Char.GetComponent(SpriteRenderer).sprite = font[0];
    14.               Char.transform.position.Set(0,0,0);
    15.                                                    
    16.     }
    17.      else {
    18.           Debug.LogWarning("Loaded no SpriteSheet for Font."); // it always ends up here
    19.      }
    20.  
    21. }
    22.  

    Any Ideas?

    Edit: If you're interested in the full code you can find it here: http://pastebin.com/KZTu3i5g
     
    Last edited: Aug 18, 2014
  17. Jeeses

    Jeeses

    Joined:
    Aug 30, 2013
    Posts:
    3
    okey, it works! this change fixed it

    Code (JavaScript):
    1.  
    2.  
    3. public var font : Object[];  //Unity recognizes a SpriteSheet as Object[]
    4.  
    5. ...
    6.  
    7. font = Resources.LoadAll(path, typeof(Sprite)); // this worked for me
    8.  
    9. ...
    10.  
    11.  
     
    rakkarage likes this.
  18. racr0x

    racr0x

    Joined:
    Nov 27, 2014
    Posts:
    1
    If you want to look for a specific name instead of the index of the array just use foreach and compare the name of the sprite:

    Code (CSharp):
    1. foreach(Sprite s in fruitSprites)
    2.             if (s.name.Equals("The name you are looking for.")){
    3.                 fruit.GetComponent<SpriteRenderer>().sprite = s;
    4.                 break;
    5.             }
    This way you can use the names you gave to each slice of the sprite.
     
  19. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    1,037
    I was really jazzed when I came across this thread, but when I went to implement it, it didn't work.

    Although as a slight difference, I'm using these as textures to display on my game's HUD. I've drawn a lot of textures on the screen before, but I'd love to be able to use "multiple sprites" instead of having a separate file for each individual graphic I want to display; especially for oddly-sized graphics and boxes that get divided into pieces.

    Anyway, here's the code I used:
    Code (CSharp):
    1.     private Texture[] ProgressIcons;
    2.  
    3. ...
    4.  
    5.         ProgressIcons = Resources.LoadAll<Texture>("HUD/ProgressIcons");
    6.  
    7. ...
    8.  
    9.         DrawScaledTexture(ProgressIcons[1], 160, 112, 16, 16, 1);
    That last function is one I made to keep everything the right size on all resolutions and aspect ratios. The first item it requires is a texture. When I run this code, it stops on that line giving me the error: "IndexOutOfRangeException: Array index is out of range." so it feels like the array doesn't include a "1" index.

    I never got any errors from telling it to load those textures though, so it *looks* like it loaded right. And I double checked the spelling of the path. I do not see why this did not work.
     
  20. Jakhongir

    Jakhongir

    Joined:
    May 12, 2015
    Posts:
    37
    Is there option not to load the whole atlas at runtime, but only frames I need, from it?
     
    EParent likes this.
  21. NG_Matthieu

    NG_Matthieu

    Joined:
    Dec 3, 2014
    Posts:
    2
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using UnityEngine.UI;
    4.  
    5. public class AnimateSprite : MonoBehaviour {
    6.  
    7.     public string _spriteName = string.Empty;
    8.     public float _totalAnimTimeInSeconds = 0;
    9.     private float _passedTime = 0;
    10.     private int _currentNumber = 0;
    11.  
    12.     public List<Sprite> _sprites;
    13.  
    14.     // Update is called once per frame
    15.     void Update () {
    16.         if (_sprites.Count > 0)
    17.         {
    18.             float singleAnimTime = _totalAnimTimeInSeconds / _sprites.Count;
    19.             if (_passedTime >= singleAnimTime)
    20.             {
    21.                 _currentNumber++;
    22.                 if (_currentNumber >= _sprites.Count)
    23.                     _currentNumber = 0;
    24.                 gameObject.GetComponent<Image>().sprite = _sprites[_currentNumber];
    25.                 _passedTime -= singleAnimTime;
    26.             }
    27.             _passedTime += Time.deltaTime;
    28.         }
    29.     }
    30. }
    small class I wrote to animate a sprite, very flexible and doesn't require to be put in resources...

    You add all sprites you want to cycle through in the list, fill in the total time in sec anim should last and script take care of all. This script loops through the animation so it keeps playing, but can easily add a boolean to interupt that.

    This should be a lot more resource friendly, still allow all sprites to be atlassed by Unity (as they are in the prefab and not coming from resources).

    If you have any questions feel free to let me know.
     
  22. bitbiome_llc

    bitbiome_llc

    Joined:
    Aug 3, 2015
    Posts:
    58
    Thank you much! If you are trying to access sprites by path name with editor scripts the key is LoadAllAssetRepresentationsAtPath.

    The following, LoadMainAssetAtPath, LoadAssetAtPath, AssetDatabase.LoadAllAssetsAtPath, will return a Texture2D and NOT the sprite.
     
  23. steveh2112

    steveh2112

    Joined:
    Aug 30, 2015
    Posts:
    314
    can anyone think why this isn't working
    Code (CSharp):
    1. Sprite pauseSprite = Resources.Load<Sprite>("player_pause");
    i have player_pause.png in Assets/Resources/Sprites

    i also tried with player_pause.png but in any case, i get null back

    oops, my bad, i need to specify "Sprites/player_pause"
     
  24. nitrofurano

    nitrofurano

    Joined:
    Jun 7, 2019
    Posts:
    92
    i'm struggling on testing it (perhaps because i'm still newbie? :D ) - what i'm doing wrong in this attachment i'm sharing here? (made on 2019.3)
     

    Attached Files:

  25. FreshYeet

    FreshYeet

    Joined:
    Oct 10, 2015
    Posts:
    12
    I rigged a character and I have separate clothing sprites for ex. Shirt, pants. My problem is that if I try to attach them they just cover the player itself since the sprite editor uses different layers or something.. How can I add individual sprites like clothes without remaking my character rig. Is this possible ?
     
  26. toonsend

    toonsend

    Joined:
    Jun 9, 2021
    Posts:
    2
    Thank you for actually answering the question rather than telling the asker what he SHOULD do.
     
  27. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,457
    Please don't necro redundant 9 year old posts. The user hasn't even been around since 2013. It's also against the forum rules.
     
Thread Status:
Not open for further replies.