agiro agiro - 3 months ago 12
C# Question

Designing initializations for unity using singletons

I have an

ImageSequencePlayer
script that has a string input to chose where to load the images from. the
Sprite[]
array for the images is static now, but still I load them multiple times to the same array. I have more prefabs using this tool, e.g. a character, the enemy, some traps, flames and so on. How to create a decent design using
Singletons
? make it so that as soon as a
prefab
using this script has a different location, the game Manager looks for it and loads the sprites then gives then to all that need that? If not like this then how?

using UnityEngine;
using System.Collections;

public class ImageSequencePlayer : MonoBehaviour
{
private int currentImageIndex;
private SpriteRenderer spriteRenderer;
private static Sprite[] spriteSheet;
[Tooltip("The location of your main spritesheet.")]
public string spritesLocation;
public float frameRate = 24.0f;
public enum PlayMode
{
order, random, uponNeeded
}
public PlayMode playMode = PlayMode.order;
private bool updateEnabled = true;
[Tooltip("Decides if the random playback can iterate. If no, it doesn't play, it is frozen.")]
public bool canIterate = true; //if the random playback can iterate. if no, it doesn't play.
[Tooltip("Is there some non looping animation before the loop? If so, right after it's done, it starts looping.")]
public bool warmUp = false;
[Tooltip("If you have a warmup sheet location, put it here.")]
public string warmUpLocation = "";
private static Sprite[] warmUpSprites;

void Start ()
{
try
{
spriteSheet = null;
spriteSheet = Resources.LoadAll<Sprite>(spritesLocation);
}
catch(MissingSpriteSheetException ex)
{
Debug.Log(ex.Message);
}

try
{
spriteRenderer = GetComponent<SpriteRenderer>();
}catch
{
spriteRenderer = GetComponentInChildren<SpriteRenderer>();
}

if(warmUp)
{
switch (this.gameObject.tag)
{
case "candleLight":
warmUpSprites = null;
warmUpSprites = Resources.LoadAll<Sprite>("warmUpFlames");
break;
default:
warmUpSprites = null;
warmUpSprites = Resources.LoadAll<Sprite>(warmUpLocation);
break;
}
//meaning we do have something to warm up
}else
{
//so if we did want some warmup, we load it,
//if we didn't, we just free the memory.
warmUpSprites = null;
warmUpLocation = null;
}
if(playMode == PlayMode.uponNeeded)
{
//if we need it occasionally, we just disable everything else.
warmUp = false;
updateEnabled = false;
warmUpLocation = null;
warmUpSprites = null;
}
}
void LateUpdate ()
{
if (warmUp)
{
currentImageIndex = Mathf.RoundToInt(Time.time * frameRate);
currentImageIndex = currentImageIndex % warmUpSprites.Length;
spriteRenderer.sprite = warmUpSprites[currentImageIndex];
if(currentImageIndex >= warmUpSprites.Length-1)
{
currentImageIndex = 0;
warmUp = false;
//now easing on the memory, not another warmup will happen of course:
warmUpLocation = null;
warmUpSprites = null;
}
}
if (updateEnabled && !warmUp)
{
switch (playMode)
{
case PlayMode.order:
currentImageIndex = Mathf.RoundToInt(Time.time * frameRate);
currentImageIndex = currentImageIndex % spriteSheet.Length;
spriteRenderer.sprite = spriteSheet[currentImageIndex];
break;
case PlayMode.random:
updateEnabled = false;
StartCoroutine(RandomizedPlay());
break;
}
}

}
IEnumerator RandomizedPlay()
{
int oldIndex = 0;
int currentIndex;
int iterNumber = 0;
while (canIterate)
{
currentIndex = Random.Range(0, spriteSheet.Length - 1);
if(currentIndex == oldIndex)
{
while((currentIndex == oldIndex) && (iterNumber < 8))
{
currentIndex = Random.Range(0, spriteSheet.Length - 1);
iterNumber++;
}
}
spriteRenderer.sprite = spriteSheet[currentIndex];

oldIndex = currentIndex;
iterNumber = 0;
yield return new WaitForSeconds(1.0f / frameRate);
}
}
public void OccasionalAnimation(bool backWardsNeeded)
{
StartCoroutine(OccasionalAnimEnum(backWardsNeeded));
}
public IEnumerator OccasionalAnimEnum(bool backWardsNeeded)
{
currentImageIndex = 0;
while (currentImageIndex < spriteSheet.Length)
{
//meaning while we do have anything to play
spriteRenderer.sprite = spriteSheet[currentImageIndex];
currentImageIndex++;
yield return new WaitForSeconds(1.0f / frameRate);
}
if (backWardsNeeded)
{
//so we need to play the shit backwards as well, like in the book
currentImageIndex = spriteSheet.Length - 2;//so we won't repeat the last again.

while (currentImageIndex >= 0)
{
//meaning while we do have anything to play
spriteRenderer.sprite = spriteSheet[currentImageIndex];
currentImageIndex--;
yield return new WaitForSeconds(1.0f / frameRate);
}
}
//at the end it should be at the starting sprite.
currentImageIndex = 0;
}
}

Answer

First thing first

Read about a bit more about singleton from docs : Singletons in Unity

Now add the following code at start of script to make it singleton:

private static ImageSequencePlayer _instance;
public static ImageSequencePlayer Instance
{
    get
    {
        if (_instance == null)
        {
            _instance = FindObjectOfType<ImageSequencePlayer>();
            if (_instance == null)
            {
                Debug.LogError("ImageSequencePlayer Instance is null");
            }
        }
        return _instance;
    }
}

Now you dont need to make anything static.

Access the non-static sprite array from anywhere like this:

ImageSequencePlayer.Instance.spriteSheet

and all the other properties/fields in same way.

Use Awake() method to load images from resources:

void Awake()
{
    // populate sprites array here.
}

EDIT :

The singleton script shouldn't be present in scene on more than one objects. Create an empty object and attach the singleton script. If you have any other common functionality (like spritesheet), move that code to singleton.make sure you dont have sprite loading code in your other script which you are applying on all objects.