0

In Unity C# script I have a singleton game controller object in which the game variables are stored but I am getting odd behaviour when accessing them in a member function. It prints the initialized value, not the current one. In the update function however it prints the correct value each frame. I summarized the class below. The controller class has a static reference to itself. If you need to know additional details you can ask. I am new to C# and Unity so I might be lacking some obvious answer.

Thanks

public class controller : MonoBehaviour {

    public int[] star = new int[64];

    void Start(){ /* calls another function to set 0 for each star index */ }

    void Update(){  // during gameplay star[0] gets a value of 1
        print(star[0]);  // prints correct value which is 1
    }

    public void checkValue(){
        print(star[0]); // prints 0 incorrectly which should be 1     
    }


}
Bobby
  • 1,585
  • 3
  • 19
  • 42
Soul
  • 35
  • 10
  • I located the problem but I dont know how to solve it. checkValue function runs when a button is pressed and it returns the right value if it was called from the update. No idea whats causing it – Soul Jul 03 '16 at 08:02
  • Start should be a no-op since the array is initialized to zero anyways. Otherwise something not visible in this snippet of code happens. – Paul Stelian Jul 03 '16 at 09:20
  • Is it the same instance of `controller` in all cases. Each instance has its own field ("class variable") when you use a non-static member `star`. – Jeppe Stig Nielsen Jul 03 '16 at 09:26
  • @JeppeStigNielsen yes it is the same instance that was first created. Please read my comment in the other answer I need to know how that solves it. – Soul Jul 03 '16 at 09:44
  • Just to make sure: you are speaking of a singleton, but this code is not implementing that pattern. Do you have it somewhere else? A singleton in unity is rather easy to implement. – Gunnar B. Jul 03 '16 at 11:12
  • @GunnarB. yes I didnt include those parts of the code. – Soul Jul 03 '16 at 11:18
  • Hi @Sangratura you have a **VERY SIMPLE MISTAKE** - - instead of "public" you want `[System.NonSerialized] public`. It's that simple. I will edit it in to Gunnar's answer – Fattie Jul 03 '16 at 14:00
  • @Sangratura please also read this: http://stackoverflow.com/a/35166004/294884 – Fattie Jul 03 '16 at 14:02
  • Wait what is this ?? *"The controller class has a static reference to itself "* you can't have "static references" in Unity. Here .. read this: http://stackoverflow.com/a/35891919/294884 – Fattie Jul 03 '16 at 14:08
  • @JoeBlow It can have static references. In fact I did it according to an official tutorial by Unity. public static controller control. Also making it NonSerialized did not change the reason why it did not work since it was about prefabs and instances. I got it all sorted now. – Soul Jul 03 '16 at 23:59
  • Do whatever you want. (The "official" tutorials are crap.) It is very easy to see that singletons are not defined in ECS (eg http://stackoverflow.com/a/35465978/294884 ) Cheers! – Fattie Jul 04 '16 at 00:56

3 Answers3

1

I've set up a little example. I created a button and an empty gameobject GameController. I added this code to the GameController:

using UnityEngine;
using System.Collections;

public class GameController : MonoBehaviour {

    public static GameController instance;

    [System.NonSerialized] public int[] star = new int[64];

    private void Awake()
    {
        if(instance == null)
            instance = this;
        else if(instance != this)
            Destroy(gameObject);

        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        StartCoroutine(SetAllTo(1));
    }

    // for simulating some changes during the game
    private IEnumerator SetAllTo(int value)
    {
        yield return new WaitForSeconds(2.0f);

        for(int i = 0; i < star.Length; i++)
            star[i] = value;

        Debug.Log("Setting done");
    }

    public void PrintFirst()
    {
        Debug.Log(star[0]);
    }
}

Now I added an OnClick event to the button, dragged the GameController gameobject into the slot and picked PrintFirst.

I start the game and click the button once before the Coroutine log and once after and the console gives the following:

0
Setting Done
1

Edit:
The gameobject for the OnClick event must be in the scene, it can't be a prefab in the assets folder.

Fattie
  • 27,874
  • 70
  • 431
  • 719
Gunnar B.
  • 2,879
  • 2
  • 11
  • 17
  • Thats interesting. Did you drag the GameController that was present in the scene into the OnClick event ? That works for me too but it doesnt work if I drag a prefab. – Soul Jul 03 '16 at 11:45
  • Oh, yes, from the scene. If `by dragging a prefab` you actually mean dragging it from the assets folder: That will **never** work. Prefabs are not part of the game, only blueprints on your harddrive. You can only reference stuff that is in the scene (hierarchy) or somehow connected to something in the scene (like a non-MonoBehaviour script in a MonoBehaviour script created with `new`). – Gunnar B. Jul 03 '16 at 12:00
  • Ok I got it but I can run the function of the prefab, for example it prints a string. It returns the default value of the class variables it seems. But how does it call them without calling a constructor? I put a print in constructor and it didnt print when the button was clicked. Thanks a lot for your help by the way! – Soul Jul 03 '16 at 12:18
  • Classes that derive from `MonoBehaviour` don't have a constructor (and you can't use `new` to create a reference to such a class). I can't really tell, what it does when you try it on a prefab. – Gunnar B. Jul 03 '16 at 12:39
  • Hi Gunnar, to avoid me entering an answer I edited yours to show that "star" should absolutely be `[System.NonSerialized] public` – Fattie Jul 03 '16 at 14:01
  • @JoeBlow it probably must not even be public, I just copied that over, but if public, yes. – Gunnar B. Jul 03 '16 at 14:10
  • The overwhelming problem is the OP doesn't realize you have to have a **preload scene** in Unity projects. The script as presented, which is a subclass of `MonoBehaviour`, is **utterly unusable in Unity3D, it is a total nonstarter**. What could it even **mean** to have a "singleton" "component"? Let's say it's attached to every object in the scene, or every enemy, or whatever. The idea is not even coherent. (If it's attached to a Prefab, that is beyond bizarre, as it's then a pool.) – Fattie Jul 03 '16 at 14:17
  • @JoeBlow There are multiple scenes in the project and in each, there is a Controller object with this script attached. For each instance, it checks if its the controller that was first created in any given starting scene, and destroys if it is not. I dont see whats wrong with that. – Soul Jul 03 '16 at 23:46
  • @JoeBlow Also can you elaborate why it should be NonSerialized? I found that Unity Inspector overrides the public variables is that why? Thanks – Soul Jul 03 '16 at 23:47
0

It seems that's the call to the start function may also occurs in your external calls before the call for ckeckValue

Try debugging by putting breakpoint in each of these functions and you can understand what is going on.

  • The start function runs once in the beginning of the game and not again. checkValue function runs when a button is pressed. If that were the case update function wouldnt be returning the correct one after the button was clicked I guess. – Soul Jul 03 '16 at 07:16
  • Add a public property and use the field star and also make field private. Add breakpoint in the setter to observe whenever value is set and use code map to view the caller – Toji Thomas Jul 03 '16 at 15:23
0

The start function runs once in the beginning of the game and not again.

For that you have pattern - Singleton

The second thing why to call external function to initialize class member? do it in your default contractor.

- C# default constractor already initialize 0 in int[].

if you still what to run Start from other scope in your code make it public (c# makes variable/methods as private by default)

Leon Barkan
  • 2,676
  • 2
  • 19
  • 43
  • Thanks for your help. I'm still learning the design. When I debug with print I see the Start runs once during the scene.The reason I call another function in Start is that it has to save and load based on the state of the game and they are class functions and not external ones. – Soul Jul 03 '16 at 09:43
  • 1
    I located what the problem is though. The function that gives wrong results is called by a button in Unity. Somehow when the button is pressed it does not look for the controller object that is present in the scene and uses the class itself which has the default value of 0 for its star variable. But more interestingly it doesnt trigger its constructor in the process. If I told the function to specifically look for the instance of that class that is present in the scene and use its variables, then boom it works. – Soul Jul 03 '16 at 09:43
  • Hi Leon. NOte that this is a UNITY3D project - there are NO singletons. – Fattie Jul 03 '16 at 13:58
  • A Singleton just way to creates an instance of your class and prevents new instances from being created more than one time it's design pattern not technology. (design pattern - is a general repeatable solution to a commonly occurring problem in software design.) – Leon Barkan Jul 03 '16 at 14:03
  • Hi Leon. **I don't know how clear I can be**, this is for Unity3D, the game engine. It is an ECS system, it does not have singletons. (How could you make a "singleton component for a GameObject". What would that even mean?) ECS systems have utterly no connection to OO. – Fattie Jul 03 '16 at 14:04
  • ok, i'm not unity developer so you suppose to know better than me the develop environment... – Leon Barkan Jul 03 '16 at 14:07
  • Quite, you may consider to delete the answer – Fattie Jul 03 '16 at 14:10