Edit: this script is awesome, and if you combine it with a ScriptableObject you’re ready to go!
Retrieving the names of your scenes at runtime with Unity is a common problem. The API of Unity doesn’t provide an easy way do to so. With the quite recent SceneManager, I thought Unity had finally provided something to get information about your scenes you have added in the Build Settings. No way !
I faced this problem when implementing a little configuration GUI, where the user has the possibility to indicate the order the scenes will be played. Yes, I could have hard-coded this, but it’s not the elegant way in my opinion. Thus, I made some investigations to get once for all the names of my scenes.
Editor Build Settings
First of all, I needed the names of my scenes, and the only place I found them was inside the Editor Build Settings
which is not documented by Unity . Oh gosh, it starts badly. This class provides a static property to get the information about the scenes available in your Build Settings (enabled AND disabled). Then, simply loop into the array and get the name of your scenes in the same order they have been added to the Build Settings !
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes ; for( int i = 0 ; i < scenes.Length ; ++i) { if( scenes[i].enabled ) Debug.Log( "Scene #" + i + " : " + scenes[i].path + " is enabled" ); else Debug.LogWarning( "Scene #" + i + " : " + scenes[i].path + " is not enabled" ); }Oh, you thought you would be able to copy-paste this code directly in your script ? I am sorry, it’s not that easy, I would not have written this post for only 6 lines of code ! 😉
As you may have noticed, the class is called EditorBuildSettingsScene, then, it belongs to the
UnityEditor
namespace, and, as you surely know, the classes in this namespace can’t be used in a standalone build.In the editor, we can easily display this list using the MenuItem attribute like this :
using UnityEngine; using UnityEditor; public class GetScenesNamesFromEditor { [MenuItem( "Scenes Names/Save Scenes Names" )] private static void GetScenesNames() { EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes; for ( int i = 0 ; i < scenes.Length ; ++i ) { if( scenes[i].enabled ) Debug.Log( "Scene #" + i + " : " + scenes[i].path + " is enabled" ); else Debug.LogWarning( "Scene #" + i + " : " + scenes[i].path + " is not enabled" ); } } }Now, you will tell me “That doesn’t solve the problem of retrieving the names of my scenes in my game, I can only call this function in my editor !” , yes, indeed ! Since we can’t use directly this function in our game, let’s use them indirectly ! How ? One word : Serialization !
Serializing a Scriptable Object
Let’s start by defining what is serialization :
Serialization is the process of converting an object into a given format in order to store this object (into a file, inside a database, …) or transmit it across a network connection link. Its main purpose is to save the state of an object in order to be able to recreate it when needed, eventually in another program context. The reverse process is called deserialization.
You surely have guessed what I want to do here :
- Save the names of my scenes from the editor
- Retrieve them at runtime
There are so many ways to do this, how about using a database ? No, I am joking, here, around ten strings must be saved. I can use a XML, JSON, YAML file or even a simple text file with one scene name per line, but I wanted to take a look at Unity’s scriptable objects.
ScriptableObject is a class that allows you to store large quantities of shared data independent from script instances.
Source : Unity Manual
Most often, scriptable objects are used as assets which are only meant to store data. Nice, it’s our case here !So let’s define our data structure, containing only a simple array of strings. Feel free to add another array if you want to know whether the scenes are enabled or not.
using UnityEngine; public class ScenesList : ScriptableObject { public string[] scenesNames; }Now, we will be able to create a new ScriptableObject using the ScriptableObject.CreateInstance, the only way to instantiate such object (the new operator doesn’t work). Then, we will fill the array of the scriptable object and store it in a place we will be able to retrieve it later to deserialize it.
Scriptable object have been designed to be easily serialized and deserialized under the form of assets. Thus, we will use the static functions provided in the AssetDatabase class.
[MenuItem( "Scenes Names/Save Scenes Names" )] private static void SaveScenesNames() { EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes; // First, try to load the list if already exists ScenesList list = (ScenesList) AssetDatabase.LoadAssetAtPath("Assets/Resources/ScenesList.asset", typeof(ScenesList)) ; // If doesn't exist, create it ! if( list == null ) { list = ScriptableObject.CreateInstance<ScenesList>() ; AssetDatabase.CreateAsset( list, "Assets/Resources/ScenesList.asset" ); } // Fill the array list.scenesNames = new string[scenes.Length]; for ( int i = 0 ; i < scenes.Length ; ++i ) { list.scenesNames[i] = scenes[i].path ; } // Writes all unsaved asset changes to disk AssetDatabase.SaveAssets(); }If you want to only save the enabled scenes, use a temporary List (in the System.Collections.Generic namespace) :
[MenuItem( "Scenes Names/Save Scenes Names" )] private static void SaveScenesNames() { EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes; List<string> scenesNames = new List<string>(); // First, try to load the list if already exists ScenesList list = (ScenesList) AssetDatabase.LoadAssetAtPath("Assets/Resources/ScenesList.asset", typeof(ScenesList)) ; // If doesn't exist, create it ! if( list == null ) { list = ScriptableObject.CreateInstance<ScenesList>() ; AssetDatabase.CreateAsset( list, "Assets/Resources/ScenesList.asset" ); } // Fill the array for ( int i = 0 ; i < scenes.Length ; ++i ) { if( scenes[i].enabled ) scenesNames.Add( scenes[i].path ); } asset.scenesNames = scenesNames.ToArray(); // Writes all unsaved asset changes to disk AssetDatabase.SaveAssets(); }Retrieving the names of the scenes
In the last script, I decided to store the list inside my Resources folder to easily retrieve it using a single line of code in any of my scripts :
ScenesList list = Resources.Load<ScenesList>( "ScenesList" );However, you could have stored the asset in an other folder, then, declared a public ScenesList in the script needing the list of scenes’ names, and dragged & dropped the created asset in the inspector.
public class RetrieveScenesNames : MonoBehaviour { public ScenesList list ; private void Start() { // Here I suppose you have saved the List in your Resources folder if( list == null ) list = Resources.Load<ScenesList>( "ScenesList" ); for( int i = 0 ; i < list.scenesNames.Length ; ++ i ) { Debug.Log( list.scenesNames[i] ); } } }And … voilà ! You have the names of your scenes at runtime ! Or the path of your scenes to be more precise. If you want only the name, I suggest you to use the magic of the Regular expressions (in the
System.Text.RegularExpressions
namespace) . Use it when you store the data or when you use it :private void Start() { Regex regex = new Regex( @"([^/]*/)*([\w\d\-]*)\.unity" ) ; for( int i = 0 ; i < list.scenesNames.Length ; ++i ) { // Will replace the full path by the second capturing group // corresponding to the name of the scene Debug.Log( regex.Replace( list.scenesNames[i], "$2" ) ); } }Warning
With the last script used to store into the scriptable object, keep in mind that the information won’t be dynamically updated if you make modifications of your scenes in the Build Settings or if you rename your scenes. You will have to call the
SaveScenesNames
using the Menu item.If you don’t want to do this by hand, you could listen for the event
EditorApplication.playmodeStateChanged
and save the names of the scenes when you click on the Play Button of the editor. Take a look here for an example of how to use it.
Hi!
First, thanks for this post, i’m learning some new things here (in fact i can only learn new things as i am really not a programmer!)
But as i am a newbie, there are some things i didn’t understood :p
If you don’t mind i will try to expose my problem:
I have copy your script (the part that was useful for me), named it BuildScenesList, and putted it in the Editor folder. And it works, i have my newly asset ScenesList in the Resources folder, great!
using UnityEditor;
using UnityEngine;
public class ScenesList : ScriptableObject
{
public string[] scenesNames;
[MenuItem(“Scenes Names/Save Scenes Names”)]
private static void SaveScenesNames()
{
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
// First, try to load the list if already exists
ScenesList list = (ScenesList)AssetDatabase.LoadAssetAtPath(“Assets/Resources/ScenesList.asset”, typeof(ScenesList));
// If doesn’t exist, create it !
if (list == null)
{
list = ScriptableObject.CreateInstance();
AssetDatabase.CreateAsset(list, “Assets/Resources/ScenesList.asset”);
}
// Fill the array
list.scenesNames = new string[scenes.Length];
for (int i = 0; i < scenes.Length; ++i)
{
list.scenesNames[i] = scenes[i].path;
}
// Writes all unsaved asset changes to disk
AssetDatabase.SaveAssets();
}
}
But when using this next code in an other script…:
ScenesList list = Resources.Load(“ScenesList”);
for ( int i = 0 ; i < list.scenesNames.Length ; ++ i )
{
Debug.Log( list.scenesNames[i] );
}
…It tells me that "The type or namespace name "ScenesList" could not be found blablabla"
So, as i don't know what to do i just recreated a Class in that same script….
public class ScenesList : ScriptableObject
{
public string[] scenesNames;
}
…. Which makes disappears the error, but when pressing the play button, i get a nullReferenceException on the list.scenesNames.Length!
I'm only sure of one thing: I don't know what i'm doing !!
I you have time to explain me what i didn't get, it would be really nice. If not, thanks again anyway.
And by the way, i'm only trying to use this script because i want to use the SceneManager.Scene.buildindex based on the NAME of the scene but there is this problem of the SceneManager only having the loaded scenes and not the buildsettings scenes… Perhaps i'm not even looking in the right direction!!
My bad, i just had to put
public class ScenesList : ScriptableObject
{
public string[] scenesNames;
}
in its how script, sorry!
this is the closest i’ve come to a solution to search scenes within a game or at runtime to be accurate…
i’ve an input field to search and need the scene to pop up as i type… then if i hit the corresponding name then load the scene that the name is attached to…
it doesn’t seem to be too hard but it seems really hard!
I also need this to happen. This is the best I’ve found but haven’r tried it yet…\
Nice solution!
Maybe using EditorBuildSettings.sceneListChanged event to keep track of the changes on the scenes of Build Settings, could be a good solution to update the scriptableobject asset dinamically.
https://docs.unity3d.com/ScriptReference/EditorBuildSettings-sceneListChanged.html
This seems to be possible at runtime now without any editor scripting necessary:
https://docs.unity3d.com/ScriptReference/SceneManagement.SceneUtility.GetScenePathByBuildIndex.html