How to preserve object references when loading new scene - unity3d

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
}

Related

The static instance of my GameAssets is not instantiating properly | Unity 3D

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

Singletons in Unity

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.

How to reference singleton that gets destroyed in the scene

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.

how to clone several game objects in a way that clone properties of one can be adjusted to match all others in scene view

I asked How can I adjust shape/dimensions of one clone to affect all other clones in the scene view and the accepted answer was spot on. It could only clone one game object. I tried making some adjustments but the only solution I came up with was adding duplicate methods for additional objects. This doesn't work well when dealing with several game objects to be cloned.
How can I clone several unique game objects so that adjusting the components/properties of one clone would affect all other clones of that object in the scene view?
Please note that I don't want to achieve this at runtime and I don’t want to use prefabs. I am using this to help with creation of complex levels so the live update of clones being adjusted is very important.
Additionally, I also need a way to turn off the this repeated property/component replication on each clone, preferably with a button.
I don’t want to use prefabs
The new prefab system in Unity is exactly what you need. It fits all of your requirements:
Clone several unique game objects
The prefab system is made for cloning unique gameobjects. It even supports prefab nesting.
I don't want to achieve this at runtime
Great, prefabs only update globally when you click the override button in the editor.
I need a way to turn off the this repeated property/component replication on each clone
That's equivalent to unpacking the object (breaking the connection).
If you have a good reason to avoid using prefabs, you can always write a custom script that tracks changes in the properties you want to share, and updates all other objects immediately. You can make that script run in edit mode by adding the [ExecuteInEditMode] attribute to the class it's in, just don't forget to disable it when running the project. Again, I highly recommend using prefabs instead.
You should use a ScriptableObject as data container and attach that to the gameobject, all clones will use the same synchronized ScriptableObject.
You should use events. Unity3d tutorials has a good, simple explanation: https://unity3d.com/learn/tutorials/topics/scripting/events
Is this only for editing the objects in the editor? If so, then it sounds like prefabs are the way to go; you can directly edit the prefab and all of its 'clones' in the scene will have all changes, including all monobehaviours, transforms, and whatnot replicated to that of the prefab.
If you need this to work at runtime, then you will likely need some code to do this for you. You haven't quite provided enough clarification as to what exactly you want to do, so for the below example I'll assume that you have a gameobject with a mesh or sprite component, and want its size/scale modified alongside all of its "clones";
using UnityEngine;
using System.Collections.Generic;
public class ShapeClone : MonoBehaviour
{
//This will hold references to the other "clone" gameobjects.
public List<GameObject> otherClones = new List<GameObject>();
//All the "clones" in the list otherClones will have their scale matched to this gameobject's scale
public bool leader;
private void Update()
{
if (leader) //Only change other clones' scales if marked as leader, to avoid every single clone
//overriding each other's scale every single frame, which could be rather chaotic
{
for (int i = 0; i < otherClones.Count; i++)
{
//setting each of the other clones' scale to that of this object.
otherClones[i].transform.localScale = this.transform.localScale;
}
}
}
}
The above is a brief example to give you an idea and is by no means extensive, but you should be able to apply it to what you're trying to do; for example, if you wanted to replicate the colour of sprites across gameobjects instead, you can modify otherClones to be a list of Sprite references instead, and instead of setting the scale in update, you can set the colour of each of the Sprite components to that of this object.
If you're only needing this functionality in the editor and not during runtime, though - I highly recommend going with the first option using prefabs, as it will give you far more functionality at a fraction of the cost, performance wise.
It sounds like you have an object that has several clones. You want changing the shape or dimensions of any of those objects to affect the other ones?
For this to happen, each object needs to know about the other ones. You can do this decentralized (each object contains a reference to each other) or centralized (one object governs the rest).
The centralized approach is more simple so I'll give a simple example.
public class Shape
{
public int length;
}
public class ShapeCentral
{
public List<Shape> shapes = new List<Shape>();
public void CloneShape()
{
//instantiate new shape
shapes.Add(new Shape());
}
public void SetCloneLength(int l)
{
shapes.ForEach(x => x.length = l);
}
}
As you can see, one object can control all the clones at once. The trick is to not create clones using other methods or you will run into trouble.
If you want to tighten up your variable access (which I recommend, its a good exercise) you could use a publisher/subscriber pattern. In this, when a new clone is instantiated, it subscribes to the SetCloneLength method. When you want to change the length, the central class publishes that message and it is sent to all the subscribers.
The difference here is that in my example, the central class needs to keep track of all the clones, in publisher/subscriber, you don't.
Create script CopycatManager that will hold a leader and then use dedicated setters for copying the other object properties that have the same type. If a property is a default one may need to set up either a proxy of such property within' the script or play with triggers. I would recommend proxy. Like this:
class CopycatManager {
public GameObject leader;
SomeAttributeType attributeToCopyFromLeader {get; private set}
void Start () {
// The first CopycatManager to start is the leader
List<CopycatManager> allCMs = parent.GetComponentsInChildren();
CopycatManager foundLeader = allCMs.Find(o => o.leader == o);
if (foundLeader == null) {
// There's no leader yet, set yourself a leader
leader = this;
} else {
// Found a leader, accept
leader = foundLeader;
}
}
public void SetAttribute (SomeAttributeType newVal) {
// If we're setting the attribute of the leader - we should set this attribute for all children
if (leader == gameObject) {
// Find all copycat manager scripts attached to children of current parent
// Meaning siblings
// WARNING: It will include children of siblings and the leader itself
// WARNING: It will not include parents of the Copycat Manager type, add if required
List<CopycatManager> allCMs = parent.GetComponentsInChildren();
foreach (CopycatManager manager in allCMs) {
SetAttributeFromLeader (newVal);
}
} else {
// Non-leader is attempting to change attribute - call leader
leader.SetAttribute(newVal);
}
}
// Called by leader to each child
public void SetAttributeFromLeader (SomeAttributeType newVal) {
attributeToCopyFromLeader = newVal;
}
}
Make sure to assign a new leader if the old one destroyed. Only destroy objects with CopycatManager through dedicated function.
make all items that need scaling children of an empty called WorldObjects then scale the world object, it will scale all its children accordingly. you can then either manually or through script remove the parent to make objects independent. best way without prefabs...
use a singleton class. add that script to all the objects, then you can make a call to one and it will adjust all of them.
you can also do this with a static class, but the singleton approach is cleaner, and gives you more options.
public class MySingleton
{
private static MySingleton fetch; // keep the static reference private
public bool myBool = false;
// and expose static members through properties
// this way, you have a lot more control over what is actually being sent out.
public static bool MyBool { get { return fetch ? fetch.myBool : false; } }
void Awake()
{
fetch = this;
}
}
read here for some great information on both options!

Use the collider of multiple objects as one collider

In my fps level (Unity), targets spawn at a random position. I want to make sure targets can't spawn behind objects or inside objects.
To make sure they don't spawn behind an object, I've made a raycast going from the player to the target. If it's obstructed I recalculate the spawn point. This works fine, but, since the targets are spheres the raycast won't be obstructed when a target is 50% inside an object, for example the floor. I don't want that, obviously.
To determine whether or not the target is in the bounds of another object, I tried using OnCollisionEnter and OnCollisionExit. While this works when simply moving a target inside another object, it seems to be unreliable when one script's Update cycle is recalculating the spawn position while the target's Update cycle is keeping track of the Collision.
So I looked for a different approach. Here's what I came up with (from the Unity docs):
m_Collider2 = spawnpoints[i].GetComponent<Collider>();
m_Collider = world.GetComponentInChildren<Collider>();
if (m_Collider.bounds.Intersects(m_Collider2.bounds))
{
Debug.Log("Bounds intersecting");
}
The Game Object world is the parent in which I put all the objects of my gaming world.
The problem is that he only takes into account the collider of the first object. I basically want to use one big collider, which is composed by all the level objects.
Is this possible? Or does anyone know a different approach on how I can achieve this?
You should use the GetComponentsInChildren method instead of GetComponentInChildren, so that you can get from it an array of colliders on which you can execute a foreach to check if the bounds are intersecting.
I.E.:
m_Collider2 = spawnpoints [i].GetComponent<Collider>();
m_Collider = world.GetComponentsInChildren<Collider>();
foreach(Collider objCollider in m_Collider) {
if (objCollider.bounds.Intersects(m_Collider2.bounds))
{
Debug.Log("Bounds intersecting");
break;
}
}
But, this way of doing things is very heavy for the CPU, since GetComponent methods are really slow, so their use should be limited inside Awake and Start methods if possible.
Another approach to the problem would be to create a List<Collider> at the start, and add to it the starting children of your World game object. If another one is instantiated, just Add it to your list, if it's destroyed, just Remove it.
Then, just before instantiation, you can check the bounds by looping inside the List with a foreach, the check will be a lot more faster.
==================================
EDIT:
Ok, here's the deal. First of all, add these lines to your World game object script (I guess you called the class World):
using UnityEngine;
using System.Collections.Generic; //Namespace needed to use the List type
public class World : MonoBehaviour {
//The list which will hold references to the children game objects colliders
public List<Collider> childrenColliders;
private void Start() {
//Code used to populate the list at start
childrenColliders = new List<Collider>(GetComponentsInChildren<Collider>());
}
Now, since in the script which spawns a new object has already a world variable which holds a reference to the World class:
foreach(Collider coll in world.childrenColliders) {
if (coll.bounds.Intersects(m_Collider2.bounds))
{
Debug.Log("Bounds intersecting");
break;
}
}
And, of course, as I said before remember to add a newly spawned game object's collider to the list with:
void AddNewGameObject() {
// spawnPoint is the transform.position Vector3 you'll use for the new game object
var newGameObject = Instantiate(yourObjectPrefab, spawnPoint, Quaternion.identity, world.transform);
world.childrenColliders.Add(newGameObject.GetComponent<Collider>());
}
That's pretty much it. ;)