Snippets

Alfish Referencing SceneAsset by GUID at runtime in Unity

Created by Alfish last modified
// Public Domain. NO WARRANTIES. License: https://opensource.org/licenses/0BSD

using System.Text.RegularExpressions;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(SceneReference))]
public class SceneReferenceDrawer : PropertyDrawer
{
  static readonly Regex rArrayItemProp = new Regex(@"\.Array\.data\[(\d+)\]$");
  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    if (property.name == "data") {
      var matchArrayItem = rArrayItemProp.Match(property.propertyPath);
      if (matchArrayItem.Success) // property is array item
        label.text = "Element " + matchArrayItem.Groups[1].Value; // label should not be GUID in array item
    }
    if (!property.NextVisible(true)) return;
    // could use 128-bit type to serialize, like rectIntValue; but it would be less legible in text assets
    var oldGuid = property.stringValue;
    // NOTE: doesn't handle missing references, which are shown as "None (Scene Asset)"
    var oldPath = AssetDatabase.GUIDToAssetPath(oldGuid);
    var oldObj = AssetDatabase.LoadAssetAtPath<SceneAsset>(oldPath);
    var newObj = EditorGUI.ObjectField(position, label, oldObj, typeof(SceneAsset), false) as SceneAsset;
    if (newObj == oldObj) return;
    if (newObj == null)
      property.stringValue = "";
    else if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(newObj, out string newGuid, out long _))
      property.stringValue = newGuid;
  }
}
// Public Domain. NO WARRANTIES. License: https://opensource.org/licenses/0BSD

using System;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

class SceneReferencesBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
  const string resourcePath = "Assets/_autoBuild_SceneIndicesByGuid.asset";
  public int callbackOrder => 0;
  public void OnPreprocessBuild(BuildReport report) {
    var sceneReferences = ScriptableObject.CreateInstance<SceneReferences>();
    AssetDatabase.CreateAsset(sceneReferences, resourcePath);
    var preloaded = PlayerSettings.GetPreloadedAssets();
    var n = preloaded.Length;
    Array.Resize(ref preloaded, n + 1);
    preloaded[n] = sceneReferences;
    PlayerSettings.SetPreloadedAssets(preloaded);
  }
  public void OnPostprocessBuild(BuildReport report) {
    var preloaded = PlayerSettings.GetPreloadedAssets();
    if (preloaded != null) {
      int n = preloaded.Length - 1, i = n;
      for (; i >= 0; i--) if (preloaded[i] is SceneReferences) break; // i = Array.FindLastIndex
      if (i >= 0) {
        var newPreloaded = new UnityEngine.Object[n];
        Array.Copy(preloaded, newPreloaded, i);
        Array.Copy(preloaded, i + 1, newPreloaded, i, n - i);
        PlayerSettings.SetPreloadedAssets(newPreloaded);
      }
    }
    AssetDatabase.DeleteAsset(resourcePath);
    AssetDatabase.SaveAssets();
  }
}
// Public Domain. NO WARRANTIES. License: https://opensource.org/licenses/0BSD

using System;
using System.Collections.Generic;
using UnityEngine;
using Guid = System.String; // could use 128-bit int type to serialize, but it would be less legible in text assets

///<summary>SceneReferences contains the GUID to build index mapping, used to reference scenes at runtime.</summary>
///<remarks>This is auto-generated on build as a preloaded asset.</remarks>
public class SceneReferences : ScriptableObject
{
  ///<summary>The singleton instance, automatically preloaded at runtime.</summary>
  public static SceneReferences instance => asset
#if UNITY_EDITOR
  ?? (asset = ScriptableObject.CreateInstance<SceneReferences>())
#endif
  ; static SceneReferences asset;
  ///<summary>The GUID for each scene asset, indexed by its build index in the array.</summary>
  public Guid[] guids => sceneGuids; [SerializeField] Guid[] sceneGuids;
  Dictionary<Guid, int> cache;
  private void Awake() {
#if UNITY_EDITOR
    base.hideFlags = HideFlags.NotEditable;
    sceneGuids = Array.ConvertAll(UnityEditor.EditorBuildSettings.scenes, s => s.guid.ToString());
#endif
    if (sceneGuids == null) sceneGuids = new Guid[0];
    int n = sceneGuids.Length;
    cache = new Dictionary<Guid, int>(n);
    for (int i = 0; i < n; i++)
      cache.Add(sceneGuids[i], i);
    asset = this;
  }
  ///<summary>The build index of a scene by GUID. It can be used to load it or to obtain scene info.</summary>
  ///<param name="sceneGuid">The GUID of the scene to look up.</param>
  ///<remarks>All mappings are cached on a dictionary when the asset is preloaded at startup.</remarks>
  public int SceneIndex(Guid sceneGuid) => cache.TryGetValue(sceneGuid, out int i) ? i : -1;
}

///<summary>SceneReference is used to reference a Scene by its guid at runtime.</summary>
[Serializable]
public struct SceneReference
{
  ///<summary>The GUID that uniquely identifies this scene asset, used to serialize scene references reliably.</summary>
  ///<remarks>Even if you move/rename the scene asset, GUID references stay valid.</remarks>
  public Guid guid => sceneGuid; [SerializeField] Guid sceneGuid;
  ///<summary>Create a reference to a scene using its GUID.</summary>
  ///<param name="guid">The GUID of the scene, found in its .scene.meta file, or obtained from AssetDatabase.</param>
  public SceneReference(Guid guid) => this.sceneGuid = guid;
  ///<summary>The build index of this scene, which can be used to load it or to obtain scene info.</summary>
#if UNITY_EDITOR
  public int sceneIndex { get {
    var s = UnityEditor.EditorBuildSettings.scenes;
    for (int i = 0, n = s.Length; i < n; i++)
      if (s[i].guid.ToString() == sceneGuid) return i;
    return -1;
  } }
#else
  public int sceneIndex => SceneReferences.instance.SceneIndex(sceneGuid);
#endif
}

Comments (2)

  1. Alfish

    This snippet will no longer be updated here.

    It works, but doesn’t handle special case of missing references (they’re displayed the same way as “none” references).

    For updated code, see: Unity Package: Scene References repository, which can be added in Unity’s Package Manager via git url or .tgz files in download section.

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.