diff --git a/Assets/Scripts/2D/Geometry/Triangle2D.cs b/Assets/Scripts/2D/Geometry/Triangle2D.cs
new file mode 100644
index 0000000..6d16785
--- /dev/null
+++ b/Assets/Scripts/2D/Geometry/Triangle2D.cs
@@ -0,0 +1,67 @@
+using UnityEngine;
+
+namespace UnityLibrary
+{
+ // if you need to run collision checks within a triangular area, the easiest
+ // way to do that in Unity is to use a polygon collider. this is not very
+ // performant. this class allows you to run those collision checks without
+ // the performance overhead.
+
+ public class Triangle2D
+ {
+ Vector2[] vertices = new Vector2[3];
+
+ public Triangle2D(Vector2 v1, Vector2 v2, Vector2 v3)
+ {
+ Update(v1, v2, v3);
+ }
+
+ // update triangle by defining all its vertices
+ public void Update(Vector2 v1, Vector2 v2, Vector2 v3)
+ {
+ vertices[0] = v1;
+ vertices[1] = v2;
+ vertices[2] = v3;
+ }
+
+ // update triangle by redefining its origin (remaining points update relative to that)
+ public void Update(Vector2 v1)
+ {
+ Vector2 delta = v1 - vertices[0];
+ vertices[0] = v1;
+ vertices[1] += delta;
+ vertices[2] += delta;
+ }
+
+ // update triangle with rotation and pivot point
+ public void Update(Vector2 v1, Vector2 v2, Vector2 v3, float rotation, Vector2 pivot)
+ {
+ vertices[0] = v1.Rotate(rotation, pivot);
+ vertices[1] = v2.Rotate(rotation, pivot);
+ vertices[2] = v3.Rotate(rotation, pivot);
+ }
+
+ float Sign(Vector2 p1, Vector2 p2, Vector2 p3)
+ {
+ return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
+ }
+
+ public bool Contains(Vector2 pt, bool debug = false)
+ {
+ float d1, d2, d3;
+ bool has_neg, has_pos;
+
+ d1 = Sign(pt, vertices[0], vertices[1]);
+ d2 = Sign(pt, vertices[1], vertices[2]);
+ d3 = Sign(pt, vertices[2], vertices[0]);
+
+ has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
+ has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
+
+ bool contains = ! (has_neg && has_pos);
+
+ return contains;
+ }
+ }
+}
+
diff --git a/Assets/Scripts/2D/Geometry/Triangle2D.cs.meta b/Assets/Scripts/2D/Geometry/Triangle2D.cs.meta
new file mode 100644
index 0000000..db09c09
--- /dev/null
+++ b/Assets/Scripts/2D/Geometry/Triangle2D.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5b3d23802c4b0df4db68d12d425fb251
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/AssetBundles/AssetBundleLoader.cs b/Assets/Scripts/AssetBundles/AssetBundleLoader.cs
index d31ae28..db53c54 100644
--- a/Assets/Scripts/AssetBundles/AssetBundleLoader.cs
+++ b/Assets/Scripts/AssetBundles/AssetBundleLoader.cs
@@ -2,47 +2,34 @@
using UnityEngine;
using UnityEngine.Networking;
-// AssetBundle cache checker & loader with caching
-// worsk by loading .manifest file from server and parsing hash string from it
-
namespace UnityLibrary
{
public class AssetBundleLoader : MonoBehaviour
{
public string assetBundleURL = "http://localhost/bundle";
+ private string bundleName = "bundle";
void Start()
{
- //StartCoroutine(DownloadAndCache(assetBundleURL));
+ StartCoroutine(DownloadAndCache(assetBundleURL));
}
- ///
- /// load assetbundle manifest, check hash, load actual bundle with hash parameter to use caching
- /// instantiate gameobject
- ///
- /// full url to assetbundle file
- /// optional parameter to access specific asset from assetbundle
- ///
IEnumerator DownloadAndCache(string bundleURL, string assetName = "")
{
- // Wait for the Caching system to be ready
while (!Caching.ready)
{
yield return null;
}
- // if you want to always load from server, can clear cache first
- // Caching.CleanCache();
+ // Clear cache for previous versions of the asset bundle
+ Caching.ClearOtherCachedVersions(bundleName, Hash128.Parse("0"));
- // get current bundle hash from server, random value added to avoid caching
UnityWebRequest www = UnityWebRequest.Get(bundleURL + ".manifest?r=" + (Random.value * 9999999));
- Debug.Log("Loading manifest:" + bundleURL + ".manifest");
+ Debug.Log("Loading manifest: " + bundleURL + ".manifest");
- // wait for load to finish
- yield return www.Send();
+ yield return www.SendWebRequest();
- // if received error, exit
- if (www.isNetworkError == true)
+ if (www.isNetworkError)
{
Debug.LogError("www error: " + www.error);
www.Dispose();
@@ -50,43 +37,38 @@ IEnumerator DownloadAndCache(string bundleURL, string assetName = "")
yield break;
}
- // create empty hash string
- Hash128 hashString = (default(Hash128));// new Hash128(0, 0, 0, 0);
+ Hash128 hashString = default(Hash128);
- // check if received data contains 'ManifestFileVersion'
if (www.downloadHandler.text.Contains("ManifestFileVersion"))
{
- // extract hash string from the received data, TODO should add some error checking here
var hashRow = www.downloadHandler.text.ToString().Split("\n".ToCharArray())[5];
hashString = Hash128.Parse(hashRow.Split(':')[1].Trim());
- if (hashString.isValid == true)
+ if (hashString.isValid)
{
- // we can check if there is cached version or not
- if (Caching.IsVersionCached(bundleURL, hashString) == true)
+ if (Caching.IsVersionCached(bundleURL, hashString))
{
Debug.Log("Bundle with this hash is already cached!");
- } else
+ }
+ else
{
- Debug.Log("No cached version founded for this hash..");
+ Debug.Log("No cached version found for this hash..");
}
- } else
+ }
+ else
{
- // invalid loaded hash, just try loading latest bundle
- Debug.LogError("Invalid hash:" + hashString);
+ Debug.LogError("Invalid hash: " + hashString);
yield break;
}
-
- } else
+ }
+ else
{
Debug.LogError("Manifest doesn't contain string 'ManifestFileVersion': " + bundleURL + ".manifest");
yield break;
}
- // now download the actual bundle, with hashString parameter it uses cached version if available
www = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL + "?r=" + (Random.value * 9999999), hashString, 0);
- // wait for load to finish
yield return www.SendWebRequest();
if (www.error != null)
@@ -97,42 +79,26 @@ IEnumerator DownloadAndCache(string bundleURL, string assetName = "")
yield break;
}
- // get bundle from downloadhandler
AssetBundle bundle = ((DownloadHandlerAssetBundle)www.downloadHandler).assetBundle;
-
GameObject bundlePrefab = null;
- // if no asset name is given, take the first/main asset
if (assetName == "")
{
bundlePrefab = (GameObject)bundle.LoadAsset(bundle.GetAllAssetNames()[0]);
- } else
- { // use asset name to access inside bundle
+ }
+ else
+ {
bundlePrefab = (GameObject)bundle.LoadAsset(assetName);
}
- // if we got something out
if (bundlePrefab != null)
{
-
- // instantiate at 0,0,0 and without rotation
Instantiate(bundlePrefab, Vector3.zero, Quaternion.identity);
-
- /*
- // fix pink shaders, NOTE: not always needed..
- foreach (Renderer r in go.GetComponentsInChildren(includeInactive: true))
- {
- // FIXME: creates multiple materials, not good
- var material = Shader.Find(r.material.shader.name);
- r.material.shader = null;
- r.material.shader = material;
- }*/
}
www.Dispose();
www = null;
- // try to cleanup memory
Resources.UnloadUnusedAssets();
bundle.Unload(false);
bundle = null;
diff --git a/Assets/Scripts/Camera/AutoResolution.cs b/Assets/Scripts/Camera/AutoResolution.cs
new file mode 100644
index 0000000..87fd874
--- /dev/null
+++ b/Assets/Scripts/Camera/AutoResolution.cs
@@ -0,0 +1,58 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class AutoResolution : MonoBehaviour
+{
+ public int setWidth = 1440;
+ public int setHeight = 2560;
+
+ private void Start()
+ {
+ // Get the main camera and its current dimensions
+ Camera camera = Camera.main;
+ Rect rect = camera.rect;
+
+ // Calculate the scale height and width of the screen
+ float scaleHeight = ((float)Screen.width / Screen.height) / ((float)9 / 16);
+ float scaleWidth = 1f / scaleHeight;
+
+ // Adjust the camera's dimensions based on the scale height and width
+ if (scaleHeight < 1)
+ {
+ rect.height = scaleHeight;
+ rect.y = (1f - scaleHeight) / 2f;
+ }
+ else
+ {
+ rect.width = scaleWidth;
+ rect.x = (1f - scaleWidth) / 2f;
+ }
+
+ camera.rect = rect;
+
+ SetResolution();
+ }
+
+ public void SetResolution()
+ {
+ // Get the current device's screen dimensions
+ int deviceWidth = Screen.width;
+ int deviceHeight = Screen.height;
+
+ // Set the screen resolution to the desired dimensions, while maintaining aspect ratio
+ Screen.SetResolution(setWidth, (int)(((float)deviceHeight / deviceWidth) * setWidth), true);
+
+ // Adjust the camera's dimensions based on the new resolution
+ if ((float)setWidth / setHeight < (float)deviceWidth / deviceHeight)
+ {
+ float newWidth = ((float)setWidth / setHeight) / ((float)deviceWidth / deviceHeight);
+ Camera.main.rect = new Rect((1f - newWidth) / 2f, 0f, newWidth, 1f);
+ }
+ else
+ {
+ float newHeight = ((float)deviceWidth / deviceHeight) / ((float)setWidth / setHeight);
+ Camera.main.rect = new Rect(0f, (1f - newHeight) / 2f, 1f, newHeight); // 새로운 Rect 적용
+ }
+ }
+}
diff --git a/Assets/Scripts/Camera/CameraShake.cs b/Assets/Scripts/Camera/CameraShake.cs
index d8814bb..48a88e6 100644
--- a/Assets/Scripts/Camera/CameraShake.cs
+++ b/Assets/Scripts/Camera/CameraShake.cs
@@ -1,58 +1,66 @@
using UnityEngine;
using System.Collections;
-// usage: attach this script into camera, call Shake() method to start
-// source: http://answers.unity3d.com/answers/992509/view.html
+/*
+ * Usage: attach this script to a camera or any other object, call Shake() method to start shaking it
+* To turn off, change influence to zero
+* Attach the camera to an empty game object to keep its local position and rotation
+*/
namespace UnityLibrary
{
public class CameraShake : MonoBehaviour
{
- public bool shakePosition;
- public bool shakeRotation;
-
- public float shakeIntensityMin = 0.1f;
- public float shakeIntensityMax = 0.5f;
- public float shakeDecay = 0.02f;
+ [Range(0f, 1f)]
+ public float shakeInfluence = 0.5f;
+ [Range(0f, 10f)]
+ public float rotationInfluence = 0f;
private Vector3 OriginalPos;
private Quaternion OriginalRot;
-
private bool isShakeRunning = false;
- // call this function to start shaking
- public void Shake()
+///
+/// Will shake the camera with a random value between minIntensity and maxIntensity for duration
+///
+///
+///
+///
+ public void Shake(float minIntensity, float maxIntensity, float duration)
{
+ if (isShakeRunning)
+ return;
+
OriginalPos = transform.position;
OriginalRot = transform.rotation;
- StartCoroutine("ProcessShake");
+
+ float shake = Random.Range(minIntensity, maxIntensity) * shakeInfluence;
+ duration *= shakeInfluence;
+
+ StartCoroutine(ProcessShake(shake, duration));
}
- IEnumerator ProcessShake()
+ IEnumerator ProcessShake(float shake, float duration)
{
- if (!isShakeRunning)
+ isShakeRunning = true;
+ float countdown = duration;
+ float initialShake = shake;
+
+ while (countdown > 0)
{
- isShakeRunning = true;
- float currentShakeIntensity = Random.Range(shakeIntensityMin, shakeIntensityMax);
-
- while (currentShakeIntensity > 0)
- {
- if (shakePosition)
- {
- transform.position = OriginalPos + Random.insideUnitSphere * currentShakeIntensity;
- }
- if (shakeRotation)
- {
- transform.rotation = new Quaternion(OriginalRot.x + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f,
- OriginalRot.y + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f,
- OriginalRot.z + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f,
- OriginalRot.w + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f);
- }
- currentShakeIntensity -= shakeDecay;
- yield return null;
- }
-
- isShakeRunning = false;
+ countdown -= Time.deltaTime;
+
+ float lerpIntensity = countdown / duration;
+ shake = Mathf.Lerp(0f, initialShake, lerpIntensity);
+
+ transform.position = OriginalPos + Random.insideUnitSphere * shake;
+ transform.rotation = Quaternion.Euler(OriginalRot.eulerAngles + Random.insideUnitSphere * shake * rotationInfluence);
+
+ yield return null;
}
+
+ transform.position = OriginalPos;
+ transform.rotation = OriginalRot;
+ isShakeRunning = false;
}
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/Docs/UnityEngine/NavMeshAgentExample.cs b/Assets/Scripts/Docs/UnityEngine/NavMeshAgentExample.cs
new file mode 100644
index 0000000..bae418b
--- /dev/null
+++ b/Assets/Scripts/Docs/UnityEngine/NavMeshAgentExample.cs
@@ -0,0 +1,23 @@
+// made with chatGPT
+
+using UnityEngine;
+using UnityEngine.AI;
+
+namespace UnityLibrary
+{
+ public class NavMeshAgentExample : MonoBehaviour
+ {
+ public Transform target;
+
+ private NavMeshAgent agent;
+
+ void Start()
+ {
+ // Get the NavMeshAgent component on this game object
+ agent = GetComponent();
+
+ // Set the destination of the NavMeshAgent to the target Transform
+ agent.destination = target.position;
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/Animation.meta b/Assets/Scripts/Editor/Animation.meta
new file mode 100644
index 0000000..a9393f4
--- /dev/null
+++ b/Assets/Scripts/Editor/Animation.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 344c9e72d34584041b61214522912d71
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Editor/Animation/LegacyAnimationCreator.meta b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator.meta
new file mode 100644
index 0000000..9fc6b69
--- /dev/null
+++ b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e78d070eb9ba01749a33425b62b0fe5c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs
new file mode 100644
index 0000000..35f7ff2
--- /dev/null
+++ b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs
@@ -0,0 +1,28 @@
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+
+public class LegacyAnimationCreator
+{
+ [MenuItem("Assets/Create/Legacy Animation", priority = 402)]
+ public static void CompressSelectedAnimationClips()
+ {
+ var clip = new AnimationClip();
+ clip.legacy = true;
+ clip.name = "New Legacy Animation";
+
+ string path;
+ var selection = Selection.activeObject;
+ if (selection == null)
+ path = "Assets";
+ else
+ path = AssetDatabase.GetAssetPath(selection.GetInstanceID());
+
+ path = Path.GetDirectoryName(path);
+ path += $"/{clip.name}.anim";
+
+ ProjectWindowUtil.CreateAsset(clip, path);
+ Selection.activeObject = clip;
+ EditorUtility.SetDirty(clip);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs.meta b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs.meta
new file mode 100644
index 0000000..5bd716f
--- /dev/null
+++ b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d536889e20faa9d47b7def4f46203b81
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
new file mode 100644
index 0000000..40bf0d0
--- /dev/null
+++ b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
@@ -0,0 +1,59 @@
+// editor tool to copy names of selected GameObjects to clipboard as a list (so you can paste them in Excel or others..)
+
+using UnityEngine;
+using UnityEditor;
+using System.Text;
+using System.Linq;
+namespace UnityLibrary.Tools
+{
+ public class CopyGameObjectNames : EditorWindow
+ {
+ private string gameObjectNames = string.Empty;
+
+ [MenuItem("Tools/Copy GameObject Names")]
+ public static void ShowWindow()
+ {
+ GetWindow("Copy GameObject Names");
+ }
+
+ private void OnGUI()
+ {
+ GUILayout.Label("Copy Names of Selected GameObjects", EditorStyles.boldLabel);
+
+ if (GUILayout.Button("Fetch Names"))
+ {
+ FetchNames();
+ }
+
+ GUILayout.Label("GameObject Names:", EditorStyles.label);
+ gameObjectNames = EditorGUILayout.TextArea(gameObjectNames, GUILayout.Height(200));
+
+ if (GUILayout.Button("Copy to Clipboard"))
+ {
+ CopyToClipboard();
+ }
+ }
+
+ private void FetchNames()
+ {
+ StringBuilder sb = new StringBuilder();
+ GameObject[] selectedObjects = Selection.gameObjects;
+
+ // Sort the selected objects by their sibling index
+ var sortedObjects = selectedObjects.OrderBy(go => go.transform.GetSiblingIndex()).ToArray();
+
+ foreach (GameObject obj in sortedObjects)
+ {
+ sb.AppendLine(obj.name);
+ }
+
+ gameObjectNames = sb.ToString();
+ }
+
+ private void CopyToClipboard()
+ {
+ EditorGUIUtility.systemCopyBuffer = gameObjectNames;
+ Debug.Log("GameObject names copied to clipboard.");
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs b/Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs
new file mode 100644
index 0000000..105bc74
--- /dev/null
+++ b/Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs
@@ -0,0 +1,69 @@
+// editor tool to replace string from selected GameObject names
+
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+
+namespace UnityLibrary.Tools
+{
+ public class ReplaceCharacterInGameObjectNames : EditorWindow
+ {
+ private string searchString = "|";
+ private string replaceString = "@";
+
+ [MenuItem("Tools/Replace Characters in GameObject Names")]
+ public static void ShowWindow()
+ {
+ GetWindow("Replace Characters");
+ }
+
+ private void OnGUI()
+ {
+ GUILayout.Label("Replace Characters in Selected GameObject Names", EditorStyles.boldLabel);
+
+ searchString = EditorGUILayout.TextField("Search String", searchString);
+ replaceString = EditorGUILayout.TextField("Replace String", replaceString);
+
+ int selectedObjectCount = Selection.gameObjects.Length;
+ GUILayout.Label($"Selected GameObjects: {selectedObjectCount}", EditorStyles.label);
+
+ if (GUILayout.Button("Replace"))
+ {
+ ReplaceCharacters();
+ }
+ }
+
+ private void ReplaceCharacters()
+ {
+ GameObject[] selectedObjects = Selection.gameObjects;
+
+ if (selectedObjects.Length == 0)
+ {
+ Debug.LogWarning("No GameObjects selected.");
+ return;
+ }
+
+ // Start a new undo group
+ Undo.IncrementCurrentGroup();
+ Undo.SetCurrentGroupName("Replace Character in GameObject Names");
+ int undoGroup = Undo.GetCurrentGroup();
+
+ foreach (GameObject obj in selectedObjects)
+ {
+ if (obj.name.Contains(searchString))
+ {
+ Undo.RecordObject(obj, "Replace Character in GameObject Name");
+ obj.name = obj.name.Replace(searchString, replaceString);
+ EditorUtility.SetDirty(obj);
+ }
+ }
+
+ // End the undo group
+ Undo.CollapseUndoOperations(undoGroup);
+
+ EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
+
+ Debug.Log($"Replaced '{searchString}' with '{replaceString}' in the names of selected GameObjects.");
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/BuildProcess/PostBuildCopyEmptyFolders.cs b/Assets/Scripts/Editor/BuildProcess/PostBuildCopyEmptyFolders.cs
new file mode 100644
index 0000000..186265b
--- /dev/null
+++ b/Assets/Scripts/Editor/BuildProcess/PostBuildCopyEmptyFolders.cs
@@ -0,0 +1,54 @@
+using System.IO;
+using UnityEditor;
+using UnityEditor.Callbacks;
+using UnityEngine;
+
+// copies empty StreamingAssets/ folders into build, as they are not automatically included
+
+namespace UnityLibrary
+{
+ public class PostBuildCopyEmptyFolders : MonoBehaviour
+ {
+ [PostProcessBuildAttribute(1)]
+ public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
+ {
+ // only for windows
+ if (target != BuildTarget.StandaloneWindows) return;
+
+ Debug.Log("### POSTBUILD : COPY EMPTY STREAMINGASSETS-FOLDERS ###");
+ Debug.Log("Build done: " + pathToBuiltProject);
+
+ // get output root
+ var root = Path.GetDirectoryName(pathToBuiltProject);
+ var appName = Path.GetFileNameWithoutExtension(pathToBuiltProject);
+
+ // copy empty streaming asset folders to build
+ var sourcePath = Application.streamingAssetsPath;
+ var targetPath = Path.Combine(root, appName + "_Data", "StreamingAssets");
+ //Debug.Log("sourcePath= "+ sourcePath);
+ //Debug.Log("targetPath= " + targetPath);
+ CopyFolderStructure(sourcePath, targetPath);
+ }
+
+ // recursive folder copier
+ static public void CopyFolderStructure(string sourceFolder, string destFolder)
+ {
+ if (Directory.Exists(destFolder))
+ {
+
+ }
+ else
+ {
+ Directory.CreateDirectory(destFolder);
+ }
+
+ string[] folders = Directory.GetDirectories(sourceFolder);
+ foreach (string folder in folders)
+ {
+ string name = Path.GetFileName(folder);
+ string dest = Path.Combine(destFolder, name);
+ CopyFolderStructure(folder, dest);
+ }
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
index 58f7b0d..ce3f298 100644
--- a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
+++ b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
@@ -1,7 +1,3 @@
-// Adjust Box Collider to fit child meshes inside
-// Usage: You have empty parent transform, with child meshes inside, add box collider to parent then use this
-// NOTE: Doesnt work if root transform is rotated
-
using UnityEngine;
using UnityEditor;
@@ -10,37 +6,66 @@ namespace UnityLibrary
public class BoxColliderFitChildren : MonoBehaviour
{
[MenuItem("CONTEXT/BoxCollider/Fit to Children")]
- static void FixSize(MenuCommand command)
+ static void FitColliderToChildren(MenuCommand command)
{
BoxCollider col = (BoxCollider)command.context;
- // record undo
+ // Record undo
Undo.RecordObject(col.transform, "Fit Box Collider To Children");
- // get child mesh bounds
- var b = GetRecursiveMeshBounds(col.gameObject);
+ // Get world-space bounds of all child meshes
+ var worldBounds = GetRecursiveMeshBounds(col.gameObject);
+
+ if (worldBounds.size == Vector3.zero)
+ {
+ Debug.LogWarning("No valid meshes found to fit the BoxCollider.");
+ return;
+ }
+
+ // Convert world-space center to local space
+ Vector3 localCenter = col.transform.InverseTransformPoint(worldBounds.center);
+
+ // Convert world-space size to local space
+ Vector3 localSize = col.transform.InverseTransformVector(worldBounds.size);
+
+ // Ensure size is positive
+ localSize = new Vector3(Mathf.Abs(localSize.x), Mathf.Abs(localSize.y), Mathf.Abs(localSize.z));
+
+ // Fix potential center flipping
+ if (Vector3.Dot(col.transform.right, Vector3.right) < 0)
+ {
+ localCenter.x = -localCenter.x;
+ }
+ if (Vector3.Dot(col.transform.up, Vector3.up) < 0)
+ {
+ localCenter.y = -localCenter.y;
+ }
+ if (Vector3.Dot(col.transform.forward, Vector3.forward) < 0)
+ {
+ localCenter.z = -localCenter.z;
+ }
- // set collider local center and size
- col.center = col.transform.root.InverseTransformVector(b.center) - col.transform.position;
- col.size = b.size;
+ // Apply to collider
+ col.center = localCenter;
+ col.size = localSize;
}
public static Bounds GetRecursiveMeshBounds(GameObject go)
{
- var r = go.GetComponentsInChildren();
- if (r.Length > 0)
- {
- var b = r[0].bounds;
- for (int i = 1; i < r.Length; i++)
- {
- b.Encapsulate(r[i].bounds);
- }
- return b;
- }
- else // TODO no renderers
+ Renderer[] renderers = go.GetComponentsInChildren();
+
+ if (renderers.Length == 0)
+ return new Bounds();
+
+ // Start with the first renderer’s bounds in world space
+ Bounds worldBounds = renderers[0].bounds;
+
+ for (int i = 1; i < renderers.Length; i++)
{
- return new Bounds(Vector3.one, Vector3.one);
+ worldBounds.Encapsulate(renderers[i].bounds);
}
+
+ return worldBounds;
}
}
}
diff --git a/Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs
new file mode 100644
index 0000000..0a5de2b
--- /dev/null
+++ b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs
@@ -0,0 +1,114 @@
+#if UNITY_EDITOR
+using System;
+using System.Reflection;
+using UnityEngine;
+
+namespace UnityEditor
+{
+ [CustomEditor(typeof(RectTransform), true)]
+ [CanEditMultipleObjects]
+ public class CustomRectTransformCopyInspector : Editor
+ {
+ // Unity's built-in editor
+ Editor defaultEditor = null;
+ RectTransform rectTransform;
+
+ private static RectTransformData copiedData;
+
+ void OnEnable()
+ {
+ // Use reflection to get the default Unity RectTransform editor
+ defaultEditor = Editor.CreateEditor(targets, Type.GetType("UnityEditor.RectTransformEditor, UnityEditor"));
+ rectTransform = target as RectTransform;
+ }
+
+ void OnDisable()
+ {
+ // Destroy the default editor to avoid memory leaks
+ if (defaultEditor != null)
+ {
+ MethodInfo disableMethod = defaultEditor.GetType().GetMethod("OnDisable",
+ BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ if (disableMethod != null)
+ disableMethod.Invoke(defaultEditor, null);
+
+ DestroyImmediate(defaultEditor);
+ }
+ }
+
+ public override void OnInspectorGUI()
+ {
+ // Draw Unity's default RectTransform Inspector
+ defaultEditor.OnInspectorGUI();
+
+ // Add Copy and Paste buttons
+ EditorGUILayout.Space();
+ GUILayout.BeginHorizontal();
+
+ if (GUILayout.Button("C", GUILayout.Width(30))) // Copy
+ {
+ CopyRectTransform(rectTransform);
+ }
+
+ if (GUILayout.Button("P", GUILayout.Width(30))) // Paste
+ {
+ PasteRectTransform(rectTransform);
+ }
+
+ GUILayout.EndHorizontal();
+ }
+
+ private void CopyRectTransform(RectTransform rectTransform)
+ {
+ copiedData = new RectTransformData(rectTransform);
+ Debug.Log("RectTransform copied!");
+ }
+
+ private void PasteRectTransform(RectTransform rectTransform)
+ {
+ if (copiedData == null)
+ {
+ Debug.LogWarning("No RectTransform data to paste!");
+ return;
+ }
+
+ Undo.RecordObject(rectTransform, "Paste RectTransform");
+
+ copiedData.ApplyTo(rectTransform);
+ Debug.Log("RectTransform pasted!");
+
+ EditorUtility.SetDirty(rectTransform);
+ }
+
+ private class RectTransformData
+ {
+ public Vector2 anchorMin;
+ public Vector2 anchorMax;
+ public Vector2 anchoredPosition;
+ public Vector2 sizeDelta;
+ public Vector2 pivot;
+ public Quaternion rotation;
+
+ public RectTransformData(RectTransform rectTransform)
+ {
+ anchorMin = rectTransform.anchorMin;
+ anchorMax = rectTransform.anchorMax;
+ anchoredPosition = rectTransform.anchoredPosition;
+ sizeDelta = rectTransform.sizeDelta;
+ pivot = rectTransform.pivot;
+ rotation = rectTransform.rotation;
+ }
+
+ public void ApplyTo(RectTransform rectTransform)
+ {
+ rectTransform.anchorMin = anchorMin;
+ rectTransform.anchorMax = anchorMax;
+ rectTransform.anchoredPosition = anchoredPosition;
+ rectTransform.sizeDelta = sizeDelta;
+ rectTransform.pivot = pivot;
+ rectTransform.rotation = rotation;
+ }
+ }
+ }
+}
+#endif
diff --git a/Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs
new file mode 100644
index 0000000..02c51af
--- /dev/null
+++ b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs
@@ -0,0 +1,294 @@
+// source https://gist.github.com/GieziJo/f80bcb24c4caa68ebfb204148ccd4b18
+// ===============================
+// AUTHOR : J. Giezendanner
+// CREATE DATE : 12.03.2020
+// MODIFIED DATE :
+// PURPOSE : Adds helper functions to the RectTransform to align the rect to the anchors and vise-versa
+// SPECIAL NOTES : Sources for certain informations:
+// Display anchors gizmos:
+// https://forum.unity.com/threads/recttransform-custom-editor-ontop-of-unity-recttransform-custom-editor.455925/
+// Draw default inspector:
+// https://forum.unity.com/threads/extending-instead-of-replacing-built-in-inspectors.407612/
+// ===============================
+// Change History:
+//==================================
+
+#if UNITY_EDITOR
+using System;
+using System.Reflection;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+
+
+namespace UnityEditor
+{
+ [CustomEditor(typeof(RectTransform), true)]
+ [CanEditMultipleObjects]
+ public class CustomRectTransformInspector : Editor
+ {
+ //Unity's built-in editor
+ Editor defaultEditor = null;
+ RectTransform rectTransform;
+
+ bool rect2Anchors_foldout = false;
+ bool anchors2Rect_foldout = false;
+ bool rect2Anchors__previousState = false;
+ bool anchors2Rect_previousState = false;
+
+ private bool playerPrefsChecked = false;
+
+ void OnEnable()
+ {
+ //When this inspector is created, also create the built-in inspector
+ defaultEditor = Editor.CreateEditor(targets, Type.GetType("UnityEditor.RectTransformEditor, UnityEditor"));
+ rectTransform = target as RectTransform;
+ }
+
+ void OnDisable()
+ {
+ //When OnDisable is called, the default editor we created should be destroyed to avoid memory leakage.
+ //Also, make sure to call any required methods like OnDisable
+
+ if (defaultEditor != null)
+ {
+ MethodInfo disableMethod = defaultEditor.GetType().GetMethod("OnDisable",
+ BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ if (disableMethod != null)
+ disableMethod.Invoke(defaultEditor, null);
+ DestroyImmediate(defaultEditor);
+ }
+ }
+
+ void checkPlayerPrefs()
+ {
+ rect2Anchors_foldout = PlayerPrefs.GetInt("giezi_tools_rect2Anchors_foldout_bool", 0) != 0;
+ anchors2Rect_foldout = PlayerPrefs.GetInt("giezi_tools_anchors2Rect_foldout_bool", 0) != 0;
+
+ rect2Anchors__previousState = rect2Anchors_foldout;
+ anchors2Rect_previousState = anchors2Rect_foldout;
+ }
+
+
+ public override void OnInspectorGUI()
+ {
+ if (!playerPrefsChecked)
+ {
+ checkPlayerPrefs();
+ playerPrefsChecked = true;
+ }
+
+ defaultEditor.OnInspectorGUI();
+
+
+ if (rectTransform.parent != null)
+ {
+ var centerButtonStyle = new GUIStyle(GUI.skin.button);
+ centerButtonStyle.fontStyle = FontStyle.Bold;
+
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("Helper Functions", EditorStyles.boldLabel);
+
+ rect2Anchors_foldout = EditorGUILayout.Foldout(rect2Anchors_foldout, "Set Rect to Anchors");
+
+ if (rect2Anchors_foldout)
+ {
+ GUILayout.BeginHorizontal();
+ GUILayout.BeginVertical();
+ if (GUILayout.Button("Top Left"))
+ setRectValue("topLeft");
+ if (GUILayout.Button("Left"))
+ setRectValue("left");
+ if (GUILayout.Button("Bottom Left"))
+ setRectValue("bottomLeft");
+ GUILayout.EndVertical();
+ GUILayout.BeginVertical();
+ if (GUILayout.Button("Top"))
+ setRectValue("top");
+ if (GUILayout.Button("All", centerButtonStyle))
+ setRectValue("all");
+ if (GUILayout.Button("Bottom"))
+ setRectValue("bottom");
+ GUILayout.EndVertical();
+ GUILayout.BeginVertical();
+ if (GUILayout.Button("Top Right"))
+ setRectValue("topRight");
+ if (GUILayout.Button("Right"))
+ setRectValue("right");
+ if (GUILayout.Button("Bottom Right"))
+ setRectValue("bottomRight");
+ GUILayout.EndVertical();
+ GUILayout.EndHorizontal();
+ }
+
+ anchors2Rect_foldout = EditorGUILayout.Foldout(anchors2Rect_foldout, "Set Anchors to Rect");
+
+ if (anchors2Rect_foldout)
+ {
+ GUILayout.BeginHorizontal();
+ GUILayout.BeginVertical();
+ if (GUILayout.Button("Top Left"))
+ setAnchorsToRect("topLeft");
+ if (GUILayout.Button("Left"))
+ setAnchorsToRect("left");
+ if (GUILayout.Button("Bottom Left"))
+ setAnchorsToRect("bottomLeft");
+ GUILayout.EndVertical();
+ GUILayout.BeginVertical();
+ if (GUILayout.Button("Top"))
+ setAnchorsToRect("top");
+ if (GUILayout.Button("All", centerButtonStyle))
+ setAnchorsToRect("all");
+ if (GUILayout.Button("Bottom"))
+ setAnchorsToRect("bottom");
+ GUILayout.EndVertical();
+ GUILayout.BeginVertical();
+ if (GUILayout.Button("Top Right"))
+ setAnchorsToRect("topRight");
+ if (GUILayout.Button("Right"))
+ setAnchorsToRect("right");
+ if (GUILayout.Button("Bottom Right"))
+ setAnchorsToRect("bottomRight");
+ GUILayout.EndVertical();
+ GUILayout.EndHorizontal();
+ }
+
+
+ if (rect2Anchors_foldout != rect2Anchors__previousState)
+ {
+ rect2Anchors__previousState = rect2Anchors_foldout;
+ PlayerPrefs.SetInt("giezi_tools_rect2Anchors_foldout_bool", rect2Anchors_foldout ? 1 : 0);
+ }
+
+ if (anchors2Rect_foldout != anchors2Rect_previousState)
+ {
+ anchors2Rect_previousState = anchors2Rect_foldout;
+ PlayerPrefs.SetInt("giezi_tools_anchors2Rect_foldout_bool", anchors2Rect_foldout ? 1 : 0);
+ }
+ }
+ }
+
+
+ private void OnSceneGUI()
+ {
+ MethodInfo onSceneGUI_Method = defaultEditor.GetType()
+ .GetMethod("OnSceneGUI", BindingFlags.NonPublic | BindingFlags.Instance);
+ onSceneGUI_Method.Invoke(defaultEditor, null);
+ }
+
+
+ private void setAnchorsToRect(string field)
+ {
+ Vector2 anchorMax = new Vector2();
+ Vector2 anchorMin = new Vector2();
+ var parent = rectTransform.parent;
+ anchorMin.x = rectTransform.offsetMin.x / parent.GetComponent().rect.size.x;
+ anchorMin.y = rectTransform.offsetMin.y / parent.GetComponent().rect.size.y;
+ anchorMax.x = rectTransform.offsetMax.x / parent.GetComponent().rect.size.x;
+ anchorMax.y = rectTransform.offsetMax.y / parent.GetComponent().rect.size.y;
+
+
+ switch (field)
+ {
+ case "topLeft":
+ anchorMax.x = 0;
+ rectTransform.anchorMax += anchorMax;
+ rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0);
+
+ anchorMin.y = 0;
+ rectTransform.anchorMin += anchorMin;
+ rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y);
+ break;
+ case "top":
+ anchorMax.x = 0;
+ rectTransform.anchorMax += anchorMax;
+ rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0);
+ break;
+ case "topRight":
+ rectTransform.anchorMax += anchorMax;
+ rectTransform.offsetMax = Vector2.zero;
+ break;
+ case "bottomLeft":
+ rectTransform.anchorMin += anchorMin;
+ rectTransform.offsetMin = Vector2.zero;
+ break;
+ case "bottom":
+ anchorMin.x = 0;
+ rectTransform.anchorMin += anchorMin;
+ rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0);
+ break;
+ case "bottomRight":
+ anchorMin.x = 0;
+ rectTransform.anchorMin += anchorMin;
+ rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0);
+ anchorMax.y = 0;
+ rectTransform.anchorMax += anchorMax;
+ rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y);
+ break;
+ case "left":
+ anchorMin.y = 0;
+ rectTransform.anchorMin += anchorMin;
+ rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y);
+ break;
+ case "right":
+ anchorMax.y = 0;
+ rectTransform.anchorMax += anchorMax;
+ rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y);
+ break;
+ case "all":
+ rectTransform.anchorMax += anchorMax;
+ rectTransform.anchorMin += anchorMin;
+ rectTransform.offsetMin = Vector2.zero;
+ rectTransform.offsetMax = Vector2.zero;
+ break;
+ }
+
+ handleChange();
+ }
+
+
+ private void setRectValue(string field)
+ {
+ switch (field)
+ {
+ case "topLeft":
+ rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0);
+ rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y);
+ break;
+ case "top":
+ rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0);
+ break;
+ case "topRight":
+ rectTransform.offsetMax = Vector2.zero;
+ break;
+ case "bottomLeft":
+ rectTransform.offsetMin = Vector2.zero;
+ break;
+ case "bottom":
+ rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0);
+ break;
+ case "bottomRight":
+ rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0);
+ rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y);
+ break;
+ case "left":
+ rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y);
+ break;
+ case "right":
+ rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y);
+ break;
+ case "all":
+ rectTransform.offsetMin = new Vector2(0, 0);
+ rectTransform.offsetMax = new Vector2(0, 0);
+ break;
+ }
+
+ handleChange();
+ }
+
+ private void handleChange()
+ {
+ EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
+ }
+ }
+}
+#endif
diff --git a/Assets/Scripts/Editor/CustomInspector/TransformEditor.cs b/Assets/Scripts/Editor/CustomInspector/TransformEditor.cs
new file mode 100644
index 0000000..152989e
--- /dev/null
+++ b/Assets/Scripts/Editor/CustomInspector/TransformEditor.cs
@@ -0,0 +1,436 @@
+// source https://gist.github.com/unitycoder/e5e6384f087639c0d9edc93aa3820468
+
+using UnityEngine;
+using UnityEditor;
+
+namespace OddTales.Framework.Core.EditorExtension
+{
+ ///
+ /// Custom inspector for Transform component. Using only DrawDefaultInspector would give different display.
+ /// Script based on Unity wiki implementation : https://wiki.unity3d.com/index.php/TransformInspector
+ /// Buttons to reset, copy, paste Transform values.
+ /// Context menu to round/truncate values, hide/show tools.
+ ///
+ [CanEditMultipleObjects, CustomEditor(typeof(Transform))]
+ public class TransformEditor : Editor
+ {
+ private const float FIELD_WIDTH = 212.0f;
+ private const bool WIDE_MODE = true;
+
+ private const float POSITION_MAX = 100000.0f;
+
+ private static GUIContent positionGUIContent = new GUIContent(LocalString("Position"));
+ private static GUIContent rotationGUIContent = new GUIContent(LocalString("Rotation"));
+ private static GUIContent scaleGUIContent = new GUIContent(LocalString("Scale"));
+
+ private static string positionWarningText = LocalString("Due to floating-point precision limitations, it is recommended to bring the world coordinates of the GameObject within a smaller range.");
+
+ private SerializedProperty positionProperty, rotationProperty, scaleProperty;
+
+ private static Vector3? positionClipboard = null;
+ private static Quaternion? rotationClipboard = null;
+ private static Vector3? scaleClipboard = null;
+
+ private const string SHOW_TOOLS_KEY = "TransformEditor_ShowTools";
+ private const string SHOW_RESET_TOOLS_KEY = "TransformEditor_ShowResetTools";
+ private const string SHOW_PASTE_TOOLS_KEY = "TransformEditor_ShowPasteTools";
+ private const string SHOW_ADVANCED_PASTE_TOOLS_KEY = "TransformEditor_ShowAdvancedPasteTools";
+ private const string SHOW_CLIPBOARD_INFORMATIONS_KEY = "TransformEditor_ShowClipboardInformations";
+ private const string SHOW_SHORTCUTS_KEY = "TransformEditor_ShowHelpbox";
+
+
+#if UNITY_2017_3_OR_NEWER
+ private static System.Reflection.MethodInfo getLocalizedStringMethod;
+#endif
+
+
+ /// Get translated Transform label
+ private static string LocalString(string text)
+ {
+#if UNITY_2017_3_OR_NEWER
+ // Since Unity 2017.3, static class LocalizationDatabase is no longer public. Need to use reflection to access it.
+ if (getLocalizedStringMethod == null)
+ {
+ System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
+ System.Type localizationDatabaseType = assembly.GetType("UnityEditor.LocalizationDatabase");
+
+ getLocalizedStringMethod = localizationDatabaseType.GetMethod("GetLocalizedString");
+ }
+
+ return (string)getLocalizedStringMethod.Invoke(null, new object[] { text });
+#else
+ return LocalizationDatabase.GetLocalizedString(text);
+#endif
+ }
+
+ public void OnEnable()
+ {
+ positionProperty = serializedObject.FindProperty("m_LocalPosition");
+ rotationProperty = serializedObject.FindProperty("m_LocalRotation");
+ scaleProperty = serializedObject.FindProperty("m_LocalScale");
+
+ // Init options
+ if (!EditorPrefs.HasKey(SHOW_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_TOOLS_KEY, true);
+ if (!EditorPrefs.HasKey(SHOW_RESET_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_RESET_TOOLS_KEY, true);
+ if (!EditorPrefs.HasKey(SHOW_PASTE_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_PASTE_TOOLS_KEY, true);
+ if (!EditorPrefs.HasKey(SHOW_ADVANCED_PASTE_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY, true);
+ if (!EditorPrefs.HasKey(SHOW_CLIPBOARD_INFORMATIONS_KEY)) EditorPrefs.SetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY, true);
+ if (!EditorPrefs.HasKey(SHOW_SHORTCUTS_KEY)) EditorPrefs.SetBool(SHOW_SHORTCUTS_KEY, true);
+ }
+
+
+ public override void OnInspectorGUI()
+ {
+ Rect beginRect = GUILayoutUtility.GetRect(0, 0);
+
+ EditorGUIUtility.wideMode = TransformEditor.WIDE_MODE;
+ EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - TransformEditor.FIELD_WIDTH; // align field to right of inspector
+
+ serializedObject.Update();
+
+ EditorGUIUtility.labelWidth = 60; // To allow float fields to expand when inspector width is increased
+
+ // Position GUI
+ EditorGUILayout.BeginHorizontal();
+ PositionPropertyField(positionProperty, positionGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead
+ if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY))
+ {
+ if (GUILayout.Button("Reset", GUILayout.Width(50)))
+ {
+ Undo.RecordObjects(targets, "Reset Positions");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localPosition = Vector3.zero;
+ }
+ GUI.FocusControl(null);
+ }
+ }
+ EditorGUILayout.EndHorizontal();
+
+ // Rotation GUI
+ EditorGUILayout.BeginHorizontal();
+ RotationPropertyField(rotationProperty, rotationGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead
+ if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY))
+ {
+ if (GUILayout.Button("Reset", GUILayout.Width(50)))
+ {
+ Undo.RecordObjects(targets, "Reset Rotations");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ TransformUtils.SetInspectorRotation(((Transform)targets[i]), Vector3.zero);
+ }
+ GUI.FocusControl(null);
+ }
+
+ }
+ EditorGUILayout.EndHorizontal();
+
+ // Scale GUI
+ EditorGUILayout.BeginHorizontal();
+ ScalePropertyField(scaleProperty, scaleGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead
+ if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY))
+ {
+ if (GUILayout.Button("Reset", GUILayout.Width(50)))
+ {
+ Undo.RecordObjects(targets, "Reset Scales");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localScale = Vector3.one;
+ }
+ GUI.FocusControl(null);
+ }
+ }
+ EditorGUILayout.EndHorizontal();
+
+
+ if (!ValidatePosition(((Transform)target).position)) EditorGUILayout.HelpBox(positionWarningText, MessageType.Warning); // Display floating-point warning message if values are too high
+
+ if (EditorPrefs.GetBool(SHOW_TOOLS_KEY))
+ {
+ // Paste Tools GUI
+ if (EditorPrefs.GetBool(SHOW_PASTE_TOOLS_KEY))
+ {
+ GUILayout.BeginHorizontal();
+ if (GUILayout.Button("Copy"))
+ {
+ positionClipboard = ((Transform)target).localPosition;
+ rotationClipboard = ((Transform)target).localRotation;
+ scaleClipboard = ((Transform)target).localScale;
+ }
+
+ if (!positionClipboard.HasValue) EditorGUI.BeginDisabledGroup(true);
+ if (GUILayout.Button("Paste"))
+ {
+ Undo.RecordObjects(targets, "Paste Clipboard Values");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localPosition = positionClipboard.Value;
+ ((Transform)targets[i]).localRotation = rotationClipboard.Value;
+ ((Transform)targets[i]).localScale = scaleClipboard.Value;
+ }
+ GUI.FocusControl(null);
+ }
+ if (!positionClipboard.HasValue) EditorGUI.EndDisabledGroup();
+ GUILayout.EndHorizontal();
+ }
+
+ // Advanced Paste Tools GUI
+ if (EditorPrefs.GetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY))
+ {
+ GUILayout.BeginHorizontal();
+
+ if (!positionClipboard.HasValue) EditorGUI.BeginDisabledGroup(true);
+ if (GUILayout.Button("Paste position"))
+ {
+ Undo.RecordObjects(targets, "Paste Position Clipboard Value");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localPosition = positionClipboard.Value;
+ }
+ GUI.FocusControl(null);
+ }
+
+ if (GUILayout.Button("Paste rotation"))
+ {
+ Undo.RecordObjects(targets, "Paste Rotation Clipboard Value");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).rotation = rotationClipboard.Value;
+ }
+ GUI.FocusControl(null);
+ }
+
+ if (GUILayout.Button("Paste scale"))
+ {
+ Undo.RecordObjects(targets, "Paste Scale Clipboard Value");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localScale = scaleClipboard.Value;
+ }
+ GUI.FocusControl(null);
+ }
+ if (!positionClipboard.HasValue) EditorGUI.EndDisabledGroup();
+
+ GUILayout.EndHorizontal();
+ }
+
+ // Clipboard GUI
+ if (EditorPrefs.GetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY))
+ {
+ if (positionClipboard.HasValue && rotationClipboard.HasValue && scaleClipboard.HasValue)
+ {
+
+ GUIStyle helpboxStyle = new GUIStyle(EditorStyles.helpBox);
+ helpboxStyle.richText = true;
+
+ EditorGUILayout.TextArea("Clipboard values :\n" +
+ "Position : " + positionClipboard.Value.ToString("f2") + "\n" +
+ "Rotation : " + rotationClipboard.Value.ToString("f2") + "\n" +
+ "Scale : " + scaleClipboard.Value.ToString("f2"), helpboxStyle);
+ }
+ }
+
+
+ // Shortcuts GUI - Related to InspectorShortcuts.cs https://github.com/VoxelBoy/Useful-Unity-Scripts/blob/master/InspectorShortcuts.cs
+ if (EditorPrefs.GetBool(SHOW_SHORTCUTS_KEY))
+ {
+ EditorGUILayout.HelpBox("Inspector shortcuts :\n" +
+ "Toggle inspector lock : Ctrl + Shift + L\n" +
+ "Toggle inspector mode : Ctrl + Shift + D", MessageType.None);
+ }
+ }
+ Rect endRect = GUILayoutUtility.GetLastRect();
+ endRect.y += endRect.height;
+
+
+ #region Context Menu
+ Rect componentRect = new Rect(beginRect.x, beginRect.y, beginRect.width, endRect.y - beginRect.y);
+ //EditorGUI.DrawRect(componentRect, Color.green); // Debug : display GenericMenu zone
+
+ Event currentEvent = Event.current;
+
+ if (currentEvent.type == EventType.ContextClick)
+ {
+ if (componentRect.Contains(currentEvent.mousePosition))
+ {
+ GUI.FocusControl(null);
+
+ GenericMenu menu = new GenericMenu();
+
+ menu.AddItem(new GUIContent("Display/Tools"), EditorPrefs.GetBool(SHOW_TOOLS_KEY), ToggleOption, SHOW_TOOLS_KEY);
+ menu.AddSeparator("Display/");
+ menu.AddItem(new GUIContent("Display/Reset Tools"), EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY), ToggleOption, SHOW_RESET_TOOLS_KEY);
+ menu.AddItem(new GUIContent("Display/Paste Tools"), EditorPrefs.GetBool(SHOW_PASTE_TOOLS_KEY), ToggleOption, SHOW_PASTE_TOOLS_KEY);
+ menu.AddItem(new GUIContent("Display/Advanced Paste Tools"), EditorPrefs.GetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY), ToggleOption, SHOW_ADVANCED_PASTE_TOOLS_KEY);
+ menu.AddItem(new GUIContent("Display/Clipboard informations"), EditorPrefs.GetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY), ToggleOption, SHOW_CLIPBOARD_INFORMATIONS_KEY);
+ menu.AddItem(new GUIContent("Display/Shortcuts informations"), EditorPrefs.GetBool(SHOW_SHORTCUTS_KEY), ToggleOption, SHOW_SHORTCUTS_KEY);
+
+ // Round menu
+ menu.AddItem(new GUIContent("Round/Three Decimals"), false, Round, 3);
+ menu.AddItem(new GUIContent("Round/Two Decimals"), false, Round, 2);
+ menu.AddItem(new GUIContent("Round/One Decimal"), false, Round, 1);
+ menu.AddItem(new GUIContent("Round/Integer"), false, Round, 0);
+
+ // Truncate menu
+ menu.AddItem(new GUIContent("Truncate/Three Decimals"), false, Truncate, 3);
+ menu.AddItem(new GUIContent("Truncate/Two Decimals"), false, Truncate, 2);
+ menu.AddItem(new GUIContent("Truncate/One Decimal"), false, Truncate, 1);
+ menu.AddItem(new GUIContent("Truncate/Integer"), false, Truncate, 0);
+
+ menu.ShowAsContext();
+ currentEvent.Use();
+ }
+ }
+ #endregion
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+
+ private bool ValidatePosition(Vector3 position)
+ {
+ if (Mathf.Abs(position.x) > POSITION_MAX) return false;
+ if (Mathf.Abs(position.y) > POSITION_MAX) return false;
+ if (Mathf.Abs(position.z) > POSITION_MAX) return false;
+ return true;
+ }
+
+ private void PositionPropertyField(SerializedProperty positionProperty, GUIContent content)
+ {
+ Transform transform = (Transform)targets[0];
+ Vector3 localPosition = transform.localPosition;
+ for (int i = 0; i < targets.Length; i++)
+ {
+ if (!localPosition.Equals(((Transform)targets[i]).localPosition))
+ {
+ EditorGUI.showMixedValue = true;
+ break;
+ }
+ }
+
+ EditorGUI.BeginChangeCheck();
+ Vector3 newLocalPosition = EditorGUILayout.Vector3Field(content, localPosition);
+ if (EditorGUI.EndChangeCheck())
+ {
+ Undo.RecordObjects(targets, "Position Changed");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localPosition = newLocalPosition;
+ }
+ positionProperty.serializedObject.SetIsDifferentCacheDirty();
+ }
+ EditorGUI.showMixedValue = false;
+ }
+
+ private void RotationPropertyField(SerializedProperty rotationProperty, GUIContent content)
+ {
+ Transform transform = (Transform)targets[0];
+ Vector3 localRotation = TransformUtils.GetInspectorRotation(transform);
+
+
+ for (int i = 0; i < targets.Length; i++)
+ {
+ if (!localRotation.Equals(TransformUtils.GetInspectorRotation((Transform)targets[i])))
+ {
+ EditorGUI.showMixedValue = true;
+ break;
+ }
+ }
+
+ EditorGUI.BeginChangeCheck();
+ Vector3 eulerAngles = EditorGUILayout.Vector3Field(content, localRotation);
+ if (EditorGUI.EndChangeCheck())
+ {
+ Undo.RecordObjects(targets, "Rotation Changed");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ //((Transform)targets[i]).localEulerAngles = eulerAngles;
+ TransformUtils.SetInspectorRotation(((Transform)targets[i]), eulerAngles);
+ }
+ rotationProperty.serializedObject.SetIsDifferentCacheDirty();
+ }
+ EditorGUI.showMixedValue = false;
+ }
+
+ private void ScalePropertyField(SerializedProperty scaleProperty, GUIContent content)
+ {
+ Transform transform = (Transform)targets[0];
+ Vector3 localScale = transform.localScale;
+ for (int i = 0; i < targets.Length; i++)
+ {
+ if (!localScale.Equals(((Transform)targets[i]).localScale))
+ {
+ EditorGUI.showMixedValue = true;
+ break;
+ }
+ }
+
+ EditorGUI.BeginChangeCheck();
+ Vector3 newLocalScale = EditorGUILayout.Vector3Field(content, localScale);
+ if (EditorGUI.EndChangeCheck())
+ {
+ Undo.RecordObjects(targets, "Scale Changed");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localScale = newLocalScale;
+ }
+ scaleProperty.serializedObject.SetIsDifferentCacheDirty();
+ }
+ EditorGUI.showMixedValue = false;
+ }
+
+
+ #region Generic Menu Callbacks
+ private void ToggleOption(object obj)
+ {
+ EditorPrefs.SetBool(obj.ToString(), !EditorPrefs.GetBool(obj.ToString()));
+ }
+
+ /// Round all values of the Transform to a given number of decimals
+ private void Round(object objNumberOfDecimals)
+ {
+ int numberOfDecimals = (int)objNumberOfDecimals;
+
+ Undo.RecordObjects(targets, "Round to " + numberOfDecimals + " decimals");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localPosition = RoundVector(((Transform)targets[i]).localPosition, numberOfDecimals);
+ ((Transform)targets[i]).localEulerAngles = RoundVector(((Transform)targets[i]).localEulerAngles, numberOfDecimals);
+ ((Transform)targets[i]).localScale = RoundVector(((Transform)targets[i]).localScale, numberOfDecimals);
+ }
+ }
+
+ /// Round all components of a Vector3
+ private Vector3 RoundVector(Vector3 vector, int numberOfDecimals)
+ {
+ vector.x = Mathf.Round(vector.x * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
+ vector.y = Mathf.Round(vector.y * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
+ vector.z = Mathf.Round(vector.z * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
+ return vector;
+ }
+
+ /// Truncate all values of the Transform to a given number of decimals
+ private void Truncate(object objNumberOfDecimals)
+ {
+ int numberOfDecimals = (int)objNumberOfDecimals;
+
+ Undo.RecordObjects(targets, "Truncate to " + numberOfDecimals + " decimals");
+ for (int i = 0; i < targets.Length; i++)
+ {
+ ((Transform)targets[i]).localPosition = TruncateVector(((Transform)targets[i]).localPosition, numberOfDecimals);
+ ((Transform)targets[i]).localEulerAngles = TruncateVector(((Transform)targets[i]).localEulerAngles, numberOfDecimals);
+ ((Transform)targets[i]).localScale = TruncateVector(((Transform)targets[i]).localScale, numberOfDecimals);
+ }
+ }
+
+ /// Truncate all components of a Vector3
+ private Vector3 TruncateVector(Vector3 vector, int numberOfDecimals)
+ {
+ vector.x = Mathf.Floor(vector.x * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
+ vector.y = Mathf.Floor(vector.y * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
+ vector.z = Mathf.Floor(vector.z * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals);
+ return vector;
+ }
+ #endregion
+ }
+}
diff --git a/Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs b/Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs
new file mode 100644
index 0000000..733ac03
--- /dev/null
+++ b/Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs
@@ -0,0 +1,65 @@
+// Checks for a .txt file with the same name as an imported .fbx file (in Assets/Models/ folder), containing a list of animation clips to add to the ModelImporter.
+// .txt file should be tab-delimited with the following columns: "title", "start frame", "end frame" (and optional description, not used).
+// example:
+// Take0 10 40 asdf
+// Take1 50 80 wasdf..
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+
+namespace UnityLibrary.Importers
+{
+ public class AnimationClipListImporter : AssetPostprocessor
+ {
+ void OnPreprocessModel()
+ {
+ ModelImporter modelImporter = assetImporter as ModelImporter;
+ if (modelImporter == null) return;
+
+ string assetPath = assetImporter.assetPath;
+ if (!assetPath.StartsWith("Assets/Models") || !assetPath.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase)) return;
+
+ string txtPath = Path.ChangeExtension(assetPath, ".txt");
+ if (!File.Exists(txtPath)) return;
+
+ try
+ {
+ List clips = new List();
+ string[] lines = File.ReadAllLines(txtPath);
+
+ foreach (string line in lines)
+ {
+ string[] parts = line.Split('\t');
+ if (parts.Length < 3) continue; // Ensure we have at least "title, start, end"
+
+ string title = parts[0].Trim();
+ if (!int.TryParse(parts[1], out int startFrame) || !int.TryParse(parts[2], out int endFrame))
+ continue;
+
+ ModelImporterClipAnimation clip = new ModelImporterClipAnimation
+ {
+ name = title,
+ firstFrame = startFrame,
+ lastFrame = endFrame,
+ loopTime = false
+ };
+
+ clips.Add(clip);
+ }
+
+ if (clips.Count > 0)
+ {
+ modelImporter.clipAnimations = clips.ToArray();
+ Debug.Log($"Added {clips.Count} animation clips to {assetPath}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Failed to process animation data for {assetPath}: {ex.Message}");
+ }
+ }
+ }
+}
diff --git a/Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs b/Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs
new file mode 100644
index 0000000..e67f45b
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs
@@ -0,0 +1,69 @@
+// prints out which buttons in current scene are referencing your given method
+
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEditor;
+using UnityEngine.Events;
+using System.Reflection;
+
+namespace UnityLibrary
+{
+ public class FindWhatButtonCallsMyMethod : EditorWindow
+ {
+ private string methodName = "MyMethodHere";
+
+ [MenuItem("Tools/Find Buttons with Method")]
+ public static void ShowWindow()
+ {
+ GetWindow("FindWhatButtonCallsMyMethod");
+ }
+
+ private void OnGUI()
+ {
+ GUILayout.Label("Find Buttons that call Method", EditorStyles.boldLabel);
+ methodName = EditorGUILayout.TextField("Method Name:", methodName);
+
+ if (GUILayout.Button("Find Buttons"))
+ {
+ FindButtonsWithMethod();
+ }
+ }
+
+ private void FindButtonsWithMethod()
+ {
+ Button[] allButtons = FindObjectsOfType