Added a bunch of utilities and modfief the character data structue

This commit is contained in:
Sebastian Bularca
2026-03-29 18:31:03 +02:00
parent 4a9c00212a
commit ee97b2fec3
110 changed files with 6752 additions and 169 deletions

View File

@@ -0,0 +1,144 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Jovian.Utilities.Editor {
[CustomEditor(typeof(CollisionExtractor))]
public class CollisionExtractorEditor : UnityEditor.Editor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
serializedObject.Update();
if(GUILayout.Button("Extract Colliders")) {
ExtractCollidersIntoPrefab();
}
}
private void ExtractCollidersIntoPrefab() {
CollisionExtractor collisionExtractor = (CollisionExtractor)target;
GameObject source = collisionExtractor.root;
GameObject targetPrefab = collisionExtractor.targetPrefab;
// Verify input
if(source == null) {
Debug.LogWarning("[CollisionExtractor] no Root assigned, please assign a root then try again!");
return;
}
if(targetPrefab == null) {
Debug.LogWarning("[CollisionExtractor] no TargetPrefab assigned, please assign a root then try again!");
return;
}
else if(targetPrefab.scene.name != null) {
Debug.LogWarning("[CollisionExtractor] TargetPrefab is not a prefab instance, please assign a proper prefab instance!");
return;
}
string prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(targetPrefab);
GameObject prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath);
// Remove any old children and build up a new hierarchy matching the source
prefabRoot.transform.DetachChildren();
// Try to remove all components on the prefab root (several iterations due to components sometime requiring each other, thus requiring a certain order of deletion)
for(int i = 0; i < 6; i++) {
foreach(var comp in prefabRoot.GetComponents<Component>()) {
//Don't remove the Transform component
if(!(comp is Transform)) {
DestroyImmediate(comp);
}
}
}
// We make a copy of the source object to ensure it does not get changed, when accessing the properties using reflection Unity notes some of them as changed even though we only read from it.
// For colliders this happens to the physics material, if it is set to "None" it will become an empty value on the source in the editor but properly pick the None value for the target.
GameObject sourceCopy = Instantiate(source);
try {
CopyCollidersRecursive(sourceCopy, prefabRoot);
}
finally {
DestroyImmediate(sourceCopy);
}
Debug.Log("[CollisionExtractor] extraction into target prefab complete.");
PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath);
PrefabUtility.UnloadPrefabContents(prefabRoot);
}
private void CopyCollidersRecursive(GameObject sourceNode, GameObject targetNode) {
// Copy all transform settings
targetNode.transform.SetPositionAndRotation(sourceNode.transform.position, sourceNode.transform.rotation);
targetNode.transform.localScale = sourceNode.transform.localScale;
GameObjectUtility.SetStaticEditorFlags(targetNode, GameObjectUtility.GetStaticEditorFlags(sourceNode));
targetNode.tag = sourceNode.tag;
targetNode.layer = sourceNode.layer;
// Copy all collider components
Collider[] colliders = sourceNode.GetComponents<Collider>();
for(int i = 0; i < colliders.Length; ++i) {
switch(colliders[i]) {
case BoxCollider sourceBoxCollider:
BoxCollider targetBoxCollider = targetNode.AddComponent<BoxCollider>();
GetCopyOf(targetBoxCollider, sourceBoxCollider);
break;
case SphereCollider sourceSphereCollider:
SphereCollider targetSphereCollider = targetNode.AddComponent<SphereCollider>();
GetCopyOf(targetSphereCollider, sourceSphereCollider);
break;
case CapsuleCollider sourceCapsuleCollider:
CapsuleCollider targetCapsuleCollider = targetNode.AddComponent<CapsuleCollider>();
GetCopyOf(targetCapsuleCollider, sourceCapsuleCollider);
break;
case MeshCollider sourceMeshCollider:
MeshCollider targetMeshCollider = targetNode.AddComponent<MeshCollider>();
GetCopyOf(targetMeshCollider, sourceMeshCollider);
break;
default:
Debug.LogError($"[CollisionExtractor] found unsupported collider type on game object {sourceNode.name}!");
break;
}
}
// Continue with all the child nodes
for(int i = 0; i < sourceNode.transform.childCount; ++i) {
Transform sourceChildNode = sourceNode.transform.GetChild(i);
GameObject targetChildNode = new GameObject();
targetChildNode.name = sourceChildNode.name;
targetChildNode.transform.parent = targetNode.transform;
CopyCollidersRecursive(sourceChildNode.gameObject, targetChildNode);
}
// If we are a leaf node without colliders we are not useful, delete this node
if(colliders.Length == 0 && targetNode.transform.childCount == 0) {
DestroyImmediate(targetNode);
}
}
// Taken from https://answers.unity.com/questions/530178/how-to-get-a-component-from-an-object-and-add-it-t.html?_ga=2.50760041.112217741.1608192858-1956498980.1555671355
private T GetCopyOf<T>(Component target, T source) where T : Component {
Type type = target.GetType();
if(type != source.GetType()) return null; // type mis-match
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Default;
var pinfos = from property in type.GetProperties(flags)
where !property.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(ObsoleteAttribute))
select property;
foreach(var pinfo in pinfos) {
if(pinfo.CanWrite) {
try {
pinfo.SetValue(target, pinfo.GetValue(source, null), null);
}
catch { } // In case of NotImplementedException being thrown. For some reason specifying that exception didn't seem to catch it, so I didn't catch anything specific.
}
}
FieldInfo[] finfos = type.GetFields(flags);
foreach(var finfo in finfos) {
finfo.SetValue(target, finfo.GetValue(source));
}
return target as T;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 945d56cc182956e4a9dfb70d9051c0d1

View File

@@ -0,0 +1,84 @@
using UnityEngine;
using UnityEditor;
namespace Jovian.Utilities.Editor {
[CustomEditor(typeof(CustomRenderQueueMaterialList))]
public class CustomRenderQueueMaterialListEditor : UnityEditor.Editor {
private const int MAXNAMELENGTH = 30;
public override void OnInspectorGUI() {
if(((CustomRenderQueueMaterialList)target).updateMaterialsInEditor) {
EditorGUILayout.HelpBox("Enabling 'updateMaterialsInEditor' will cause the Editor to change material files. Only use in debug and please review your changelist before submitting.", MessageType.Warning);
}
DrawAddGUI();
base.OnInspectorGUI();
DrawAddGUI();
}
private void DrawAddGUI() {
GUILayout.BeginHorizontal();
if(GUILayout.Button("Add Empty")) {
AddEmpty();
}
if(Selection.activeGameObject) {
if(GUILayout.Button($"Add from '{GetNiceName(Selection.activeGameObject)}'")) {
AddGameObject(Selection.activeGameObject);
}
}
if(Selection.activeObject is Material) {
if(GUILayout.Button($"Add '{GetNiceName(Selection.activeObject)}'")) {
AddMaterial(Selection.activeObject as Material);
}
}
GUILayout.EndHorizontal();
}
private string GetNiceName(Object obj) {
var objectName = obj.name;
if(objectName.Length > MAXNAMELENGTH) {
objectName = objectName.Substring(0, MAXNAMELENGTH - 2) + "..";
}
return objectName;
}
private void AddEmpty() {
AddElement(null, -1);
}
private void AddGameObject(GameObject gameObject) {
var renderer = gameObject.GetComponentInChildren<Renderer>();
if(renderer == null) {
Debug.LogError($"Cannot add Renderer from {gameObject}, there is none.");
return;
}
AddMaterial(renderer.sharedMaterial);
}
private void AddMaterial(Material material) {
if(material == null) {
Debug.LogError($"Cannot add Material, it is null.");
return;
}
if(((CustomRenderQueueMaterialList)target).DoesMaterialExistInList(material)) {
Debug.LogError($"Cannot add Material, it is already listed.");
return;
}
AddElement(material, material.renderQueue);
}
private void AddElement(Material material, int renderQueue) {
var listProperty = serializedObject.FindProperty("materialRenderQueueList");
var count = listProperty.arraySize;
listProperty.arraySize = count + 1;
var elementProperty = listProperty.GetArrayElementAtIndex(count);
var materialProperty = elementProperty.FindPropertyRelative("material");
var renderQueueProperty = elementProperty.FindPropertyRelative("renderQueue");
materialProperty.objectReferenceValue = material;
renderQueueProperty.intValue = renderQueue;
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fe8e754720c57ef4089250664029c499

View File

@@ -0,0 +1,26 @@
using UnityEngine;
using UnityEditor;
namespace Jovian.Utilities.Editor {
[CustomPropertyDrawer(typeof(CustomRenderQueueMaterialList.MaterialRenderQueue))]
public class CustomRenderQueueMaterialListPropertyDrawer : PropertyDrawer {
private const float RENDERQUEUE_PROPERTY_WIDTH = 45f;
private const float GUI_PADDING = 2f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var materialRect = new Rect(position.x, position.y, position.width - RENDERQUEUE_PROPERTY_WIDTH, position.height);
var renderQueueRect = new Rect(materialRect.xMax + GUI_PADDING, position.y, RENDERQUEUE_PROPERTY_WIDTH - GUI_PADDING, position.height);
EditorGUI.PropertyField(materialRect, property.FindPropertyRelative("material"), new GUIContent(string.Empty, "Material"));
EditorGUI.PropertyField(renderQueueRect, property.FindPropertyRelative("renderQueue"), new GUIContent(string.Empty, "RenderQueue"));
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a5ca9116ca3b8724dad5a329325ff93d

View File

@@ -0,0 +1,29 @@
using UnityEngine;
using UnityEditor;
namespace Jovian.Utilities.Editor {
public class NumberRangePropertyDrawer : PropertyDrawer {
private const float PADDING = 1f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
float width = position.width * 0.5f - PADDING;
Rect minRect = new Rect(position.x, position.y, width, position.height);
Rect maxRect = new Rect(minRect.xMax + PADDING * 2f, position.y, width, position.height);
int indentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(minRect, property.FindPropertyRelative("min"), GUIContent.none);
EditorGUI.PropertyField(maxRect, property.FindPropertyRelative("max"), GUIContent.none);
EditorGUI.indentLevel = indentLevel;
EditorGUI.EndProperty();
}
}
[CustomPropertyDrawer(typeof(FloatRange))]
public class FloatRangePropertyDrawer : NumberRangePropertyDrawer { }
[CustomPropertyDrawer(typeof(IntRange))]
public class IntRangePropertyDrawer : NumberRangePropertyDrawer { }
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 225b3cfe03be1ff41b3f72b06ae2fa23

View File

@@ -0,0 +1,18 @@
{
"name": "JovianUtilities.Editor",
"rootNamespace": "",
"references": [
"JovianUtilities"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b9747d4f21927cf4c9f4a1c9c0680d86
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: