Skip to content

Instantly share code, notes, and snippets.

@havchr
Created January 4, 2023 13:43
Show Gist options
  • Save havchr/56424a524fa990d424e930108f1727c4 to your computer and use it in GitHub Desktop.
Save havchr/56424a524fa990d424e930108f1727c4 to your computer and use it in GitHub Desktop.
CollectionBrowser For Unity. Browse through prefabs
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using Random = UnityEngine.Random;
/*
* CollectionBrowser together with the CollectionBrowserInspector
* is a little tool for placing a set of prefabs that can be browsed through.
* The rationale is to be able to drop a prefab that contains a predefined set of suitable prefabs
* for level-building.
* As an example , imagine a level editor for a skating game, where you would like to cycle through different ramps.
*
* There is an option to either spawn the prefab at runtime (with the ability to randomize which one to spawn),
* or you can keep the specific prefab saved in the scene, and avoid spawning at runtime.
*
* Free to use this snippet if you want to :
* Agens
* https://mastodon.gamedev.place/@havchr
*/
[ExecuteInEditMode]
public class CollectionBrowser : MonoBehaviour
{
public enum CollectionBrowserSpawnMode
{
RandomSpawnAtRuntimeAwake,
RandomSpawnAtRuntimeOnEnabled,
SpecificSpawnAtRuntime,
SpecificAsSelected
}
public CollectionBrowserSpawnMode spawnMode = CollectionBrowserSpawnMode.SpecificAsSelected;
public GameObject[] prefabList;
public GameObject spawnedObject;
public GameObject selectedPrefab;
private void Awake()
{
//for spawning when dropping into the scene
if (!Application.isPlaying && spawnedObject == null)
{
SpawnInitialize();
}
if (Application.isPlaying)
{
switch (spawnMode)
{
case CollectionBrowserSpawnMode.SpecificSpawnAtRuntime:
Spawn(selectedPrefab);
break;
case CollectionBrowserSpawnMode.RandomSpawnAtRuntimeAwake:
SpawnRandom();
break;
}
}
}
public void SpawnInitialize()
{
if (selectedPrefab == null)
{
SpawnRandom();
}
else
{
Spawn(selectedPrefab);
}
}
private void OnEnable()
{
switch (spawnMode)
{
case CollectionBrowserSpawnMode.RandomSpawnAtRuntimeOnEnabled:
SpawnRandom();
break;
}
}
public void PreviousObstacle()
{
var index = Array.IndexOf(prefabList, selectedPrefab);
if (index != -1)
{
index--;
if (index < 0)
{
index = prefabList.Length - 1;
}
Spawn(prefabList[index]);
}
}
public void NextObstacle()
{
var index = Array.IndexOf(prefabList, selectedPrefab);
if (index != -1)
{
index++;
if (index >= prefabList.Length)
{
index = 0;
}
Spawn(prefabList[index]);
}
}
public void SetRandom()
{
SpawnRandom();
}
void SpawnRandom()
{
if (prefabList != null && prefabList.Length >= 1)
{
var index = Array.IndexOf(prefabList, selectedPrefab);
var newIndex = Random.Range(0, prefabList.Length);
if (newIndex == index)
{
newIndex = (newIndex+1)%prefabList.Length;
}
Spawn(prefabList[newIndex]);
}
}
public void Spawn(GameObject gob)
{
selectedPrefab = gob;
if (Application.isPlaying)
{
if (spawnedObject != null)
{
Destroy(spawnedObject);
}
spawnedObject = Instantiate(gob,transform.position,transform.rotation,transform);
AdjustTransform(gob.transform,spawnedObject.transform);
}
else
{
#if UNITY_EDITOR
SpawnInEditorNonPlaymode(gob);
#endif
}
}
void AdjustTransform(Transform prefabTransform, Transform spawned)
{
spawned.localRotation = prefabTransform.transform.localRotation;
spawned.localPosition = prefabTransform.transform.localPosition;
spawned.localScale = prefabTransform.transform.localScale;
}
#if UNITY_EDITOR
void SpawnInEditorNonPlaymode(GameObject gob)
{
if (spawnedObject != null)
{
try
{
DestroyImmediateAllChildren();
}
catch (InvalidOperationException ex)
{
Debug.Log("Open prefab to browse");
return;
}
}
spawnedObject = PrefabUtility.InstantiatePrefab(gob) as GameObject;
spawnedObject.transform.parent = transform;
AdjustTransform(gob.transform,spawnedObject.transform);
switch (spawnMode)
{
case CollectionBrowserSpawnMode.RandomSpawnAtRuntimeAwake:
case CollectionBrowserSpawnMode.RandomSpawnAtRuntimeOnEnabled:
case CollectionBrowserSpawnMode.SpecificSpawnAtRuntime:
SetFlagsInHierarchy(spawnedObject.transform,HideFlags.HideInHierarchy | HideFlags.DontSaveInEditor);
break;
}
}
#endif
/*
* Sometimes you can end up with "ghost" objects in the hierarchy which are hidden, this allows you to manually clear them out.
*/
public void SetHideFlagsOff()
{
SetFlagsInHierarchy(transform,HideFlags.None);
}
void SetFlagsInHierarchy(Transform tf,HideFlags flags)
{
tf.gameObject.hideFlags = flags;
for (int i = 0; i < tf.childCount; i++)
{
SetFlagsInHierarchy(tf.GetChild(i),flags);
}
}
/*
* This is useful for adjusting pivots of the prefabs and getting an overview of the collection.
*/
public void SpawnAll()
{
DestroyImmediateAllChildren();
for (int i = 0; i < prefabList.Length; i++)
{
var gob = prefabList[i];
spawnedObject = PrefabUtility.InstantiatePrefab(gob) as GameObject;
spawnedObject.transform.parent = transform;
AdjustTransform(gob.transform,spawnedObject.transform);
}
}
private void DestroyImmediateAllChildren()
{
Transform[] children = new Transform[transform.childCount];
for (int i = 0; i < transform.childCount; i++)
{
children[i] = transform.GetChild(i);
}
for (int i = 0; i < children.Length; i++)
{
DestroyImmediate(children[i].gameObject);
}
}
}
using System;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
/*
* CollectionBrowser together with the CollectionBrowserInspector
* is a little tool for placing a set of prefabs that can be browsed through.
* The rationale is to be able to drop a prefab that contains a predefined set of suitable prefabs
* for level-building.
* As an example , imagine a level editor for a skating game, where you would like to cycle through different ramps.
*
* There is an option to either spawn the prefab at runtime (with the ability to randomize which one to spawn),
* or you can keep the specific prefab saved in the scene, and avoid spawning at runtime.
*
* Free to use this snippet if you want to :
* Agens
* https://mastodon.gamedev.place/@havchr
*/
[CustomEditor(typeof(CollectionBrowser))]
public class CollectionBrowserInspector : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
CollectionBrowser browser = target as CollectionBrowser;
GUILayout.BeginVertical();
var intro = "Cycles through stuff in a collection";
EditorGUILayout.HelpBox(intro,MessageType.Info);
GameObject outerMost = PrefabUtility.GetOutermostPrefabInstanceRoot(browser.gameObject);
var nestedPrefabLockingCollection = (outerMost != null && outerMost != browser.gameObject);
if (nestedPrefabLockingCollection)
{
HandleNestedPrefabLockingCollection(outerMost, browser);
}
else
{
GUILayout.BeginHorizontal();
if (GUILayout.Button("Previous"))
{
browser.PreviousObstacle();
EditorUtility.SetDirty(browser);
}
if (GUILayout.Button("Random"))
{
browser.SetRandom();
EditorUtility.SetDirty(browser);
}
if (GUILayout.Button("Next"))
{
browser.NextObstacle();
EditorUtility.SetDirty(browser);
}
GUILayout.EndHorizontal();
GUILayout.Space(4);
GUILayout.Label("SpawnMode");
var selStrings = Enum.GetNames(typeof(CollectionBrowser.CollectionBrowserSpawnMode));
var previousIndex = (int)browser.spawnMode;
var currentIndex = GUILayout.SelectionGrid(previousIndex, selStrings, 1);
browser.spawnMode = (CollectionBrowser.CollectionBrowserSpawnMode)currentIndex;
if (previousIndex!= currentIndex)
{
browser.SpawnInitialize();
}
GUILayout.Space(4);
GUILayout.Label("Misc");
if (GUILayout.Button("HideFlags off"))
{
browser.SetHideFlagsOff();
EditorUtility.SetDirty(browser);
}
if (GUILayout.Button("Spawn All"))
{
browser.SpawnAll();
}
}
GUILayout.EndVertical();
}
private static void HandleNestedPrefabLockingCollection(GameObject outerMost, CollectionBrowser browser)
{
var flatList = outerMost.GetComponentsInChildren<Transform>(true);
EditorGUILayout.HelpBox("Nested prefab is locking the browser. To browse, enter prefab edit mode.", MessageType.Info);
if (GUILayout.Button("Open Prefab"))
{
var flatListIndexForBrowser = -1;
var parent = browser.transform.parent;
for (int i = 0; flatList != null && i < flatList.Length; i++)
{
if (flatList[i] == browser.transform)
{
flatListIndexForBrowser = i;
break;
}
}
var prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(outerMost);
PrefabStageUtility.OpenPrefab(prefabPath, outerMost, PrefabStage.Mode.InContext);
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage != null)
{
var prefabContent = prefabStage.prefabContentsRoot;
var flatListPrefabContent = prefabContent.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < flatListPrefabContent.Length; i++)
{
var child = flatListPrefabContent[i];
if (i == flatListIndexForBrowser)
{
Selection.activeObject = child;
EditorGUIUtility.PingObject(child);
}
}
}
}
}
}
@havchr
Copy link
Author

havchr commented Jan 4, 2023

In action.
CollectionBrowser

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment