Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How to create a door that transfers between scenes?

Discussion in 'Scripting' started by jleven22, Aug 22, 2019.

  1. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    I have been through four complete tutorials on this, but none of them explains what I'm trying to do.

    Goal: to set up a door in one scene that transports player to an empty object in another scene.

    I'm making a top down RPG and I just want the player to step into a trigger and end up in a specific spot in another scene. No fades, no scene transitions, nothing fancy.

    Is this that hard? How is it that I cannot seem to find a tutorial on this? Thank you!
     
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    No.
    Maybe you didn't search for the right key words? I searched for "unity tutorial scene door" and found out there is a system included in Unity's 3D game kit that's available free on the asset store:

    https://learn.unity.com/tutorial/3d...4897edbc2a001fd5bdd0#5c7f8528edbc2a002053b758

    Scroll down to where it says "teleporting to another scene". You might need to download the 3D game kit to get the components referenced in there, or you might figure out how to make your own system based on the description there.

    There's not always a tutorial for everything. Sometimes you have to break down the problem into steps that you can research and then synthesize it all together. For example you could look up:
    How do I know if a game object is colliding with another? (the player and the door)
    How do I load another scene?
    How do I make a game object persistent during a scene change? (the player)
    How do I change the position of one object to another object's position (the player and the destination object)

    And so on.
     
  3. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,745
    1. Add a collider in trigger mode to that spot.
    2. Add new mono behaviour to the spot.
    3. In the spot script define variables for target scene name and target teleport position.
    4. Use OnTriggerEnter callback to detect when player enters the trigger
    5. Load target scene in background using SceneManager API
    6. When target scene activated, move player to target scene to that position
    7. Unload original scene using the same StaticManager
    8. DONE

    Every line here is about 3-7 clicks and 1-2 lines of code, so it should be easy.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,769
    Here are the parts and steps you need:

    1. a door

    2. a sensing mechanism on the door (trigger, collider, UI element, etc.)

    3. code that senses that "a player has hit this door"

    Do NOT move forward until you at LEAST do something like Debug.Log("Opened Door!") at point 3.

    Now that you have those three things working, here are the next things you need:

    4. a way to know the destination scene and the name of a GameObject in that target scene

    The above can be simply a MonoBehaviour you put on the actual door such as:

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class DoorLinkInfo : MonoBehaviour
    4. {
    5.     public string DestinationSceneName;
    6.     public string DestinationGameObjectName;
    7. }
    Now back at step 3 above, you will do:

    3a. GetComponent<DoorLinkInfo>() on the door object to get a reference to the link info

    3b. Put this reference somewhere static or persistent such as a game manager

    3c. Load the new scene specified by DestinationSceneName

    Finally, in your new scene, when the player spawns, he should:

    5. check for a pending DoorLinkInfo reference, and if he finds it:

    6. Look for the DestinationGameObjectName in scene and if he finds it:

    7. Teleport to that point in the game, whatever that means in your context

    8. In all cases, erase the pending DoorLinkInfo by setting it to null
     
    Joe-Censored likes this.
  5. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    Okay so I did a little variation of this but I'm getting a new error:

    "NullReferenceException: Object reference not set to an instance of an object
    Door.OnTriggerEnter2D (UnityEngine.Collider2D other) (at Assets/Scripts/Door.cs:21)"

    I'm really not sure what this reference is referring to, as I definitely have my player (BitchFaceController) as a public static instance.

    New code placed on door:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5.  
    6. public class Door : MonoBehaviour
    7. {
    8.     public string sceneToLoad;
    9.     public string doorName;
    10.     public string doorNameToLoad;
    11.     public float newPositionAdjustX;
    12.     public float newPositionAdjustY;
    13.  
    14.  
    15.  
    16.     private void OnTriggerEnter2D(Collider2D other)
    17.         {
    18.             if (other.tag == "Player" && !other.isTrigger && BitchFaceController.instance.isDodging == false)
    19.             {
    20.                 Debug.Log("Switched Scenes");
    21.                 SceneManager.LoadScene(sceneToLoad);
    22.                 BitchFaceController.instance.transform.position = new Vector2(GameObject.Find(doorNameToLoad).transform.position.x + newPositionAdjustX, GameObject.Find(doorNameToLoad).transform.position.y + newPositionAdjustY);
    23.             }
    24.         }
    25.  
    26.  
    27. }
    28.  
    Does this make sense? The newPositionAdjustX/Y is intended to allow me to set the player anywhere in relation to "doorNameToLoad"
     
  6. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    Well then, it must be the other object on that line. GameObject.Find(doorNameToLoad)
     
  7. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    First off, GREAT profile pic.

    Second, if I have the string of my Door "A" doorNameToLoad set to the same as Door "B" doorName, then why would this be returning a null reference?

    Is this an issue with Find(string)?
     
  8. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    It must not be able to find the Door. Since you are switching scenes, I think the door with that name is not loaded yet.

    Here's a quote from the documentation:
    https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html

    When using SceneManager.LoadScene, the loading does not happen immediately, it completes in the next frame. This semi-asynchronous behavior can cause frame stuttering and can be confusing because load does not complete immediately.

    You'll probably need to set-up a coroutine or something so that you can wait and try to find the door a little bit later.
     
  9. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    Dang ok, I'm not super familiar with co-routines (pretty new to coding and Unity). Couldn't I just do something in Awake()? Then again, not sure what I would do...
     
  10. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    You mean on Awake on a component that's in the newly loaded scene? That should work. In your OnTrigger, instead of trying to move the instance, just assign a member (call it instance.newDoorName or something like that). Then the door in the new scene can find compare newDoorName with it's own and move the player if it matches.
     
  11. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    Ok I think I understand, but you'll have to help a dummy out. What do you mean by "assign a member?"
     
  12. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    A "member" is just another word for variable that belongs to class. I simply meant adding
    public string newDoorName; 

    to your controller singleton class.

    Then in your OnTrigger enter
    Code (csharp):
    1.  
    2. private void OnTriggerEnter2D(Collider2D other)
    3.         {
    4.             if (other.tag == "Player" && !other.isTrigger && BitchFaceController.instance.isDodging == false)
    5.             {
    6.                 Debug.Log("Switched Scenes");
    7.                 SceneManager.LoadScene(sceneToLoad);
    8.                 BitchFaceController.instance.newDoorName=doorNameToLoad;
    9.             }
    10.         }
    11.  
     
  13. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    Oofh man well I guess I am a dummy because now you lost me.

    How does this accomplish adjusting the transform of the player in the next scene?
     
  14. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    On the destination in the next scene, put some code in the Awake() or Start () method like this:
    Code (csharp):
    1.  
    2. if (BitchFaceController.instance.newDoorName == name)
    3. {
    4.     BitchFaceController.instance.transform.position = new Vector2(transform.position.x + newPositionAdjustX, transform.position.y + newPositionAdjustY);
    5. }
    6.  
    This way, you eliminate the "find". I assumed this is what you meant by "Couldn't I just do something in Awake()?"
     
  15. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    Ok I've been working on this for a few days, and still cannot get anything to work properly.

    Here's what I've got.

    I've got one door gameobject in "Town" and another door gameobject in "Town_Hospital." I've also added a "Checkpoint" gameobject in case the game can't find the doors.

    Door code looks like this:
    Code (CSharp):
    1.     private void Awake()
    2.     {
    3.         if (thisDoorName == GameManager.instance.startDoorName)
    4.         {
    5.             GameManager.instance.doorHasPartner = true;
    6.             GameManager.instance.startPosition = transform.position;
    7.         }
    8.         else
    9.         {
    10.             GameManager.instance.doorHasPartner = false;
    11.         }
    12.  
    13.      
    14.     }
    15.  
    16.  
    17.     private void OnTriggerEnter2D(Collider2D other)
    18.     {
    19.         if (other.tag == "Player" && !other.isTrigger && BitchFaceController.instance.isDodging == false)
    20.         {
    21.             Debug.Log("Switched Scenes");
    22.             GameManager.instance.startDoorName = doorNameToLoad;
    23.             SceneManager.LoadScene(sceneToLoad);
    24.         }
    25.     }
    26.  
    27.  
    28. }
    Player controller start script looks like this:
    Code (CSharp):
    1.         //Set start position
    2.         if(GameManager.instance.doorHasPartner == true)
    3.         {
    4.             transform.position = GameManager.instance.startPosition;
    5.         }
    6.         else
    7.         {
    8.             transform.position = GameObject.Find("Checkpoint").transform.position;
    9.         }
    10.     }

    In my mind, in my theoretical script, this should work solid! But neither checkpoint, nor door location is working. The second scene IS loading... but the player's transform isn't doing jack.

    Might be prudent to note two other thoughts. I have a Scene Essentials controller that loads essential objects into new scenes being opened, namely the Player, UI, and GameManager. I also have DontDestroyOnLoad(transform.gameObject) on the player.

    Second, I am using the script execution order as follows:



    Maybe these two things are affecting the situation?
     
  16. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,357
    There are two problems that are kind-of jumping out at me here:

    all of this GameManager.instance.doorHasPartner stuff in your door's OnAwake is not going to work if you have more than one door. GameManager.instance.doorHasPartner can only have one value, so each door is just going to replace whatever values the previous door entered.

    In your player controller script- is that the same object as BitchFaceController.instance? If so, do create a new instance whenever there is a scene change? I don't suppose that you would, but if it's always the same instance then don't use Start because that'll never be called except at the very beginning of the game.
     
  17. jleven22

    jleven22

    Joined:
    Mar 26, 2019
    Posts:
    399
    Oh my god, two glaring issues. Didn't even think about them. Thank you so much for the prompt reply!

    Yes, player == BitchFaceController.instance. Right now my Essentials Loader script is cloning the player from the previous scene:

    Code (CSharp):
    1.     {
    2.         if(UIFade.instance == null)
    3.         {
    4.             UIFade.instance = Instantiate(UIScreen).GetComponent<UIFade>();
    5.         }
    6.  
    7.         if(BitchFaceController.instance == null)
    8.         {
    9.             BitchFaceController clone = Instantiate(player).GetComponent<BitchFaceController>();
    10.             BitchFaceController.instance = clone;
    11.         }
    12.  
    13.         if(GameManager.instance == null)
    14.         {
    15.             Instantiate(gameManager);
    16.         }
    17.  
    18.     }
     
  18. JTAGames

    JTAGames

    Joined:
    Mar 7, 2019
    Posts:
    13
    We made a tutorial on how to change scenes and end up in the right spot on our channel.



    Hope this helps!
     
    FishDude936 likes this.