Search Unity

[Solved] How to reference a scene in your custom script in inspector?

Discussion in 'Scripting' started by Tymianek, Nov 9, 2020.

  1. Tymianek

    Tymianek

    Joined:
    May 16, 2015
    Posts:
    97
    In this post I'm talking about references to scenes in the inspector, like this:
    upload_2020-11-9_18-28-12.png

    This is the best I found, it's almost usable:
    https://github.com/starikcetin/unity-scene-reference
    but it has:
    - Poor support for lists.
    - References are completely lost when you multi-edit.
    - Has unnecessary features and adds a lot of clutter.
    - Sometimes bugs out with a full-page red error in the console.

    Other methods I found were pretty bad, like

    Scene picker from https://docs.unity3d.com/ScriptReference/SceneAsset.html
    - No support for lists
    - Needs Components attached to GameObjects
    - Can't be used as a simple field in your scripts
    - Loses the reference when the name is changed

    Storing a scene as an Object reference is good for prototyping, but it doesn't work in a build.
    Storing a scene in string works in a build, but it obviously doesn't track name changes.
    Also found a couple of Github gists, but they had the same issues.

    Do you have any solutions to this?
    Or, how do you store references to scenes?
    Or, how can we move SceneAsset outside UnityEditor?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    I just use strings. It is ... sub-optimal shall we say, an unfortunate shortcoming of Unity.
     
  3. Tymianek

    Tymianek

    Joined:
    May 16, 2015
    Posts:
    97
  4. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    81
    I feel your pain. I was surprised that there aren't any good implementations for this - I guess everybody is doing their own thing.
    I've also stumbled upon unity-scene-reference implementation and was not happy. It didn't work with ScriptableObjects and didn't dirtify the objects outside the scene - the user never sees pending changes even when looking the scene reference in the inspector. But it has a good idea - using the ISerializationCallbackReceiver which gets called during build allowing it to bake the final scene paths to the player build.

    I've created my own implementation utilizing the ISerializationCallbackReceiver interface - it keeps everything as simple as possible giving you what you expect:

    The green "+" and red "-" buttons respectively add or remove the scene from the build settings.



    Check it out here: https://github.com/NibbleByte/UnitySceneReference

    Cheers
     
  5. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    I am also using string but may I ask if you have a good solution for the case that I decide to rename or move my scenes? Do I have to find all those string references by hand?
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    I almost always have string constants defined for them;

    Code (csharp):
    1. public const string s_Mainmenu = "mainmenu1";
    2. public const string s_Options = "options1";
    I also rarely rename my scenes. Very frequently when I make a scene I tack on a "1" and if I end up changing it, it only becomes a "2" for instance, and I just switch it over to the "2" variant in code.

    In the rare cases I have to put scene name strings in a scene or prefab, I never put it there.

    Instead I define a ScriptableObject, make an instance of it and use that, placing that reference in the scene.

    And I keep all those in one directory so I can easily find and hand-edit the strings.
     
    Olipool likes this.
  7. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Oh... nice idea with the scriptable objects, I will change that in my project right now :) I need that in the inspector to let the game manager know which scenes are levels in the game.
    And in my case, it was not a renaming but reorganizing my project folder e.g. putting everything under a new root folder.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Just so you know, if you don't have duplicate scene names, you don't need to give it a full path.

    For instance loading
    mainmenu
    will work regardless of which directory it is in, as long as it's in your scene list (Editor Build Settings).

    The only catch is if you move it, sometimes you also must enter the Editor Build Settings and enable/disable it for Unity to figure out the new path.
     
    Olipool likes this.
  9. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Oh, that is good to know.
    I am in a "3 games in one project" situation where every game has its "MainMenu" scene, but of course I can make the names unique.

    Thanks! :)
     
  10. Dermotek

    Dermotek

    Joined:
    May 7, 2019
    Posts:
    4
    Thanx so good point :)
     
  11. Laym_

    Laym_

    Joined:
    Apr 22, 2021
    Posts:
    9
    You can use "UnityEngine.SceneManagement.Scene" i believe!
     
  12. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    I was the maintainer of the project mentioned in the original post. That project is now archived. I have written a new library from scratch for this purpose:

    https://github.com/starikcetin/Eflatun.SceneReference

    Give it a go and don't hesitate to open issues if you experience any problems.
     
    Selmar and SisusCo like this.
  13. ChocoDarkStudio

    ChocoDarkStudio

    Joined:
    May 22, 2020
    Posts:
    2
    This also works on prefabs and when exporting:

    Code (CSharp):
    1.     // the scene in string
    2.     [HideInInspector]
    3.     public string targetScene;
    4.  
    5. #if UNITY_EDITOR
    6.  
    7.     // the scene in asset
    8.     public UnityEditor.SceneAsset targetSceneAsset;
    9.  
    10.     // whenever you modify the scene in the project, this sets the new name in the
    11.     // targetScene variable automatically.
    12.     private void OnValidate()
    13.     {
    14.         targetScene = "";
    15.         if (targetSceneAsset != null)
    16.         {
    17.             targetScene = targetSceneAsset.name;
    18.         }
    19.     }
    20.  
    21. #endif
     
    Last edited: Feb 28, 2023
  14. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,326
    @starikcetin Those dynamically appearing Add to Build and Enable in Build buttons are an amazing idea! And you're serializing the reference using the asset guid (and Object reference to boot), just as it should be, so renaming the scenes won't be an issue - just perfect!

    I can't really think of anything I'd change... well, maybe a context menu item or something for opening Build Settings? (
    EditorApplication.ExecuteMenuItem("File/Build Settings...")
    ) I think I'd rather go to Build Settings and add the scene there manually if it's missing, so I can control it's exact placement, rather than just press the Add to Build button and leave it at that.

    Edit: The dialog that opens when you press the Add to Build button could actually also contain an Open Build Settings... option. That would be more useful for me than the Add to Build as Disabled option.
     
    Last edited: Feb 28, 2023
  15. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,326
    @ChocoDarkStudio Do note that OnValidate does not only get called when changes are made to the component in the Inspector, but also when the component is loaded in the editor. This means that your serialized scene data could get wiped clean when you open a scene or inspect a prefab with your current implementation.

    One quick fix for this is to check whether or not the currently selected objects contain the component's game object, and assume that changes are being made in the editor only if they do.
    Code (CSharp):
    1. private void OnValidate()
    2. {
    3.     if(targetSceneAsset != null)
    4.     {
    5.         targetScene = targetSceneAsset.name;
    6.     }
    7.     else if(IsProbablyBeingEditedInTheInspector())
    8.     {
    9.         targetScene = "";
    10.     }
    11.  
    12.     bool IsProbablyBeingEditedInTheInspector() => Array.IndexOf(Selection.gameObjects, gameObject) != -1;
    13. }
    This is not 100% fool-proof though, since objects could also be edited in a locked Inspector or a Properties... window, in which case the object might be being edited, but the code wouldn't know to clear the targetScene data even if the user sets the SceneAsset value to null.

    Another option is to always just ignore the first OnValidate call. I believe this method should work reliably.
    Code (CSharp):
    1. private bool firstOnValidateHasOccurred;
    2.  
    3. private void OnValidate()
    4. {
    5.     if(!firstOnValidateHasOccurred)
    6.     {
    7.         firstOnValidateHasOccurred = true;
    8.         return;
    9.     }
    10.  
    11.     OnValuesChangedInTheInspector();
    12. }
    13.  
    14. private void OnValuesChangedInTheInspector()
    15. {
    16.     targetScene = "";
    17.     if(targetSceneAsset != null)
    18.     {
    19.         targetScene = targetSceneAsset.name;
    20.     }
    21. }
     
  16. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    Thank you for your kind words. That's a very nice idea. Would you mind opening an issue on GitHub for it as a feature request?
     
  17. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,326
    starikcetin likes this.