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,8 @@
fileFormatVersion: 2
guid: 9e079e01a6b0deb44865d02d9d276a3f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b0c62d31f10f1bb4ebd18d23cb9e50b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using System.Collections;
using System.Text;
namespace Jovian.Utilities {
public static class ArrayUtility {
public static string ListToString(this IList list, bool newLinePerEntry = false) {
if(list == null) {
return "<NULL>";
}
StringBuilder sb = new();
sb.Append("[");
sb.Append(list.Count);
sb.Append("]{");
if(newLinePerEntry) {
sb.AppendLine();
}
for(int i = 0, c = list.Count; i < c; i++) {
sb.Append(list[i]);
if(i < c - 1) {
if(newLinePerEntry) {
sb.AppendLine(",");
}
else {
sb.Append(", ");
}
}
}
if(newLinePerEntry) {
sb.AppendLine();
}
sb.Append("}");
return sb.ToString();
}
}
}

View File

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

View File

@@ -0,0 +1,42 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Jovian.Utilities {
public static class BowserLog {
private const string PREFIX = "Bowser:";
[MethodImpl(MethodImplOptions.AggressiveInlining), DebuggerHidden]
public static void Log(string log, object obj = null) {
Debug.Log(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object);
}
[MethodImpl(MethodImplOptions.AggressiveInlining), DebuggerHidden]
public static void LogWarning(string log, object obj = null) {
Debug.LogWarning(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object);
}
[MethodImpl(MethodImplOptions.AggressiveInlining), DebuggerHidden]
public static void LogError(string log, object obj = null) {
Debug.LogError(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object);
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD"), DebuggerHidden]
public static void LogDebug(string log, object obj = null) {
Debug.Log(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object);
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD"), DebuggerHidden]
public static void LogWarningDebug(string log, object obj = null) {
Debug.LogWarning(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object);
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD"), DebuggerHidden]
public static void LogErrorDebug(string log, object obj = null) {
Debug.LogError(obj == null ? $"{PREFIX}{log}" : $"{PREFIX}[{obj.GetType().Name}] {log}", obj as Object);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 79607e8a014ff1841b183019d9102aff

View File

@@ -0,0 +1,38 @@
using UnityEngine;
namespace Jovian.Utilities {
public static class CachedMainCamera {
private static int lastFrame = -1;
private static Camera mainCamera;
private static Transform mainCameraTransform;
public static Camera MainCamera {
get {
if(mainCamera) {
return mainCamera;
}
AssignCameraReferences();
return mainCamera;
}
}
public static Transform MainCameraTransform {
get {
if(mainCameraTransform) {
return mainCameraTransform;
}
AssignCameraReferences();
return mainCameraTransform;
}
}
private static void AssignCameraReferences() {
int frame = Time.frameCount;
if(lastFrame != frame) {
mainCamera = Camera.main;
mainCameraTransform = (mainCamera ? mainCamera.transform : null);
lastFrame = frame;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0701523b57a704b4780d0a226f0d0d1b

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace Jovian.Utilities {
[RequireComponent(typeof(Canvas))]
public class CanvasAutoAssignWorldCamera : MonoBehaviour {
public Canvas canvas;
public bool autoDisableOnceCameraFound = false;
#if UNITY_EDITOR
public void Reset() {
SerializedObjectUtility.SaveObjectProperties(this, nameof(canvas), GetComponent<Canvas>());
}
#endif
private void Update() {
if(canvas && !canvas.worldCamera) {
canvas.worldCamera = CachedMainCamera.MainCamera;
if(canvas.worldCamera && autoDisableOnceCameraFound) {
enabled = false;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 59842e0a1599f854cae803d724ecb1e7

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Random = UnityEngine.Random;
namespace Jovian.Utilities.Utilities {
public static class CollectionUtility {
public static T RandomElementFromCollection<T>(this ICollection<T> enumerableObject) {
int count = enumerableObject.Count;
if (count == 0) {
throw new IndexOutOfRangeException("Cannot get RandomElement, collection size is 0.");
}
int index = Random.Range(0, count);
return enumerableObject.ElementAt(index);
}
}
}
public static class EnumerableUtility {
private static System.Random random;
public static T RandomElement<T>(this IEnumerable<T> source) {
random ??= new System.Random();
return source.RandomElement(random);
}
//https://stackoverflow.com/a/648240/584774
public static T RandomElement<T>(this IEnumerable<T> source, System.Random rng) {
T current = default(T);
int count = 0;
foreach (T element in source) {
count++;
if (rng.Next(count) == 0) {
current = element;
}
}
if (count == 0) {
throw new InvalidOperationException("Sequence was empty");
}
return current;
}
public static bool TryGetRandomElement<T>(this IEnumerable<T> source, out T outElement) {
random ??= new System.Random();
return source.TryGetRandomElement(random, out outElement);
}
public static bool TryGetRandomElement<T>(this IEnumerable<T> source, System.Random rng, out T outElement) {
T current = default(T);
int count = 0;
foreach (T element in source) {
count++;
if (rng.Next(count) == 0) {
current = element;
}
}
outElement = current;
if (count == 0) {
return false;
}
return true;
}
public static string EnumerableToString(this IEnumerable enumerable, bool newLinePerEntry = false) {
if (enumerable == null) {
return "<NULL>";
}
StringBuilder sb = new();
sb.Append("{");
if (newLinePerEntry) {
sb.AppendLine();
}
foreach (object item in enumerable) {
sb.Append(item);
if (newLinePerEntry) {
sb.AppendLine(",");
}
else {
sb.Append(", ");
}
}
if (newLinePerEntry) {
sb.AppendLine();
}
sb.Append("}");
return sb.ToString();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9a6194020ff13054597ed18112a7acff

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace Jovian.Utilities {
public static class ColliderUtilities {
public static bool ContainsPoint(this Collider collider, Vector3 point) {
return (collider.ClosestPoint(point) - point).sqrMagnitude < Mathf.Epsilon;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 033c7401511b25948be41b7ab4774a03

View File

@@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Jovian.Utilities {
public class CollisionExtractor : MonoBehaviour {
public GameObject root;
public GameObject targetPrefab;
// Script only used inside the editor
private void Awake() {
if(!Application.isEditor) {
Destroy(this);
}
}
}
}

View File

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

View File

@@ -0,0 +1,64 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Jovian.Utilities {
public class CustomRenderQueueMaterialList : MonoBehaviour {
[System.Serializable]
public class MaterialRenderQueue {
public Material material;
public int renderQueue;
public int storedRenderQueue;
}
public bool updateMaterialsInEditor;
[SerializeField]
private MaterialRenderQueue[] materialRenderQueueList;
private void Awake() {
#if UNITY_EDITOR
if(updateMaterialsInEditor) {
Debug.LogWarning("Updating Materials will cause asset files to change. Please review your change log to ensure only valid changes are submitted.");
}
else {
return;
}
#endif
foreach(var materialRenderQueue in materialRenderQueueList) {
if(materialRenderQueue.material) {
materialRenderQueue.storedRenderQueue = materialRenderQueue.material.renderQueue; // store the materials original render queue
materialRenderQueue.material.renderQueue = materialRenderQueue.renderQueue; // overwrite the render queue
}
}
}
private void OnDestroy() {
#if UNITY_EDITOR
if(updateMaterialsInEditor) {
Debug.LogWarning("Updating Materials will cause asset files to change. Please review your change log to ensure only valid changes are submitted.");
}
else {
return;
}
#endif
foreach(var materialRenderQueue in materialRenderQueueList) {
if(materialRenderQueue.material) {
materialRenderQueue.material.renderQueue = materialRenderQueue.storedRenderQueue;
}
}
}
public bool DoesMaterialExistInList(Material material) {
foreach(var materialRenderQueue in materialRenderQueueList) {
if(materialRenderQueue.material == material) {
return true;
}
}
return false;
}
}
}

View File

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

View File

@@ -0,0 +1,43 @@
using UnityEngine;
namespace Jovian.Utilities {
public class CustomRenderQueueRenderer : MonoBehaviour {
[SerializeField, Tooltip("Override RenderQueue for shader, 2000 = Opaque, 3000 = Transparent, -1 = Shader Default")]
private int renderQueue = -1;
[SerializeField]
private MeshRenderer meshRenderer;
[SerializeField, Tooltip("If true, only this renderer is affected because a new material is created.")]
private bool createMaterialInstance;
private void Awake() {
var material = createMaterialInstance ? meshRenderer.material : meshRenderer.sharedMaterial;
material.renderQueue = renderQueue;
}
#if UNITY_EDITOR
private void Reset() {
var serializedObject = new UnityEditor.SerializedObject(this);
var meshRendererProperty = serializedObject.FindProperty("meshRenderer");
var meshRenderer = gameObject.GetComponent<MeshRenderer>();
if(meshRenderer != null) {
meshRendererProperty.objectReferenceValue = meshRenderer;
serializedObject.ApplyModifiedProperties();
CopyRenderQueueFromMaterial(); // applies serializedObject modified properties
}
}
[ContextMenu("Copy RenderQueue from Material")]
private void CopyRenderQueueFromMaterial() {
if(meshRenderer == null) {
Debug.LogError(@"MeshRenderer is null, cannot copy RenderQueue", this);
return;
}
var serializedObject = new UnityEditor.SerializedObject(this);
var renderQueueProperty = serializedObject.FindProperty("renderQueue");
renderQueueProperty.intValue = meshRenderer.sharedMaterial.renderQueue;
serializedObject.ApplyModifiedProperties();
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 45fad90effb685541beabcc012dca7f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,299 @@
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Diagnostics;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Jovian.Utilities {
public static class AssetUtility {
public static TAsset FindAssetInProject<TAsset>(string assetName = "") where TAsset : Object {
#if UNITY_EDITOR
var filter = $"t:{typeof(TAsset).Name} {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
if (asset != null)
{
return asset;
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return default(TAsset);
}
public static Object FindAssetInProject(Type assetType, string assetName = "") {
#if UNITY_EDITOR
var filter = $"t:{assetType.Name} {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (asset != null)
{
return asset;
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return default(Object);
}
public static List<TAsset> FindAllAssetsInProject<TAsset>(string assetName = "") where TAsset : Object {
var list = new List<TAsset>();
#if UNITY_EDITOR
var filter = $"t:{typeof(TAsset).Name} {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
if (asset != null)
{
list.Add(asset);
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return list;
}
public static List<Object> FindAllAssetsInProject(Type assetType, string assetName = "") {
var list = new List<Object>();
#if UNITY_EDITOR
var filter = $"t:{assetType.Name} {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (asset != null)
{
list.Add(asset);
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return list;
}
public static List<Object> FindAllObjectsInProject(Type objectType, string filter) {
List<Object> list = new List<Object>();
#if UNITY_EDITOR
string[] guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Object asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (typeof(Component).IsAssignableFrom(objectType) &&
TryGetTypeObjectWithPrefab(asset, objectType, out Component _))
{
list.Add(asset);
}
else if (asset.GetType().IsAssignableFrom(objectType))
{
list.Add(asset);
}
else if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return list;
}
// Prefabs
public static TAsset FindPrefabInProject<TAsset>(string assetName = "") where TAsset : Component {
#if UNITY_EDITOR
var filter = $"t:Prefab {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
if (TryGetTypeObjectWithPrefab(asset, out TAsset component))
{
return component;
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
Debug.LogError($"Failed to find asset '{assetName}' <{typeof(TAsset)}>");
return default(TAsset);
}
public static Object FindPrefabInProject(Type assetType, string assetName = "") {
#if UNITY_EDITOR
var filter = $"t:Prefab {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (TryGetTypeObjectWithPrefab(asset, assetType, out Component component))
{
return component;
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return default(Object);
}
public static List<TAsset> FindAllPrefabsInProject<TAsset>(string assetName = "") where TAsset : Component {
var list = new List<TAsset>();
#if UNITY_EDITOR
var filter = $"t:Prefab {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
if (TryGetTypeObjectWithPrefab(asset, out TAsset component))
{
list.Add(component);
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return list;
}
public static List<Object> FindAllPrefabsInProject(Type assetType, string assetName = "") {
var list = new List<Object>();
#if UNITY_EDITOR
var filter = $"t:Prefab {assetName}";
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
if (TryGetTypeObjectWithPrefab(asset, assetType, out Component component))
{
list.Add(component);
}
else
{
if (ShouldUnloadAsset(asset))
{
Resources.UnloadAsset(asset);
}
}
}
#else
Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
return list;
}
// Util
#if UNITY_EDITOR
public static bool ShouldUnloadAsset(Object asset) {
if (asset == null)
{
return false;
}
return !(asset is GameObject or Component or AssetBundle ||
PrefabUtility.GetPrefabAssetType(asset) != PrefabAssetType.NotAPrefab);
}
private static bool TryGetTypeObjectWithPrefab(Object prefab, Type type, out Component component) {
if (prefab != null && prefab is GameObject gameObject && gameObject.TryGetComponent(type, out component))
{
return true;
}
component = default;
return false;
}
private static bool TryGetTypeObjectWithPrefab<TComponent>(Object prefab, out TComponent component)
where TComponent : Component {
if (prefab != null && prefab is TComponent prefabAsComponent)
{
component = prefabAsComponent;
return true;
}
if (prefab != null && prefab is GameObject gameObject && gameObject.TryGetComponent(out component))
{
return true;
}
component = default;
return false;
}
#endif
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8f53e63ec5b4b3744bda48fb1159ac40

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Jovian.Utilities {
/// <summary>
/// <b>Editor use-only.</b><br/>
/// Allows easy access between different instances. Supports 1 instance per type. <br/>
/// Add instances and retrieve elsewhere.<br/><br/>
/// Read more: https://en.wikipedia.org/wiki/Service_locator_pattern
/// </summary>
public static class EditorServiceLocator {
private sealed class ServiceNotFoundException : Exception {
public ServiceNotFoundException(Type type) : base($"ServiceNotFoundException. Type={type}") { }
}
private static Dictionary<Type, object> serviceContainer = new();
public static bool IsDirty { get; private set; }
#if UNITY_EDITOR
public static Dictionary<Type, object> ServiceContainer => serviceContainer;
#endif
[RuntimeInitializeOnLoadMethod]
public static void Init() {
serviceContainer = new();
}
#if UNITY_EDITOR
[InitializeOnEnterPlayMode]
private static void Reset(EnterPlayModeOptions options) {
serviceContainer?.Clear();
IsDirty = true;
}
#endif
/// <summary>
/// Add an instance to the locator. Will throw an exception if a type already exists in the locator.
/// <seealso cref="AddOrReplace{T}"/>
/// </summary>
[Conditional("UNITY_EDITOR")]
public static void Add<T>(T service) {
#if UNITY_EDITOR
if (service == null) {
throw new NullReferenceException($"Service is null. Expected instance of type '{typeof(T)}'");
}
serviceContainer.Add(typeof(T), service);
IsDirty = true;
#else
UnityEngine.Debug.LogWarning($"EditorServiceLocator should not be used outside of the Editor");
#endif
}
/// <summary>
/// Add an instance to the locator. Will replace any existing instance without an exception.
/// An alias for <see cref="Set{T}"/>
/// </summary>
[Conditional("UNITY_EDITOR")]
public static void AddOrReplace<T>(T service) {
Set(service);
}
/// <summary>
/// Add an instance to the locator. Will replace any existing instance without an exception.
/// An alias for <see cref="AddOrReplace{T}"/>
/// </summary>
[Conditional("UNITY_EDITOR")]
public static void Set<T>(T service) {
#if UNITY_EDITOR
if (service == null) {
throw new NullReferenceException($"Service is null. Expected instance of type '{typeof(T)}'");
}
serviceContainer[typeof(T)] = service;
IsDirty = true;
#else
UnityEngine.Debug.LogWarning($"EditorServiceLocator should not be used outside of the Editor");
#endif
}
/// <summary>
/// Removes any type matching the instance passed in from the locator. This is good practice.
/// </summary>
/// <param name="service"></param>
/// <typeparam name="T"></typeparam>
/// <exception cref="NullReferenceException"></exception>
[Conditional("UNITY_EDITOR")]
public static void Remove<T>(T service) {
#if UNITY_EDITOR
if (service == null) {
throw new NullReferenceException($"Service is null. Expected instance of type '{typeof(T)}'");
}
serviceContainer.Remove(typeof(T));
IsDirty = true;
#else
UnityEngine.Debug.LogWarning($"EditorServiceLocator should not be used outside of the Editor");
#endif
}
/// <summary>
/// Retrieves an instance from the locator matching the type. Will throw an exception if nothing is found.
/// <seealso cref="TryGet{T}(out T)"/>
/// </summary>
public static T Get<T>() {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
return (T)service;
}
#endif
throw new ServiceNotFoundException(typeof(T));
}
[Obsolete("Use Get<T> or TryGet<T>(out T) since they follow the C# conventions for TryGet")]
public static T TryGet<T>() {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
return (T)service;
}
#endif
return default;
}
/// <summary>
/// Retrieves an instance from the locator matching the type. Returns true/false based on success.
/// </summary>
public static bool TryGet<T>(out T instance) {
#if UNITY_EDITOR
if (serviceContainer.TryGetValue(typeof(T), out object service)) {
instance = (T)service;
return instance != null;
}
#endif
instance = default;
return false;
}
[Conditional("UNITY_EDITOR")]
public static void Clear() {
serviceContainer.Clear();
IsDirty = true;
}
[Conditional("UNITY_EDITOR")]
public static void Clean() {
IsDirty = false;
}
}
}

View File

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

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Jovian.Utilities.Utilities {
public static class GizmosUtility {
public static bool IsGameObjectOrChildSelected(GameObject gameObject) {
#if UNITY_EDITOR
return GameObjectUtilities.IsGameObjectAChildOfGameObject(Selection.activeGameObject, gameObject);
#else
return false;
#endif
}
public static void DrawColliders(IEnumerable<Collider> colliders) {
foreach(Collider collider in colliders) {
if(collider != null) {
DrawCollider(collider);
}
}
}
public static void DrawWireColliders(IEnumerable<Collider> colliders) {
foreach(Collider collider in colliders) {
if(collider != null) {
DrawWireCollider(collider);
}
}
}
public static void DrawCollider(Collider collider)
=> DrawColliderInternal(collider, Gizmos.DrawSphere, Gizmos.DrawCube, Gizmos.DrawMesh);
public static void DrawWireCollider(Collider collider)
=> DrawColliderInternal(collider, Gizmos.DrawWireSphere, Gizmos.DrawWireCube, Gizmos.DrawWireMesh);
private static void DrawColliderInternal(Collider collider, Action<Vector3, float> drawSphere, Action<Vector3, Vector3> drawCube,
Action<Mesh> drawMesh) {
Gizmos.matrix = collider.transform.localToWorldMatrix;
switch(collider) {
case BoxCollider boxCollider:
drawCube(boxCollider.center, boxCollider.size);
break;
case SphereCollider sphereCollider:
drawSphere(sphereCollider.center, sphereCollider.radius);
break;
case CapsuleCollider capsuleCollider: {
Vector3 direction = GetAxis(capsuleCollider.direction);
drawSphere(capsuleCollider.center + direction * capsuleCollider.height * 0.5f, capsuleCollider.radius);
drawSphere(capsuleCollider.center - direction * capsuleCollider.height * 0.5f, capsuleCollider.radius);
break;
}
case MeshCollider meshCollider:
drawMesh(meshCollider.sharedMesh);
break;
default:
throw new NotSupportedException($"Cannot draw collider of type '{typeof(Collider)}'");
}
}
private static Vector3 GetAxis(int direction) {
switch(direction) {
case 0:
return Vector3.right;
case 1:
return Vector3.up;
case 2:
return Vector3.forward;
default:
throw new NotSupportedException($"Direction '{direction}' does not map to an axis.");
}
}
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using System;
using UnityEditor.SceneManagement;
#endif
namespace Jovian.Utilities {
public static class HierarchyUtility {
#if UNITY_EDITOR
public static List<TComponent> FindComponentsInHierarchy<TComponent>(bool includeInactive = false) where TComponent : Component {
PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if(prefabStage == null) {
return SceneUtility.FindComponentsInActiveScene<TComponent>(includeInactive);
}
else {
return new List<TComponent>(prefabStage.prefabContentsRoot.GetComponentsInChildren<TComponent>(includeInactive));
}
}
public static List<Component> FindComponentsInHierarchy(Type componentType, bool includeInactive = false) {
PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if(prefabStage == null) {
return SceneUtility.FindComponentsInActiveScene(componentType, includeInactive);
}
else {
return new List<Component>(prefabStage.prefabContentsRoot.GetComponentsInChildren(componentType, includeInactive));
}
}
#endif
}
}

View File

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

View File

@@ -0,0 +1,68 @@
#if UNITY_EDITOR
using System;
using UnityEngine;
using UnityEditor;
namespace Jovian.Utilities {
public static class InspectorGUIUtility {
public static object DrawField(string name, Type type, object value) {
GUIContent label = new(name, $"<{type.Name}> = {value}");
if(type == typeof(string)) {
return EditorGUILayout.TextField(label, (string)value);
}
if(type == typeof(bool)) {
return EditorGUILayout.Toggle(label, (bool)value);
}
if(type == typeof(float)) {
return EditorGUILayout.FloatField(label, (float)value);
}
if(type == typeof(int)) {
return EditorGUILayout.IntField(label, (int)value);
}
if(type == typeof(byte)) {
return (byte)EditorGUILayout.IntField(label, (byte)value);
}
if(type == typeof(Vector2)) {
return EditorGUILayout.Vector2Field(label, (Vector2)value);
}
if(type == typeof(Vector3)) {
return EditorGUILayout.Vector3Field(label, (Vector3)value);
}
if(type == typeof(Vector4)) {
return EditorGUILayout.Vector4Field(label, (Vector4)value);
}
if(type == typeof(Bounds)) {
return EditorGUILayout.BoundsField(label, (Bounds)value);
}
if(type == typeof(BoundsInt)) {
return EditorGUILayout.BoundsIntField(label, (BoundsInt)value);
}
if(type == typeof(Color)) {
return EditorGUILayout.ColorField(label, (Color)value);
}
if(type == typeof(AnimationCurve)) {
return EditorGUILayout.CurveField(label, (AnimationCurve)value);
}
if (type == typeof(double)) {
return EditorGUILayout.DoubleField(label, (double)value);
}
if (type == typeof(Gradient)) {
return EditorGUILayout.GradientField(label, (Gradient)value);
}
if (type == typeof(long)) {
return EditorGUILayout.LongField(label, (long)value);
}
if(type.IsEnum) {
return EditorGUILayout.EnumPopup(label, (Enum)value);
}
if(type.IsSubclassOf(typeof(UnityEngine.Object)) || value is UnityEngine.Object) {
return EditorGUILayout.ObjectField(label, (UnityEngine.Object)value, type, true);
}
string stringValue = value == null ? "<NULL>" : value.ToString();
EditorGUILayout.TextField(label, stringValue);
return value;
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8b62c711143ba3e4a8d4e941f00a8d2c

View File

@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Jovian.Utilities {
public static class SceneUtility {
public static List<GameObject> FindGameObjectsInScene(this Scene scene, bool includeInactive = false, Func<GameObject, bool> predicate = null) {
if (scene.IsValid() == false)
{
Debug.LogError("Scene is invalid");
return null;
}
var list = new List<GameObject>();
GameObject[] sceneObjects = scene.GetRootGameObjects();
GameObject[] loadedObjects = FindDontDestroyOnLoadObjects();
GameObject[] combinedGameobjects = new GameObject[sceneObjects.Length + loadedObjects.Length];
sceneObjects.CopyTo(combinedGameobjects, 0);
loadedObjects.CopyTo(combinedGameobjects, sceneObjects.Length);
foreach (var rootObject in combinedGameobjects)
{
var allChildTransforms = rootObject.GetComponentsInChildren<Transform>(includeInactive);
var allGameObjects = allChildTransforms.Select(transform => transform.gameObject);
if (predicate != null)
{
allGameObjects = allGameObjects.Where(predicate);
}
list.AddRange(allGameObjects);
}
return list;
}
public static List<GameObject> FindGameObjectsInActiveScene(bool includeInactive = false, Func<GameObject, bool> predicate = null)
=> FindGameObjectsInScene(SceneManager.GetActiveScene(), includeInactive, predicate);
// Adding support for finding objects not in the main scenes but in DontDestroyOnLoad
// From https://forum.unity.com/threads/editor-script-how-to-access-objects-under-dontdestroyonload-while-in-play-mode.442014/#post-3570916
private static GameObject[] FindDontDestroyOnLoadObjects() {
if (!Application.isPlaying)
{
return new GameObject[] { }; // return an empty array as this method creates issues in edit mode
}
GameObject temp = null;
try
{
temp = new GameObject();
Object.DontDestroyOnLoad(temp);
UnityEngine.SceneManagement.Scene dontDestroyOnLoad = temp.scene;
Object.DestroyImmediate(temp);
temp = null;
return dontDestroyOnLoad.GetRootGameObjects();
}
finally
{
if (temp != null)
Object.DestroyImmediate(temp);
}
}
public static List<TComponent> FindComponentsInScene<TComponent>(this Scene scene, bool includeInactive = false) where TComponent : Component {
if (scene.IsValid() == false)
{
Debug.LogError("Scene is invalid");
return null;
}
var list = new List<TComponent>();
foreach (var rootObject in scene.GetRootGameObjects())
{
list.AddRange(rootObject.GetComponentsInChildren<TComponent>(includeInactive));
}
return list;
}
public static List<Component> FindComponentsInScene(this Scene scene, Type componentType, bool includeInactive = false) {
if (scene.IsValid() == false)
{
Debug.LogError("Scene is invalid");
return null;
}
var list = new List<Component>();
foreach (GameObject rootObject in scene.GetRootGameObjects())
{
list.AddRange(rootObject.GetComponentsInChildren(componentType, includeInactive));
}
return list;
}
public static List<TComponent> FindComponentsInActiveScene<TComponent>(bool includeInactive = false) where TComponent : Component
=> FindComponentsInScene<TComponent>(SceneManager.GetActiveScene(), includeInactive);
public static List<Component> FindComponentsInActiveScene(Type componentType, bool includeInactive = false)
=> FindComponentsInScene(SceneManager.GetActiveScene(), componentType, includeInactive);
public static List<TComponent> FindComponentsInAllScenes<TComponent>(bool includeInactive = false) where TComponent : Component {
List<TComponent> allComponents = new();
for (int i = 0, c = SceneManager.sceneCount; i < c; i++)
{
allComponents.AddRange(FindComponentsInScene<TComponent>(SceneManager.GetSceneAt(i), includeInactive));
}
return allComponents;
}
public static TComponent FindComponentInScene<TComponent>(this Scene scene, bool includeInactive = false) where TComponent : Component {
if (scene.IsValid() == false)
{
Debug.LogError("Scene is invalid");
return null;
}
var list = new List<TComponent>();
foreach (var rootObject in scene.GetRootGameObjects())
{
list.AddRange(rootObject.GetComponentsInChildren<TComponent>(includeInactive));
}
return list.FirstOrDefault();
}
public static Component FindComponentInScene(this Scene scene, Type componentType, bool includeInactive = false) {
if (scene.IsValid() == false)
{
Debug.LogError("Scene is invalid");
return null;
}
var list = new List<Component>();
foreach (GameObject rootObject in scene.GetRootGameObjects())
{
list.AddRange(rootObject.GetComponentsInChildren(componentType, includeInactive));
}
return list.FirstOrDefault();
}
public static TComponent FindComponentInActiveScene<TComponent>(bool includeInactive = false) where TComponent : Component
=> FindComponentInScene<TComponent>(SceneManager.GetActiveScene(), includeInactive);
public static Component FindComponentInActiveScene(Type componentType, bool includeInactive = false)
=> FindComponentInScene(SceneManager.GetActiveScene(), componentType, includeInactive);
public static TComponent FindComponentInAllScenes<TComponent>(bool includeInactive = false) where TComponent : Component
=> FindComponentsInAllScenes<TComponent>(includeInactive).FirstOrDefault();
public static GameObject FindGameObject(string nameQuery) {
char delimiter = '/';
string[] querySegments = nameQuery.Split(delimiter);
return FindGameObjectsInActiveScene(true, (gameObject) => {
int queryIndex = querySegments.Length - 1;
Transform compareTransform = gameObject.transform;
do
{
if (compareTransform != null && compareTransform.name.Equals(querySegments[queryIndex]))
{
compareTransform = compareTransform.parent;
queryIndex--;
}
// if compare is null and string is null that means the first query segment was /, so that's the root element - then our parent MUST be null
else if (compareTransform == null && string.IsNullOrEmpty(querySegments[queryIndex]))
{
return true;
}
else
{
return false;
}
} while (queryIndex >= 0);
return true;
}).FirstOrDefault();
}
private static Transform GetChildWithName(this Transform transform, string childName) {
foreach (Transform child in transform.transform)
{
if (child.name.Equals(childName))
{
return child;
}
}
return null;
}
}
}

View File

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

View File

@@ -0,0 +1,211 @@
using System;
using System.Collections;
using System.Diagnostics;
using System.Text.RegularExpressions;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
using Type = System.Type;
#if UNITY_EDITOR
using UnityEditor;
using System.Reflection;
#endif
namespace Jovian.Utilities {
public static class SerializedObjectUtility {
[Conditional("UNITY_EDITOR")]
public static void SaveObjectProperties(Object targetObject, params object[] args) {
#if UNITY_EDITOR
EditorSerializedObjectUtility.SaveObjectProperties(targetObject, args);
#endif
}
}
#if UNITY_EDITOR
public static class EditorSerializedObjectUtility {
//https://answers.unity.com/questions/929293/get-field-type-of-serializedproperty.html
public static Type GetTypeFromProperty(SerializedProperty property) {
//gets parent type info
string[] slices = property.propertyPath.Split('.');
System.Type type = property.serializedObject.targetObject.GetType();
for(int i = 0; i < slices.Length; i++)
if (slices[i] == "Array")
{
i++; //skips "data[x]"
type = type.GetElementType(); //gets info on array elements
}
//gets info on field and its type
else type = type.GetField(slices[i], BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance).FieldType;
//type is now the type of the property
return type;
}
private static readonly Regex isArrayElementRegex = new Regex("Array.data\\[\\d+\\]$");
public static bool IsPropertyAnArrayElement(SerializedProperty property) {
return isArrayElementRegex.IsMatch(property.propertyPath);
}
public static SerializedProperty GetArrayPropertyWithElementProperty(SerializedProperty property) {
SerializedObject serializedObject = property.serializedObject;
string propertyPath = property.propertyPath;
int arrayDataIndex = propertyPath.LastIndexOf(".Array.data", StringComparison.Ordinal);
string propertyPathWithoutArray = propertyPath.Substring(0, arrayDataIndex);
int pathDividerIndex = propertyPathWithoutArray.LastIndexOf(".", StringComparison.Ordinal);
string parentPropertyName = propertyPathWithoutArray;
if(pathDividerIndex != -1) {
parentPropertyName = propertyPathWithoutArray.Substring(pathDividerIndex);
}
return serializedObject.FindProperty(parentPropertyName);;
}
public static void SaveObjectProperties(Object targetObject, params object[] args) {
SerializedObject serializedObject = new SerializedObject(targetObject);
for(int i = 0; i < args.Length; i += 2) {
var keyArg = args[i];
var keyType = keyArg.GetType();
if((keyType == typeof(string)) == false) {
throw new System.NotSupportedException(string.Format("Key must be string. {0} is {1}", args[i], keyType));
}
else {
var property = serializedObject.FindProperty((string)keyArg);
object argValue = args[i + 1];
if(property == null) {
throw new System.Exception(string.Format("No property found for key {0}", keyArg));
}
if(argValue == null) {
property.objectReferenceValue = null;
}
else {
if(property.isArray) {
property.arraySize = 0;
IEnumerable argArray = (IEnumerable)argValue;
IEnumerator enumerator = argArray.GetEnumerator();
int index = 0;
while(enumerator.MoveNext()) {
property.InsertArrayElementAtIndex(index);
var arrayProperty = property.GetArrayElementAtIndex(index);
SetSerializedPropertyValue(arrayProperty, enumerator.Current);
index++;
}
}
else {
SetSerializedPropertyValue(property, argValue);
}
}
}
}
serializedObject.ApplyModifiedProperties();
}
private static void SetSerializedPropertyValue(SerializedProperty property, object value) {
if(property == null) {
UnityEngine.Debug.LogError("SetSerializedPropertyValue failed, property is null");
return;
}
switch(property.propertyType) {
case SerializedPropertyType.AnimationCurve:
property.animationCurveValue = (AnimationCurve)value;
break;
case SerializedPropertyType.Boolean:
property.boolValue = (bool)value;
break;
case SerializedPropertyType.BoundsInt:
property.boundsIntValue = (BoundsInt)value;
break;
case SerializedPropertyType.Character:
property.intValue = (int)(char)value;
break;
case SerializedPropertyType.Color: {
if(value is Color32) {
property.colorValue = (Color)(Color32)value;
}
else {
property.colorValue = (Color)value;
}
break;
}
case SerializedPropertyType.ExposedReference:
case SerializedPropertyType.ObjectReference:
property.objectReferenceValue = (Object)value;
break;
case SerializedPropertyType.Float:
property.floatValue = (float)value;
break;
case SerializedPropertyType.Integer:
property.intValue = (int)value;
break;
case SerializedPropertyType.LayerMask:
property.intValue = ((LayerMask)value).value;
break;
case SerializedPropertyType.Quaternion:
property.quaternionValue = (Quaternion)value;
break;
case SerializedPropertyType.Rect:
property.rectValue = (Rect)value;
break;
case SerializedPropertyType.RectInt:
property.rectIntValue = (RectInt)value;
break;
case SerializedPropertyType.String:
property.stringValue = (string)value;
break;
case SerializedPropertyType.Vector2:
property.vector2Value = (Vector2)value;
break;
case SerializedPropertyType.Vector2Int:
property.vector2IntValue = (Vector2Int)value;
break;
case SerializedPropertyType.Vector3:
property.vector3Value = (Vector3)value;
break;
case SerializedPropertyType.Vector3Int:
property.vector3IntValue = (Vector3Int)value;
break;
case SerializedPropertyType.Vector4:
property.vector4Value = (Vector4)value;
break;
case SerializedPropertyType.Enum:
property.enumValueIndex = (int)value; // need to test this
// flags???
break;
case SerializedPropertyType.Generic:
SaveGenericProperty(property, value);
break;
default:
throw new System.NotSupportedException($"PropertyType {property.propertyType} is not supported - yet. Array? {property.isArray} Path: {property.propertyPath}");
}
}
private static void SaveGenericProperty(SerializedProperty property, object instance) {
Type type = instance.GetType();
var fields = type.GetRuntimeFields();
foreach(FieldInfo field in fields) {
if(field.IsNotSerialized || field.IsStatic) {
continue;
}
SerializedProperty fieldProperty = property.FindPropertyRelative(field.Name);
if(fieldProperty != null) {
SetSerializedPropertyValue(property.FindPropertyRelative(field.Name), field.GetValue(instance));
}
else {
UnityEngine.Debug.Log($"SaveGenericProperty cannot field serializedProperty named '{field.Name}'");
}
}
}
}
#endif
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 51917079be732084cbaae41d702e72ce

View File

@@ -0,0 +1,45 @@
using System;
using UnityEngine;
namespace Jovian.Utilities {
public abstract class NumberRange<T> {
public T min;
public T max;
public abstract float Lerp(float t);
public abstract float LerpUnclamped(float t);
// returns 0 to 1
public abstract float InverseLerp(float t);
// returns values -1 to 1
public abstract float InverseLerpSigned(float t);
public abstract T Random();
}
[Serializable]
public class FloatRange : NumberRange<float> {
public FloatRange(float min, float max) {
this.min = min;
this.max = max;
}
public override float Lerp(float t) => Mathf.Lerp(min, max, t);
public override float LerpUnclamped(float t) => Mathf.LerpUnclamped(min, max, t);
public override float InverseLerp(float t) => Mathf.InverseLerp(min, max, t);
public override float InverseLerpSigned(float t) => Mathf.InverseLerp(min, max, t) * 2f - 1f;
public override float Random() => UnityEngine.Random.Range(min, max);
}
[Serializable]
public class IntRange : NumberRange<int> {
public IntRange(int min, int max) {
this.min = min;
this.max = max;
}
public override float Lerp(float t) => Mathf.Lerp(min, max, t);
public override float LerpUnclamped(float t) => Mathf.LerpUnclamped(min, max, t);
public override float InverseLerp(float t) => Mathf.InverseLerp(min, max, t);
public override float InverseLerpSigned(float t) => Mathf.InverseLerp(min, max, t) * 2f - 1f;
public override int Random() => UnityEngine.Random.Range(min, max);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 01901fe75ec22104b866b8a243adf3c3

View File

@@ -0,0 +1,59 @@
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Jovian.Utilities {
public static class GameObjectUtilities {
public static void DestroyGameObjectsOfType<T>() where T : MonoBehaviour {
T[] objects = Object.FindObjectsOfType<T>();
for (int i = 0; i < objects.Length; ++i) {
Object.Destroy(objects[i].gameObject);
}
}
public static bool IsGameObjectAChildOfGameObject(this GameObject targetGameObject, GameObject potentialParentGameObject) {
if (targetGameObject == null) {
return false;
}
Transform potentialParentTransform = potentialParentGameObject.transform;
Transform checkTransform = targetGameObject.transform;
do {
if (checkTransform == potentialParentTransform) {
return true;
}
checkTransform = checkTransform.parent;
} while (checkTransform != null);
return false;
}
public static bool TryFindChildByName(this GameObject gameObject,
string name,
out GameObject childGameObject,
bool includeInactive = false,
bool allowPartialMatch = false,
StringComparison stringComparison = StringComparison.Ordinal) {
childGameObject = null;
if (gameObject == null) {
return false;
}
Transform[] children = gameObject.GetComponentsInChildren<Transform>(includeInactive);
foreach (Transform child in children) {
if (allowPartialMatch && child.name.Contains(name, stringComparison)) {
childGameObject = child.gameObject;
return true;
}
if (!allowPartialMatch && child.name.Equals(name, stringComparison)) {
childGameObject = child.gameObject;
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 42dbc9bd96270ef4fb84e4df2590e3ad

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Jovian.Utilities
{
public interface ILoadingProcess {
bool HasLoaded { get; }
}
public class LoadingProcessHandler {
private readonly HashSet<ILoadingProcess> allProcesses = new();
private readonly HashSet<ILoadingProcess> anyProcesses = new();
public event Action OnLoadComplete;
public bool IsLoadingComplete() {
foreach(ILoadingProcess loadingProcess in anyProcesses) {
if(loadingProcess.HasLoaded) {
return true;
}
}
bool areAllProcessesComplete = true;
foreach(ILoadingProcess loadingProcess in allProcesses) {
if(loadingProcess.HasLoaded == false) {
areAllProcessesComplete = false;
break;
}
}
return areAllProcessesComplete;
}
public void AddAllProcess(ILoadingProcess loadingProcess) {
allProcesses.Add(loadingProcess);
}
public void AddAnyProcess(ILoadingProcess loadingProcess) {
anyProcesses.Add(loadingProcess);
}
public void Complete() {
OnLoadComplete?.Invoke();
}
}
public class TimeElapsedLoadingProcess : ILoadingProcess {
private readonly float endTime;
public bool HasLoaded => Time.realtimeSinceStartup > endTime;
public TimeElapsedLoadingProcess(float duration) {
endTime = Time.realtimeSinceStartup + duration;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 65bcc9fd235ddb54e991d2799a5e2e2d

View File

@@ -0,0 +1,7 @@
{
"name": "com.jovian.utilities",
"displayName": "Jovian Utilities",
"version": "0.1.18",
"description": "General purpose utility scripts",
"hideInEditor": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1067449040171e9439c6e3aca37af40f
PackageManifestImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: