in many of my scripts I have to deal with the playerObject. I don't want to search for it in each script.
I thought about creating an static class to have permanent access to the player.
This is how I do it
public class Globals
{
public static GameObject playerObject = GameObject.FindGameObjectWithTag(StringCollection.PLAYER); // The playerObject
public static Rigidbody playerRigid = GameObject.FindGameObjectWithTag(StringCollection.PLAYER).GetComponent<Rigidbody>();
} // The rigidbody of the player
So I want to store all the variables that I need many times. When loading a new scene, it says the rigidbody tries to access an object, that got destroyed previously.
How can I fix it?
I thought about creating an object inheriting from Monobehaviour that gets never destroyed but then I would have to create a reference to it in all my scripts..
Anything that inherits from Unity's UnityEngine.Object are destroyed even when made static.
If you want it to stay, you have to call DontDestroyOnLoad on it. In this case call:
DontDestroyOnLoad(Globals.playerObject);
This should make the playerObject and every component that is attached to it to stay through the next scene.
Related
Using Unity. When instantiating a new gameObjectA with ScriptA, I access gameObjectB's script (ScriptB) while doing so. The thing is, I can only use the prefab of gameObjectB, instead of an already instantiated gameObjectB. For example -
public class ScriptA : MonoBehaviour
{
public ScriptB scrptB;
void Start()
{
float X = scrptB.IntegerThatIncreasesEveryFrame;
Debug.Log(X);
}
In every frame that a gameObjectB has been instantiated, I have integer IntegerThatIncreasesEveryFrame (I'll call it ITIEF now) that, of course, adds 1 to itself every frame.
When gameObjectA and ScriptA gets instantiated, I want to use gameObjectB's ScriptB's ITIEF (e.g. 100 after 100 frames after being instantiated).
I have to use a prefab of gameObjectB, though. When gameObjectA gets instantiated, it uses the DEFAULT value of ITIEF (which is zero).
If this makes sense please help!
In order to access the information from an instanced object, you need to have a reference to the object. In your case, you are referencing the prefab, not the instanced one in game.
The reason why you cannot drag the script itself into the Inspector is because a reference needs to reference a script that is instanced. ScriptA cannot make a reference to ScriptB unless ScriptB is instanced into a gameobject. A prefab acts as an instanced object that doesn't 'exist' in the game. If you want a reference to the instanced script that IS in the game, then you need to set the ScriptB variable to be that in-game script.
ScriptB instance = GameObject.FindObjectObType<ScriptB>();
This line finds the first instanced form of ScriptB.
This only works if there is ONE instance of ScriptB in the game. If there are more, you need to do a more complicated bit of code.
Are there the same amount of ScriptA and ScriptB? Then when they are both first instanced, store ScriptB somewhere, and then update ScriptA with it.
Your question leaves a lot more questions, but I think I've covered most of the solutions. Pretty much, if you have more than one ScriptB instance, then store the one you want to use and then set it on the ScriptA instance. Otherwise, use FindObjectOfType to get a single instance.
I created a football score calculator. I have a button that changes the scene, but when I change the scene and reopen it the score value is reset to 0. Here is the code:
public class Main : MonoBehaviour {
public Text plusUp;
public int value = 0;
public void button(int sc)
{
SceneManager.LoadScene(sc);
}
public void plus()
{
value++;
plusUp.text = value.ToString();
}
}
1. You can use the method Object.DontDestroyOnLoad on the object that hold the variable you want to save. It will keep the GameObject this script is attached too alive when another scene is loaded:
void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
See the documentation for more informations
You can also make a Singleton but this design pattern is a little more complex as Unity will destroy it when you load another scene. You still have to use DontDestroyOnLoad see how to implement this pattern on their GitHub page
2. Or you can save the value on the disk before loading another scene and then load the value with PlayerPrefs helpers methods:
public int value = 0;
void Awake()
{
//Load the saved score (this value will be saved even if you restart the app)
value = PlayerPrefs.GetInt("Score");
}
public void button1(int sc)
{
//Save your value before you load the scene
PlayerPrefs.SetInt("Score", value);
SceneManager.LoadScene(sc);
}
See the documentation for more informations on the types.
There's a few core concepts that would prove useful to understand this issue.
Scope: Each scene operates in it's own scope. Any variables, objects, or changes that occur in one scene do not automatically transfer to another scene. When you start a scene, all objects in the scene are instantiated and initalized, and their Awake()/Start() methods are called if they are Monobehaviours.
Initialization - When an object is instantiated, it is initialized with constructors or default values. Monobehaviours do not have constructors, so any variables will defer back to default values.
Data persistence - When you change scenes, all game objects in the previous scene are destroyed, while all objects in the new scene are instantiated and initialized. Because all objects in the previous scene are destroyed, any values set on those objects disappear. You can prevent a GameObject from being destroyed with DoNotDestroyOnLoad(), but that does not overwrite the objects defined in the new scene. It is usually not advised to use DoNotDestroyOnLoad() as a core part of your game logic, as it often results in scenes being dependent on one another ("scene 1 has to define the values of a GameObject and pass it to scene 2 to be usable" = bad practice).
Solving your problem
It looks like you want score to persist as a value regardless of scene. Since all GameObjects and Monobehaviours are scoped within the scene, you can:
Force the object to be scene-analagous using the Singleton pattern.
Store score data to a file whenever it changes, and read from that file in the Start() method.
My recommended approach: Use a ScriptableObject to hold the score, and refrence that object when changing the score and updating your gameObjects. ScriptableObjects are scoped at the project level, so they automatically persist between scenes.
How do I set a gameObject true/false GameObject.SetActive(true); that is present in another scene while I do the above through a script from my current scene?
As #Eddge said, you can't access objects from scenes that aren't loaded.
Scene objects are actually created when the scene is loaded, so it's not possible to access them before.
The problem being, when the new scene is loaded, the old scene (with the script that wanted to call SetActive in your case) has been unloaded.
DontDestroyOnLoad
Do not destroy the target Object when loading a new Scene.
The load of a new Scene destroys all current Scene objects. Call Object.DontDestroyOnLoad to preserve an Object during level loading.
-- https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
This is suitable if having an object from your current scene persist in the loaded scene helps solving your issue.
LoadScene additively
For cases when DontDestroyOnLoad doesn't cut it, it's possible to load your new scene without unloading the old one, giving you a chance to pass information between the two.
(this is actually what DontDestroyOnLoad does, as it puts the target object into a "scene" called DontDestroyOnLoad, that never gets unloaded)
You can use SceneManagement.LoadScene with the mode LoadSceneMode.Additive to achieve that. Be aware that both scenes will be active at the same time until you unload the first one!
See LoadScene, LoadSceneAsync and UnloadSceneAsync.
Statics
You can also simply store data in static fields, which are kept throughout your app's lifetime, to retain data and references across scenes.
That might be a simpler solution to pass a few primitive types, but remember that they are globals, and thus suffer the same drawbacks.
See Singleton pattern
There is a hacky way I just found out recently, scriptable objects will also work fine if you want to send data from scene to scene because scriptable is saved as an asset in the project so it is independent of the scene. Just create a scriptable object, then you can update your data from one scene and access them from another scene.
Example Code:
Simply create a scriptable object code:
[CreateAssetMenu(fileName = "StoreData")]
public class StoreData : ScriptableObject {
public bool objectActive;
}
Then access your scriptable object in the first scene and set your boolean in the scriptable object to true:
public class FirstSceneClass : MonoBehaviour {
public StoreData storeData;
public SetObjectActive() {
storeData.objectActive = true;
}
}
Then when you change your scene you can access your scriptable object again and set your gameObject to be active or not based on the boolean:
public class SecondSceneClass : MonoBehaviour {
public StoreData storeData;
void Start() {
if (storeData.objectActive) {
// Set your game object active here
}
}
}
I am trying to understand why I am getting a missing reference exception after a scene change when using singletons.
So, I have two GameObjects in the first scene. A main camera with a GameManager script attached and another shop object with a Purchaser script attached. Both scripts are also singletons created like this for example:
public static Purchaser Instance
void Awake(){
Instance = this
}
They then both reference each other in the Start() function, again like this for example:
void Start(){
game = GameManager.Instance
}
Before a scene change, both scripts use each others singleton references to call methods from one another and everything seems to be working fine. Once I change scenes neither of those objects is in the next scene so they both get destroyed. However, once I go back to the main scene I receive a missing reference exception when the purchaser script attempts to call a method from GameManager using the singleton reference it gets from Start(), that changes the text of a text object attached to the main camera. This function is called after a button is pressed that is attached to the shop object that calls this function in the Purchaser script:
UpdateMoney(){
game.UpdateMoney(100);
}
I read around and was seeing that this may be because Start() will only being called once throughout the whole game run. Which meant the GameManger singleton instance of the Purchaser script was still the old one from before the scenes changed and that instance was destroyed. But, I just tested to see if this was true by putting a debug log in each scripts Start() function, and saw that after each scene change back to the main scene the debug log would go through from both scripts Start(). So, would it be right to say Start() is only ever called once for the life of script but not for the whole game run right? Shouldn't this also mean that once the game changes back to the main scene and both GameObjects are created again, the Purchaser script should now have an updated reference to the newly created GameManager script since Start() was called again?
What I also found was that this worked instead of using the game reference in UpdateMoney():
UpdateMoney(){
GameManager.Instance.UpdateMoney(100);
}
So, why does this work instead of using the game reference retrieved in Start()? Does this mean when Start() is called GameManager.Instance is still the old GameManager.Instance which is why game = GameManager.Instance does not work? Sorry this is very wordy. Any help is much appreciated.
Based on what I understand, you have a singleton made in the main scene. The game object where the singleton is attached to is destroyed after switching scenes.
First of all, is there a reason why you need the singleton to be a MonoBehaviour and attached to a game object? Because you can just make a class with
private static MyClass instance = null
public static MyClass Instance
{
get {
if(instance == null)
instance = new MyClass();
return instance;
}
}
This way, your singleton will always have a value and can be passed thru scenes unless instance is set to null.
Second, Start() is called once only. However, I encountered issues before that if the MonoBehaviour is attached to a disabled game object, the Start() will not be called. You can check if this is what's happening to you when you switch back to the main scene.
Third, if you really need the singleton to be a MonoBehaviour, you can use DontDestroyOnLoad(instance.gameObject) so the game object of the singleton will not be destroyed even after a scene switch. However. I assume the game objects are set on the scene. If it is not from a prefab, you can just do something like this
private static MyClass instance = null;
public static MyClass Instance
{
get {
if(instance == null){
GameObject inst = new GameObject("MyClass Singleton");
instance = inst.AddComponent<MyClass>();
DontDestroyOnLoad(inst);
}
return instance;
}
}
If you do this, then you can remove the preset game objects from the main scene and let the first call to MyClass.Instance make the game object for you.
Lastly, if you do not want to do that, you should set instance = null; on the game object's OnDestroy() so that when you enter the main scene, the new instances will be set to the singleton. This would mean the singleton will not have a value after you switch scenes outside of the main scene.
Being a student and having finished my school year I studied the development of Unity in C # to learn how to use the engine but also and above all to have fun.
One of the advantages of Unity is that you can pass a game object parameter directly in drag/drop, but for this to be possible it is necessary that the said variable is in public, which goes against what I have learned in class (the attributes must be private as often as possible).
My question was, how to make sure to have the same result with private attributes, i.e., recovered the game object manually without using the drag/drop system?
Thanks in advance and sorry for my English level.
It is not necessary to mark the variable as public in order to make it appear in the Editor. Just just put the SerializeField attribute on top of the private variable and it should show up in the Editor.
[SerializeField]
private GameObject cam1;
You can also do the opposite which is make public variable not show in the Editor. This can be done with the HideInInspector attribute.
[HideInInspector]
public GameObject cam1;
But there is another method in Unity to get a GameObject, by avoiding
drag/drop ?
Yes. Many ways.
If the GameObject already exist in the scene then You can with one of the GameObject.FindXXX functions such as GameObject.Find, GameObject.FindGameObjectWithTag
For example,
[HideInInspector]
public GameObject cam1;
// Use this for initialization
void Start()
{
cam1 = GameObject.Find("Name of GameObject");
}
If the GameObject is just a prefab then put the prefab in a folder named Resources then use Resources.Load to load it. See this for more thorough examples.
Finally if you just want to get the component of that GameObject, just like your cam1 variable which is not a GameObject but a component, First, find the GameObject like I did above then use the GetComponent function to get the component from it.
private Camera cam1;
// Use this for initialization
void Start()
{
GameObject obj = GameObject.Find("Main Camera");
cam1 = obj.GetComponent<Camera>();
}