Singleton as package for various general functions

I am complete Unity beginner and I am trying to figure out certain behaviour.

I created a singleton – GameTools – hoping to serve me as a mere package of general functions and coroutines. Now in that script I have a public function that look like this:

private bool colorNew = false;
private float colorG = 1.0f;
private float colorB = 1.0f;

[...]

public void SpriteRedAlert(GameObject obj)
{
    if (!colorNew)
    {
        if(colorG >= 0.0f && colorB >= 0.0f)
        {
            colorG -= 2.0f * Time.deltaTime;
            colorB -= 2.0f * Time.deltaTime;
        }
        else
        {
            colorNew = true;
            colorG = 0.0f;
            colorB = 0.0f;
        }
    }
    else
    {
        if(colorG <= 1.0f && colorB <= 1.0f)
        {
            colorG += 4.0f * Time.deltaTime;
            colorB += 4.0f * Time.deltaTime;
        }
        else
        {
            colorNew = false;
            colorG = 1.0f;
            colorB = 1.0f;
        }
    }
    obj.GetComponent<SpriteRenderer>().color = new Color(1.0f, colorG, colorB);
}

It just cycles between white and red color, on assigned game object’s SpriteRenderer.

In my project there are few scripts where I call this function like this:

void Update()
{
    //If the enemy has 1/3 hit point left...
    if(hitPoints > 0 && hitPoints <= HP * 0.33f)
        GameTools.instance.SpriteRedAlert(mainSprite);
    [...]
}

I will try to explain what is going on. I have object A and object B, who are completely independent of each other. When object A fulfils condition to trigger the function within its own script, everything happens the way I expected and it’s fine. However, when object B later, in it’s own script fulfil this same condition, and call it’s own – GameTools.instance.SpriteRedAlert(mainSprite) – this effect on object A suddenly speeds up, clearly affected by the fact that object B came under same effect, and speed of object B’s effect is also speeded up. In fact, it looks like red alert effects on both object A and object B, are synchronized, both are blinking the same way, at same speed, despite starting at different times.

I can fix this, by removing this function from GameTools and copying it in both scripts of object A and object B, then everything behaves the way it should for both objects, but that is essentially duplicating the code, which is never good idea in the long run…

Why is GameTools-way not working and can I do something about it to work?

Instead of using a singleton, make an instance of this class in every script that you use this, and call the method from that instance. If the instance is made on Start or only once, it will pretty much be the same. A singleton should only be used if you need to make sure there is only 1 instance of a specified class.

Thanks for this Blinker script! :slight_smile:

If I understood you two right…

Instead of using singleton like this:

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

public class GameTools : MonoBehaviour
{
    public static GameTools instance = null;

    public GameObject explosionSmallPrefab;
    public GameObject explosionMediumPrefab;
    public GameObject explosionPlayerPrefab;
    public GameObject fireOnePrefab;
    public GameObject fireTwoPrefab;
    public GameObject sparksFirePrefab;
    public GameObject sparksForcePrefab;
    public GameObject sparksShellPrefab;

    //Awake is always called before any Start functions
    void Awake()
    {
        if(instance == null)
            instance = this;

        else if(instance != this)
            Destroy(gameObject);    

        DontDestroyOnLoad(gameObject);
    }

    //Calculate surface area of an object
    public float ObjectSurfaceXY(GameObject obj)
    {
        float objSurfaceXY = obj.transform.localScale.x * obj.transform.localScale.y;
        return objSurfaceXY;
    }

    //Cycle between red and white tint of the sprite renderer
    private bool colorNew = false;
    private float colorG = 1.0f;
    private float colorB = 1.0f;
    public void SpriteRedAlert(GameObject obj)
    {
        if (!colorNew)
        {
            if(colorG >= 0.0f && colorB >= 0.0f)
            {
                colorG -= 2.0f * Time.deltaTime;
                colorB -= 2.0f * Time.deltaTime;
            }
            else
            {
                colorNew = true;
                colorG = 0.0f;
                colorB = 0.0f;
            }
        }
        else
        {
            if(colorG <= 1.0f && colorB <= 1.0f)
            {
                colorG += 4.0f * Time.deltaTime;
                colorB += 4.0f * Time.deltaTime;
            }
            else
            {
                colorNew = false;
                colorG = 1.0f;
                colorB = 1.0f;
            }
        }
        obj.GetComponent<SpriteRenderer>().color = new Color(1.0f, colorG, colorB);
    }

    //Explosion effect
    public void OnExplode(GameObject obj, string type, float offsetX, float offsetY, float sizeFact, bool attach)
    {
        GameObject explosionPrefab = null;
        if(type == "Small")
            explosionPrefab = explosionSmallPrefab;
        else if(type == "Medium")
            explosionPrefab = explosionMediumPrefab;
        else if(type == "Player")
            explosionPrefab = explosionPlayerPrefab;

        //Useful type of rotation for non-particles, sprite explosions
        //Quaternion randomRotation = Quaternion.Euler(0f, 0f, Random.Range(0f, 360f));
        GameObject explosion = Instantiate(explosionPrefab,
            new Vector2(obj.transform.position.x + offsetX, obj.transform.position.y + offsetY),
            Quaternion.identity);
        explosion.transform.localScale *= sizeFact;
        if(attach)
            explosion.transform.parent = obj.transform;
    }

    //Setting object on fire
    public void OnFire(GameObject obj, string type, float offsetX, float offsetY, float sizeFact, int sortingOrder, bool attach)
    {
        GameObject firePrefab = null;
        if(type == "FireFlame")
            firePrefab = fireOnePrefab;
        else if(type == "FireTrail")
            firePrefab = fireTwoPrefab;

        GameObject fire = Instantiate(firePrefab,
            new Vector2(obj.transform.position.x + offsetX, obj.transform.position.y + offsetY),
            Quaternion.identity);
        fire.transform.localScale *= sizeFact;
        if(sortingOrder > 0)
            fire.GetComponent<ParticleSystemRenderer>().sortingOrder = sortingOrder;
        if(attach)
        {
            fire.transform.parent = obj.transform;
            fire.GetComponent<RemoveParticleSource>().timer = 1800.0f;
        }
    }

    //Sparks
    public void Sparks(GameObject obj, string type, float offsetX, float offsetY, float sizeFact, bool attach)
    {

        GameObject sparksPrefab = null;
        if(type == "SparksFire")
            sparksPrefab = sparksFirePrefab;
        else if(type == "SparksForce")
            sparksPrefab = sparksForcePrefab;
        else if(type == "SparksShell")
            sparksPrefab = sparksShellPrefab;

        GameObject sparks = Instantiate(sparksPrefab,
            new Vector2(obj.transform.position.x + offsetX, obj.transform.position.y + offsetY),
            Quaternion.identity);
        sparks.transform.localScale *= sizeFact;
        if(attach)
            sparks.transform.parent = obj.transform;
    }
}

I should remove all singleton code, and make it like this:

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

public class GameTools : MonoBehaviour
{
    public GameObject explosionSmallPrefab;
    public GameObject explosionMediumPrefab;
    public GameObject explosionPlayerPrefab;
    public GameObject fireOnePrefab;
    public GameObject fireTwoPrefab;
    public GameObject sparksFirePrefab;
    public GameObject sparksForcePrefab;
    public GameObject sparksShellPrefab;

    //Calculate surface area of an object
    public float ObjectSurfaceXY(GameObject obj)
    {
        [...]
    }

    //Cycle between red and white tint of the sprite renderer
    private bool colorNew = false;
    private float colorG = 1.0f;
    private float colorB = 1.0f;
    public void SpriteRedAlert(GameObject obj)
    {
        [...]
    }

    //Explosion effect
    public void OnExplode(GameObject obj, string type, float offsetX, float offsetY, float sizeFact, bool attach)
    {
        [...]
    }

    //Setting object on fire
    public void OnFire(GameObject obj, string type, float offsetX, float offsetY, float sizeFact, int sortingOrder, bool attach)
    {
        [...]
    }

    //Sparks
    public void Sparks(GameObject obj, string type, float offsetX, float offsetY, float sizeFact, bool attach)
    {
        [...]
    }
}

Then attach this GameTools script to every object that happens to use any of these functions. And then grab the function within that other script with, GetComponent?

E.g "gameObject.GetComponent : GameTools : ().OnExplode(etc.)? (<> don’t work for some reason here)