- Home /
OnLevelWasLoaded still called from destroyed gameobject
I have a Generic Singleton script so I can tie persistence and instances of certain scripts like PlayerController and GameManager. OnLevelWasLoaded is in the GameManager so I can spawn the player in the correct location depending on the Trigger object they hit in previous scene. The GameManager saves their spawnPoint (location) to the trigger and the spawnName of the trigger object. That trigger will load the scene.
These triggers allow the player to go from scene to scene. If I send the player back to first scene, the original GameManager and PlayerControllers are still in the scene. My generic Singleton Destroys the objects.
The problem is: OnLevelWasLoaded still gets run even though my console shows the GameManager get destroyed before even correct GameManager runs its OnLevelWasLoaded.
The console looks like this after I go back to the first scene:
- Only one PlayerController is allowed, destroying Constructor
- Only one GameManager is allowed, destroying GameManager
- OnLevelWasLoaded
- spawnName: Alleyway
- OnLevelWasLoaded
- spawnName:
As you can see, the destroyed GameManager is not only still doing OnLevelWasLoaded but its even running after the real GameManager. spawnName is blank on the 2nd because the destroyed GameManager doesn't get the spawnName until a trigger is hit.
Generic Singleton script: http://pastebin.com/CYhp9Nm4
OnLevelWasLoaded part of GameManager:
void OnLevelWasLoaded() {
Debug.Log ("OnLevelWasLoaded");
Debug.Log ("spawnName: " + spawnName);
foreach(GameObject trigger in GameObject.FindGameObjectsWithTag("SceneTrigger")) {
if (trigger.name == spawnName) {
// SceneTriggers will spawn player on its forward vector (blue) by 1, and right vector (red) by the opposite X they entered.
Vector3 pos = trigger.transform.position + trigger.transform.forward + (trigger.transform.right * -spawnPoint.x);
PlayerController.Instance.transform.position = pos;
break;
}
}
}
I used a debug output in OnDestroy for the Game$$anonymous$$anager. It shows that even though my Singleton destroys the extra Game$$anonymous$$anager before anything runs, the OnDestroy doesn't happen until the end.
Is there any way to make it stop running anything before it gets destroyed?
The only other way I can think of using a presistant singleton Game$$anonymous$$anager and be able to go to any scene I want without duplicates is not having them in the scene and have some kind of script on every single scene that loads the gamemanager if its missing on start. That seems silly to me.
Answer by Bunny83 · Nov 26, 2013 at 12:52 AM
Your problem is that things like manager scripts must not being placed in game scenes. You should create an empty loading scene which contains your manager objects. This scene should be loaded only once per session.
If you have a singleton class it could load the loading scene additive if the object isn't loaded yet. That way you can test every scene in the editor without starting via the loading scene.
Something like that:
public class SomeManager : MonoBehaviour
{
private static SomeManager m_Instance = null;
public static SomeManager Instance
{
get
{
if (m_Instance == null)
{
m_Instance = FindObjectOfType(typeof(SomeManager)) as SomeManager;
if (m_Instance == null)
{
Application.LoadLevelAdditive("_LoaderScene");
m_Instance = FindObjectOfType(typeof(SomeManager)) as SomeManager;
}
}
return m_Instance;
}
}
}
btw: The definition of the singleton pattern actually is that you ensure there's only one instance. Allowing multiple instances (even when you destroy them as fast as you can) is not a singleton.
edit
NOTE!
Since Unity needs one frame to actually load the "additional level" it's becomming useless in this case. An alternative is to use a prefab for the singleton:
public class SomeManager : MonoBehaviour
{
private static SomeManager m_Instance = null;
public static SomeManager Instance
{
get
{
if (m_Instance == null)
{
m_Instance = FindObjectOfType(typeof(SomeManager)) as SomeManager;
if (m_Instance == null)
{
SomeManager prefab = Resources.Load("SomeManager", typeof(SomeManager)) as SomeManager;
m_Instance = Instantiate(prefab) as SomeManager;
}
}
return m_Instance;
}
}
}
Just make sure you have a prefab called "SomeManager" in a resources folder which has the "SomeManager" script attached.
This seems to make sense. I was trying to look for a way to load the $$anonymous$$anager(s) without having to put them in every scene so I can test each scene. How do you get it to call the _LoaderScene?
I don't seem to have any thing that uses the Game$$anonymous$$anager on awake, start, etc. $$anonymous$$ost of my initializing stuff like cameras are done from the Game$$anonymous$$anager. Should I just make an empty game object for every scene that calls an empty function in Game$$anonymous$$anager just so it creates an instance?
Sorry, these questions may sound dumb but I'm just trying to wrap my head around it all.
When I try to Instance Game$$anonymous$$anager from Start(), it gives me a NullReferenceException for it. And when I move the player to the next scene, it now loads OnLevelWasLoaded 5 times in a row which is strange.
EDIT: I Re-imported my project and now it's only doing OnLevelWasLoaded once. Don't know why I had to do that. I still have the NullReferenceException though.
I changed my Generic Singleton script with your suggestion and the PlayerController has an Start function to instance the Game$$anonymous$$anager. The only way I was able to get rid of the NullReferenceException was use the LoadLevelAddictive in the Awake of the singleton but it does pull the Game$$anonymous$$anager twice because of that. Fortunately, I catch it and destroy it immediately.
New GenericSingleton: http://pastebin.com/4TnZ10tt
If I can figure out why its doing the NullReferenceException, it will probably fix that issue completely. Then, I'll only need to figure how to Instance the Game$$anonymous$$anager without some empty game object or even the PlayerController.
After further testing, it seems that _instance is null throughout the entire Instance method even after the LoadLevelAddictive. It's not able to get the instance until Awake. Apparently this is just how Unity works, the only way to bypass this is by using a Coroutine to do the LoadLevelAddictive and WaitToEndofFrame. Then, it will allow you immediately grab the objects spawned from the _LoaderScene. Unfortunately, I can't figure out how to call a Coroutine from a static singleton. Even doing the coroutine from outside doesn't work.
I've resorted to calling the Game$$anonymous$$anager instance from the Player, and the Instance creates a new GameObject and adds component Game$$anonymous$$anager. This ends up calling the awake automatically and creates the instance.
Hmm, it seems like LoadLevelAdditive infact needs one frame to actually load the objects into existance...
Well, that's bad. I actually never tried this approach ;) So thanks for the feedback. Well I guess the only alternative for such a setup might be using a prefab in the resources folder. Those load instantly ;)
I'll add an example to the answer...
Answer by KarnEdge · Nov 27, 2013 at 02:40 AM
Thank @Bunny83 for the help. This works perfectly. If anyone is wondering how to use this in a Generic Singleton class, here you go:
using UnityEngine;
using System.Collections;
public class Singleton<T> : MonoBehaviour where T : Component {
[SerializeField]
bool _persistent = true;
private static T _instance;
public static bool IsInstantianted { get { return _instance != null; } }
protected virtual void Init() {}
public static T Instance {
get {
if (_instance == null) {
_instance = (T)FindObjectOfType(typeof(T));
if (_instance == null) {
T prefab = Resources.Load (typeof(T).Name, typeof(T)) as T;
_instance = Instantiate(prefab) as T;
_instance.name = typeof(T).Name; // Removes (clone) naming
}
}
return _instance;
}
}
void Awake() {
if (Instantiation())
Init();
}
bool Instantiation() {
if (IsInstantianted) {
Debug.LogWarning("Only one " + typeof(T) + " is allowed, destroying " + gameObject.name + ".");
Destroy(gameObject);
return false;
}
_instance = FindObjectOfType(typeof(T)) as T;
if (_persistent)
DontDestroyOnLoad(this);
return true;
}
}
Your answer
Follow this Question
Related Questions
Execution order of Destroy() and DontDestroyOnLoad() between Scenes 0 Answers
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
How to disable Dont destroy on load 0 Answers