Skip to content

Instantly share code, notes, and snippets.

@havchr
Created January 4, 2023 13:43
Show Gist options
  • Select an option

  • Save havchr/56424a524fa990d424e930108f1727c4 to your computer and use it in GitHub Desktop.

Select an option

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

havchr commented Jan 4, 2023

Copy link
Copy Markdown
Author

In action.
CollectionBrowser

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