I would init event in OnDestroy() on Unity3D Statement because after reloading scene some destroyed script get event and throw MissingReferenceException on gameObject.
here how I declare event :
public delegate void SelectDelegate();
public static event SelectDelegate OnSelected;
and how I init it
void Destroy()
{
OnSelected = new SelectDelegate(OnSelected); //Dont work :'(
}
How can I init cleanly event for reloading scene
Ok, I am going to try a few things here:
In my dealings with Unity3D and C# I never instantiate a delegate as you do in the Destroy() method. Once declared, it can be accessed. When you register a delegate an instance should be created for you. See #4.
OnDestroy() is probably not the best place to init anything as it is called before that GameObject is destroyed. Therefore whatever you initialize will be gone soon.
I normally register delegates with something like myClassInstance.Onselected += MyHandlerFunction;. This is normally done in the OnEnable() function. To clean up I remove my delegates in the OnDisable() function with myClassInstance.Onselected -= MyHandlerFunction;.
Before dispatching an event always check it is not null with something like if( OnSelected != null ) OnSelected();.
If you want to avoid having a specific GameObject being destroyed when you load another scene use the DontDestroyOnLoad class.
Shouldn't Destroy() be OnDestroy()?
Related
I have a system that does initialization logic in Awake() and has an appropriate [DefaultExecutionOrder(-100)] attribute to guarantee its Awake() be called before each other "playable" game objects Awake(). Now I need the reverse logic, i need to call a function after each game object being destroyed.
I tried to traverse over root objects in main component's OnDestroy(), check if there's an object that is still alive (i.e. cast to bool gives true), and "pass the baton" of deinitialization to it (adding a CleanUp component on it). Then when this object's OnDestroy() is called it does the same logic, checks if there is at least one object alive, etc... The problem is that it doesn't work as i expected.
Also tried to make OnDestroy() async and check if there's no alive objects in the scene and wait for 100ms in a loop. The result is that playmode exits but the loop still goes on valid objects outside the playmode
Another approach was to instantiate an object with cleanup component in main component's OnDestroy() but Unity does not allow to instantiate in OnDestroy() event
Also tried to implement this logic in component's destructor but it was weirdly been called even before the Awake()
The other possible approach would be to use ISubsystem interface Unity provides but there's no clear documentation on how to use it.
I don't want to use any kind of "global factories" to instantiate and control objects
A possible way to workaround is to use other components' dependent logic in OnApplicationQuit() instead of OnDestroy() but it is the most fallback solution for me
What is the best way for component to be initialized before each other and outlive everyone?
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.
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 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.
I have a script component on a GameObject (simplified here) which is disabled upon creation, using testObject.setActive(false).
using UnityEngine;
public TestObject : MonoBehaviour {
public int testValue = 5;
void Start() {
testValue = 0;
}
public int GetTestValue() {
return testValue;
}
}
Before disabling, the return of GetTestValue is 0. Once I re-enable the object, the return is 5.
The Unity docs say:
Making a GameObject inactive will disable every component ... Any scripts that you have attached to the GameObject will no longer have Update() called ...
However the behaviour of the Component suggests to me that the MonoBehaviour made by the script is not really 'disabled', but rather is destroyed. If it were only Update() that stopped being called, how does that explain the loss of state?
The underlying question here is: what is the intended way to temporarily disable a script without destroying it?
Well what actually happens is that the Start() function is only called once in the lifetime of an object. So at the beginning its called and sets the value to 0. But when you disable it and reactivate it, it isn't called.
This doesn't change the fact that even after deactivating and reactivating the script your value should still be the same (as we figured out in the comments section).
One way you could make this work is by using OnEnable() which will be called each time the Script is setActive. More info on OnEnable:
This function is called when the object becomes enabled and active.
So in your script you would have :
private void OnEnable()
{
testValue = 0;
}
If you know this object will be Activated and deactivated many times and that you absolutely need to do something each time this happens.
Which brings me to my second point and my advice:
Dont keep important values on scripts that will get deactivated and reactivated.
Keep important information on a script that will never be deactivated so in that way you're always sure the value is always the correct one. So that way you don't always need to regenerate that correct value and check each time if it's actually the good value you got.
The problem, it turns out, was a race condition. TestObject was being deactivated before Start() could complete, and this prevented the object from being initialized properly. By moving the initialization code to Awake(), the object correctly set its state before deactivating.
I have the opposite situation..I tried to set an animator parameter in Awake() and deactivate immediately. the parameter value did not changed. But if i put it in Start(), the value is set.