Scene Name Attribute

Lots of times, I have scripts that need to be configured with the name of a specific scene. A button might need the name of the menu it loads when clicked, or a door trigger might need to know which level to transition to. Scenes are represented by strings, and therefore are easy to mistype in the Inspector. But in the editor, we have the luxury of a comprehensive list of the scenes in our build, something I leveraged before in my Scene Switch window. Let’s use that same idea to power an attribute (and attribute drawer) to provide a convenient and friendly way to enforce correct scene names every time.

// --------------------------------
// <copyright file="SceneNameAttribute.cs" company="Rumor Games">
//     Copyright (C) Rumor Games, LLC.  All rights reserved.
// </copyright>
// --------------------------------

using UnityEngine;

/// <summary>
/// SceneNameAttribute class.
/// </summary>
public class SceneNameAttribute : PropertyAttribute
{
}

Just like the HideInEditModeAttribute, SceneNameAttribute has no special functionality. It’s just a key that activates the SceneNameAttributeDrawer, which handles the real heavy lifting:

// --------------------------------
// <copyright file="SceneNameAttributeDrawer.cs" company="Rumor Games">
//     Copyright (C) Rumor Games, LLC.  All rights reserved.
// </copyright>
// --------------------------------

using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

/// <summary>
/// SceneNameAttributeDrawer class.
/// </summary>
[CustomPropertyDrawer(typeof(SceneNameAttribute))]
public class SceneNameAttributeDrawer : PropertyDrawer
{
    /// <summary>
    /// Draws the GUI for the property.
    /// </summary>
    /// <param name="position">Rectangle on the screen to use for the property GUI.</param>
    /// <param name="property">The SerializedProperty to make the custom GUI for.</param>
    /// <param name="label">The label of this property.</param>
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var sceneNames = EditorBuildSettings.scenes.Where(x => x.enabled).Select(x => Path.GetFileNameWithoutExtension(x.path)).ToList();

        // draw the enumeration selector
        EditorGUI.BeginProperty(position, label, property);
        EditorGUI.BeginChangeCheck();

        var index = Mathf.Clamp(sceneNames.IndexOf(property.stringValue), 0, sceneNames.Count - 1);
        index = EditorGUI.Popup(position, label.text, index, sceneNames.ToArray());

        if (EditorGUI.EndChangeCheck())
        {
            if (index >= 0)
            {
                property.stringValue = sceneNames[index];
            }
            else if (sceneNames.Count > 0)
            {
                property.stringValue = sceneNames[0];
            }
        }

        EditorGUI.EndProperty();
    }
}

The script works like the Scene Switch window, aggregating the scene names from the build settings, but then uses that collection to power a popup menu. This way, assigning a scene name to a string variable is as simple as selecting an item in an enumeration.

Here it is in use, powering a button that loads a specific scene when clicked:

// --------------------------------
// <copyright file="LoadSceneOnClick.cs" company="Rumor Games">
//     Copyright (C) Rumor Games, LLC.  All rights reserved.
// </copyright>
// --------------------------------

using UnityEngine;

/// <summary>
/// LoadSceneOnClick class.
/// </summary>
public class LoadSceneOnClick : MonoBehaviour
{
    /// <summary>
    /// Name of scene to load.
    /// </summary>
    [SceneNameAttribute]
    public string sceneName;

    /// <summary>
    /// Whether to load the next scene asynchronously.
    /// </summary>
    public bool isAsync;

    /// <summary>
    /// If loading asynchronously, whether to disable this script after beginning load.
    /// </summary>
    [HideUnless("isAsync")]
    public bool isOneShot = true;

    /// <summary>
    /// Handles an OnClick event.
    /// </summary>
    public void OnClick()
    {
        if (!string.IsNullOrEmpty(this.sceneName))
        {
            if (this.isAsync)
            {
                Application.LoadLevelAsync(this.sceneName);
                if (this.isOneShot)
                {
                    Destroy(this);
                }
            }
            else
            {
                Application.LoadLevel(this.sceneName);
            }
        }
    }
}

And this is what it looks like in the Inspector:

loadsceneonclick

Easy to implement, easy to set, easy to maintain. No more copy-pasting or typos!

Bookmark the permalink.

Comments are closed