Unity: Get an Instance to UI Object - c#

I have a working singleton class for game objects that I use for GameManager or Camera like common objects.It uses FindObjectOfType() function to create the instance if it is null. Everything works great on gameobjects. Now I'm trying to keep a panel under canvas above a game object. So I need an instance to the panel. My already written singleton class returns null when attached to a panel. How can I get an instance to a panel?

You can get the instance of panel by declaring in the singleton class with GameObject type.
There is another way too.
You said you can get the instance of game object from singleton class.
And panel is above that game object.
So, you can get that panel like this.
GameObject panel = SingletonClass.Instance.GetGameObject().transform.parent.gameobject;
This will return the panel gameobject. You can get components of panel in normal ways.
e.g.,
panel.GetComponent<Image>();

Just a suggestion, it is not a good practice to use FindObjectOfType() in runtime especially if you have a lot of objects in the game as it may become a cause of performance issues. If you are only doing it on once on setup, then you can disregard this suggestion.
One thing you can do is make a UIContainer singleton which is a MonoBehaviour. Then, let UIContainer have a serialized array of GameObject, and make a getter of UI instances from the UIContainer. You then assign the UI instances to the serialized array.
public class UIContainer : MonoBehaviour {
private static UIContainer instance = null;
public static UIContainer Instance {
get {
return instance;
}
}
[SerializeField] GameObject[] uiInstances;
void Awake() {
instance = this;
}
void OnDestroy() {
instance = null;
}
public GameObject GetUI(string uiName)
{
GameObject ui = null;
foreach(GameObject inst in uiInstances){
if(inst.name == uiName){
ui = inst;
break;
}
}
return ui;
}
}
Then if you need a UI instance, you can just call UIContainer.Instance.GetUI("ui name");.
This is just a sample of it so feel free to make your own or adjust this. This can also work if your UI is made from prefabs. Just change the array from instances to prefabs, but you have to do the instantiation logic from there. Keep the a reference of the instances then use that when using GetUI.

Related

Unity Audio Singleton Across Scenes

So I have a basic singleton that handles audio across scenes.
private void Awake() {
if (Instance == null) {
Instance = this;
} else {
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
}
Let's say this audio singleton is placed in Scene1, if I switch to Scene2 it seems to work. The only issue is if I start from Scene2, the audio no longer works. I'm guessing this is because the singleton is only created in Scene1 so there is no singleton reference in Scene2. I have tried making my singleton into a prefab so I can have them in each of my scenes which solves my issue of not having an existing singleton in Scene2 but if I switch scenes then it stops working.
Is there a way I can have an audio singleton that works even if I don't start at Scene1? I guess it doesn't have to be a singleton but that's what I have so far. I'm new to unity so I've only been looking up basic Unity tutorials.
Edit: The reason I want to start from Scene2 is because I want to test specific scenes.
You could probably use a lazy instantiation like e.g.
[RequireComponent(typeof(AudioSource))]
public class CentralAudio : MonoBehaviour
{
[SerializeField] private AudioSource _source;
public AudioSource Source => _source;
private static CentralAudio _instance;
public static CentralAudio Instance
{
get
{
// instance already exists -> return it right away
if(_instance) return _instance;
// look in the scene for one
_instance = FindObjectOfType<CentralAudio>();
// found one -> return it
if(_instance) return _instance;
// otherwise create a new one from scratch
_instance = new GameObject(nameof(CentralAudio), typeof(AudioSource)).AddComponent<CentralAudio>();
}
}
private void Awake()
{
// ensure singleton
if(_instance && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
// lazy initialization of AudioSource component
if(!_source)
{
if(!TryGetComponent<AudioSource>(out _source))
{
_source = gameObject.AddComponent<AudioSource>();
}
}
DontDestroyOnLoad(gameObject);
}
}
so now you can use e.g.
CentralAudio.Instance.Source.PlayOneShot(someAudioClip);
and the first time the instance will be created on demand.
You can use my Singleton template below. When declaring your class use "public class MyClass : Singleton<MyClass>". What you need to do, is have a scene like Bootstrap or Startup and this is where you place your singleton gameobjects. They should use some standard singleton code very similar to what derHugo posted except I would not instantiate the component if it's not found - it should show an error in the log instead. Usually my manager singletons have useful properties and arrays on them that are set in the inspector, so just creating a component like that would lose all that functionality.
Once you have your Bootstrap or Startup scene, you move it to the top of the load order in your Build scene list. You should have another singleton gameobject also in the Startup scene that then loads Scene1 or Scene2 or whatever you need. I make a singleton called GameManager that has a state machine and determines what scenes to load and know where we are.
Quite often it will load in a GameUI scene or multiple UI scenes, and you can load them additively. That way you break up your game and UI into multiple scenes for organization. It's also important for collaboration when working on a team to have multiple scenes since they tend not to merge easily. Quite often people want an Intro scene, so the GameManager will have different states and move between them loading the different scenes. Don't call the singleton SceneManager though, Unity already has a class named that way.
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T: MonoBehaviour
{
protected virtual void Awake()
{
if (instance != null)
{
Debug.LogError($"Duplicate Singleton: {name} of type {typeof(T)}, destroying self");
GameObject.DestroyImmediate(gameObject);
}
else
instance = gameObject.GetComponent<T>();
}
static bool doneOnce;
/// <summary>
/// Returns the instance of this singleton.
/// </summary>
public static T Instance
{
[System.Diagnostics.DebuggerStepThrough]
get
{
if (instance == null)
{
instance = (T)GameObject.FindObjectOfType(typeof(T)); // not really any point to doing this (it will fail), Awake would have happened but it's possible your code got here before Awake
if (instance == null && !doneOnce)
{
doneOnce = true;
Debug.LogError($"!!! An instance of type {typeof(T)} is needed in the scene, but there is none !!!");
}
}
return instance;
}
}
private static T instance;
}

Call object on other scene C#

I have this problem that I want to call an object from my first scene then call that object on my second scene . I tried doing this
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
and put it on the object I don't want to destroy then changed my scene on the
void Start(){
SceneManagement.LoadScene("Menu",LoadSceneMode.Single);
}
But it's not there on the heirarchy
Could someone help me out
EDIT:
Now when the next scene is loaded
The object I wanted is not there anymore. It is being destroyed
Create a persistent object
Create a preloader scene here you can place a splash screen or whatever you prefer but the important thing is loading things that should be persistent(maybe such as a network or gamemanager)
Create a script PersistentObject.cs and put the following code in it
private void Awake(){
DontDestroyOnLoad(this.gameObject);
}
Put this script on any object you initialize in the preloader
Access object from anywhere
If you want to access an object in another scene there are several ways but I will assume you do not have any specific reference to the object
So if we have a GameManager.cs and we created a Persistent cube in our preloader called Cube we can get a reference to the gameobject by saying GameObject cube = GameObject.FindGameobjectWithName("Cube");
Now you are able to do whatever you want by using cube
Write less, Do more with singletons
Creating a singleton will also be very useful as well
public static class Singleton<T>: MonoBehavior where T: MonoBehavior{
private static T instance;
//Notice the lower and upper case difference here
public static T Instance{
get{
if(instance == null){
instance = GameObject.FindGameObjectOfType<T>();
}
return instance;
}
}
}
You can then add this to your script make accessing properties easier and reduces the amount of code you have to write
public class Cube: Singleton<Cube>{
private string cubeName = "Jeff";
public void ChangeCubeName(string newName){
cubeName = newName;
}
}
To access this methods of this class you could now call the singleton from anywhere in your code
Example
public class GameManager: MonoBehavior{
private void Start(){
cube.Instance.ChangeCubeName("Joe");
}
}

Unity: Singleton DontDestoryOnLoad script false reference

So I have this manager in my start scene with DontdesoryOnload, that managers all the UIs, etc. It follows singleton pattern, so if I go to scene 2 and come back to my start-scene, the first manager will remain the same, and the manager in the newly opened start-scene will figure that there's already a manager, and destroy itself.
From here let's call the Manager remains alive Manager-Singleton and the manager that is destroyed as planned Manager-Dead.
The problem I'm having is that the references in my Manager-Singleton seem to false-reference.
When Manager-Dead is destroyed as planned, if I access a public GameObject under my Manager-Singleton, it will show me an error. Where if I click on those References fields in Inspector, it will lead me to the correct Gameobject which not not destoryed at all.
MissingReferenceException: The object of type 'GameObject' has been destroyed, but you are still trying to access it.
However, if I avoid Manager-Dead from being destroyed, (So there will be two managers in one scene), the code worked just fine.
I know you might be thinking, if there are two managers in the scene, there might be a UI overlap so that I might be clicking on ManagerDead's Button, and accessing its References. So after I got back to the Start scene, I manually disable the ManagerDead. And it turns out ManagerSingleton is changing ManagerDead's UI !
I really couldn't figure out where it went wrong. Any suggestions would be appreciated.
Following is some of my codes in case they might be useful:
[RequireComponent(typeof(UIManager))]
[RequireComponent(typeof(DataManager))]
[RequireComponent(typeof(StateManager))]
public class Managers : MonoBehaviour {
private static UIManager _UIManager;
public static UIManager UI
{
get { return _UIManager; }
}
private static DataManager _DataManager;
public static DataManager Data
{
get { return _DataManager; }
}
private static StateManager _StateManager;
public static StateManager State
{
get { return _StateManager; }
}
public string debugString = "";
void Awake(){
//Only one dataControl obj is allowed to exist and pass along.
if (GameObject.FindObjectsOfType<Managers> ().Length > 1) {
Destroy (gameObject);
} else {
DontDestroyOnLoad (gameObject);
}
_UIManager = GetComponent<UIManager> (); //!!!!! This is a Singleton class.
_DataManager = GetComponent<DataManager> ();
_StateManager = GetComponent<StateManager> ();
}
}
Problem solved, the Component referencing part is what's causing the trouble, Since _UIManager etc is static, that means when component referencing is called in Awake function of Manager-Dead, it will refer those reference to the GameObjects Under it even after it's destroyed.
This results in the static _UIManager from Manager-Singleton will be referred to the Destroyed Gameobjects under Manager-Dead.

how to make a c# script run at scene start in unity

I am doing a school project. I need to check the destroyed objects in my scene as soon as the scene starts. problem is I don't know how to make it load or where to attach the c# script.
public static class DestroyedObject {
static List<GameObject> objs = new List<GameObject>();
public static void Add(GameObject obj)
{
if(!objs.Contains(obj))
objs.Add(obj);
}
}
If you want it to run when you start the scene you need to attach it to a GameObject. Create empty and attach it as a component.
The code that you want to run on start should be in the:
void Awake
{
//Your code here
}
or
void Start
{
//Your code here
}
functions.
Start is called as soon as the class is instantiated and Awake is called when the scene is started. Depends where you want it in the call stack, but in your case i think it will do essentially the same thing.
I think what you're looking for is a way to "save" what objects have been deleted : you simply have to make your class inherit from MonoBehaviour and call DontDestroyOnLoad() so your object containing the script will exist between the scenes.
public static class DestroyedObject : MonoBehaviour
{
public static DestroyedObject Instance;
private static List<GameObject> objs = new List<GameObject>();
private void Awake()
{
if (!Instance)
{
Instance = this;
}
else
{
DestroyImmediate(gameObject);
}
DontDestroyOnLoad(gameObject);
}
public static void Add(GameObject obj)
{
if(!objs.Contains(obj))
objs.Add(obj);
}
public static List<GameObject> GetDestroyedObjects()
{
return objs;
}
}
Then you simply access your script using DestroyedObject.Instance.Add() or DestroyedObject.Instance.GetDestroyedObjects() (some people don't like this kind of design pattern but it has proven to be very effective when using Unity).
Also as #Sergey asked, why creating objects (on scene loading) in order to delete them afterward : you could do the revers operation (only instantiate the needed ones).
Hope this helps,
Can you describe what you are trying to achieve in total? Because it looks like your way is not the best way to do it ;).
If all you want to know is how to execute a script at scene start: create a script that inherits from MonoBehaviour (no need for static class), attach it to a gameobject in your scene, and thats it!
If you want to execute code as soon as the scene starts (and the gameobject is loaded), put your code in Awake() or Start(). You can read about the execution order of those functions here: https://docs.unity3d.com/Manual/ExecutionOrder.html
Making a script static means it will be active in all scenes and even before any scene is loaded.
Additionally, i would not recommend the use of static classes unless you really need them.

How to pass data (and references) between scenes in Unity

How can I pass score value from one scene to another?
I've tried the following:
Scene one:
void Start () {
score = 0;
updateScoreView ();
StartCoroutine (DelayLoadlevel(20));
}
public void updateScoreView(){
score_text.text = "The Score: "+ score;
}
public void AddNewScore(int NewscoreValue){
score = score + NewscoreValue;
updateScoreView ();
}
IEnumerator DelayLoadlevel(float seconds){
yield return new WaitForSeconds(10);
secondsLeft = seconds;
loadingStart = true;
do {
yield return new WaitForSeconds(1);
} while(--secondsLeft >0);
// here I should store my last score before move to level two
PlayerPrefs.SetInt ("player_score", score);
Application.LoadLevel (2);
}
Scene two:
public Text score_text;
private int old_score;
// Use this for initialization
void Start () {
old_score = PlayerPrefs.GetInt ("player_score");
score_text.text = "new score" + old_score.ToString ();
}
but nothing displayed on screen, and there's no error.
Is this the correct way to pass data ?
I am using Unity 5 free edition, develop game for Gear VR (meaning the game will run in android devices).
Any suggestion?
There are many ways to do this but the solution to this depends on the type of data you want to pass between scenes. Components/Scripts and GameObjects are destroyed when new scene is loaded and even when marked as static.
In this answer you can find
Use the static keyword
Use DontDestroyOnLoad
Store the data local
3a PlayerPrefs
3b serialize to XML/JSON/Binary and use FileIO
1. Use the static keyword.
Use this method if the variable to pass to the next scene is not a component, does not inherit from MonoBehaviour and is not a GameObject then make the variable to be static.
Built-in primitive data types such as int, bool, string, float, double. All those variables can be made a static variable.
Example of built-in primitive data types that can be marked as static:
static int counter = 0;
static bool enableAudio = 0;
static float timer = 100;
These should work without problems.
Example of Objects that can be marked as static:
public class MyTestScriptNoMonoBehaviour
{
}
then
static MyTestScriptNoMonoBehaviour testScriptNoMono;
void Start()
{
testScriptNoMono = new MyTestScriptNoMonoBehaviour();
}
Notice that the class does not inherit from MonoBehaviour. This should work.
Example of Objects that cannot be marked as static:
Anything that inherits from Object, Component or GameObject will not work.
1A.Anything that inherits from MonoBehaviour
public class MyTestScript : MonoBehaviour
{
}
then
static MyTestScript testScript;
void Start()
{
testScript = gameObject.AddComponent<MyTestScript>();
}
This will not work because it inherits from MonoBehaviour.
1B.All GameObject:
static GameObject obj;
void Start()
{
obj = new GameObject("My Object");
}
This will not work either because it is a GameObject and GameObject inherit from an Object.
Unity will always destroy its Object even if they are declared with the static keyword.
See #2 for a workaround.
2.Use the DontDestroyOnLoad function.
You only need to use this if the data to keep or pass to the next scene inherits from Object, Component or is a GameObject. This solves the problem described in 1A and 1B.
You can use it to make this GameObject not to destroy when scene unloads:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
You can even use it with the static keyword solve problem from 1A and 1B:
public class MyTestScript : MonoBehaviour
{
}
then
static MyTestScript testScript;
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
void Start()
{
testScript = gameObject.AddComponent<MyTestScript>();
}
The testScript variable will now be preserved when new scene loads.
3.Save to local storage then load during next scene.
This method should be used when this is a game data that must be preserved when the game is closed and reopened. Example of this is the player high-score, the game settings such as music volume, objects locations, joystick profile data and so on.
Thare are two ways to save this:
3A.Use the PlayerPrefs API.
Use if you have just few variables to save. Let's say player score:
int playerScore = 80;
And we want to save playerScore:
Save the score in the OnDisable function
void OnDisable()
{
PlayerPrefs.SetInt("score", playerScore);
}
Load it in the OnEnable function
void OnEnable()
{
playerScore = PlayerPrefs.GetInt("score");
}
3B.Serialize the data to json, xml or binaray form then save using one of the C# file API such as File.WriteAllBytes and File.ReadAllBytes to save and load files.
Use this method if there are many variables to save.
General, you need to create a class that does not inherit from MonoBehaviour. This class you should use to hold your game data so that in can be easily serialized or de-serialized.
Example of data to save:
[Serializable]
public class PlayerInfo
{
public List<int> ID = new List<int>();
public List<int> Amounts = new List<int>();
public int life = 0;
public float highScore = 0;
}
Grab the DataSaver class which is a wrapper over File.WriteAllBytes and File.ReadAllBytes that makes saving data easier from this post.
Create new instance:
PlayerInfo saveData = new PlayerInfo();
saveData.life = 99;
saveData.highScore = 40;
Save data from PlayerInfo to a file named "players":
DataSaver.saveData(saveData, "players");
Load data from a file named "players":
PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
There is another way:
ScriptableObject
ScriptableObjects are basically data containers but may also implement own logic. They "live" only in the Assets like prefabs. They can not be used to store data permanently, but they store the data during one session so they can be used to share data and references between Scenes ... and - something I also often needed - between Scenes and an AnimatorController!
Script
First you need a script similar to MonoBehaviours. A simple example of a ScriptableObject might look like
// fileName is the default name when creating a new Instance
// menuName is where to find it in the context menu of Create
[CreateAssetMenu(fileName = "Data", menuName = "Examples/ExamoleScriptableObject")]
public class ExampleScriptableObject : ScriptableObject
{
public string someStringValue = "";
public CustomDataClass someCustomData = null;
public Transform someTransformReference = null;
// Could also implement some methods to set/read data,
// do stuff with the data like parsing between types, fileIO etc
// Especially ScriptableObjects also implement OnEnable and Awake
// so you could still fill them with permanent data via FileIO at the beginning of your app and store the data via FileIO in OnDestroy !!
}
// If you want the data to be stored permanently in the editor
// and e.g. set it via the Inspector
// your types need to be Serializable!
//
// I intentionally used a non-serializable class here to show that also
// non Serializable types can be passed between scenes
public class CustomDataClass
{
public int example;
public Vector3 custom;
public Dictionary<int, byte[]> data;
}
Create Instances
You can create instances of ScriptableObject either via script
var scriptableObject = ScriptableObject.CreateInstance<ExampleScriptableObject>();
or to make things easier use the [CreateAssetMenu] as shown in the example above.
As this created ScriptabeObject instance lives in the Assets it is not bound to a scene and can therefore be referenced everywhere!
This when you want to share the data between two Scenes or also e.g. the Scene and an AnimatorController all you need to do is reference this ScriptableObject instance in both.
Fill Data
I often use e.g. one component to fill the data like
public class ExampleWriter : MonoBehaviour
{
// Here you drag in the ScriptableObject instance via the Inspector in Unity
[SerializeField] private ExampleScriptableObject example;
public void StoreData(string someString, int someInt, Vector3 someVector, List<byte[]> someDatas)
{
example.someStringValue = someString;
example.someCustomData = new CustomDataClass
{
example = someInt;
custom = someVector;
data = new Dictionary<int, byte[]>();
};
for(var i = 0; i < someDatas.Count; i++)
{
example.someCustomData.data.Add(i, someDatas[i]);
}
example.someTransformReference = transform;
}
}
Consume Data
So after you have written and stored your required data into this ExampleScriptableObject instance every other class in any Scene or AnimatorController or also other ScriptableObjects can read this data on just the same way:
public class ExmpleConsumer : MonoBehaviour
{
// Here you drag in the same ScriptableObject instance via the Inspector in Unity
[SerializeField] private ExampleScriptableObject example;
public void ExampleLog()
{
Debug.Log($"string: {example.someString}", this);
Debug.Log($"int: {example.someCustomData.example}", this);
Debug.Log($"vector: {example.someCustomData.custom}", this);
Debug.Log($"data: There are {example.someCustomData.data.Count} entries in data.", this);
Debug.Log($"The data writer {example.someTransformReference.name} is at position {example.someTransformReference.position}", this);
}
}
Persistence
As said the changes in a ScriptableObject itself are only in the Unity Editor really persistent.
In a build they are only persistent during the same session.
Therefore if needed I often combine the session persistence with some FileIO (as described in this answer's section 3b) for loading and deserializing the values once at session begin (or whenever needed) from the hard drive and serialize and store them to a file once on session end (OnApplicationQuit) or whenever needed.
(This won't work with references of course.)
Besides playerPrefs another dirty way is to preserve an object during level loading by calling DontDestroyOnLoad on it.
DontDestroyOnLoad (transform.gameObject);
Any script attached to the game object will survive and so will the variables in the script.
The DontDestroyOnLoad function is generally used to preserve an entire GameObject, including the components attached to it, and any child objects it has in the hierarchy.
You could create an empty GameObject, and place only the script containing the variables you want preserved on it.
I use a functional approach I call Stateless Scenes.
using UnityEngine;
public class MySceneBehaviour: MonoBehaviour {
private static MySceneParams loadSceneRegister = null;
public MySceneParams sceneParams;
public static void loadMyScene(MySceneParams sceneParams, System.Action<MySceneOutcome> callback) {
MySceneBehaviour.loadSceneRegister = sceneParams;
sceneParams.callback = callback;
UnityEngine.SceneManagement.SceneManager.LoadScene("MyScene");
}
public void Awake() {
if (loadSceneRegister != null) sceneParams = loadSceneRegister;
loadSceneRegister = null; // the register has served its purpose, clear the state
}
public void endScene (MySceneOutcome outcome) {
if (sceneParams.callback != null) sceneParams.callback(outcome);
sceneParams.callback = null; // Protect against double calling;
}
}
[System.Serializable]
public class MySceneParams {
public System.Action<MySceneOutcome> callback;
// + inputs of the scene
}
public class MySceneOutcome {
// + outputs of the scene
}
You can keep global state in the caller's scope, so scene inputs and outputs states can be minimized (makes testing easy). To use it you can use anonymous functions:-
MyBigGameServices services ...
MyBigGameState bigState ...
Splash.loadScene(bigState.player.name, () => {
FirstLevel.loadScene(bigState.player, (firstLevelResult) => {
// do something else
services.savePlayer(firstLevelResult);
})
)}
More info at https://corepox.net/devlog/unity-pattern:-stateless-scenes
There are various way, but assuming that you have to pass just some basic data, you can create a singelton instance of a GameController and use that class to store the data.
and, of course DontDestroyOnLoad is mandatory!
public class GameControl : MonoBehaviour
{
//Static reference
public static GameControl control;
//Data to persist
public float health;
public float experience;
void Awake()
{
//Let the gameobject persist over the scenes
DontDestroyOnLoad(gameObject);
//Check if the control instance is null
if (control == null)
{
//This instance becomes the single instance available
control = this;
}
//Otherwise check if the control instance is not this one
else if (control != this)
{
//In case there is a different instance destroy this one.
Destroy(gameObject);
}
}
Here is the full tutorial with some other example.
you have several options.
The first one I see is to use static variables, which you will not lose their information or value passing from scenes to scenes (since they are not bound to the object). [you lose the information when closing the game, but not when passing between scenes]
the second option is that the player or the object of which you do not want to lose the information, you pass it through the DontDestroyOnLoad function
Here I give you the documentation and the sample code. [You lose the information when you close the game, but not when you go between scenes]
https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
Third is to use the playerPrefab [https://docs.unity3d.com/ScriptReference/PlayerPrefs.html]
that allow you to save information and retrieve it at any time without hanging it even after closing the game [you must be very careful with the latter if you plan to use it to save data even after closing the game since you can lose the data if you close the game suddenly , since player prefab creates a file and retrieves the information from there, but it saves the file at the end or closes the app correctly]

Categories

Resources