UnityHack
A secret recipe to extend Unity Editor.
Install / Use
/learn @pjc0247/UnityHackREADME
UnityHack
A set of snippets to extend Unity Editor.
Before you start
All of snippets from this repository only work inside Unity Editor.<br>
You must place your code inside Editor directory to avoid build errors.
Bootstrap your plugin
It all begins with bootstrapping code. This step is pretty easy because Unity already has API for this.
[InitializeOnLoadMethod]
public static void Initialize() {
/* ... Your code goes here ... */
}
Input
Because of Input class does not work on Unity Editor, you have to write special code to handle it.
Handling global editor input
System.Reflection.FieldInfo info = typeof(EditorApplication).GetField("globalEventHandler", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
EditorApplication.CallbackFunction value = (EditorApplication.CallbackFunction)info.GetValue(null);
value += HandleUnityEvent;
info.SetValue(null, value);
static void HandleUnityEvent()
{
var e = Event.current;
if (e.type == EventType.KeyDown && e.keyCode == KeyCode.LeftControl)
{
/* Do your stuffs here */
}
}
Handling scene window input<br>
If you want to execute your code ONLY inside the scene view, try this instead of globalEventHandler. It also seems less scary.
SceneView.duringSceneGui += HandleSceneEvent;
static void HandleSceneEvent() {
var e = Event.current;
if (e == null) return;
if (e.type == EventType.KeyDown && e.keyCode == KeyCode.LeftControl)
{
/* Do your stuffs here */
}
}
Property change detection
There's no official API to get notification when your changed the property. However, this can be done with Undo.postprocessModifications since every changes will be recorded to Undo.
Undo.postprocessModifications += OnPostProcessModifications;
private static UndoPropertyModification[] OnPostProcessModifications(UndoPropertyModification[] propertyModifications)
{
foreach (var m in propertyModifications)
{
if (m.currentValue.target is Text textTarget) {
if (m.currentValue.propertyPath == "m_FontData.m_FontSize") {
/* User has changed `FontSize` from `Text` Component. */
}
}
}
}
Rendering inside SceneView
OnDrawGizmo is a traditional way to draw something inside SceneView. However, you must have a GameObject to receive OnDrawGizmo callback from Unity.<br>
You can also use SceneView.duringSceneGui to draw your stuffs without active GameObject.
SceneView.duringSceneGui += OnDrawSceneGUI;
static void OnDrawSceneGUI()
{
/* Do your stuffs! */
}
Render Texts<br>
There are APIs for drawing Rect, Line, Polygon and even Texture. But they don't have an API to render texts.<br>
You can render texts by below script:
public static void DrawString(string text, Vector3 position, Color? color = null, bool showTexture = true, int offsetY = 0)
{
Handles.BeginGUI();
var restoreColor = GUI.color;
var viewSize = SceneView.currentDrawingSceneView.position;
if (color.HasValue) GUI.color = color.Value;
var view = SceneView.currentDrawingSceneView;
Vector3 screenPos = view.camera.WorldToScreenPoint(
position + view.camera.transform.forward);
if (screenPos.y < 0 || screenPos.y > Screen.height || screenPos.x < 0 || screenPos.x > Screen.width || screenPos.z < 0)
{
GUI.color = restoreColor;
Handles.EndGUI();
return;
}
var style = new GUIStyle(GUI.skin.label);
style.alignment = TextAnchor.MiddleCenter;
style.fontSize = 30;
Vector2 size = style.CalcSize(new GUIContent(text));
GUI.Label(new Rect(screenPos.x - size.x/2, viewSize.height - screenPos.y - offsetY - size.y/2, size.x, size.y), text, style);
GUI.color = restoreColor;
Handles.EndGUI();
}
Detecting object creation
Detect GameObject creation
EditorApplication.hierarchyChanged += OnHierarchyChanged;
private static GameObject lastChangedGO;
private static void OnHierarchyChanged()
{
if (Selection.objects.Length == 0)
return;
var go = Selection.objects[0] as GameObject;
if (go == null)
return;
if (go == lastChangedGO)
return;
lastChangedGO = go;
/* Do your stuff! */
}
Detect Component creation
private static GameObject lastChangedGO;
private static int lastGOComponentCount;
private static void OnHierarchyChanged()
{
if (Selection.objects.Length == 0)
return;
var go = Selection.objects[0] as GameObject;
if (go == null)
return;
if (go != lastChangedGO)
return;
lastChangedGO = go;
var comps = go.GetComponents<Component>();
if (comps.Length == lastGOComponentCount)
return;
lastGOComponentCount = comps.Length;
var addedComponent = comps.Last();
/* Do your stuff! */
}
Drag and Drop
Asset drag detection<br>
You can watch BeginDrag for your assets with below code:
EditorApplication.update += OnUpdate;
static void OnUpdate()
{
if (DragAndDrop.paths.Length > 0 &&
!(Selection.activeObject is GameObject))
{
// DragAndDrop.objectReferences;
}
}
Object Picking
You can retrive gameObjects with a XY position using HandleUtility.PickGameObject. This is slightly better than Physics.Raycast since it does not require Collider on the GameObject.
SceneView.duringSceneGui += HandleSceneEvents;
static void HandleSceneEvents() {
if (e == null) return;
if (e.type == EventType.MouseDown) {
var picked = HandleUtility.PickGameObject(
Event.current.mousePosition, false);
Debug.Log(picked);
}
}
Pick ALL objects with current mouse position
var ignoreList = new List<GameObject>();
while (true)
{
var picked = HandleUtility.PickGameObject(
Event.current.mousePosition, false,
ignoreList.ToArray());
if (picked == null)
break;
Debug.Log(picked);
ignoreList.Add(picked);
}
Security Score
Audited on Aug 25, 2024
