How Do I Get a Reference To An Object In Another Scene In A Field In The Inspector?
I want to link the exit of one room to the entrance of another. The appropriate Unity way is usually dragging an object onto a public field in the inspector. However, the other room is in another scene, and I can't just drag a reference to an inspector.
One solution for a static game would be to keep track of these "doors" in an enum, and we can use that to build a drop-down for the inspector. However, I'd like to be able to dynamically generate these rooms and their doors as well as design some in the editor.
I realize these also function as spawn points of a sort, so I'm hoping it's a well-worn area of study. Can anyone recommend best practices?
$$anonymous$$ost people would just set "spawn" points in the map, and when you load that other scene, then if it has more than one possible spawn location in it, you would have some sort of player information that the scene will compare to. Like:
void OnEnable()
{
playerObject player = FindObjectOfType<playerObject>();
if (player.exitLocation == myDoorName)
{
player.gameObject.transform.position = spawnPoint1.transform.position;
}
else if (playerObject.exitLocation == myDoorName2)
{
player.gameObject.transform.position = spawnPoint2.transform.position;
}
}
Aside from all that, the Unity drag'n'drop system is kind of nice, but perpetuates bad practices. A lot of people avoid getters/setters because they won't show up in inspector, and most people find objects by the name of the object, or the tag, which should be kind of a last resort. Especially if it is a single player game, the player object should never need searched for, and should be easy to access from whatever core GameData file you create.
Answering just the title - you can't. Objects in a scene don't exist yet. There's no way to get a reference to them until they are created.
For the rest, Unity wants to wipe everything when you leave a scene. You should be able to look up examples of getting one scene to talk to another (it usually, as toshimo writes, it usually involves dontdestroyonload.)
Answer by templewulf · Jan 16, 2017 at 06:45 AM
For a more complete answer, I've discovered that the Unity docs now recommend against using DontDestroyOnLoad, instead preferring a "manager scene" and LoadSceneMode.Additive. https://docs.unity3d.com/Manual/MultiSceneEditing.html
The solution I went with was using string fields on my Gate objects that contain the names of Gates in other scenes. In order to get that information across, I pass it into my SceneChanger via a lambda in a PostLoad() Action.
In the process, I discovered that you can't unload scenes during a trigger. https://forum.unity3d.com/threads/unity-hangs-on-scenemanager-unloadscene.380116/ Putting the whole method in a coroutine allows us to yield until the load is done. In order to ensure that the coroutine doesn't execute the unload in the middle of the frame, we yield until the end of the frame.
Gate.cs:
void OnTriggerEnter2D(Collider2D other)
{
Action postLoad = () =>
{
if (string.IsNullOrEmpty(otherGate)) { return; }
Gate newGate = GameObject.Find(otherGate).GetComponent<Gate>();
newGate.SetOccupied();
other.transform.position = newGate.transform.position;
};
StartCoroutine(SceneChanger.ChangeScene(otherScene, this.gameObject.scene.name, postLoad));
}
SceneChanger.cs:
public static IEnumerator ChangeScene(string newSceneName, string oldSceneName, Action postLoad)
{
if (IsSceneNameValid(newSceneName, oldSceneName))
{
yield return SceneManager.LoadSceneAsync(newSceneName, LoadSceneMode.Additive);
}
SceneManager.SetActiveScene(SceneManager.GetSceneByName(newSceneName));
postLoad();
if (IsSceneNameValid(oldSceneName, newSceneName)
&& !string.IsNullOrEmpty(newSceneName))
{
yield return new WaitForEndOfFrame();
SceneManager.UnloadScene(oldSceneName);
}
}
Cool. Setting up dontdestroyonload was always a pain. It looks like this new method is just a formal way to do what we've been doing before (you had to pick a manager scene before, where the DDOL was loaded, or else do some error-prone dance where every scene checked for it and maybe loaded it.)
The most standard way to represented non-pointer pointers is with ints. Likes scene# and door# in a struct. With strings, you can have problems with misspellings, upper/lowercase, letter-O vs. zero. The more descriptive the strings get, the more change for errors.
Yeah, it seems like they're encouraging a transition from a manager object to a manager scene. Also, if anyone has a better or more comprehensive answer, I'd rather accept that than my own.
Answer by tanoshimi · Jan 11, 2017 at 09:05 AM
We do this by having a [DontDestroyOnLoad] manager object that persists between scenes and holds a dictionary containing reference pairs of all the connected entrance/exits in different scenes.
Whenever the player leaves a scene, the ID of the scene exit through which they left is looked up. This gives the ID of the scene that needs to be loaded and the entrance in that scene at which the player prefab is instantiated.
I was afraid that would be the answer. Thanks, though!
What kinds of types do you store in your dictionary, and how does the manager use those to distinguish between entrances / spawn points in the second scene?
$$anonymous$$y first idea is just to store strings that contain object names, but that seems like a brittle reference system.
Answer by yassir_amry · Oct 29, 2017 at 06:20 AM
If you don't mind, I recommend this official tutorial
Yea I've watched that series several times and they don't fully explain dictionaries and how to write your own.
Answer by brian1gramm1 · Oct 29, 2017 at 01:32 AM
If (this is old) print("Opps!") Else { Okay, I'm an intermediate coder, but a newbie at Unity (three months). I understand most of what this topic is about, but I wanted to ask a question about my own project, which seems to relate. I created a point and click game (3D) where you interact with NPC's and they give you quests. Click on the quest object, and the quest completes (generally speaking). However, I realized late into my coding that I can't just create a new scene and add all my code to new characters and have them understand what is going on in other scenes. Using GameObject.Find() in every script would open up too many glitches (Null References). I created my code to me modular, drag and drop, for easy creation of new characters, quests and quest objectives, etc, so I wouldn't have to keep adding more code with new elements. But, now that I can't create new scenes, I either have to create a massive world, or rewrite my entire code from scratch to handle scene movement. AKA what this topic is all about. I've just started learning about Lambda expressions and also had the idea of string fields, but wondered if it was worth the effort. I'd love to explain what I"m working on if anyone has the time to work with me on it. }