Snippets

Alfish Referencing SceneAsset by GUID at runtime in Unity

Updated by Alfish

File Editor/SceneReferenceDrawer.cs Modified

  • Ignore whitespace
  • Hide word diff
 // 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;

File Editor/SceneReferencesBuildProcessor.cs Modified

  • Ignore whitespace
  • Hide word diff
     PlayerSettings.SetPreloadedAssets(preloaded);
   }
   public void OnPostprocessBuild(BuildReport report) {
-    if (PlayerSettings.GetPreloadedAssets() is {} preloaded) {
+    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) {

File Runtime/SceneReferences.cs Modified

  • Ignore whitespace
  • Hide word diff
   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];
   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 => this.sceneGuid switch {
-    var sceneGuid => Array.FindIndex(UnityEditor.EditorBuildSettings.scenes, s => s.guid.ToString() == sceneGuid)
-  };
+  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
Updated by Alfish

File Editor/SceneReferencesBuildProcessor.cs Modified

  • Ignore whitespace
  • Hide word diff
     PlayerSettings.SetPreloadedAssets(preloaded);
   }
   public void OnPostprocessBuild(BuildReport report) {
-    var preloaded = PlayerSettings.GetPreloadedAssets();
-    var i = Array.FindLastIndex(preloaded, o => o is SceneReferences);
-    if (i >= 0) {
-      var n = preloaded.Length - 1;
-      var newPreloaded = new UnityEngine.Object[n];
-      Array.Copy(preloaded, newPreloaded, i);
-      Array.Copy(preloaded, i + 1, newPreloaded, i, n - i);
-      PlayerSettings.SetPreloadedAssets(newPreloaded);
+    if (PlayerSettings.GetPreloadedAssets() is {} preloaded) {
+      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();
Updated by Alfish

File Runtime/SceneReferences.cs Modified

  • Ignore whitespace
  • Hide word diff
     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>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 => this.sceneGuid switch {
Updated by Alfish

File Editor/SceneReferencesBuildProcessor.cs Modified

  • Ignore whitespace
  • Hide word diff
     PlayerSettings.SetPreloadedAssets(preloaded);
   }
   public void OnPostprocessBuild(BuildReport report) {
-    AssetDatabase.DeleteAsset(resourcePath);
     var preloaded = PlayerSettings.GetPreloadedAssets();
     var i = Array.FindLastIndex(preloaded, o => o is SceneReferences);
-    if (i < 0) return;
-    var n = preloaded.Length - 1;
-    var newPreloaded = new UnityEngine.Object[n];
-    Array.Copy(preloaded, newPreloaded, i);
-    Array.Copy(preloaded, i + 1, newPreloaded, i, n - i);
-    PlayerSettings.SetPreloadedAssets(newPreloaded);
+    if (i >= 0) {
+      var n = preloaded.Length - 1;
+      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();
   }
 }
Created by Alfish

File Editor/SceneReferenceDrawer.cs Added

  • Ignore whitespace
  • Hide word diff
+// Public Domain. NO WARRANTIES. License: https://opensource.org/licenses/0BSD
+
+using UnityEngine;
+using UnityEditor;
+
+[CustomPropertyDrawer(typeof(SceneReference))]
+public class SceneReferenceDrawer : PropertyDrawer
+{
+  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+    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;
+    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;
+  }
+}

File Editor/SceneReferencesBuildProcessor.cs Added

  • Ignore whitespace
  • Hide word diff
+// 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) {
+    AssetDatabase.DeleteAsset(resourcePath);
+    var preloaded = PlayerSettings.GetPreloadedAssets();
+    var i = Array.FindLastIndex(preloaded, o => o is SceneReferences);
+    if (i < 0) return;
+    var n = preloaded.Length - 1;
+    var newPreloaded = new UnityEngine.Object[n];
+    Array.Copy(preloaded, newPreloaded, i);
+    Array.Copy(preloaded, i + 1, newPreloaded, i, n - i);
+    PlayerSettings.SetPreloadedAssets(newPreloaded);
+  }
+}

File Runtime/SceneReferences.cs Added

  • Ignore whitespace
  • Hide word diff
+// 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
+    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>
+  ///<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>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 => this.sceneGuid switch {
+    var sceneGuid => Array.FindIndex(UnityEditor.EditorBuildSettings.scenes, s => s.guid.ToString() == sceneGuid)
+  };
+#else
+  public int sceneIndex => SceneReferences.instance.SceneIndex(sceneGuid);
+#endif
+}
HTTPS SSH

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