Created
January 4, 2023 13:43
-
-
Save havchr/56424a524fa990d424e930108f1727c4 to your computer and use it in GitHub Desktop.
CollectionBrowser For Unity. Browse through prefabs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In action.
