Inspector Field for Scene Asset

Pretty straightforward question: is there a way to create an inspector field for either a Scene asset or an array of Scene assets?

I’m trying to create a custom editor window that allows our designers to easily modify our level packs and the levels in them. It would be ideal if, rather than having to add scenes manually to the Build Settings list, I could provide an inspector field to slot in a Scene (.unity3d) asset. This way, I could throw warnings when a level isn’t assigned a scene file, and ensure that all the scenes needed for the game are included when we do a build.

So far, this seems much harder than necessary because I can’t find an appropriate asset type that corresponds to the scene asset type.

Thanks!

I have come across this issue a few times and I don’t want to make a custom inspector for every class I need a scene reference I need so I have made this script.

You can just use it like public SceneField myScene and drag a Scene to the Inspector and use Application.loadLevel( myScene ) or SceneManager.LoadScene( myScene ) and it will work. It will be serialized and work in builds. Include this script in your project and never worry about it again.

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[System.Serializable]
public class SceneField
{
	[SerializeField]
	private Object m_SceneAsset;

	[SerializeField]
	private string m_SceneName = "";
	public string SceneName
	{
		get { return m_SceneName; }
	}

	// makes it work with the existing Unity methods (LoadLevel/LoadScene)
	public static implicit operator string( SceneField sceneField )
	{
		return sceneField.SceneName;
	}
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(SceneField))]
public class SceneFieldPropertyDrawer : PropertyDrawer 
{
	public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
	{
		EditorGUI.BeginProperty(_position, GUIContent.none, _property);
		SerializedProperty sceneAsset = _property.FindPropertyRelative("m_SceneAsset");
		SerializedProperty sceneName = _property.FindPropertyRelative("m_SceneName");
		_position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label);
		if (sceneAsset != null)
		{
			sceneAsset.objectReferenceValue = EditorGUI.ObjectField(_position, sceneAsset.objectReferenceValue, typeof(SceneAsset), false); 

			if( sceneAsset.objectReferenceValue != null )
			{
				sceneName.stringValue = (sceneAsset.objectReferenceValue as SceneAsset).name;
			}
		}
		EditorGUI.EndProperty( );
	}
}
#endif

Found a workable solution through experimentation, thought I would share:

There is no class exposed in Unity’s API to create an inspector reference to a Scene, but it turns out that a Scene does inherit from Object, so it is possible to have an Object reference and then slot in a Scene asset:

public Object[] mySceneAssets;

The only problem with this solution is that you can’t really enforce that the list contain only Scene objects at compile time; there is nothing stopping someone on the team from inserting a reference to some other asset entirely. Still, it is better than nothing, and you can do some runtime checks to make sure it works.

With the scene asset reference, you can use the AssetDatabase object to get path information, and manually add the scene data to the build settings list of scenes.

List<EditorBuilderSettingsScene> scenes = new List<EditorBuilderSettingsScene>();

//have some sort of for loop to cycle through scenes...
string pathToScene = AssetDatabase.GetAssetPath(mySceneAssets*);*

EditorBuildSettingsScene scene = new EditorBuildSettingsScene(pathToScene, true);
scenes.Add(scene);
//later on…
EditorBuildSettings.scenes = scenes.ToArray();

Just storing a path string (as the SceneAsset documentation suggests) is inadequate for production, because if the scene file is renamed or moved you’ve lost your reference. We can use an Object reference to the SceneAsset but we no longer have access to AssetDatabase to look up the path when not in editor.

To reliably handle both file renames and runtime paths you need two serialized pieces of data (Object reference in editor, string path at runtime). You can create a SceneReference object that uses ISerializationCallbackReceiver to ensure that the stored path is always valid based on the specified SceneAsset Object.

As an example, here is my approach to this problem: [Unity3D] A Reliable, user-friendly way to reference SceneAssets by script. · GitHub

alt text


Features

  • Custom PropertyDrawer that displays the current Build Settings status, including BuildIndex and convenient buttons for managing it with destructive action confirmation dialogues.
  • If (and only if) the serialized Object reference is invalid but path is still valid (for example if someone merged incorrectly) will recover object using path.
  • Buttons collapse to smaller text if full text cannot be displayed.
  • Includes detailed tooltips and respects Version Control if build settings is not checked out (tested with Perforce)

alt text

UnityEngine.SceneAsset is not recognized by the compiler, but you can check if the Object.ToString() ends with “(UnityEngine.SceneAsset)”.
I used a workaround to create a level manager which creates the order in which levels will be played.

This is how I managed it:

public string LevelName;
private Object _levelScene;
public Object LevelScene
{
	get {return _levelScene;}
	set
	{
	    //Only set when the value is changed
		if(_levelScene != value && value != null)
		{
			string name = value.ToString();
			if(name.Contains(" (UnityEngine.SceneAsset)"))
			{
				_levelScene = value;
				LevelName = name.Substring(0,name.IndexOf(" (UnityEngine.SceneAsset)"));
			}
		}
	}
}

And in editor window:

LevelScene = EditorGUILayout.ObjectField("Scene",LevelScene,typeof(Object),false);

Later, I can load the correct level using the LevelName.

I’m assuming you could make a script or such and have a public variable so it can be assigned in the editor window. I am unsure ifna similar thing applies to asset packages.

I found easier to simply create something to identify my scenes, like an enum, than add this field to my class:

public enum Scenes {
	Master,
	Stage_01_main,
	Stage_01_buildings,
	//Etc...
}

public class MyStageLoader : UnityEngine.MonoBehaviour {
	public Scenes myScene; //exposed

	private void Start() {
		UnityEngine.SceneManagement.SceneManager.LoadScene((int)myScene);
	}
}

Now, you must add ALL scenes you want to load to your Scenes In Build (Ctrl + Shif + B) and order them as you did in your enum (or vice-sersa).

|Scene | Build Index |    
Master - 0
Stage_01_main - 1
Stage_01_buildings - 2
...
Final_Boss - 50
  • (int)myScene will cast int to Scenes, so if you selected “Master” in Inspector, it results 0;
  • LoadScene(int) loads a scene by its index, in this example, Master scene.

Here’s a simple solution that I just found that worked for my game. I attached this “scene loader” script to an object with a collider on it. This lets you drag a scene from your project files into the inspector. You can rename the scene in your project folder, and it still works with no additional tweaking :slight_smile:


[SerializeField] Object scene;

private void OnTriggerEnter(Collider other) {
    if (other.CompareTag("Player")){
        SceneManager.LoadScene(scene.name);
    }
}

Now you can do this:

[SerializeField] [Scene] private string _gameScene;

It allows you to drag a scene asset into the inspector field and it stores its name.