Skip to content

Instantly share code, notes, and snippets.

@thelebaron
Created February 3, 2025 04:16
Show Gist options
  • Save thelebaron/fbce52112bfacda38f943ccd7a6f3af9 to your computer and use it in GitHub Desktop.
Save thelebaron/fbce52112bfacda38f943ccd7a6f3af9 to your computer and use it in GitHub Desktop.
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.ShortcutManagement;
using UnityEngine;
using UnityEngine.UIElements;
namespace Junk.Entities.Editor
{
[InitializeOnLoad]
public static partial class CustomEntityEditor
{
private static UnityEditor.Editor m_EntityEditor;
static CustomEntityEditor()
{
// Hook into the Editor lifecycle when the inspector is created
UnityEditor.Editor.finishedDefaultHeaderGUI += OnFinishedDefaultHeaderGUI;
EditorApplication.update += InitializeFrameShortcut;
}
// Add these new members to your class
private static bool m_IsSubscribedToSceneView;
private static KeyCode m_FrameKey = KeyCode.F;
private static EventModifiers m_FrameModifiers = EventModifiers.None;
private static void InitializeFrameShortcut()
{
if (!m_IsSubscribedToSceneView)
{
SceneView.duringSceneGui += DuringSceneGui;
m_IsSubscribedToSceneView = true;
}
}
private static void DuringSceneGui(SceneView sceneView)
{
KeyDown(Event.current);
}
private static void KeyDown(Event evt)
{
// if this check isnt here, any clicked entity will frame automatically. could be useful but would need a checkbox
if (evt.keyCode != KeyCode.F)
return;
var combination = ShortcutManager.instance.GetShortcutBinding("Main Menu/Edit/Frame Selected in Window under Cursor")
.keyCombinationSequence.ToList();
if (combination.Count == 1)
{
EditorComponentManager.FrameSelectedEntity(m_EntityEditor);
// Prevent default framing behavior
//Event.current.Use();
}
}
// Add this to your cleanup if you implement proper disposal
// [InitializeOnLoad] classes should generally remain subscribed though
private static void OnDestroy()
{
SceneView.duringSceneGui -= DuringSceneGui;
EditorApplication.update -= InitializeFrameShortcut;
}
private static void OnFinishedDefaultHeaderGUI(UnityEditor.Editor editor)
{
m_EntityEditor = editor;
// Check if the target editor is an EntityEditor
if (editor.GetType().Name != "EntityEditor")
return;
// Try to retrieve the root visual element
var root = GetRootVisualElement(editor);
// Check if the container already exists
if (root.Q<VisualElement>("JunkEntityInspectorHeaderContainer") == null)
{
var container = new VisualElement
{
name = "JunkEntityInspectorHeaderContainer",
style =
{
flexDirection = FlexDirection.Row,
marginBottom = 5,
marginLeft = 5,
marginRight = 5,
paddingLeft = 0,
paddingRight = 0
}
};
// Schedule the addition of the container
root.schedule.Execute(() =>
{
// Double-check if the container still doesn't exist
if (root.Q<VisualElement>("JunkEntityInspectorHeaderContainer") == null)
{
root.Insert(0, container);
}
});
TryAddFoldoutElements(root, container, 0);
TryAddDeleteElements(root, container, 1);
}
}
internal static VisualElement GetRootVisualElement(UnityEditor.Editor editor)
{
// Access the private `m_Root` field from the EntityEditor
var rootField = editor.GetType().GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance);
return rootField?.GetValue(editor) as VisualElement;
}
}
}
using System.Linq;
using Unity;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEditor;
using UnityEngine.UIElements;
namespace Junk.Entities.Editor
{
internal static class EditorComponentManager
{
public static void FrameSelectedEntity(UnityEditor.Editor entityEditor)
{
if (entityEditor == null)
return;
var root = CustomEntityEditor.GetRootVisualElement(entityEditor);
var componentsTab = root?.Query<VisualElement>("ComponentsTab").First();
if (componentsTab == null)
return;
var localTransformData = LocalTransform.Identity;
var isLocalToWorld = false;
var localToWorldData = new LocalToWorld();
var localTransform = componentsTab.Query("LocalTransform").First();
if (localTransform != null)
{
//Debug.Log("Framing selected entity");
var vector3 = localTransform.Query("Position").First();
var data = vector3 as Vector3Field;
localTransformData.Position = data.value;
}
var localToWorld = componentsTab.Query("LocalToWorld").First();
var haveTransform = localTransform != null || localToWorld != null;
if (localToWorld != null)
{
//Debug.Log("Found localToWorld" + localToWorld.GetType());
// get all children of localToWorld
var children = localToWorld.Children();
//var child = localToWorld.ElementAt(1).ElementAt(1).ElementAt(1).ElementAt(1);
//Unity.Debug.Log(children.Count());
// get child
var first = children.First();
var second = first.Children().First(); // bindableelement
// Get all Vector4Fields
var floatFields = second.Children().ToList().Select(x => x.Q<Vector4Field>()).ToList();
//Debug.Log("Found " + floatFields.Count + " Vector4Fields");
//Debug.Log($"floatFields {floatFields.Count}");
localToWorldData = new LocalToWorld{Value = new float4x4
{
c0 = floatFields[0].value,
c1 = floatFields[1].value,
c2 = floatFields[2].value,
c3 = floatFields[3].value
}};
//Debug.Log($"localToWorld {localToWorldData.Position}");
isLocalToWorld = true;
}
if (haveTransform)
{
var position = localTransformData.Position;
//if (position.Equals(Vector3.zero))
{
if (localToWorld != null)
{
//Debug.Log($" localToWorldData {localToWorldData.Position}");
position = localToWorldData.Position;
}
}
// get scene view camera
var sceneView = SceneView.lastActiveSceneView;
if (sceneView != null)
{
//Frame selected in scene view
sceneView.pivot = position;
sceneView.Repaint();
}
}
}
public static void DeleteEntity(VisualElement root, bool b)
{
var entityNameField = root.Q<TextField>("Entity Name");
var child = entityNameField.Children().First();
var textElement = child.Children().First();
var entityTextElement = textElement as TextElement;
var entityNameText = entityNameField.text;
// name is parsed as: Entity {105:1}, we are only interested in the index and version of the entity which is {index:version}
Entity entity = new Entity {Index = int.Parse(entityNameText.Split('{')[1].Split(':')[0]), Version = int.Parse(entityNameText.Split(':')[1].Split('}')[0])};
var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
if (entityManager.Exists(entity))
{
entityManager.DestroyEntity(entity);
}
Debug.Log($"Deleted entity: {entityNameText}, {entity.Index } {entity.Version}");
Selection.activeObject = null;
}
}
}
using Unity.Entities.Editor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Junk.Entities.Editor
{
public static partial class CustomEntityEditor
{
private static void TryAddFoldoutElements(VisualElement root, VisualElement containerElement, int index)
{
if (containerElement != null && !IsCollapseButtonAdded(containerElement))
{
AddFoldoutButtons(root, containerElement, index);
}
}
private static bool IsCollapseButtonAdded(VisualElement containerElement)
{
// Check if the button already exists to prevent duplication
return containerElement.Q<Button>("CollapseExpandContainer") != null;
}
private static void AddFoldoutButtons(VisualElement root, VisualElement containerElement, int index)
{
// Schedule the button addition to prevent hierarchy modification during layout calculation
containerElement.schedule.Execute(() =>
{
if (!IsCollapseButtonAdded(containerElement))
{
var subContainer = new VisualElement
{
name = "CollapseExpandContainer",
style =
{
//position = Position.Absolute,
//top = 0,
//right = 0,
paddingTop = 0,
paddingBottom = 0,
paddingLeft = 0,
paddingRight = 0,
flexDirection = FlexDirection.Row,
}
};
var expand = new Button(() => { ExpandComponentsTabFoldout(root, true); })
{
name = "Expand",
text = "Expand",
tooltip = "Expand all components",
style =
{
paddingTop = 4,
paddingBottom = 4,
paddingLeft = 8,
paddingRight = 8,
backgroundColor = new Color(0.35f, 0.35f, 0.35f, 0.8f), // Modern green
color = Color.white,
borderTopLeftRadius = 0,
borderTopRightRadius = 0,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
whiteSpace = WhiteSpace.NoWrap,
width = Length.Auto(),
flexShrink = 0,
marginLeft = 0,
marginRight = 0
}
};
var collapse = new Button(() => { ExpandComponentsTabFoldout(root, false); })
{
name = "Collapse",
text = "Collapse",
tooltip = "Collapse all components",
style =
{
paddingTop = 4,
paddingBottom = 4,
paddingLeft = 8,
paddingRight = 8,
backgroundColor = new Color(0.35f, 0.35f, 0.35f, 0.8f),
color = Color.white,
borderTopLeftRadius = 0,
borderTopRightRadius = 0,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
whiteSpace = WhiteSpace.NoWrap,
width = Length.Auto(),
flexShrink = 0,
marginLeft = 0,
marginRight = 0
}
};
// Add hover effects
expand.RegisterCallback<MouseEnterEvent>(_ => expand.style.backgroundColor = new Color(0.20f, 0.72f, 0.42f));
expand.RegisterCallback<MouseLeaveEvent>(_ => expand.style.backgroundColor = new Color(0.35f, 0.35f, 0.35f, 0.8f));
collapse.RegisterCallback<MouseEnterEvent>(_ => collapse.style.backgroundColor = new Color(0.96f, 0.41f, 0.37f));
collapse.RegisterCallback<MouseLeaveEvent>(_ => collapse.style.backgroundColor = new Color(0.35f, 0.35f, 0.35f, 0.8f));
subContainer.Add(expand);
subContainer.Add(collapse);
containerElement.Insert(index, subContainer);
}
});
}
private static void ExpandComponentsTabFoldout(VisualElement root, bool value)
{
var componentsTab = root.Query<VisualElement>("ComponentsTab").First();
if (componentsTab != null)
{
// get all children and log their count
var allComponentsTabs = root.Query<ComponentElementBase>().ToList();
foreach (var component in allComponentsTabs)
{
var foldout = component.Query<Foldout>().First(); // Assumes one foldout per component
if (foldout != null)
{
foldout.value = value;
}
}
}
}
}
}
using Unity.Entities.Editor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Junk.Entities.Editor
{
public static partial class CustomEntityEditor
{
private static void TryAddDeleteElements(VisualElement root, VisualElement container, int index)
{
if (container != null && !IsDeleteButtonAdded(container))
{
AddDeleteButtons(root, container, index);
}
}
private static bool IsDeleteButtonAdded(VisualElement container)
{
// Check if the button already exists to prevent duplication
return container.Q<Button>("DeleteEntity") != null;
}
private static void AddDeleteButtons(VisualElement root, VisualElement container, int index)
{
// Schedule the button addition to prevent hierarchy modification during layout calculation
container.schedule.Execute(() =>
{
if (!IsDeleteButtonAdded(container))
{
var buttonContainer = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
marginBottom = 0,
marginLeft = 0,
marginRight = 0,
paddingLeft = 0,
paddingRight = 0
}
};
var deleteEntityButton = new Button(() => { EditorComponentManager.DeleteEntity(root, false); })
{
name = "DeleteEntity",
text = "Delete Entity",
tooltip = "Destroy the entity",
style =
{
paddingTop = 4,
paddingBottom = 4,
paddingLeft = 8,
paddingRight = 8,
backgroundColor = new Color(0.35f, 0.35f, 0.35f, 0.8f),
color = Color.white,
borderTopLeftRadius = 0,
borderTopRightRadius = 0,
borderBottomLeftRadius = 6,
borderBottomRightRadius = 6,
whiteSpace = WhiteSpace.NoWrap,
width = Length.Auto(),
flexShrink = 0,
marginLeft = 0,
marginRight = 0
}
};
// Add hover effects
deleteEntityButton.RegisterCallback<MouseEnterEvent>(_ => deleteEntityButton.style.backgroundColor = new Color(0.96f, 0.41f, 0.37f));
deleteEntityButton.RegisterCallback<MouseLeaveEvent>(_ => deleteEntityButton.style.backgroundColor = new Color(0.35f, 0.35f, 0.35f, 0.8f));
buttonContainer.Add(deleteEntityButton);
container.Insert(index, buttonContainer);
}
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment