Skip to content

Instantly share code, notes, and snippets.

@mmechtley
Last active April 2, 2021 18:57
Show Gist options
  • Save mmechtley/b603f8c1221bcfad5e64771aaa7d52b6 to your computer and use it in GitHub Desktop.
Save mmechtley/b603f8c1221bcfad5e64771aaa7d52b6 to your computer and use it in GitHub Desktop.
A reusable Unity editor window to show a list of gameobjects, with buttons to ping them in the scene / prefab stage / project / etc.
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Code.Utilities.Editor
{
public class PingableGameObjectList : EditorWindow
{
private const string TypeNameEditorKey = "PingableGameObjectList_ActionTypeName";
private const string MethodNameEditorKey = "PingableGameObjectList_ActionMethodName";
/// <summary>
/// Show pingable list of objects with the given title. If refreshAction is supplied, we'll show a button to run
/// that action (which would then usually re-invoke this method). If it's a static method with 0 params, also
/// support re-binding the refresh action after script reload etc.
/// </summary>
public static void ShowWithObjects( string title, IEnumerable<GameObject> objects, Action refreshAction=null,
bool refreshOnSceneLoad=false )
{
var w = GetWindow<PingableGameObjectList>();
if( w == null )
{
w = CreateWindow<PingableGameObjectList>();
}
var methodInfo = refreshAction?.GetMethodInfo();
if( methodInfo != null
&& methodInfo.IsStatic
&& methodInfo.GetParameters().Length == 0 )
{
EditorPrefs.SetString( TypeNameEditorKey, methodInfo.DeclaringType?.FullName );
EditorPrefs.SetString( MethodNameEditorKey, methodInfo.Name );
}
w.titleContent = new GUIContent( title );
w.ShowUtility();
w.minSize = new Vector2(320, 450);
w.objects = new List<GameObject>( objects );
w.refreshAction = refreshAction;
w.refreshOnSceneLoad = refreshOnSceneLoad;
}
private List<GameObject> objects;
private Action refreshAction;
private bool refreshOnSceneLoad;
private Vector2 scroll;
/// <summary>
/// This attempts to rebind the refresh action if this window is open and we lose the method reference after a
/// script refresh etc.
/// </summary>
private void rebindIfNeeded()
{
var methodName = EditorPrefs.GetString( MethodNameEditorKey );
var typeName = EditorPrefs.GetString( TypeNameEditorKey );
if( refreshAction == null && !string.IsNullOrEmpty( typeName ) && !string.IsNullOrEmpty( methodName ))
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach( var assembly in assemblies )
{
var type = assembly.GetType( typeName );
var method = type?.GetMethod( methodName );
var neededParams = method?.GetParameters();
if( type != null && method != null && neededParams.Length == 0 && method.IsStatic )
{
// the cached action is anonymous now instead of the original reference but that doesn't matter because
// we'll reload from the strings again anyway
refreshAction = () => method.Invoke( null, null );
break;
}
}
}
}
private void OnGUI()
{
rebindIfNeeded();
if( refreshAction != null )
{
if( GUILayout.Button( "Refresh" ) )
{
refreshAction.Invoke();
}
EditorGUILayout.Separator();
}
using( var scope = new GUILayout.ScrollViewScope( scroll ) )
{
scroll = scope.scrollPosition;
foreach( var gameObj in objects )
{
// deleted or something
if( gameObj == null )
continue;
if( GUILayout.Button( gameObj.name ) )
{
Selection.SetActiveObjectWithContext( gameObj, null );
}
}
}
}
private void OnDidOpenScene()
{
if( refreshOnSceneLoad )
refreshAction?.Invoke();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment