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