Search Unity

Singleton in javascript

Discussion in 'Scripting' started by Scared, May 3, 2010.

  1. Scared

    Scared

    Joined:
    May 3, 2010
    Posts:
    9
    Hello all.

    I am new to Unity and have been learning it for the past week and I have gotten to the stage where I need a way to have information persist through levels, the best way I have been told to go about it is to use a singleton. I have done a fair bit of c++ programming so I understand a singleton and what it does but I have been unable to make it work using Unity and javascript (I am not looking to use c# for certain reasons).

    I have had a look around trying to find a javascript implementation and driven myself (almost) crazy trying to get it to work from the numerous C# examples and my own knowledge but I have had no luck.

    So can someone please help me out in regards to the basic class structure of a singleton in javascript, the creation of the instance and accessing the instance? (I know I have to DontDestroyOnLoad)

    Thanks to anyone willing to help :)
     
  2. oxl

    oxl

    Joined:
    Nov 21, 2008
    Posts:
    325
  3. Scared

    Scared

    Joined:
    May 3, 2010
    Posts:
    9
    Thanks for the link, it is one that I found when looking for examples.

    I don't know why but I was able to get it working this time. It is taking me some time to get my head around the way things are done/work in javascrip/Unity compared to C++ and other (c++)engines I have used.

    Thanks, and for future reference for anyone else searching for the same thing I will post what my code was since that is one of my main downfalls, taking code excerpts and guessing the rest.

    GameManager.js
    Code (csharp):
    1. class GameManager extends UnityEngine.MonoBehaviour
    2. {  
    3.     public var score : int;
    4.    
    5.     // Constructor basically....I think
    6.     function Start()
    7.     {
    8.         score = 10;
    9.     }  
    10. }
    11.  
    12. function Awake()
    13. {  
    14.     GameObject.DontDestroyOnLoad(this);
    15. }
    TitleMenu.js
    Code (csharp):
    1. var instance : GameManager;
    2.  
    3. function Start()
    4. {
    5.     instance = FindObjectOfType(GameManager);
    6.    
    7.     if (instance == null) print("There is no instance");
    8.     else print("There is an instance. Score: " + instance.score);
    9.    
    10.     instance.score += 1;
    11. }
    Player.js
    Code (csharp):
    1. var managerInstance : GameManager;
    2.  
    3. function Start()
    4. {
    5.     managerInstance = FindObjectOfType(GameManager);
    6.    
    7.     print("Score: " + managerInstance.score);
    8. }
     
  4. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    865
    i tried to implement this, but it doesnt work.
    i basically pasted your code and still doesnt work.

    i keep getting in the Debug.Log "there is no instance"

    i am looking at some implementation of singleton in javascript...anybody that can help me?
     
  5. Chris-Sinclair

    Chris-Sinclair

    Joined:
    Jun 14, 2010
    Posts:
    1,326
    Do you want a singleton of any generic non-Unity class or a singleton of a physical GameObject?

    For a normal singleton it's simple as UnityScript has classes just as any other decent language, so the singleton implementation isn't that much different:

    (off the top of my head, untested, pseudo-code, etc.)
    Code (csharp):
    1. class MySingletonClass
    2. {
    3.     private static var Instance : MySingletonClass = new MySingletonClass();
    4.    
    5.     public static function GetInstance() : MySingletonClass
    6.     {
    7.         return Instance;
    8.     }
    9.    
    10.     //though I don't know if in UnityScript if you can have a private constructor, guess we'll find out!
    11.     //If not, just don't call it.
    12.     private function MySingletonClass()
    13.     {
    14.         //if the constructor must be public, you can do this:
    15.         if (Instance != null)
    16.         {
    17.             throw new Exception("This is a singleton class!  Use MySingletonInstance.GetInstance() instead!");
    18.         }
    19.     }
    20. }
    If you're looking to get a singleton of a GameObject, that'd be an extension of the same:
    (again, off the top of my head, untested, pseudo-code, etc.)
    Code (csharp):
    1. //in "MySingletonGameObject.js" script that you attach to a GameObject
    2. //Naturally, GetInstance() may be null if the attached GameObject has not yet been created by Unity
    3.  
    4. private static var Instance : MySingletonGameObject = null;
    5.  
    6. public static function GetInstance() : MySingletonGameObject
    7. {
    8.     return Instance;
    9. }
    10.  
    11. //though I don't know if in UnityScript if you can have a private constructor, guess we'll find out!
    12. //If not, just don't call it.
    13. public function MySingletonGameObject()
    14. {
    15.     //if the constructor must be public, you can do this:
    16.     if (Instance != null)
    17.     {
    18.         //this might be a bad idea, would throw the exception likely to the Unity player instead of your user code
    19.         throw new Exception("This is a singleton class!  Use MySingletonInstance.GetInstance() instead!");
    20.     }
    21. }
    22.  
    23. function Awake()
    24. {
    25.     Instance = this;
    26. }
    Just don't add to the scene more than one instance of this script.
     
  6. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    865
    ok, i tried this and it is confusing for me, it doesnt work or i dont know how to use it.

    here is the class i made (non-unity for start), basically same as yours

    Code (csharp):
    1. class MySingletonClass
    2. {
    3.  
    4.     private static var Instance : MySingletonClass=new MySingletonClass();
    5.    
    6.     public static function GetInstance(): MySingletonClass
    7.     {
    8.         return Instance;
    9.     }
    10.    
    11.     private function MySingletonClass()
    12.     {
    13.         if(Instance!=null)
    14.         {
    15.             Debug.Log("this a singleton class, use MySingletonInstance.GetInstance() instead");
    16.         }
    17.        
    18.     }
    19.  
    20. }
    then in the script in which i want to use this class
    i made variable declaration like this:

    Code (csharp):
    1. var singleton : MySingletonClass;
    and later in the Awake() i put this:

    Code (csharp):
    1. singleton.GetInstance();
    and i get this notification : "this a singleton class, use MySingletonInstance.GetInstance() instead"


    then i tried this:

    Code (csharp):
    1. singleton=new MySingletonClass();
    2. singleton.GetInstance();
    3.  
    i get error that constructor is inaccessible due to its protection level...

    please help me resolve this, it seems very confusing
     
  7. Chris-Sinclair

    Chris-Sinclair

    Joined:
    Jun 14, 2010
    Posts:
    1,326
    The GetInstance() method returns the single instance (singleton!) of the class you are supposed to use.

    MySingletonClass.GetInstance().SomeMethod();

    or

    var mySingletonReference:MySingletonClass = MySingletonClass.GetInstance();
    mySingletonReference.SomeMethod();

    EDIT: since UnityScript is honouring the private constructor, you can remove that extra check/error in it.
     
  8. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    865
    now it works beautifully. thank you very much!
     
  9. tonyd

    tonyd

    Joined:
    Jun 2, 2009
    Posts:
    1,224
    You actually don't need to use a singleton, you can use static vars as long as you remember to only create one instance of them.

    Put this in a script named "master.js":

    Code (csharp):
    1. static var score = 100;
    From other script;

    Code (csharp):
    1. Debug.Log(master.score);
     
  10. Chris-Sinclair

    Chris-Sinclair

    Joined:
    Jun 14, 2010
    Posts:
    1,326
    But this is a GameManager (which screams "Singleton!"), not just a score property. Besides, the beauty of a singleton is that you don't have to remember to create only one instance of them. Better to code/limit the API's intent directly into it rather than keep it in your head.


    EDIT: but yeah, if you wanted to your GameManager could exist purely of static members. There's usually little difference between a static class and a singleton, so unless you have a specific reason to make a singleton, you might as well make the whole class static.
     
  11. Scared

    Scared

    Joined:
    May 3, 2010
    Posts:
    9
    It could possibly be related to the way my state/scene flow worked.

    What I had was an initalisation scene that included this GameManager (which is a singleton) as well as another script which only loaded the next scene.

    Code (csharp):
    1. function Start()
    2. {
    3.     Application.LoadLevel("_Title");
    4. }
    The reason for this is so that when the next scene starts I can just assume that the GameManager has been created without having to worry "Will this code be called because the GameManager is created?"

    When you run the code do you see the GameManager game object in the list of objects in the scene? (I assume you did create a game object and gave it the GameManager script).
     
  12. leegod

    leegod

    Joined:
    May 5, 2010
    Posts:
    2,477
    @Scared
    Hi.
    Sorry but can you explain more about how you implemented?

    I tested like this,

    and just revised GameManager.js to,

    Code (csharp):
    1.  
    2. class GameManager extends UnityEngine.MonoBehaviour
    3. {  
    4.     public var score : int;
    5.  
    6.     // Constructor basically....I think
    7.     function Start()
    8.     {
    9.         score = 10;
    10.     }  
    11. }
    12.  
    13. function Awake()
    14. {  
    15.     GameObject.DontDestroyOnLoad(this);
    16. }
    17.  function Update(){
    18.   if(Input.GetKeyDown("3")){
    19.    Application.LoadLevel(0);
    20.    Debug.Log("Scene 1 loaded");
    21.   }
    22.   if(Input.GetKeyDown("4")){
    23.    Application.LoadLevel(1);  
    24.    Debug.Log("Scene 2 loaded");
    25.   }
    26.  }
    27.  
    And attached image files is showing the status of setting scene 1,2
    All attached scripts are from your code example's. (GameManager, TitleMenu, Player)

    So after game play, I repeatedly press 3,4 key, and whenever I came back to scene1(test1), GameManager gameobject increased by 1. (and also Debug.log's message too)

    So can you thoroughly explain how you implemented this? Scene status, gameobject status, etc...

    Thank you in advance.


    $scene1-2.PNG $scene1.PNG

    $scene2.PNG
     
    Last edited: Mar 13, 2012
  13. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Instead of that whole complicated instancing thing, isn't it possible to simply do this?:

    Code (csharp):
    1. function Awake ()
    2. {
    3.     // Make LevelData a singleton, so we keep ourself alive and kill any clones
    4.     if ( FindObjectsOfType(LevelData).Length != 1 )     // If one of me already exists I must be a clone, so destroy me (don't feel bad about it)
    5.         Destroy (gameObject);
    6.     else        // I'm the original, so keep me around
    7.         DontDestroyOnLoad (gameObject);
    8. }
     
  14. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Here's how I do my static classes from memory. Example of a static AudioClip manager. There still needs to be one instance somewhere. In my case I create a GameObject called GameSystem and add these sorts of scripts, then assign the values to the List as required.

    Code (csharp):
    1.  
    2.  
    3.  
    4. class AudioManager : MonoBehaviour
    5. {
    6.   public List<AudioClip> AudioClips;
    7.  
    8.   void Start()
    9.   {
    10.     _instance = this;
    11.   }
    12.  
    13.   static AudioManager _instance;
    14.   public static AudioClip GetAudioClip(string name)
    15.   {
    16.      if(_instance == null || _instance.AudioClips == null)
    17.        return null;
    18.  
    19.      foreach(AudioClip clip in _instance.AudioClips)
    20.      {
    21.          if(clip == null)
    22.             continue;
    23.  
    24.          if(clip.name == name)
    25.              return clip;
    26.      }
    27.  
    28.      return null;
    29.   }
    30. }
    31.  
    32.  
    from anywhere I can now call
    Code (csharp):
    1. AudioManager.GetAudioClip("example");

    this is c# though, so you will have to convert. shouldnt be too hard
     
    Last edited: Apr 2, 2013
  15. KnightRiderGuy

    KnightRiderGuy

    Joined:
    Nov 23, 2014
    Posts:
    515
    I have a similar issue with trying to figure out how to carry information across scenes , my first issue was that my just putting in:

    functionAwake () {
    DontDestroyOnLoad (transform.gameObject);
    }

    was duplicating my game object with the script on it whenever I returned to the main scene. Now what Essential posted above with:

    Code (JavaScript):
    1. function Awake ()
    2. {
    3.     // Make LevelData a singleton, so we keep ourself alive and kill any clones
    4.     if ( FindObjectsOfType(CountdownTimerManager).Length != 1 )     // If one of me already exists I must be a clone, so destroy me (don't feel bad about it)
    5.         Destroy (gameObject);
    6.     else        // I'm the original, so keep me around
    7.         DontDestroyOnLoad (gameObject);
    8. }
    Now this did solve my duplication issue but I'm still having difficulties with trying to figure out why and how to solve another issue which is that my UI text is a child of a panel on a canvas and my timer game object that carries accros to other scenes ends up loosing the UI text.

    I have tried putting my timer game object as a child of the canvas too but then my timer fails to work at all. I'll post my entire script along with a screen capture of how my Hierarchy is set up. I could REALLY use some help on this.


    And here is my code for my CountdownTimerManager:

    Code (JavaScript):
    1. #pragma strict
    2. var Alarm : AudioClip ;
    3. var timer: float = 3600;
    4. var isFinishedLevel : boolean = true;
    5. public var displayText : UnityEngine.UI.Text;
    6. public var timeText : UnityEngine.UI.Text;
    7. var minsDisplay : String;
    8. var secsDisplay : String;
    9. var mySeconds : int = 0;
    10. private var oldTimer : float;
    11. //Begin New
    12. function Awake ()
    13. {
    14.     // Make LevelData a singleton, so we keep ourself alive and kill any clones
    15.     if ( FindObjectsOfType(CountdownTimerManager).Length != 1 )     // If one of me already exists I must be a clone, so destroy me (don't feel bad about it)
    16.         Destroy (gameObject);
    17.     else        // I'm the original, so keep me around
    18.         DontDestroyOnLoad (gameObject);
    19. }
    20. //End New
    21. /*function Awake () {
    22.          DontDestroyOnLoad (transform.gameObject);
    23.      }*/
    24. function Start(){
    25.    
    26.      oldTimer = timer;
    27. }
    28. function Update(){
    29.  
    30.      if (!isFinishedLevel) {
    31.          timer -= Time.deltaTime;
    32.      }
    33.    
    34.      CurrentTime();
    35. }
    36. function CurrentTime() {
    37.      var dt : System.DateTime = System.DateTime.Now;
    38.      var h : int = dt.Hour;
    39.      var m : int = dt.Minute;
    40.      var s : int = dt.Second;
    41.      timeText.text = h + ":" + m + ":" + s;
    42.    
    43.      if(mySeconds != s)
    44.      {
    45.          mySeconds = s;
    46.          Timing();
    47.      }
    48.    
    49. }
    50. function Timing()
    51. {
    52.      if (timer > 0) {
    53.          //var minsDisplay : String = parseInt( timer / 60 ).ToString();
    54.          minsDisplay = parseInt( timer / 60 ).ToString();
    55.        
    56.          //var secsDisplay : String = parseInt( timer ).ToString();
    57.          secsDisplay = parseInt( timer ).ToString();
    58.          
    59.          if ( (timer - ( parseInt(minsDisplay) * 60)) > 10 ) {
    60.               secsDisplay = parseInt( timer - ( parseInt(minsDisplay) * 60) ).ToString();
    61.          }
    62.          else {
    63.              secsDisplay = "0" + parseInt( timer - ( parseInt(minsDisplay) * 60) ).ToString();
    64.          }
    65.        
    66.          //displayText.text = minsDisplay + " : " + secsDisplay;
    67.      }
    68.      //Timer Reaches End We Can Do Something Here
    69.      else {
    70.           timer += oldTimer;
    71.           audio.PlayOneShot(Alarm);//Plays Alarm Sound
    72.           isFinishedLevel = true;//Sets Inspector Value to true or false based on what is set here
    73.           yield WaitForSeconds (0.8);//Wait Time Setting
    74.           //Do Something if Desired
    75.          
    76.           Debug.Log ("Timer Ended");
    77.      }
    78.      displayText.text = minsDisplay + " : " + secsDisplay;
    79. }
    80. //Timer Stop Button
    81. public function GoTimerStop()
    82. {
    83.      isFinishedLevel = true;
    84. }
    85. //Timer Start Button
    86. public function GoTimerStart()
    87. {
    88.      isFinishedLevel = false;
    89. }
    90. //Timer Settings
    91. public function GoTimerSetting60Sec()
    92. {
    93.      timer = 60;
    94. }
    95. public function GoTimerSetting5Min()
    96. {
    97.      timer = 300;
    98. }
    99. public function GoTimerSetting10Min()
    100. {
    101.      timer = 600;
    102. }
    103. public function GoTimerSetting20Min()
    104. {
    105.      timer = 1200;
    106. }
    107. public function GoTimerSetting30Min()
    108. {
    109.      timer = 1800;
    110. }
    111. public function GoTimerSetting40Min()
    112. {
    113.      timer = 2400;
    114. }
    115. public function GoTimerSetting50Min()
    116. {
    117.      timer = 3000;
    118. }
    119. public function GoTimerSetting1Hr()
    120. {
    121.      timer = 3600;
    122. }
    123. public function GoTimerSetting1Point5Hr()
    124. {
    125.      timer = 5400;
    126. }
    127. public function GoTimerSetting2Hr()
    128. {
    129.      timer = 7200;
    130. }
    131. public function GoTimerSetting2Point5Hr()
    132. {
    133.      timer = 9000;
    134. }
    135. public function GoTimerSetting3Hr()
    136. {
    137.      timer = 10800;
    138. }