- Home /
How to get names of all available levels
I know that with Application.loadedLevelName I can get the name of the current level. But how do I get the names of all available levels? My interest in doing this is to create a level-select menu that lists all levels by name.
Answer by Eric5h5 · Nov 08, 2010 at 06:38 PM
I think you'd have to do that manually, since there are no functions that return all level names.
That's a shame, it would be useful to enumerate available level names.
@kruncher: Yes it would be useful ;) I'm pretty sure they implement it the other day, but it's just a $$anonymous$$or issue. Until then you could use the script i've just written.
Unity crew, oh please implement a runtime method to get the names of the available scenes.
What about, you put all your scenes in one folder and using .Net framework IO functions read the content in that folder and listed as available levels.
Other idea would be the use assets files or X$$anonymous$$L with the description (text and image), location and configuration on each level, parse the info and display it.
But is mandatory you add all the scenes at the building configuration.
Answer by Bunny83 · Apr 29, 2012 at 03:09 AM
I've just written a little helper that makes it easier to get the names "manually".
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ReadSceneNames : MonoBehaviour
{
public string[] scenes;
#if UNITY_EDITOR
private static string[] ReadNames()
{
List<string> temp = new List<string>();
foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes)
{
if (S.enabled)
{
string name = S.path.Substring(S.path.LastIndexOf('/')+1);
name = name.Substring(0,name.Length-6);
temp.Add(name);
}
}
return temp.ToArray();
}
[UnityEditor.MenuItem("CONTEXT/ReadSceneNames/Update Scene Names")]
private static void UpdateNames(UnityEditor.MenuCommand command)
{
ReadSceneNames context = (ReadSceneNames)command.context;
context.scenes = ReadNames();
}
private void Reset()
{
scenes = ReadNames();
}
#endif
}
You just have to setup your build settings and add all scenes you want to include and then just press the "Update Scene Names" button. This will store the current active scene names in the public array of the script.
It doesn't matter where you attach it. Just attach it on an object in the scene. When you want to use the level names you can easily read the scenes array of that script by referencing this script instance in your script.
$$anonymous$$eep in $$anonymous$$d to press the Update Scene Names before you create a build / run the game.
You could also make the scenes array static so it can accessed more easily from other scripts.
Well I though so also so I just attached it to one of the game objects but when I press update the array remains empty. Any ideas ?
I currently have 2 scenes in my assets folder though when I press the UpdateScene Names it does not update the array it still remains 0
You have to add the scenes to your build array first. Only the scenes which are "ticked" in the build list are even included in your game. Just Press "File --> Build Settings" and drag and drop the scenes which should be part of your game into the "Scenes in build" list.
Answer by Beijerinc · Oct 20, 2013 at 01:44 PM
I've written a solution, inspired by the lack of viable answer in this thread. Feel free to use it:
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Assets.Scenes.Scripts
{
/// <summary>
/// A context that is always available in the game. It provides global game features and information.
/// </summary>
public static class Game
{
#region Properties
/// <summary>
/// The folder the levels file is located when the game is hosted in the editor.
/// </summary>
private const string EDITOR_LEVELS_FILE_DIRECTORY = "Assets/Scenes/";
/// <summary>
/// The folder the levels file is located when the game has been deployed.
/// </summary>
private const string BUILD_LEVELS_FILE_DIRECTORY = "";
/// <summary>
/// The filename and extension of the file that contains the available levels.
/// </summary>
private const string LEVEL_FILE_NAME = "Levels.ini";
/// <summary>
/// The name of the world object. The world object and all of it's children are notified of game events.
/// </summary>
private const string WORLD_OBJECT = "World";
/// <summary>
/// The name of the game paused event.
/// </summary>
private const string ON_GAME_PAUSED_EVENT = "OnGamePaused";
/// <summary>
/// The name of the game resumed event.
/// </summary>
private const string ON_GAME_RESUMED_EVENT = "OnGameResumed";
/// <summary>
/// Indicates if the levels file has been updated this run. If so, it is not updated again. This only applies to the game when it is hosted
/// in the editor.
/// </summary>
private static bool _hasUpdatedLevelsFile;
/// <summary>
/// The names of all levels in the game.
/// </summary>
private static string[] _levels;
/// <summary>
/// The previous Time.timeScale. This value is used to resume the game at the same pace it was paused at.
/// </summary>
private static float _previousTimeScale;
/// <summary>
/// Indicates if the game is currently paused.
/// </summary>
private static bool _isGamePaused;
/// <summary>
/// Gets the names of all levels in the game.
/// </summary>
public static string[] Levels
{
get
{
if (_levels == null)
{
string directory;
// The directory depends on the environment. In the editor, relative paths can be used.
if (Application.isEditor)
{
directory = EDITOR_LEVELS_FILE_DIRECTORY;
}
else
{
string dataPath = Application.dataPath;
Debug.Log( string.Format("Data path detected at '{0}'.", dataPath) );
directory = Path.Combine(dataPath ?? string.Empty, BUILD_LEVELS_FILE_DIRECTORY);
}
_levels = ReadLevelsFile(directory);
Debug.Log( string.Format( "Discovered level names: {0}.", string.Join( ", ", _levels ) ) );
}
return _levels;
}
}
/// <summary>
/// Gets a value that indicates if the game is currently paused.
/// </summary>
public static bool IsGamePaused
{
get { return _isGamePaused; }
}
#endregion
#region Constructors & Destructors
/// <summary>
/// Initializes the Game.
/// </summary>
static Game()
{
// Get an initial previous time scale so that we won't accidentally resume with a scale of 0.
_previousTimeScale = Time.timeScale;
}
#endregion
#region Game Management
/// <summary>
/// Pauses the game. The game paused event is fired for all game objects in the world object.
/// </summary>
public static void Pause()
{
Debug.Log("Game pausing.");
// Pause the game and indicate that the game is actually paused.
_previousTimeScale = Time.timeScale;
Time.timeScale = 0.0f;
_isGamePaused = true;
// Give every game object in the world the chance to react to the game pausing.
foreach (GameObject @object in GameObject.FindGameObjectsWithTag(WORLD_OBJECT))
{
@object.BroadcastMessage(ON_GAME_PAUSED_EVENT, SendMessageOptions.DontRequireReceiver);
}
Debug.Log(String.Format("Game paused on Time.TimeScale = {0}.", Time.timeScale));
}
/// <summary>
/// Resumes the game. The game resumed event is fired for all game objects in the world object.
/// </summary>
public static void Resume()
{
Debug.Log( "Game resuming." );
// Indicate that the game is unpaused. Should anyone check this value, it would be in the game resume event, which is called just before the game is
// actually resumed.
_isGamePaused = false;
// Give every game object in the world the chance to react to the game pausing.
foreach (GameObject @object in GameObject.FindGameObjectsWithTag(WORLD_OBJECT))
{
@object.BroadcastMessage(ON_GAME_RESUMED_EVENT, SendMessageOptions.DontRequireReceiver);
}
// Unpause the game.
Time.timeScale = _previousTimeScale;
Debug.Log(String.Format("Game resumed on Time.TimeScale = {0}.", Time.timeScale));
}
#endregion
#region Level Management
#if UNITY_EDITOR
/// <summary>
/// This method is called when post-processing a build, which occurs after a build has been made. This method updates the levels file in the build directory.
/// </summary>
/// <param name="target"></param>
/// <param name="pathToBuiltProject"></param>
[UnityEditor.Callbacks.PostProcessBuild]
public static void PostProcessBuild(UnityEditor.BuildTarget target, string pathToBuiltProject)
{
const string DATA_FOLDER = "{0}_Data";
Debug.Log(string.Format("Post-processing build '{0}' at '{1}'.", target, pathToBuiltProject));
// The file name is integrated in some folder/file names of the built game. It may be needed to create references to these dynamic folders/files.
string fileName = Path.GetFileNameWithoutExtension( pathToBuiltProject );
// The build directory is the build path, without file name and extension and appended with the custom path.
string buildDirectory = Path.Combine( Path.Combine( Path.GetDirectoryName( pathToBuiltProject ) ?? string.Empty,
string.Format( DATA_FOLDER, fileName ) ),
BUILD_LEVELS_FILE_DIRECTORY );
Debug.Log(string.Format("Detected levels file directory '{0}'.", buildDirectory));
WriteLevelsFile(buildDirectory);
Debug.Log("Post-processed build.");
}
/// <summary>
/// This method is called when post-processing a scene, which occurs in either the editor when running a scene or at build time when building a scene. This
/// method updates the levels file, if applicable.
/// </summary>
[UnityEditor.Callbacks.PostProcessScene]
public static void PostProcessScene()
{
Debug.Log( "Post-processing scene." );
if ( !_hasUpdatedLevelsFile )
{
// Only write a levels file if we're in the editor. If not, the PostProcessBuild method will do this, because the PostProcessScene is called for all scenes.
if ( Application.isEditor )
{
Debug.Log( string.Format( "Detected editor, writing levels file to '{0}'.", EDITOR_LEVELS_FILE_DIRECTORY ) );
WriteLevelsFile( EDITOR_LEVELS_FILE_DIRECTORY );
_hasUpdatedLevelsFile = true;
}
}
else
{
Debug.Log( "Already updated levels file." );
}
Debug.Log( "Post-processed scene." );
}
/// <summary>
/// Writes or creates the levels file by collecting all levels configured in the build and (re-)writing the levels file at the provided directory.
/// </summary>
/// <param name="directory">The directory to write the levels file to.</param>
private static void WriteLevelsFile(string directory)
{
List<string> levelNames = new List<string>();
// Collect the names of all levels in the build settings.
foreach (UnityEditor.EditorBuildSettingsScene buildSettingsScene in UnityEditor.EditorBuildSettings.scenes)
{
if (buildSettingsScene.enabled)
{
string name = buildSettingsScene.path.Substring(buildSettingsScene.path.LastIndexOf(Path.AltDirectorySeparatorChar) + 1);
name = name.Substring(0, name.Length - 6);
levelNames.Add( name );
Debug.Log(string.Format("Detected level at '{0}' with name '{1}'.", buildSettingsScene.path, name));
}
}
string path = Path.Combine(directory, LEVEL_FILE_NAME);
Debug.Log( string.Format("Writing levels file to '{0}'.", path) );
// Write the names of all levels to a file, so that it can be retrieved when running.
using (FileStream stream = File.Open(path, FileMode.Create, FileAccess.Write))
{
using (StreamWriter writer = new StreamWriter(stream))
{
foreach (string levelName in levelNames)
{
writer.WriteLine( levelName );
}
}
}
}
#endif
/// <summary>
/// Reads the levels file from the provided directory.
/// </summary>
/// <param name="directory">The directory that contains the levels file.</param>
/// <returns>The discovered levels.</returns>
private static string[] ReadLevelsFile(string directory)
{
string path = Path.Combine(directory, LEVEL_FILE_NAME);
Debug.Log(string.Format("Reading levels file from '{0}'.", path));
List<string> levelNames = new List<string>();
if (File.Exists(path))
{
// Read the names of all levels from the levels file.
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read))
{
using (StreamReader reader = new StreamReader(stream))
{
// Possibly use ReadToEnd and string.Split(fileContent, Environment.NewLine).
while (!reader.EndOfStream)
{
levelNames.Add(reader.ReadLine());
}
}
}
}
else
{
Debug.LogWarning("Levels file does not exist, no level names available at run-time.");
}
return levelNames.ToArray();
}
#endregion
}
}
The game pausing logic has nothing to do with it, but it fills in another hole in Unity.
Excuse me for bneing naive, but how do you actually go about using this script in unity? Is it attached to a gameobject... or?
Simply make sure Unity knows about this class by including it in your Unity C# project. It's a static class, so it cannot/should not be attached to a GameObject. It is always available to all other scripts in Unity. Also, you may want to change the paths at the top of the file to suit your situation. By default it outputs the levels file to /Assets/Scenes/Levels.ini while in the Unity editor, and the /{gamename}_Data/Levels.ini when you create a build.
wow. looks like it does the job. thanks! but how do you use it? I mean read the level names. read the .ini file?
Just use the public static string[] Levels property (Game.Levels) and it will automatically load the levels file if it has not already done so.
Answer by dishmop · Feb 12, 2016 at 01:52 PM
Hi,
I've made some mods to Bunny83's solution so that it works with non-editor builds on mac and pc and also on web player (should also work on mobile builds, but haven't tested that).
Usage instructions are in the comments at the top of the file (though feel free to comment if anything is unclear).
// This is a modification of a file posted by "Bunny83" - available here:
// http://answers.unity3d.com/questions/33263/how-to-get-names-of-all-available-levels.html
//
// However, this version works even in non editor builds (but you must have run it in the editor at some point first).
//
// Usage
// ------
// Place this component in one game object somewhere in your scene.
// When it starts up in the editor, it will fill the "scenes" array with the names of all your scenes in the build settings
// it will also save them as a binary file in your Resources folder in assets
//
// When you start this up in non editor mode it will load in the file from your resources folder (this uses the resource managemnet system
// Rather than the raw file IO so should also work on web player builds).., and fills the scene array that way
//
// You can force these functions to be called from the editor by right clicking the component in the inspector
//
// To access the filenames from anywhere in your code, use somethign like
// string sceneName = ReadSceneNames.singleton.scenes[2];
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
public class ReadSceneNames : MonoBehaviour
{
public static ReadSceneNames singleton = null;
public string sceneFilename = "LevelNames";
public string[] scenes;
#if UNITY_EDITOR
void Start(){
Reset ();
}
#else
void Start(){
LoadLevelNames();
}
#endif
void Awake(){
if (singleton != null) Debug.LogError ("Error assigning singleton - Have you got two components of this type in the scene?");
singleton = this;
}
void OnDestroy(){
singleton = null;
}
string BuildLoadFilename(){
return "LevelNames/" + sceneFilename;
}
void LoadLevelNames(){
TextAsset levelNamesAsset = Resources.Load(BuildLoadFilename()) as TextAsset;
if (levelNamesAsset != null){
Stream s = new MemoryStream(levelNamesAsset.bytes);
BinaryReader br = new BinaryReader(s);
int numLevels = br.ReadInt32();
if (numLevels > 0){
scenes = new string[numLevels];
for (int i = 0; i < numLevels; ++i){
scenes[i] = br.ReadString();
}
}
}
}
#if UNITY_EDITOR
private static string[] ReadNames()
{
List<string> temp = new List<string>();
foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes)
{
if (S.enabled)
{
string name = S.path.Substring(S.path.LastIndexOf('/')+1);
name = name.Substring(0,name.Length-6);
temp.Add(name);
}
}
return temp.ToArray();
}
[UnityEditor.MenuItem("CONTEXT/ReadSceneNames/Update Scene Names")]
private static void UpdateNames(UnityEditor.MenuCommand command)
{
ReadSceneNames context = (ReadSceneNames)command.context;
context.scenes = ReadNames();
context.SaveNameFile();
}
[UnityEditor.MenuItem("CONTEXT/ReadSceneNames/Force Reload")]
private static void ForceReload(UnityEditor.MenuCommand command)
{
ReadSceneNames context = (ReadSceneNames)command.context;
context.LoadLevelNames();
}
private void Reset()
{
scenes = ReadNames();
SaveNameFile();
}
void CreateResourceDirs(){
if (!System.IO.Directory.Exists(Application.dataPath + "/Resources")){
System.IO.Directory.CreateDirectory(Application.dataPath + "/Resources");
}
if (!System.IO.Directory.Exists(Application.dataPath + "/Resources/LevelNames")){
System.IO.Directory.CreateDirectory(Application.dataPath + "/Resources/LevelNames");
}
}
string BuildSaveFilename(){
return Application.dataPath + "/Resources/LevelNames/" + sceneFilename + ".bytes";
}
void SaveNameFile(){
CreateResourceDirs();
Debug.Log ("Saving level names..." + BuildSaveFilename());
FileStream file = File.Create(BuildSaveFilename());
BinaryWriter bw = new BinaryWriter(file);
bw.Write(scenes.Count ());
for (int i = 0; i < scenes.Count(); ++i){
bw.Write(scenes[i]);
}
bw.Close();
file.Close();
// Ensure the assets are all realoaded and the cache cleared.
UnityEditor.AssetDatabase.Refresh();
}
#endif
}
Really awesome. Thanks for making this such an easy solution to implement. Deserves to be voted best answer!
Answer by tarahugger · Aug 21, 2014 at 06:37 AM
I put something together for my purposes based on Beijerinc's code. For those of you using Full Inspector 2.4+ - this creates/updates a ScriptableObject rather than a txt file. You can attach your own properties to a level, and changes made in play or edit mode are persisted. Its not pretty but might be useful for ideas.
Looks like this in inspector
GameEvents.cs
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
[InitializeOnLoad]
public static class GameEvents
{
private static bool _isInPlayMode;
private static bool _isInEditMode;
private static bool _preloadStarted;
static GameEvents()
{
EditorApplication.playmodeStateChanged += PlaymodeStateChanged;
}
public delegate void PlayModeStateDelegate();
public static event PlayModeStateDelegate OnStartPlay;
public static event PlayModeStateDelegate OnEndPlay;
public static event PlayModeStateDelegate OnStartEdit;
public static event PlayModeStateDelegate OnEndEdit;
private static void PlaymodeStateChanged()
{
if (Application.isEditor && !Application.isPlaying && !_isInPlayMode)
{
if (_isInEditMode)
{
_preloadStarted = true;
_isInEditMode = false;
//Debug.Log("Entered Edit Mode");
if (OnStartEdit != null)
OnStartEdit.Invoke();
}
else
{
//Debug.Log("Exit Edit Mode");
if (OnEndEdit != null)
OnEndEdit.Invoke();
}
}
if (Application.isEditor && _isInPlayMode)
{
_isInPlayMode = false;
_isInEditMode = true;
//Debug.Log("Exited Play Mode");
if (OnEndPlay != null)
OnEndPlay.Invoke();
}
else
{
if (!Application.isPlaying) return;
if (!_isInPlayMode)
{
//Debug.Log("Entered Play Mode");
if (OnStartPlay != null)
OnStartPlay.Invoke();
}
_isInPlayMode = true;
}
}
}
SceneManager.cs
using System.IO;
using FullInspector;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
[InitializeOnLoad]
public class SceneManager
{
private const string EDITOR_LEVELS_FILE_DIRECTORY = "Assets/_Scenes/";
private const string BUILD_LEVELS_FILE_DIRECTORY = "";
private const string LEVEL_FILE_NAME = "Scenes";
private static string pathToAsset;
private static SceneCollection cachedAsset;
private static string serializedState;
private static int hash;
private static bool objectChanged;
static SceneManager()
{
GameEvents.OnEndPlay += GameEventsOnOnEndPlay;
GameEvents.OnStartPlay += GameEventsOnOnStartPlay;
GameEvents.OnStartEdit += GameEventsOnOnStartEdit;
}
private static void GameEventsOnOnStartEdit()
{
if (objectChanged)
{
Debug.Log("Loading State..");
cachedAsset.RestoreState();
}
}
private static void GameEventsOnOnStartPlay()
{
//Debug.Log("Caching..");
var sceneNames = GetSceneNames();
var asset = SceneCollectionAsset;
asset.Refresh(sceneNames);
asset.SaveState();
cachedAsset = asset;
serializedState = Serialize(asset.MyScenes2);
}
private static string Serialize(SceneHolder sceneHolder)
{
return SerializationHelpers.SerializeToContent<SceneHolder, JsonNetSerializer>(sceneHolder);
}
private static void GameEventsOnOnEndPlay()
{
if (Serialize(cachedAsset.MyScenes2) != serializedState)
{
//Debug.Log("Object has changed, Saving State...");
cachedAsset.SaveState();
objectChanged = true;
}
else
{
objectChanged = false;
}
}
private static string PathToAsset
{
get
{
return Path.Combine(EDITOR_LEVELS_FILE_DIRECTORY, LEVEL_FILE_NAME + ".asset");
}
}
private static SceneCollection SceneCollectionAsset
{
get
{
return AssetDatabase.LoadMainAssetAtPath(PathToAsset) as SceneCollection ??
ScriptableObjectCreator.CreateAsset<SceneCollection>(PathToAsset);
}
}
private static List<string> GetSceneNames()
{
// Collect the names of all levels in the build settings.
return (from buildSettingsScene in EditorBuildSettings.scenes
where buildSettingsScene.enabled
select buildSettingsScene.path.Substring(buildSettingsScene.path.LastIndexOf(Path.AltDirectorySeparatorChar) + 1)
into name
select name.Substring(0, name.Length - 6)).ToList();
}
public static class ScriptableObjectCreator
{
public static T CreateAsset<T>(string path) where T : ScriptableObject, new()
{
var asset = ScriptableObject.CreateInstance<T>();
if (string.IsNullOrEmpty(path))
{
Debug.Log("No path provided");
return new T();
}
string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path);
AssetDatabase.CreateAsset(asset, assetPathAndName);
AssetDatabase.SaveAssets();
return AssetDatabase.LoadMainAssetAtPath(path) as T;
}
}
}
SceneObjects.cs
using System;
using System.Linq;
using FullInspector;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class Scene
{
public int BuildOrder;
public string Name;
public string Description;
public ISceneProperties Properties;
}
public interface ISceneProperties
{
}
[Serializable]
public class MenuScene : ISceneProperties
{
public bool Enabled;
}
[Serializable]
public class LevelScene : ISceneProperties
{
public bool Enabled;
public string Description;
//public Level LevelMarker;
}
[Serializable]
public class SceneHolder
{
[SerializeField]
public List<Scene> Scenes;
}
[Serializable]
public class SceneCollection : BaseScriptableObject<JsonNetSerializer>
{
public void Refresh(List<string> sceneNames)
{
if (MyScenes2 == null)
MyScenes2 = new SceneHolder();
if(MyScenes2.Scenes == null)
MyScenes2.Scenes = new List<Scene>();
sceneNames.ForEach((sceneName, index) =>
{
var scene = MyScenes2.Scenes.FirstOrDefault(s => s.Name == sceneName);
if (scene != null)
{
// Update
scene.BuildOrder = index;
}
else
{
// Add
MyScenes2.Scenes.Add(new Scene()
{
Name = sceneName,
BuildOrder = index
});
}
});
MyScenes2.Scenes = MyScenes2.Scenes.OrderBy(scene => scene.BuildOrder).ToList();
}
[SerializeField]
public SceneHolder MyScenes2;
}
I also use this extension.
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
public static class LinqExtensions {
public static void ForEach<T>(this IEnumerable<T> ie, Action<T, int> action)
{
var i = 0;
foreach (var e in ie) action(e, i++);
}
}