I want to keep my player when switching scenes but without getting lots of copies of it , so I found out about singletons.You do something like this:
public class Player : Monobehaviour
{
private static Player instance = null;
void Start()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameobject);
}
else
Destroy(gameobject);
}
}
And it's pretty hard for me to understand.I get how static variables work , they belong to the class not to the instance.So , first my objects is in scene A.I switch to scene B,a new gameobject will be created with the script Player on it.Now , since instance holds the reference to the gameobject in scene A , why won't the else statement get called and a new gameobject will actually be created?Also , if you could explain a little bit more detailed than unitydoc how DontDestroyOnLoad() works , I'd be thankful.
This code runs after the object is created. In fact it runs on a script on the GameObject. The else statement will run, and it will destroy the GameObject this script is attached to, which is what you want because you already had another copy in the world from previously (the one that is referenced by the "instance" variable).
JSchiff is correct that this code will run after an instance of this object is created, storing the current instance in your static variable for unconditional access to "the current instance" or destroying new instances if there is one already. The else branch in your code will run.
To answer your extended question about DontDestroyOnLoad and it's interaction with this static variable: all the objects in the Engine are held in memory by the Engine. A Scene can be thought of as a "collection of objects in memory" by the engine. When a scene is unloaded, all objects in that collection are destroyed, unless the engine has been told DontDestroyOnLoad (as in un-load, although documentation refers to it as a "swap". You can load scenes without unloading scenes, this might have been terminology which just grew outdated but that's conjecture on my part).
These ideas are combined to make your Singleton, a highly accessible (in this case static instance variable), persistent (through DontDestroyOnLoad), one of a kind (through destroying any instance after the first) object. This isn't the only way to apply the Singleton pattern, but it is a common one in Unity.
Related
I'm trying to create a static GameAssets class where I can drag into it's references my Prefabs in order to manage every GameObject of my game.
The problem I have here is that when I start the game, the instance of my GameAssets is null (which I don't want) and it's instantiating a clone of GameAssets without the references linked to it.
Code of the GameAssets class
public class GameAssets : MonoBehaviour
{
private static GameAssets _i;
public static GameAssets i
{
get
{
if (_i == null)
_i = Instantiate(Resources.Load<GameAssets>("GameAssets"));
return _i;
}
}
public GameObject ProjectileLaserBall;
}
Hierarchy & Inspector
We can see that I have an empty GameObject called GameAssets with prefabs linked to it's references already!
How can I make Unity understand to use the existing GameAssets instead of creating a clone of it without it's references?
(As asked in my Script, a clone is created)
Clone of class
In this case as shown in your hierarchy image, you don’t need to create a new instance of your class. You already have an instance in your scene.
As such, when you start the game, you want the static reference to point to the already instantiated class that Unity already created after the scene was loaded.
The code would then look like:
public class GameAssets : MonoBehaviour
{
private static GameAssets _i;
public static GameAssets i
{
get
{
if (_i == null)
_i = this;
return _i;
}
}
public GameObject ProjectileLaserBall;
}
Your code does make use of the Resources folder. This make me think that you’ve also got a prefab if this GameAssets item in there as well. In this case, maybe your intention was to not define a GamesAssets object in your scene, but to load one in at runtime.
With prefabs, you can’t add references to scene objects (in the common sense), but you can add references to other prefabs. So, if you want to do it that way instead, make sure that your ProjectileLaserBall is also a prefab, instantiate the GameAssets object, add make sure you don’t already have a GameAssets object in the scene (as a new one is going to be created). The code for this class would still be the same as I’ve shown above, but you’d instantiate the asset from a different class altogether. If you’ve done everything correctly, you should have a single GameAssets item in the hierarchy that can be accessed by GameAssets.i
I found a way around that problem, still it is not solving it.
So instead of including my GameAssets directly into the scene, I leave it in the project folder and assign the references in the prefab GameAssets.
This works as it is creating a clone of GameAssets with the references when I'm calling it.
The problem here is that when I call it from a script, because it is not instantiated, the function doesnt show after GameAssets.i.
And we can't get the instance of a static class with the 'this' keyword so I really don't know how to get this to work properly
I am trying to implement a simple racing game in Unity 2021.2 and I have a GameManager Object that holds state across different scenes (i.e. races). To achieve this I use this code in my GameManager:
public static GameManager instance;
private void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
} else if (instance != this)
{
Destroy(gameObject);
}
}
This works fine as long as the fields of the GameManager are of primitive types, such as int and string. However, my GameManager also has a field of type RacerData[], where RacerData is a class that I created in a separate script, which holds data associated with a racer, such as the name, score, etc.
Using this approach I ran into the following problem: When I transition from the first race to the second, i.e. when a new scene is loaded, the GameManager keeps the values of all the fields of primitive types, but loses the references to the RacerData objects.
My first attempt to solve this was that I turned my RacerData script into a MonoBehavior. Instead of just instantiating RacerData Objects in GameManager I created a prefab with the RacerData script attached and instantiated GameObjects from this prefab in my GameManager.
GameObject myRacerDataInstance = Instantiate(myRacerDataPrefab, …, …);
DontDestroyOnLoad(myRacerDataInstance);
instead of:
RacerData myRacerData = new RacerData();
This had the desired effect fo keeping the RacerData game objects when loading a new scene, however the references of the GameManager to those game objects is still lost when a new scene is loaded. I then proceeded to write a hacky workaround to reassign the objects in the next scene which, so far, I failed to make work. However, I'm less interested in making this workaround work rather than to understand why the references to the RacerData gameObjects are lost in the first place and what better way there is to keep data across scene loads.
This appears to be a very common use case to me and I can't quite believe that a developer is supposed to rewire the references manually, especially since those could form a much more complex object graph than mine.
So: Is there a way to preserve those references across scenes? Or is DontDestroyOnLoad() not the right mechanism in the first place and in that case: what better option is there to preserve data (including object references) accross scenes?
It turns out the problem wasn't quite what I thought it to be: The references weren't set on the GameManager object at the time I tried to access them, i.e. from the Start() method of another game object called RaceManager.
A quick fix to the problem was to simply wait for the references to be recreated like so (in RaceManager):
void Start()
{
StartCoroutine(WaitForRacers());
}
private IEnumerator WaitForRacers()
{
while (racers == null)
{
yield return new WaitForSeconds(1);
gameManager = FindObjectOfType<GameManager>();
racers = gameManager.GetRacers();
}
InitializeRace(); // initialization logic that relies on racers to be != null
}
Suppose I have an audio manager singleton (present in all scenes) and all scenes have a button to enable and disable audio.
Suppose I'm in the scene A and I go to scene B.
In scene B, the buttons that referenced the functions of the singleton lose their reference because the audio manager object is destroyed, because a previous audio manager comes from scene A and the one from scene B is destroyed (since there cannot be 2 of it).
Is there a smart way to solve this problem?
Of course I can create a class whose methods call singleton functions and let them in all scenes, but that doesn't seem smart.
On mobile so sorry for the formatting. One way I can think of doing this is having an Init() function in your singleton. In the way you are describing it, this object is DontDestroyOnLoad() as well. In the method of Start() or Awake() you're find all other instances of this object and if another exists deleting the new one so the old one persists.
Inside of this function if another object is found, simply call a new Init() function that takes all the references you need as parameters so the old Singleton gets all the information of the new one, then the new one is deleted.
public class myClass : MonoBehaviour {
public static myClass i;
[SerializeField] private Gameobject myReference = null;
void Awake () {
if(!i) {
i = this;
DontDestroyOnLoad(gameObject);
}else
i.Init(myReference);
Destroy(gameObject);
}
public void Init(GameObject myNewRef)
{
myReference = myNewRef;
}
...
Hope the snippet adds a bit more clarify. Again formatting might be iffy.
I have a "Main" scene consisting of a softozor game object which is controlled by the player. It's a flappy dinosaur. As a child of that softozor game object, I have setup another game object, Installer, consisting of a Transform component and a PlayerInstaller (Script) component:
The PlayerInstaller installs everything necessary for my player's logic. Finally, in the softozor game object, I have added a Game Object Context (Script) where I register the PlayerInstaller:
In addition to the softozor game object, I have also defined a SceneContext:
You'll notice that all installers lists are empty in that SceneContext. However, without that SceneContext registering nothing, the PlayerInstaller is not triggered. Playing the game with that setup works perfectly well, i.e. the PlayerInstaller is called and I can control my dinosaur to do whatever I want in my game.
So far, so good. Now, consider the following Scene Test:
public class PlayerTests : SceneTestFixture
{
[Inject]
private IPlayer _player;
[UnityTest]
public IEnumerator TestScene()
{
yield return LoadScene("Main");
_player.Flap();
yield return new WaitForSeconds(0.01f);
[...]
}
}
In that test, the _player member variable is not injected with an object satisfying the IPlayer contract. In fact, the PlayerInstaller.InstallBindings() is not called.
If I, instead, get rid of the Game Object Context (Script) component in my softozor game object, and register the PlayerInstaller in the SceneContext:
then I can play the game too, as before, and my test is running, i.e. the PlayerInstaller.InstallBindings() method is called during my Scene Test.
What is wrong with my first attempt where I register the PlayerInstaller in the softozor game object context?
I am working with
Zenject ver. 7.3.1
Unity 2019.1.8f1 PC, Mac & Linux Standalone
So you have two containers, the SceneContext and a GameObjectContext.
I think what happens here is that they both get installed, but your GameObjectContext is not added to the SceneContext -- it worked until the SceneContext actually needed to know about the GameObjectContext (which was the case in your scene test).
It's difficult to give more precise directions not knowing what is IPlayer and what you expect to be injected there, but it makes sense that it's not injected in your SceneContext, but only in your GameObjectContext.
By putting the PlayerInstaller in the SceneContext's MonoInstallers list, you technically solved that problem, but that's clearly not what you want since it makes the sub-container useless and breaks any kind of separation you were going for.
Instead, you need to interface both contexts using a façade: Sub-containers and Facades: Using GameObjectContexts (the explanation consists of an example, so it doesn't make sense for me to quote parts of it, but it's detailed and helpful)
Thanks to hugo, I'm starting to understand how to make it run. My current game components setup is as follows:
My PlayerInstaller is defined like this:
public class PlayerInstaller : MonoInstaller
{
[SerializeField]
Settings _settings;
public override void InstallBindings()
{
Container.BindInterfacesTo<Softozor>()
.AsSingle()
.WithArguments(_settings.Rigidbody);
Container.BindInterfacesTo<InputController>().AsSingle();
Container.Bind<InputState>().AsSingle();
Container.BindInterfacesTo<PlayerInputHandler>().AsSingle();
Container.BindInterfacesTo<PlayerMoveHandler>().AsSingle();
}
[Serializable]
public class Settings
{
public Rigidbody2D Rigidbody;
}
}
After reading the documentation advised by hugo, I checked which contexts are created during my scene tests. I was able to see that the method PlayerInstaller.InstallBindings was called in my scene tests. In addition, my Softozor object implementing the IPlayer interface was also injected into the PlayerMoveHandler class. That means that the Game Object Context was successfully built during the scene tests. The objects registered in that context are, however, not available from the Scene Context, which is the one I can access to in my Scene Tests. I naively assumed that whatever I would register in my Game Object Context could be retrieved in my Scene Test. It is apparent, however, that I can only access objects registered in the Scene Context.
At that point, if I really want to access the instance implementing IPlayer, I have one alternative: either I create a facade on the softozor game object and make the IPlayer publicly available on it, or I find a way to resolve instances from the Game Object Context. Is the latter possible at all?
In the end, I think I will redesign my scene test in such a way that I don't need access to the instance of type IPlayer, which is totally possible in my case. But I would be curious to know whether or not it's possible to resolve an object from the Game Object Context in a Scene Test. How do I do that?
One time I was playing around with Resouce system in Unity I discovered this:
Say if you only load but do not Instantiate the prefab:
GameObject testObj = Resource.Load("testPrefab");
And you have a script called "TestScript", and you add this script to testObj
testObj.AddComponent<TestScript>();
You run it, and close it, this TestScript will still be inside testPrefab inside Resource folder, and if you run it again, the testPrefab in Resource folder will have two TestScript component.
I understand that Resouce.Load() is just point to that prefab in Resource folder, no copy has occured.
But would it be a good idea says, put a CleanPrefab() function in this prefab to make sure it restore to the original configuration first and then add other conponents as needed?
Do not modify a prefab. Prefabs are made to be used as re-usable Objects. There are many reasons you shouldn't try to modify it. One of them is that it will affect instantiated objects references that prefab. With modifications done to your prefab, there is no guarantee your levels will start the way they are supposed to start. This will mess up your game.
You run it, and close it, this TestScript will still be inside
testPrefab inside Resource folder, and if you run it again, the
testPrefab in Resource folder will have two TestScript component.
The behavior you described is only true in the Editor. One you build it, the behavior is totally different. In a standalone version, once you restart the program, the prefabs will automatically reset itself. What you are really modifying is the loaded prefab in the memory not the one stored on the disk. Also note that everything in the Resources folder is read-only. They can't be modified.
If you modify a prefab directly, it stays in the memory even when you reload the scene. The only time that modification will be gone is when you restart the application. Do not modify a prefab.
As mentioned here, Resources.Load doesn't create a new instance of an object because it does not create an Object in the scene; that's what Instantiate does.
To avoid this problem of linking to the original prefab and avoid overriding, use Instantiate() and then add all your components with respect to that variable.
It is important to use Instantiate before apply new components to the object or modify the existing ones.
For instance you have this snippet of code:
void Start()
{
SelectCharacter();
InstantiateObj();
ControllerAssignment();
AddColliderComponent();
AddScriptComponent();
}
private void InstantiateObj()
{
obj = Instantiate(obj_original_prefab); //apply changes to this variable
}
This code is a correct implementation, where you call the Instantiate method before applying all the changes to the object obj, which is a 'deep copy' of your original prefab and not a reference!