Persistent GameManager and object Instantiation - unity3d

What I'm trying to do is to make the GameManager object persistent between scenes, reading a bit I realized that the DontDestroyOnLoad() method allows this behavior, however I don't understand why it doesn't allow me to instantiate objects in new scenes.
The following code perfectly replicates the main problem:
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public GameObject objectPrefab;
private void Awake()
{
DontDestroyOnLoad(gameObject);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
SceneManager.LoadScene("Scene2");
GameObject instance = Instantiate(objectPrefab);
}
}
}

From Unity docs:
"When using SceneManager.LoadScene, the scene loads in the next frame, that is it does not load immediately. This semi-asynchronous behavior can cause frame stuttering and can be confusing because load does not complete immediately."
The instantiation happens right away and then one frame latter scene change is triggered. So your game object is instantiated in the old scene and lives there for one frame.
As shingo proposed in the comments you can use SceneManager.sceneLoaded to execute code right after a scene is loaded.

Related

GetComponentInParent is not working as expected, returns self instead b

There is an issue about GetComponentInParent method on Unity. I'm using 2021.3.0f1 version of editor.
The thing is, in the below code,
playerController = GetComponentInParent<PlayerController>(includeInactive:true);
part works fine. But the second line
playerCollider = GetComponentInParent<BoxCollider>(includeInactive:true);
doesn't return the collider in the parent, but the collider on itself.
Setup of the prefab is like this: There is a parent GameObject, that has a box collider attached to it, and there is another GameObject as a child of it, that also has a box collider, but this one is only trigger. And I'm calling GetComponentInParent method from the child object.
I looked up for solutions to this, and one said to send parameter includeInactive:true but it didn't help either.
Note this, it only occurs when it is called from an instantiated prefab. I tried to call GetComponentInParent method in the Awake method, the result was the same.
Is there a way to solve this without changing the prefab's hierarchy structure?
public PlayerController playerController;
public BoxCollider playerCollider;
private void Start()
{
playerController = GetComponentInParent<PlayerController>(includeInactive:true);
playerCollider = GetComponentInParent<BoxCollider>(includeInactive:true);
}

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.

detect hierarchy change during runtime

I have a script attached to an UI gameobject that find and get reference to the canvas root gameobject. Right now i keep refreshing the reference every update() in case the gameobject is moved to other place in hierarchy and the canvas root changed .
But i found it performance heavy for my script to keep running GetComponentInParent<Canvas>().rootCanvas every single update() especially when the object is at the bottom of a hierarchy with 1000+ gameobject. So i want my script to only find root canvas at start() and when the object hierarchy changed.
I've found https://docs.unity3d.com/ScriptReference/EditorApplication-hierarchyChanged.html but it is editor-only and won't follow after build. Is there any way to do something similar to OnHierarchyChanged() ? Also using loop to check the current state of hierarchy is out of option .
Some ideas:
First one, GetComponentInParent<Canvas> is expensive, but what
about simply call Transform.parent? You only need to instantiate
parent reference at the begining, and even if you check it on
Update, is less expensive.
Second one, if you know (and you should know) which are the events
that changes your hierarchy, you can create your own delegate,
event, action whatever to track it.
And finally, I'm not sure about this one, but have you checked
Transform.hasChanged? I think this last one won't work, cause
only affect rotation, position etc...but I can't assure it right
now.
To exemplify Idea 2 (I think the other 2 are pretty clear):
using UnityEngine;
using System.Collections;
public class ClassThatCanChangeHierarchy : MonoBehaviour
{
private List<GameObject> objectsThatWantToKnow = new List<GameObject>();
private void MethodThatChangeHierarchy()
{
//your code that affects hierarchy...
foreach(GameObject go in objectsThatWantToKnow)
{
go.GetComponent<ClassThatWantsToKnowWhenHierarchyChanges>().OnHierarchyChange?.Invoke();
}
}
}
using UnityEngine;
using System;
public class ClassThatWantsToKnowWhenHierarchyChanges : MonoBehaviour
{
public Action OnHierarchyChange = null;
private void Awake()
{
OnHierarchyChange = () => Debug.log("hierarchy has changed");
}
}

Can't drag scene object onto public UnityEvent

I have a script with a public UnityEvent and I am trying to drag a scene object to the object slot so I can access its methods but its not accepting the object. I'm not sure what i'm doing wrong.
The scene object is a prefab instance and I did try unpacking the prefab but it didnt make a difference.
In a comment you mentioned the script you are talking about is a StateMachineBehaviour.
StateMachineBehaviour does not inherit from MonoBehaviour but rather from ScriptableObject
ScriptableObject instances "live" in the Assets not a certain Scene
You (usually) can not have any Scene references in any assets like Prefabs or ScriptableObjects.
There are some workarounds however that still allow you to do that. You can e.g. create ScriptableObject containers for every value you want to pass. Something like e.g.
[CreateAssetMenu]
public class GameObjectReference : ScriptableObject
{
public GameObject gameObject;
public void SetActive(bool value)
{
if(gameObject) gameObject.SetActive(value);
}
}
such an instance of GameObjectReference now is a ScriptableObject and thus also "lives" in the Assets => you can reference it in any other asset as Prefabs and other ScriptableObjects.
So all you need to do is make sure that you set this value from within your scene e.g. using
[ExecuteInEditMode]
public class GameObjectReferenceSetter : MonoBehaviour
{
public GameObjectReference gameObjectReferenceAsset;
private void Awake()
{
gameObjectReferenceAsset.gameObject = gameObject;
}
}
You can create such a reference-asset and setter pair for each type you need and transpass the required public methods so you can call them from the UnityEvent.
Having the [ExecuteInEditMode] this should also be set already in EditMode. However since "officially" it is still not possible to have a scene reference on a ScriptableObject field the value usually says Type missmatch but references the correct object as you should see when you click on the field.
Another alternative is using some kind of dependency-injection (for Unity an often mentioned and free solution is e.g. Zenject). It is a bit complex to set it up the first time but once you got it working it is more flexible and better scaleable since you wouldn't need to implement a wrapper for each type you want to pass in to a StateMachineBehaviour.
More information and how-tos about Zenject can be found on their github page

FixedUpdate is called before Start of the same MonoBehavior

I Have a MonoBehavior doing something like below:
public class MyMonoBehavior : MonoBehaviour
{
private Rigidbody m_Rigidbody;
private void Start()
{
m_RigidBody = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
m_Rigidbody.AddForce(Vector3.one); // May throw NullRefrenceException
}
}
The MyMonoBehavior is attached to a GameObject which is created at runtime.
Occasionally, m_Rigidbody.AddForce(Vector3.one); throws NullRefrenceException.
It seems that FixedUpdate run before Start. Is it a bug?
According to the Unity3d documentation:
Before the first frame update Start:
Start is called before the first
frame update only if the script instance is enabled.
For objects added to the scene, the Start function will be called on
all scripts before Update, etc are called for any of them. Naturally,
this cannot be enforced when an object is instantiated during
gameplay.
So it seems above all scripts include "MyMonoBehavior" its self? So its FixedUpdate can be executed before its Start.
Is my understanding correct? Or is this a bug of Unity3d?
My Unity3d version is 2017.3.1f1
That's why you have Awake. Basically, you should do initialization that's specific to your object in Awake, and leave interaction with other objects for Start. Awake is called as part of the object instantiation process, so I highly doubt you'd run into similar problems.