From 3088d763e2446029ad68119f1eb516d943cd3625 Mon Sep 17 00:00:00 2001
From: Shubham Kumar <96144990+KatyaanShubham@users.noreply.github.com>
Date: Sun, 16 Jul 2023 19:20:28 +0530
Subject: [PATCH 01/31] Update Glow.shader

Explanation of changes:

1. Indentation: The entire code block inside Shader "Custom/Glow" is indented one level for better readability.

2. Spacing: Added proper spacing between the curly braces {} for clarity.
---
 Assets/Shaders/2D/Effects/Glow.shader | 94 ++-------------------------
 1 file changed, 4 insertions(+), 90 deletions(-)

diff --git a/Assets/Shaders/2D/Effects/Glow.shader b/Assets/Shaders/2D/Effects/Glow.shader
index 775b7d2..97da796 100644
--- a/Assets/Shaders/2D/Effects/Glow.shader
+++ b/Assets/Shaders/2D/Effects/Glow.shader
@@ -1,117 +1,31 @@
-﻿Shader "Custom/Glow" {
-
- 
-
+@@ -1,117 +1,30 @@
+Shader "Custom/Glow" {
     Properties {
-
- 
-
         _Color ("Color", Color) = (1,1,1,1)
-
- 
-
     }
 
- 
-
     SubShader {
-
- 
-
         Tags { "RenderType"="Transparent" }
-
- 
-
         LOD 200
-
- 
-
         ZTest Always
-
- 
-
         Cull Off
 
- 
-
-        
-
- 
-
- 
-
- 
-
         CGPROGRAM
-
- 
-
         #pragma surface surf Lambert decal:add
 
- 
-
- 
-
- 
-
         float4 _Color;
 
- 
-
- 
-
- 
-
         struct Input {
-
- 
-
             float3 viewDir;
-
- 
-
             float3 worldNormal;
-
- 
-
         };
 
- 
-
- 
-
- 
-
         void surf (Input IN, inout SurfaceOutput o) {
-
- 
-
-            o.Alpha = _Color.a * pow(abs(dot(normalize(IN.viewDir),
-
- 
-
-                normalize(IN.worldNormal))),4.0);
-
- 
-
+            o.Alpha = _Color.a * pow(abs(dot(normalize(IN.viewDir), normalize(IN.worldNormal))), 4.0);
             o.Emission = _Color.rgb * o.Alpha;
-
- 
-
         }
-
- 
-
         ENDCG
-
- 
-
-    } 
-
- 
+    }
 
     FallBack "Diffuse"
-
- 
-
 }

From dda79f9892ef3668a5b2d19b3f416491f1e53c3e Mon Sep 17 00:00:00 2001
From: Shubham Kumar <96144990+KatyaanShubham@users.noreply.github.com>
Date: Sun, 16 Jul 2023 19:25:50 +0530
Subject: [PATCH 02/31] Update AssetBundleLoader.cs

Explanation of changes:

1. Added using UnityEngine.Networking; to import the required namespace.

2. Created a bundleName variable to specify the name of the asset bundle you are loading. Adjust it according to your asset bundle's name.

3. Added Caching.ClearOtherCachedVersions(bundleName, Hash128.Parse("0")); before starting the download. This line clears the cache for previous versions of the asset bundle with the specified name.
---
 .../Scripts/AssetBundles/AssetBundleLoader.cs | 76 +++++--------------
 1 file changed, 21 insertions(+), 55 deletions(-)

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));
         }
 
-        /// <summary>
-        /// load assetbundle manifest, check hash, load actual bundle with hash parameter to use caching
-        /// instantiate gameobject
-        /// </summary>
-        /// <param name="bundleURL">full url to assetbundle file</param>
-        /// <param name="assetName">optional parameter to access specific asset from assetbundle</param>
-        /// <returns></returns>
         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<Renderer>(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;

From d2ee8cfde0a18206086ca7917700fca25b163ca3 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 19 Oct 2023 09:14:56 +0300
Subject: [PATCH 03/31] Create Diffuse (2 Sided Light).shader

---
 .../2D/Sprites/Diffuse (2 Sided Light).shader | 79 +++++++++++++++++++
 1 file changed, 79 insertions(+)
 create mode 100644 Assets/Shaders/2D/Sprites/Diffuse (2 Sided Light).shader

diff --git a/Assets/Shaders/2D/Sprites/Diffuse (2 Sided Light).shader b/Assets/Shaders/2D/Sprites/Diffuse (2 Sided Light).shader
new file mode 100644
index 0000000..cf47668
--- /dev/null
+++ b/Assets/Shaders/2D/Sprites/Diffuse (2 Sided Light).shader	
@@ -0,0 +1,79 @@
+// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
+// lights up sprites from both sides
+
+Shader "Sprites/Diffuse (2 Sided Light)"
+{
+	Properties
+	{
+		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
+		_Color("Tint", Color) = (1,1,1,1)
+		[MaterialToggle] PixelSnap("Pixel snap", Float) = 0
+		[HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
+		[HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
+		[PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
+		[PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
+	}
+
+		SubShader
+		{
+			Tags
+			{
+				"Queue" = "Transparent"
+				"IgnoreProjector" = "True"
+				"RenderType" = "Transparent"
+				"PreviewType" = "Plane"
+				"CanUseSpriteAtlas" = "True"
+			}
+
+			Cull Off
+			Lighting Off
+			ZWrite Off
+			Blend One OneMinusSrcAlpha
+
+			CGPROGRAM
+			//#pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
+			#pragma surface surf TwoSidedLambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
+			#pragma multi_compile_local _ PIXELSNAP_ON
+			#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
+			#include "UnitySprites.cginc"
+
+			struct Input
+			{
+				float2 uv_MainTex;
+				fixed4 color;
+			};
+
+			// added "abs" 2 sided lighting
+			half4 LightingTwoSidedLambert(SurfaceOutput s, half3 lightDir, half atten) {
+				half NdotL = abs(dot(s.Normal, lightDir));
+				half4 c;
+				c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
+				c.a = s.Alpha;
+				return c;
+			}
+
+			void vert(inout appdata_full v, out Input o)
+			{
+				v.vertex = UnityFlipSprite(v.vertex, _Flip);
+
+				#if defined(PIXELSNAP_ON)
+				v.vertex = UnityPixelSnap(v.vertex);
+				#endif
+
+				//v.normal.z *= -1;
+
+				UNITY_INITIALIZE_OUTPUT(Input, o);
+				o.color = v.color * _Color * _RendererColor;
+			}
+
+			void surf(Input IN, inout SurfaceOutput o)
+			{
+				fixed4 c = SampleSpriteTexture(IN.uv_MainTex) * IN.color;
+				o.Albedo = c.rgb * c.a;
+				o.Alpha = c.a;
+			}
+			ENDCG
+		}
+
+			Fallback "Transparent/VertexLit"
+}

From 329f7d8f2bb8306b5df409f5ab03d1cd44595455 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 17 May 2024 11:35:26 +0300
Subject: [PATCH 04/31] Add editor tool to replace string in selected
 gameobject names

---
 .../ReplaceCharacterInGameObjectNames.cs      | 69 +++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100644 Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs

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<ReplaceCharacterInGameObjectNames>("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.");
+        }
+    }
+}

From 2296f02dabe31b8d5a1491308c82e9f13c761010 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 17 May 2024 11:55:08 +0300
Subject: [PATCH 05/31] add editor tool to copy selected gameobject names to
 clipboard as a list

---
 .../Editor/BatchTools/CopyGameObjectNames.cs  | 55 +++++++++++++++++++
 1 file changed, 55 insertions(+)
 create mode 100644 Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs

diff --git a/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
new file mode 100644
index 0000000..de28cd4
--- /dev/null
+++ b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
@@ -0,0 +1,55 @@
+// 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;
+namespace UnityLibrary.Tools
+{
+    public class CopyGameObjectNames : EditorWindow
+    {
+        private string gameObjectNames = string.Empty;
+
+        [MenuItem("Tools/Copy GameObject Names")]
+        public static void ShowWindow()
+        {
+            GetWindow<CopyGameObjectNames>("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;
+
+            foreach (GameObject obj in selectedObjects)
+            {
+                sb.AppendLine(obj.name);
+            }
+
+            gameObjectNames = sb.ToString();
+        }
+
+        private void CopyToClipboard()
+        {
+            EditorGUIUtility.systemCopyBuffer = gameObjectNames;
+            Debug.Log("GameObject names copied to clipboard.");
+        }
+    }
+}
\ No newline at end of file

From a39816f5b4a5a2a9d305ec13ac3aef017d49c9b9 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 17 May 2024 12:21:40 +0300
Subject: [PATCH 06/31] Fix CopyGameObjectNames.cs to copy in correct hierarchy
 order

---
 Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
index de28cd4..40bf0d0 100644
--- a/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
+++ b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs
@@ -3,6 +3,7 @@
 using UnityEngine;
 using UnityEditor;
 using System.Text;
+using System.Linq;
 namespace UnityLibrary.Tools
 {
     public class CopyGameObjectNames : EditorWindow
@@ -38,7 +39,10 @@ private void FetchNames()
             StringBuilder sb = new StringBuilder();
             GameObject[] selectedObjects = Selection.gameObjects;
 
-            foreach (GameObject obj in selectedObjects)
+            // 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);
             }
@@ -52,4 +56,4 @@ private void CopyToClipboard()
             Debug.Log("GameObject names copied to clipboard.");
         }
     }
-}
\ No newline at end of file
+}

From f13f9beb250f96c99661b967b4fce3e92ccc8949 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Tue, 6 Aug 2024 13:17:11 +0300
Subject: [PATCH 07/31] Create RendererTypeCounter.cs

---
 .../Editor/Tools/RendererTypeCounter.cs       | 108 ++++++++++++++++++
 1 file changed, 108 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/RendererTypeCounter.cs

diff --git a/Assets/Scripts/Editor/Tools/RendererTypeCounter.cs b/Assets/Scripts/Editor/Tools/RendererTypeCounter.cs
new file mode 100644
index 0000000..0cc0ce2
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/RendererTypeCounter.cs
@@ -0,0 +1,108 @@
+// shows renderer stats from selected objects in the editor
+
+using UnityEngine;
+using UnityEditor;
+
+namespace UnityLibrary
+{
+    public class RendererTypeCounter : EditorWindow
+    {
+        private int activeMeshRendererCount;
+        private int inactiveMeshRendererCount;
+        private int activeSkinnedMeshRendererCount;
+        private int inactiveSkinnedMeshRendererCount;
+        private int activeSpriteRendererCount;
+        private int inactiveSpriteRendererCount;
+        private int totalGameObjectCount;
+
+        [MenuItem("Tools/Renderer Type Counter")]
+        public static void ShowWindow()
+        {
+            GetWindow<RendererTypeCounter>("Renderer Type Counter");
+        }
+
+        private void OnGUI()
+        {
+            if (GUILayout.Button("Count Renderers"))
+            {
+                CountRenderersInSelection();
+            }
+
+            GUILayout.Space(10);
+
+            GUILayout.Label("Active Mesh Renderers: " + activeMeshRendererCount);
+            GUILayout.Label("Inactive Mesh Renderers: " + inactiveMeshRendererCount);
+            GUILayout.Label("Active Skinned Mesh Renderers: " + activeSkinnedMeshRendererCount);
+            GUILayout.Label("Inactive Skinned Mesh Renderers: " + inactiveSkinnedMeshRendererCount);
+            GUILayout.Label("Active Sprite Renderers: " + activeSpriteRendererCount);
+            GUILayout.Label("Inactive Sprite Renderers: " + inactiveSpriteRendererCount);
+            GUILayout.Label("Total GameObjects: " + totalGameObjectCount);
+        }
+
+        private void CountRenderersInSelection()
+        {
+            activeMeshRendererCount = 0;
+            inactiveMeshRendererCount = 0;
+            activeSkinnedMeshRendererCount = 0;
+            inactiveSkinnedMeshRendererCount = 0;
+            activeSpriteRendererCount = 0;
+            inactiveSpriteRendererCount = 0;
+            totalGameObjectCount = 0;
+
+            foreach (GameObject obj in Selection.gameObjects)
+            {
+                CountRenderersRecursively(obj);
+            }
+
+            Repaint();
+        }
+
+        private void CountRenderersRecursively(GameObject obj)
+        {
+            totalGameObjectCount++;
+
+            bool isActive = obj.activeInHierarchy;
+
+            if (obj.GetComponent<MeshRenderer>())
+            {
+                if (isActive)
+                {
+                    activeMeshRendererCount++;
+                }
+                else
+                {
+                    inactiveMeshRendererCount++;
+                }
+            }
+
+            if (obj.GetComponent<SkinnedMeshRenderer>())
+            {
+                if (isActive)
+                {
+                    activeSkinnedMeshRendererCount++;
+                }
+                else
+                {
+                    inactiveSkinnedMeshRendererCount++;
+                }
+            }
+
+            if (obj.GetComponent<SpriteRenderer>())
+            {
+                if (isActive)
+                {
+                    activeSpriteRendererCount++;
+                }
+                else
+                {
+                    inactiveSpriteRendererCount++;
+                }
+            }
+
+            foreach (Transform child in obj.transform)
+            {
+                CountRenderersRecursively(child.gameObject);
+            }
+        }
+    }
+}

From 4532dd6984ad76ed9b4ea0d1458be943307b6b7f Mon Sep 17 00:00:00 2001
From: Hasan Bayat <hasanbayat1393@gmail.com>
Date: Fri, 4 Oct 2024 11:49:26 +0330
Subject: [PATCH 08/31] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e7f51b0..f185093 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ Feel free to [:postbox: Post your ideas/comments/improvements/recommendations](h
 ## Credits
 
 - [UnityCoder (mika)](https://github.com/unitycoder) (Owner)
-- [EmpireWorld (Hasan Bayat)](https://github.com/EmpireWorld) (Owner)
+- [hasanbayatme (Hasan Bayat)](https://github.com/hasanbayatme) (Owner)
 - [Lootheo (Manuel Otheo)](https://github.com/Lootheo) (Member)
 - [igorrafael (Igor Rafael de Sousa)](https://github.com/igorrafael) (Member)
 - [nrlnd](https://github.com/nrlnd) (Member)

From 3e2d55b01d812be62b5239fc8f2d4c3b3edd2a2b Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Tue, 22 Oct 2024 21:46:37 +0300
Subject: [PATCH 09/31] Create FindWhatButtonCallsMyMethod.cs

---
 .../Tools/FindWhatButtonCallsMyMethod.cs      | 69 +++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs

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>("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<Button>();
+
+            foreach (var button in allButtons)
+            {
+                UnityEvent clickEvent = button.onClick;
+
+                for (int i = 0; i < clickEvent.GetPersistentEventCount(); i++)
+                {
+                    Object target = clickEvent.GetPersistentTarget(i);
+                    string method = clickEvent.GetPersistentMethodName(i);
+
+                    if (method == methodName)
+                    {
+                        MethodInfo methodInfo = target.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+                        if (methodInfo != null)
+                        {
+                            ParameterInfo[] parameters = methodInfo.GetParameters();
+
+                            // Check if the method has arguments
+                            if (parameters.Length > 0)
+                            {
+                                Debug.Log($"Button '{button.gameObject.name}' calls method '{methodName}' with parameters on object '{target.name}'", button.gameObject);
+                            }
+                            else
+                            {
+                                Debug.Log($"Button '{button.gameObject.name}' calls method '{methodName}' with no parameters on object '{target.name}'", button.gameObject);
+                            }
+                        }
+                    }
+                }
+            }
+
+            Debug.Log("Search Complete");
+        }
+    } // class
+} // namespace

From 05f3e40b927e53d59a5b06f02837082b74a2eee9 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 24 Oct 2024 09:26:34 +0300
Subject: [PATCH 10/31] Create SelectAssetsByType.cs

---
 .../Editor/Tools/SelectAssetsByType.cs        | 122 ++++++++++++++++++
 1 file changed, 122 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/SelectAssetsByType.cs

diff --git a/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs b/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs
new file mode 100644
index 0000000..adc5893
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs
@@ -0,0 +1,122 @@
+// select multiple files or folders from Project window, then can filter by type and select all objects of that type
+
+using UnityEditor;
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace UnityLibrary
+{
+    public class SelectAssetsByType : EditorWindow
+    {
+        private Dictionary<System.Type, List<Object>> groupedObjects;
+        private List<Object> allObjects;
+
+        [MenuItem("Tools/UnityLibrary/Select Assets By Type")]
+        public static void ShowWindow()
+        {
+            GetWindow<SelectAssetsByType>("Select Assets By Type");
+        }
+
+        private void OnEnable()
+        {
+            // Automatically refresh whenever selection changes
+            Selection.selectionChanged += RefreshSelection;
+            RefreshSelection();
+        }
+
+        private void OnDisable()
+        {
+            // Unsubscribe from selection change event
+            Selection.selectionChanged -= RefreshSelection;
+        }
+
+        private void OnGUI()
+        {
+            if (groupedObjects != null)
+            {
+                foreach (var group in groupedObjects)
+                {
+                    if (group.Value.Count > 0)
+                    {
+                        // Show type and number of objects
+                        if (GUILayout.Button($"{group.Value.Count} {group.Key.Name}(s)"))
+                        {
+                            // Select objects of this type in the editor
+                            SelectObjects(group.Value);
+                        }
+                    }
+                }
+            }
+        }
+
+        private void RefreshSelection()
+        {
+            groupedObjects = new Dictionary<System.Type, List<Object>>();
+            allObjects = new List<Object>();
+
+            // Get selected objects and their children
+            var selectedObjects = Selection.objects;
+            foreach (var obj in selectedObjects)
+            {
+                allObjects.Add(obj);
+
+                // If the object is a folder, include assets inside the folder recursively
+                if (AssetDatabase.IsValidFolder(AssetDatabase.GetAssetPath(obj)))
+                {
+                    AddFolderContents(AssetDatabase.GetAssetPath(obj));
+                }
+                else
+                {
+                    // Add children if it's a GameObject
+                    if (obj is GameObject gameObject)
+                    {
+                        AddChildren(gameObject);
+                    }
+                }
+            }
+
+            // Group by type
+            foreach (var obj in allObjects)
+            {
+                var type = obj.GetType();
+                if (!groupedObjects.ContainsKey(type))
+                {
+                    groupedObjects[type] = new List<Object>();
+                }
+                groupedObjects[type].Add(obj);
+            }
+
+            // Refresh the window UI
+            Repaint();
+        }
+
+        private void AddChildren(GameObject obj)
+        {
+            foreach (Transform child in obj.transform)
+            {
+                allObjects.Add(child.gameObject);
+                AddChildren(child.gameObject);
+            }
+        }
+
+        private void AddFolderContents(string folderPath)
+        {
+            // Get all assets in the folder and subfolders
+            string[] guids = AssetDatabase.FindAssets("", new[] { folderPath });
+            foreach (string guid in guids)
+            {
+                string path = AssetDatabase.GUIDToAssetPath(guid);
+                Object asset = AssetDatabase.LoadAssetAtPath<Object>(path);
+                if (asset != null)
+                {
+                    allObjects.Add(asset);
+                }
+            }
+        }
+
+        private void SelectObjects(List<Object> objects)
+        {
+            Selection.objects = objects.ToArray();
+        }
+    }
+}

From 34f32554a9078fca5247fa4771c647d35a1ce0a1 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 30 Oct 2024 13:10:05 +0200
Subject: [PATCH 11/31] Update SelectAssetsByType.cs (handle sprites under
 gameobject asset)

---
 .../Editor/Tools/SelectAssetsByType.cs        | 48 +++++++++++++++++--
 1 file changed, 43 insertions(+), 5 deletions(-)

diff --git a/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs b/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs
index adc5893..51f326b 100644
--- a/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs
+++ b/Assets/Scripts/Editor/Tools/SelectAssetsByType.cs
@@ -67,10 +67,15 @@ private void RefreshSelection()
                 }
                 else
                 {
-                    // Add children if it's a GameObject
+                    // Add children if it's a GameObject and find sprites in it
                     if (obj is GameObject gameObject)
                     {
-                        AddChildren(gameObject);
+                        AddChildrenAndSprites(gameObject);
+                    }
+                    // If it's a Texture2D, load its Sprites (if any)
+                    else if (obj is Texture2D texture)
+                    {
+                        AddTextureSprites(texture);
                     }
                 }
             }
@@ -90,12 +95,22 @@ private void RefreshSelection()
             Repaint();
         }
 
-        private void AddChildren(GameObject obj)
+        private void AddChildrenAndSprites(GameObject obj)
         {
+            // Add the GameObject itself
+            allObjects.Add(obj);
+
+            // Check if the GameObject has a SpriteRenderer and add the Sprite
+            var spriteRenderer = obj.GetComponent<SpriteRenderer>();
+            if (spriteRenderer != null && spriteRenderer.sprite != null)
+            {
+                allObjects.Add(spriteRenderer.sprite); // Add the sprite separately
+            }
+
+            // Recursively add children and their sprites
             foreach (Transform child in obj.transform)
             {
-                allObjects.Add(child.gameObject);
-                AddChildren(child.gameObject);
+                AddChildrenAndSprites(child.gameObject);
             }
         }
 
@@ -107,9 +122,32 @@ private void AddFolderContents(string folderPath)
             {
                 string path = AssetDatabase.GUIDToAssetPath(guid);
                 Object asset = AssetDatabase.LoadAssetAtPath<Object>(path);
+
+                // Add the asset itself
                 if (asset != null)
                 {
                     allObjects.Add(asset);
+
+                    // If it's a Texture2D, also add any Sprites inside it
+                    if (asset is Texture2D texture)
+                    {
+                        AddTextureSprites(texture);
+                    }
+                }
+            }
+        }
+
+        private void AddTextureSprites(Texture2D texture)
+        {
+            // Check if the texture contains any sprites (e.g., from a sprite sheet)
+            string path = AssetDatabase.GetAssetPath(texture);
+            Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path);
+
+            foreach (Object asset in assets)
+            {
+                if (asset is Sprite sprite)
+                {
+                    allObjects.Add(sprite); // Add each sprite separately
                 }
             }
         }

From ace9f47f3dbffe7c611816fb0f85eea13643eb36 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 30 Jan 2025 11:14:11 +0200
Subject: [PATCH 12/31] Update BoxColliderFitChildren.cs

fixed collider for rotated or scaled children
---
 .../ContextMenu/BoxColliderFitChildren.cs     | 45 +++++++++++--------
 1 file changed, 26 insertions(+), 19 deletions(-)

diff --git a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
index 58f7b0d..3287208 100644
--- a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
+++ b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
@@ -14,33 +14,40 @@ static void FixSize(MenuCommand command)
         {
             BoxCollider col = (BoxCollider)command.context;
 
-            // record undo
+            // Record undo for undo functionality
             Undo.RecordObject(col.transform, "Fit Box Collider To Children");
 
-            // get child mesh bounds
-            var b = GetRecursiveMeshBounds(col.gameObject);
+            // Get transformed bounds relative to the collider object
+            Bounds localBounds = GetLocalBounds(col.transform);
 
-            // set collider local center and size
-            col.center = col.transform.root.InverseTransformVector(b.center) - col.transform.position;
-            col.size = b.size;
+            // Set collider local center and size
+            col.center = localBounds.center;
+            col.size = localBounds.size;
         }
 
-        public static Bounds GetRecursiveMeshBounds(GameObject go)
+        public static Bounds GetLocalBounds(Transform parent)
         {
-            var r = go.GetComponentsInChildren<Renderer>();
-            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
+            var renderers = parent.GetComponentsInChildren<Renderer>();
+            if (renderers.Length == 0)
+                return new Bounds(Vector3.zero, Vector3.zero); // No renderers
+
+            // Initialize bounds in local space
+            Bounds bounds = new Bounds(parent.InverseTransformPoint(renderers[0].bounds.center), 
+                                       parent.InverseTransformVector(renderers[0].bounds.size));
+
+            // Encapsulate all child renderers
+            for (int i = 1; i < renderers.Length; i++)
             {
-                return new Bounds(Vector3.one, Vector3.one);
+                var worldBounds = renderers[i].bounds;
+
+                // Convert world bounds to local space
+                Vector3 localCenter = parent.InverseTransformPoint(worldBounds.center);
+                Vector3 localSize = parent.InverseTransformVector(worldBounds.size);
+
+                bounds.Encapsulate(new Bounds(localCenter, localSize));
             }
+
+            return bounds;
         }
     }
 }

From 3039e539f96efcb645ad7d64a787db92edd60d89 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 30 Jan 2025 14:47:29 +0200
Subject: [PATCH 13/31] Update BoxColliderFitChildren.cs

fix rotated objects, fix negative scale
---
 .../ContextMenu/BoxColliderFitChildren.cs     | 61 +++++++++++--------
 1 file changed, 34 insertions(+), 27 deletions(-)

diff --git a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
index 3287208..2690929 100644
--- a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
+++ b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
@@ -1,6 +1,5 @@
 // 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,44 +9,52 @@ 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 for undo functionality
+            // record undo
             Undo.RecordObject(col.transform, "Fit Box Collider To Children");
 
-            // Get transformed bounds relative to the collider object
-            Bounds localBounds = GetLocalBounds(col.transform);
+            // first reset transform rotation
+            var origRot = col.transform.rotation;
+            col.transform.rotation = Quaternion.identity;
 
-            // Set collider local center and size
-            col.center = localBounds.center;
-            col.size = localBounds.size;
-        }
+            // get child mesh bounds
+            var b = GetRecursiveMeshBounds(col.gameObject);
 
-        public static Bounds GetLocalBounds(Transform parent)
-        {
-            var renderers = parent.GetComponentsInChildren<Renderer>();
-            if (renderers.Length == 0)
-                return new Bounds(Vector3.zero, Vector3.zero); // No renderers
+            // set collider local center and size
+            col.center = col.transform.root.InverseTransformVector(b.center) - col.transform.position;
 
-            // Initialize bounds in local space
-            Bounds bounds = new Bounds(parent.InverseTransformPoint(renderers[0].bounds.center), 
-                                       parent.InverseTransformVector(renderers[0].bounds.size));
+            // keep size positive
+            var size = b.size;
+            size.x = Mathf.Abs(size.x);
+            size.y = Mathf.Abs(size.y);
+            size.z = Mathf.Abs(size.z);
 
-            // Encapsulate all child renderers
-            for (int i = 1; i < renderers.Length; i++)
-            {
-                var worldBounds = renderers[i].bounds;
+            col.size = b.size;
 
-                // Convert world bounds to local space
-                Vector3 localCenter = parent.InverseTransformPoint(worldBounds.center);
-                Vector3 localSize = parent.InverseTransformVector(worldBounds.size);
+            // restore rotation
+            col.transform.rotation = origRot;
+        }
 
-                bounds.Encapsulate(new Bounds(localCenter, localSize));
+        public static Bounds GetRecursiveMeshBounds(GameObject go)
+        {
+            var r = go.GetComponentsInChildren<Renderer>();
+            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?
+            {
+                //return new Bounds(Vector3.one, Vector3.one);
+                return new Bounds();
             }
-
-            return bounds;
         }
     }
 }

From 812e45e185d1885710d3b8f12211949cabfd5464 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 5 Feb 2025 10:13:04 +0200
Subject: [PATCH 14/31] Create AnimationClipListImporter.cs

---
 .../Importers/AnimationClipListImporter.cs    | 65 +++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs

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<ModelImporterClipAnimation> clips = new List<ModelImporterClipAnimation>();
+                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}");
+            }
+        }
+    }
+}

From 43c11eeb4fa933d4adbe2159f0ef1074fd3c6f7c Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 12 Feb 2025 15:02:15 +0200
Subject: [PATCH 15/31] Create CustomRectTransformInspector.cs

---
 .../CustomRectTransformInspector.cs           | 294 ++++++++++++++++++
 1 file changed, 294 insertions(+)
 create mode 100644 Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs

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<RectTransform>().rect.size.x;
+            anchorMin.y = rectTransform.offsetMin.y / parent.GetComponent<RectTransform>().rect.size.y;
+            anchorMax.x = rectTransform.offsetMax.x / parent.GetComponent<RectTransform>().rect.size.x;
+            anchorMax.y = rectTransform.offsetMax.y / parent.GetComponent<RectTransform>().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

From 9e2158100a30b276a333993f54676ce5d00d200a Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 12 Feb 2025 15:02:46 +0200
Subject: [PATCH 16/31] Create TransformEditor.cs

---
 .../Editor/CustomInspector/TransformEditor.cs | 436 ++++++++++++++++++
 1 file changed, 436 insertions(+)
 create mode 100644 Assets/Scripts/Editor/CustomInspector/TransformEditor.cs

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
+{
+    /// <summary>
+    /// 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.
+    /// </summary>
+    [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
+
+
+        /// <summary> Get translated Transform label </summary>
+        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()));
+        }
+
+        /// <summary> Round all values of the Transform to a given number of decimals </summary>
+        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);
+            }
+        }
+
+        /// <summary> Round all components of a Vector3 </summary>
+        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;
+        }
+
+        /// <summary> Truncate all values of the Transform to a given number of decimals </summary>
+        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);
+            }
+        }
+
+        /// <summary> Truncate all components of a Vector3 </summary>
+        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
+    }
+}

From 84cf73c2e725da11b92be389d5401d4f70dbb82a Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 12 Feb 2025 15:10:51 +0200
Subject: [PATCH 17/31] Create CustomRectTransformCopyInspector.cs

---
 .../CustomRectTransformCopyInspector.cs       | 114 ++++++++++++++++++
 1 file changed, 114 insertions(+)
 create mode 100644 Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs

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

From 2d15f04fed4a6d7b3f2903dcae6b0daba706582a Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Mon, 3 Mar 2025 16:05:17 +0200
Subject: [PATCH 18/31] Update BoxColliderFitChildren.cs

fix fitting if some children are wonky
---
 .../ContextMenu/BoxColliderFitChildren.cs     | 75 +++++++++++--------
 1 file changed, 43 insertions(+), 32 deletions(-)

diff --git a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
index 2690929..ce3f298 100644
--- a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
+++ b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs
@@ -1,6 +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
-
 using UnityEngine;
 using UnityEditor;
 
@@ -13,48 +10,62 @@ static void FitColliderToChildren(MenuCommand command)
         {
             BoxCollider col = (BoxCollider)command.context;
 
-            // record undo
+            // Record undo
             Undo.RecordObject(col.transform, "Fit Box Collider To Children");
 
-            // first reset transform rotation
-            var origRot = col.transform.rotation;
-            col.transform.rotation = Quaternion.identity;
+            // Get world-space bounds of all child meshes
+            var worldBounds = GetRecursiveMeshBounds(col.gameObject);
 
-            // get child mesh bounds
-            var b = GetRecursiveMeshBounds(col.gameObject);
+            if (worldBounds.size == Vector3.zero)
+            {
+                Debug.LogWarning("No valid meshes found to fit the BoxCollider.");
+                return;
+            }
 
-            // set collider local center and size
-            col.center = col.transform.root.InverseTransformVector(b.center) - col.transform.position;
+            // Convert world-space center to local space
+            Vector3 localCenter = col.transform.InverseTransformPoint(worldBounds.center);
 
-            // keep size positive
-            var size = b.size;
-            size.x = Mathf.Abs(size.x);
-            size.y = Mathf.Abs(size.y);
-            size.z = Mathf.Abs(size.z);
+            // Convert world-space size to local space
+            Vector3 localSize = col.transform.InverseTransformVector(worldBounds.size);
 
-            col.size = b.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;
+            }
 
-            // restore rotation
-            col.transform.rotation = origRot;
+            // Apply to collider
+            col.center = localCenter;
+            col.size = localSize;
         }
 
         public static Bounds GetRecursiveMeshBounds(GameObject go)
         {
-            var r = go.GetComponentsInChildren<Renderer>();
-            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?
-            {
-                //return new Bounds(Vector3.one, Vector3.one);
+            Renderer[] renderers = go.GetComponentsInChildren<Renderer>();
+
+            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++)
+            {
+                worldBounds.Encapsulate(renderers[i].bounds);
             }
+
+            return worldBounds;
         }
     }
 }

From 5ed776a118bd48c141f01095fddf333303f1f7d2 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 5 Mar 2025 10:19:22 +0200
Subject: [PATCH 19/31] Create Standard Stipple Transparency.shader

---
 .../Standard Stipple Transparency.shader      | 58 +++++++++++++++++++
 1 file changed, 58 insertions(+)
 create mode 100644 Assets/Shaders/3D/Effects/Standard Stipple Transparency.shader

diff --git a/Assets/Shaders/3D/Effects/Standard Stipple Transparency.shader b/Assets/Shaders/3D/Effects/Standard Stipple Transparency.shader
new file mode 100644
index 0000000..2e312da
--- /dev/null
+++ b/Assets/Shaders/3D/Effects/Standard Stipple Transparency.shader	
@@ -0,0 +1,58 @@
+// Standard shader with stipple transparency
+// by Alex Ocias - https://ocias.com
+// source: https://ocias.com/blog/unity-stipple-transparency-shader/
+// based on an article by Digital Rune: https://www.digitalrune.com/Blog/Post/1743/Screen-Door-Transparency
+
+Shader "Ocias/Standard (Stipple Transparency)" {
+	Properties {
+		_Color ("Color", Color) = (1,1,1,1)
+		_MainTex ("Albedo (RGB)", 2D) = "white" {}
+		_Glossiness ("Smoothness", Range(0,1)) = 0.5
+		_Metallic ("Metallic", Range(0,1)) = 0.0
+	}
+	SubShader {
+		Tags { "RenderType"="Opaque" }
+		LOD 100
+		
+		CGPROGRAM
+		// Physically based Standard lighting model, and enable shadows on all light types
+		#pragma surface surf Standard fullforwardshadows
+
+		// Use shader model 3.0 target, to get nicer looking lighting
+		#pragma target 3.0
+
+		sampler2D _MainTex;
+
+		struct Input {
+			float2 uv_MainTex;
+			float4 screenPos;
+		};
+
+		half _Glossiness;
+		half _Metallic;
+		fixed4 _Color;
+
+		void surf (Input IN, inout SurfaceOutputStandard o) {
+			// Albedo comes from a texture tinted by color
+			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
+			o.Albedo = c.rgb;
+			// Metallic and smoothness come from slider variables
+			o.Metallic = _Metallic;
+			o.Smoothness = _Glossiness;
+
+			// Screen-door transparency: Discard pixel if below threshold.
+			float4x4 thresholdMatrix =
+			{  1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
+			  13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
+			   4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
+			  16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
+			};
+			float4x4 _RowAccess = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
+			float2 pos = IN.screenPos.xy / IN.screenPos.w;
+			pos *= _ScreenParams.xy; // pixel position
+			clip(c.a - thresholdMatrix[fmod(pos.x, 4)] * _RowAccess[fmod(pos.y, 4)]);
+		}
+		ENDCG
+	}
+	FallBack "Diffuse"
+}

From 4005a5acf5ad463d1c8ca6f014e15cc4b9cd87d9 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 5 Mar 2025 10:19:59 +0200
Subject: [PATCH 20/31] Create Diffuse Stipple Transparency.shader

---
 .../Diffuse Stipple Transparency.shader       | 52 +++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 Assets/Shaders/3D/Effects/Diffuse Stipple Transparency.shader

diff --git a/Assets/Shaders/3D/Effects/Diffuse Stipple Transparency.shader b/Assets/Shaders/3D/Effects/Diffuse Stipple Transparency.shader
new file mode 100644
index 0000000..853c4cf
--- /dev/null
+++ b/Assets/Shaders/3D/Effects/Diffuse Stipple Transparency.shader	
@@ -0,0 +1,52 @@
+// Diffuse shader with stipple transparency
+// by Alex Ocias - https://ocias.com
+// source: https://ocias.com/blog/unity-stipple-transparency-shader/
+// based on an article by Digital Rune: https://www.digitalrune.com/Blog/Post/1743/Screen-Door-Transparency
+
+// Simplified Diffuse shader. Differences from regular Diffuse one:
+// - no Main Color
+// - fully supports only 1 directional light. Other lights can affect it, but it will be per-vertex/SH.
+
+Shader "Ocias/Diffuse (Stipple Transparency)" {
+Properties {
+	_MainTex ("Base (RGB)", 2D) = "white" {}
+	_Transparency ("Transparency", Range(0,1)) = 1.0
+}
+SubShader {
+	Tags { "RenderType"="Opaque" }
+	LOD 150
+
+CGPROGRAM
+#pragma surface surf Lambert noforwardadd
+
+sampler2D _MainTex;
+
+struct Input {
+	float2 uv_MainTex;
+	float4 screenPos;
+};
+
+half _Transparency;
+
+void surf (Input IN, inout SurfaceOutput o) {
+	fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
+	o.Albedo = c.rgb;
+	o.Alpha = c.a;
+
+	// Screen-door transparency: Discard pixel if below threshold.
+	float4x4 thresholdMatrix =
+	{  1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0,
+	  13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0,
+	   4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0,
+	  16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0
+	};
+	float4x4 _RowAccess = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
+	float2 pos = IN.screenPos.xy / IN.screenPos.w;
+	pos *= _ScreenParams.xy; // pixel position
+	clip(_Transparency - thresholdMatrix[fmod(pos.x, 4)] * _RowAccess[fmod(pos.y, 4)]);
+}
+ENDCG
+}
+
+Fallback "Mobile/VertexLit"
+}

From 26927e8a1e5175fb35c7983ec469e3c8d3a42f8f Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Wed, 9 Apr 2025 09:29:58 +0300
Subject: [PATCH 21/31] Create ReferenceImageViewer2.cs

---
 .../Editor/Tools/ReferenceImageViewer2.cs     | 110 ++++++++++++++++++
 1 file changed, 110 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/ReferenceImageViewer2.cs

diff --git a/Assets/Scripts/Editor/Tools/ReferenceImageViewer2.cs b/Assets/Scripts/Editor/Tools/ReferenceImageViewer2.cs
new file mode 100644
index 0000000..e8371c5
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/ReferenceImageViewer2.cs
@@ -0,0 +1,110 @@
+// reference image viewer from external folder
+
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+
+namespace UnityLibrary.Tools
+{
+    public class ReferenceImageViewer2 : EditorWindow
+    {
+        private const string EditorPrefsKey = "ReferenceImageViewer_ImagePath";
+
+        [SerializeField]
+        private string imagePath = "";
+        [SerializeField]
+        private Texture2D loadedImage;
+
+        [MenuItem("Window/Reference Image Viewer")]
+        public static void ShowWindow()
+        {
+            GetWindow<ReferenceImageViewer2>("Reference Image Viewer");
+        }
+
+        private void OnEnable()
+        {
+            // Delay the reload to ensure Unity's layout system is ready
+            EditorApplication.delayCall += TryLoadImage;
+        }
+
+        private void OnDisable()
+        {
+            if (!string.IsNullOrEmpty(imagePath))
+                EditorPrefs.SetString(EditorPrefsKey, imagePath);
+            else
+                EditorPrefs.DeleteKey(EditorPrefsKey);
+        }
+
+        private void TryLoadImage()
+        {
+            imagePath = EditorPrefs.GetString(EditorPrefsKey, "");
+            if (!string.IsNullOrEmpty(imagePath) && File.Exists(imagePath))
+            {
+                LoadImageFromPath(imagePath);
+                Repaint(); // Ensure it's drawn
+            }
+        }
+
+        void OnGUI()
+        {
+            GUILayout.Label("Reference Image Viewer", EditorStyles.boldLabel);
+
+            if (GUILayout.Button("Browse for Image"))
+            {
+                string path = EditorUtility.OpenFilePanel("Select Image", "", "png,jpg,jpeg");
+                if (!string.IsNullOrEmpty(path))
+                {
+                    imagePath = path;
+                    EditorPrefs.SetString(EditorPrefsKey, imagePath);
+                    LoadImageFromPath(imagePath);
+                }
+            }
+
+            // Fallback in case delayCall missed
+            if (loadedImage == null && !string.IsNullOrEmpty(imagePath) && File.Exists(imagePath))
+            {
+                LoadImageFromPath(imagePath);
+                Repaint();
+            }
+
+            if (loadedImage != null)
+            {
+                GUILayout.Space(10);
+
+                float windowWidth = position.width - 20;
+                float imageAspect = (float)loadedImage.width / loadedImage.height;
+
+                float displayWidth = windowWidth;
+                float displayHeight = windowWidth / imageAspect;
+
+                if (displayHeight > position.height - 100)
+                {
+                    displayHeight = position.height - 100;
+                    displayWidth = displayHeight * imageAspect;
+                }
+
+                Rect imageRect = GUILayoutUtility.GetRect(displayWidth, displayHeight, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
+                EditorGUI.DrawPreviewTexture(imageRect, loadedImage, null, ScaleMode.ScaleToFit);
+            }
+        }
+
+        private void LoadImageFromPath(string path)
+        {
+            try
+            {
+                byte[] fileData = File.ReadAllBytes(path);
+                loadedImage = new Texture2D(2, 2, TextureFormat.RGBA32, false);
+                if (!loadedImage.LoadImage(fileData))
+                {
+                    Debug.LogError("Failed to load image.");
+                    loadedImage = null;
+                }
+            }
+            catch (System.Exception e)
+            {
+                Debug.LogError($"Could not load image from path: {path}\n{e.Message}");
+                loadedImage = null;
+            }
+        }
+    }
+}

From 13b1f1630bf03876994fd545c6a7a43d069e4f26 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Mon, 14 Apr 2025 16:07:26 +0300
Subject: [PATCH 22/31] Create PreciseOffsetEditor.cs

---
 .../Editor/Tools/PreciseOffsetEditor.cs       | 118 ++++++++++++++++++
 1 file changed, 118 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/PreciseOffsetEditor.cs

diff --git a/Assets/Scripts/Editor/Tools/PreciseOffsetEditor.cs b/Assets/Scripts/Editor/Tools/PreciseOffsetEditor.cs
new file mode 100644
index 0000000..f3c5a44
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/PreciseOffsetEditor.cs
@@ -0,0 +1,118 @@
+// gameobject position fiddling tool (when dragging x,y,z axist in transform panel is too fast)
+
+using UnityEngine;
+using UnityEditor;
+
+namespace UnityLibrary.SceneTools
+{
+    public class PreciseOffsetEditor : EditorWindow
+    {
+        private const string SpeedMultiplierKey = "UnityLibrary_PreciseOffset_SpeedMultiplier";
+
+        private GameObject selectedObject;
+        private Vector3 offsetSliderValues = Vector3.zero;
+        private float speedMultiplier = 0.01f;
+
+        private Vector3 originalPosition;
+        private GameObject lastSelectedObject;
+
+        [MenuItem("Tools/UnityLibrary/Precise Model Offset")]
+        public static void ShowWindow()
+        {
+            var win = GetWindow<PreciseOffsetEditor>("Precise Model Offset");
+            win.minSize = new Vector2(300, 220);
+            win.maxSize = new Vector2(300, 220);
+        }
+
+        private void OnEnable()
+        {
+            speedMultiplier = EditorPrefs.GetFloat(SpeedMultiplierKey, 0.01f);
+        }
+
+        private void OnDisable()
+        {
+            EditorPrefs.SetFloat(SpeedMultiplierKey, speedMultiplier);
+        }
+
+        private void OnGUI()
+        {
+            EditorGUILayout.LabelField("Precise Model Offset", EditorStyles.boldLabel);
+            EditorGUILayout.Space();
+
+            int selectedCount = Selection.gameObjects.Length;
+            selectedObject = Selection.activeGameObject;
+
+            if (selectedCount == 0)
+            {
+                EditorGUILayout.HelpBox("No GameObject selected.", MessageType.Warning);
+                return;
+            }
+
+            if (selectedCount == 1)
+            {
+                EditorGUILayout.LabelField("Selected: " + selectedObject.name);
+            }
+            else
+            {
+                EditorGUILayout.LabelField("Selected: (multiple)");
+            }
+
+            if (selectedObject != lastSelectedObject)
+            {
+                originalPosition = selectedObject.transform.position;
+                offsetSliderValues = Vector3.zero;
+                lastSelectedObject = selectedObject;
+            }
+
+            EditorGUILayout.Space();
+            EditorGUI.BeginChangeCheck();
+            speedMultiplier = EditorGUILayout.Slider("Speed Multiplier", speedMultiplier, 0.001f, 1f);
+            if (EditorGUI.EndChangeCheck())
+            {
+                EditorPrefs.SetFloat(SpeedMultiplierKey, speedMultiplier);
+            }
+
+            EditorGUILayout.Space();
+            EditorGUI.BeginChangeCheck();
+
+            // Store old values to detect changes
+            Vector3 oldOffset = offsetSliderValues;
+
+            offsetSliderValues.x = EditorGUILayout.Slider("X Offset", offsetSliderValues.x, -100f, 100f);
+            offsetSliderValues.y = EditorGUILayout.Slider("Y Offset", offsetSliderValues.y, -100f, 100f);
+            offsetSliderValues.z = EditorGUILayout.Slider("Z Offset", offsetSliderValues.z, -100f, 100f);
+
+            Vector3 delta = offsetSliderValues - oldOffset;
+
+            if (delta != Vector3.zero)
+            {
+                Undo.RecordObject(selectedObject.transform, "Precise Offset");
+
+                Vector3 newPosition = selectedObject.transform.position;
+
+                if (!Mathf.Approximately(delta.x, 0f))
+                    newPosition.x = originalPosition.x + offsetSliderValues.x * speedMultiplier;
+
+                if (!Mathf.Approximately(delta.y, 0f))
+                    newPosition.y = originalPosition.y + offsetSliderValues.y * speedMultiplier;
+
+                if (!Mathf.Approximately(delta.z, 0f))
+                    newPosition.z = originalPosition.z + offsetSliderValues.z * speedMultiplier;
+
+                selectedObject.transform.position = newPosition;
+
+                EditorUtility.SetDirty(selectedObject);
+            }
+
+            EditorGUILayout.Space();
+            if (GUILayout.Button("Reset Offset"))
+            {
+                offsetSliderValues = Vector3.zero;
+                if (selectedObject != null)
+                {
+                    selectedObject.transform.position = originalPosition;
+                }
+            }
+        }
+    }
+}

From 48de7352cfc6db616b2a0e66a267c763dede2f7d Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 17 Apr 2025 14:31:36 +0300
Subject: [PATCH 23/31] Create PasteScript.cs

---
 Assets/Scripts/Editor/Tools/PasteScript.cs | 85 ++++++++++++++++++++++
 1 file changed, 85 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/PasteScript.cs

diff --git a/Assets/Scripts/Editor/Tools/PasteScript.cs b/Assets/Scripts/Editor/Tools/PasteScript.cs
new file mode 100644
index 0000000..84a1561
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/PasteScript.cs
@@ -0,0 +1,85 @@
+// new version of the https://github.com/UnityCommunity/UnityLibrary/blob/master/Assets/Scripts/Editor/Tools/CopyPasteHelper.cs
+// creates c# script from clipboard when click button
+
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace UnityLibrary.Tools
+{
+    public class PasteScript : EditorWindow
+    {
+        private string statusMessage = "";
+        private string lastCreatedScriptPath = "";
+
+        [MenuItem("Tools/UnityLibrary/Clipboard To C# Script")]
+        public static void ShowWindow()
+        {
+            GetWindow<PasteScript>("Clipboard to Script");
+        }
+
+        void OnGUI()
+        {
+            if (GUILayout.Button("Create Script from Clipboard"))
+            {
+                TryCreateScriptFromClipboard();
+            }
+
+            GUILayout.Space(10);
+
+            // Draw clickable HelpBox
+            EditorGUILayout.HelpBox(statusMessage, MessageType.Info);
+
+            Rect helpBoxRect = GUILayoutUtility.GetLastRect();
+            if (!string.IsNullOrEmpty(lastCreatedScriptPath) && Event.current.type == EventType.MouseDown && helpBoxRect.Contains(Event.current.mousePosition))
+            {
+                var asset = AssetDatabase.LoadAssetAtPath<Object>(lastCreatedScriptPath);
+                if (asset != null)
+                {
+                    EditorGUIUtility.PingObject(asset);
+                }
+                Event.current.Use(); // Consume the click
+            }
+        }
+
+        void TryCreateScriptFromClipboard()
+        {
+            string clipboard = EditorGUIUtility.systemCopyBuffer;
+
+            if (IsProbablyCSharp(clipboard))
+            {
+                string folderPath = "Assets/Scripts/Generated";
+                Directory.CreateDirectory(folderPath);
+
+                string className = GetClassName(clipboard) ?? "GeneratedScript";
+                string path = AssetDatabase.GenerateUniqueAssetPath($"{folderPath}/{className}.cs");
+
+                File.WriteAllText(path, clipboard);
+                AssetDatabase.Refresh();
+
+                statusMessage = $"Script created: {path}";
+                lastCreatedScriptPath = path;
+            }
+            else
+            {
+                statusMessage = "Clipboard does not contain valid C# code.";
+                lastCreatedScriptPath = "";
+            }
+        }
+
+        bool IsProbablyCSharp(string text)
+        {
+            if (string.IsNullOrWhiteSpace(text)) return false;
+
+            // Basic heuristic checks
+            return Regex.IsMatch(text, @"\b(class|struct|interface|using|namespace)\b");
+        }
+
+        string GetClassName(string text)
+        {
+            Match match = Regex.Match(text, @"\bclass\s+(\w+)");
+            return match.Success ? match.Groups[1].Value : null;
+        }
+    }
+}

From 8b590f2c74ee4af3424e08c1cc8dc466021a9711 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 25 Apr 2025 11:43:12 +0300
Subject: [PATCH 24/31] Create GameViewGridOverlay.cs

---
 .../Editor/Tools/GameViewGridOverlay.cs       | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs

diff --git a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
new file mode 100644
index 0000000..a7f87ae
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
@@ -0,0 +1,45 @@
+// draws grid lines in the game view (useful for seeing the resolution of ui elements in the game view)
+// usage: attach to a game object in the scene, set gameobject tag to "EditorOnly" to remove from builds
+
+using UnityEngine;
+
+namespace UnityLibrary.EditorTools
+{
+    [ExecuteAlways]
+    public class GameViewGridOverlay : MonoBehaviour
+    {
+#if UNITY_EDITOR
+        public bool drawGrid = true;
+
+        public int gridSpacingX = 64;
+        public int gridSpacingY = 64;
+
+        public int startOffsetX = 0;
+        public int startOffsetY = 0;
+
+        public Color gridColor = new Color(1f, 1f, 1f, 0.5f);
+
+        private void OnGUI()
+        {
+            if (!drawGrid || Application.isPlaying) return;
+
+            Color oldColor = GUI.color;
+            GUI.color = gridColor;
+
+            // Horizontal lines
+            for (int y = startOffsetX; y < Screen.height; y += gridSpacingY)
+            {
+                GUI.DrawTexture(new Rect(0, y, Screen.width, 1), Texture2D.whiteTexture);
+            }
+
+            // Vertical lines
+            for (int x = startOffsetY; x < Screen.width; x += gridSpacingX)
+            {
+                GUI.DrawTexture(new Rect(x, 0, 1, Screen.height), Texture2D.whiteTexture);
+            }
+
+            GUI.color = oldColor;
+        }
+#endif
+    }
+}

From 437db6375f25c0d47d08b8cf543dc3a7e5c754f0 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 25 Apr 2025 11:48:26 +0300
Subject: [PATCH 25/31] Create README.md

---
 Assets/Scripts/Editor/Tools/README.md | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/README.md

diff --git a/Assets/Scripts/Editor/Tools/README.md b/Assets/Scripts/Editor/Tools/README.md
new file mode 100644
index 0000000..20d6e2a
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/README.md
@@ -0,0 +1,2 @@
+### GameViewGridOverlay.cs
+![Image](https://github.com/user-attachments/assets/48fbced4-48e0-49fe-9acc-666f5449a958)

From acb71e7a1e95430c371cdf3e054986f200a858c5 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 25 Apr 2025 12:31:50 +0300
Subject: [PATCH 26/31] Update GameViewGridOverlay.cs

---
 Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
index a7f87ae..143bedc 100644
--- a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
+++ b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
@@ -26,14 +26,14 @@ private void OnGUI()
             Color oldColor = GUI.color;
             GUI.color = gridColor;
 
-            // Horizontal lines
-            for (int y = startOffsetX; y < Screen.height; y += gridSpacingY)
+            // vertical lines
+            for (int y = startOffsetY; y < Screen.height; y += gridSpacingY)
             {
                 GUI.DrawTexture(new Rect(0, y, Screen.width, 1), Texture2D.whiteTexture);
             }
 
-            // Vertical lines
-            for (int x = startOffsetY; x < Screen.width; x += gridSpacingX)
+            // horizontal lines
+            for (int x = startOffsetX; x < Screen.width; x += gridSpacingX)
             {
                 GUI.DrawTexture(new Rect(x, 0, 1, Screen.height), Texture2D.whiteTexture);
             }

From 69fe35d66df49842a2152e6bb6f7c472cdc2f939 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 25 Apr 2025 12:51:50 +0300
Subject: [PATCH 27/31] Update GameViewGridOverlay.cs - adding spacing support

---
 .../Editor/Tools/GameViewGridOverlay.cs       | 35 ++++++++++++-------
 1 file changed, 22 insertions(+), 13 deletions(-)

diff --git a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
index 143bedc..380bf2a 100644
--- a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
+++ b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
@@ -1,6 +1,3 @@
-// draws grid lines in the game view (useful for seeing the resolution of ui elements in the game view)
-// usage: attach to a game object in the scene, set gameobject tag to "EditorOnly" to remove from builds
-
 using UnityEngine;
 
 namespace UnityLibrary.EditorTools
@@ -11,9 +8,15 @@ public class GameViewGridOverlay : MonoBehaviour
 #if UNITY_EDITOR
         public bool drawGrid = true;
 
-        public int gridSpacingX = 64;
-        public int gridSpacingY = 64;
+        [Header("Grid Cell Size (visible area)")]
+        public int gridSizeX = 64;
+        public int gridSizeY = 64;
+
+        [Header("Spacing Between Cells (invisible gap)")]
+        public int spacingX = 16;
+        public int spacingY = 16;
 
+        [Header("Start Offsets")]
         public int startOffsetX = 0;
         public int startOffsetY = 0;
 
@@ -26,16 +29,22 @@ private void OnGUI()
             Color oldColor = GUI.color;
             GUI.color = gridColor;
 
-            // vertical lines
-            for (int y = startOffsetY; y < Screen.height; y += gridSpacingY)
-            {
-                GUI.DrawTexture(new Rect(0, y, Screen.width, 1), Texture2D.whiteTexture);
-            }
+            int cellStrideX = gridSizeX + spacingX;
+            int cellStrideY = gridSizeY + spacingY;
 
-            // horizontal lines
-            for (int x = startOffsetX; x < Screen.width; x += gridSpacingX)
+            for (int y = startOffsetY; y + gridSizeY <= Screen.height; y += cellStrideY)
             {
-                GUI.DrawTexture(new Rect(x, 0, 1, Screen.height), Texture2D.whiteTexture);
+                for (int x = startOffsetX; x + gridSizeX <= Screen.width; x += cellStrideX)
+                {
+                    // Left line
+                    GUI.DrawTexture(new Rect(x, y, 1, gridSizeY), Texture2D.whiteTexture);
+                    // Right line
+                    GUI.DrawTexture(new Rect(x + gridSizeX - 1, y, 1, gridSizeY), Texture2D.whiteTexture);
+                    // Top line
+                    GUI.DrawTexture(new Rect(x, y, gridSizeX, 1), Texture2D.whiteTexture);
+                    // Bottom line
+                    GUI.DrawTexture(new Rect(x, y + gridSizeY - 1, gridSizeX, 1), Texture2D.whiteTexture);
+                }
             }
 
             GUI.color = oldColor;

From 61f149cbada8d0668cf65d95ede339975156a439 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Fri, 25 Apr 2025 12:54:11 +0300
Subject: [PATCH 28/31] Update GameViewGridOverlay.cs

fix: need to  draw past screensize to see last edges
---
 .../Scripts/Editor/Tools/GameViewGridOverlay.cs   | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
index 380bf2a..06acb2e 100644
--- a/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
+++ b/Assets/Scripts/Editor/Tools/GameViewGridOverlay.cs
@@ -32,17 +32,20 @@ private void OnGUI()
             int cellStrideX = gridSizeX + spacingX;
             int cellStrideY = gridSizeY + spacingY;
 
-            for (int y = startOffsetY; y + gridSizeY <= Screen.height; y += cellStrideY)
+            // Loop until start of the cell is beyond screen, not end of cell
+            for (int y = startOffsetY; y < Screen.height; y += cellStrideY)
             {
-                for (int x = startOffsetX; x + gridSizeX <= Screen.width; x += cellStrideX)
+                for (int x = startOffsetX; x < Screen.width; x += cellStrideX)
                 {
-                    // Left line
+                    // Draw full box even if it goes beyond screen edges
+
+                    // Left
                     GUI.DrawTexture(new Rect(x, y, 1, gridSizeY), Texture2D.whiteTexture);
-                    // Right line
+                    // Right
                     GUI.DrawTexture(new Rect(x + gridSizeX - 1, y, 1, gridSizeY), Texture2D.whiteTexture);
-                    // Top line
+                    // Top
                     GUI.DrawTexture(new Rect(x, y, gridSizeX, 1), Texture2D.whiteTexture);
-                    // Bottom line
+                    // Bottom
                     GUI.DrawTexture(new Rect(x, y + gridSizeY - 1, gridSizeX, 1), Texture2D.whiteTexture);
                 }
             }

From 5ede7f09e9f63080223111ed2e09b4473e33dd92 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Mon, 28 Apr 2025 14:59:35 +0300
Subject: [PATCH 29/31] Create RectTransformCloner.cs

---
 .../Editor/Tools/RectTransformCloner.cs       | 151 ++++++++++++++++++
 1 file changed, 151 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/RectTransformCloner.cs

diff --git a/Assets/Scripts/Editor/Tools/RectTransformCloner.cs b/Assets/Scripts/Editor/Tools/RectTransformCloner.cs
new file mode 100644
index 0000000..fd55097
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/RectTransformCloner.cs
@@ -0,0 +1,151 @@
+// clones UI RectTransform values from one GameObject to another in Unity Editor (only for identical hierarchy)
+
+using UnityEngine;
+using UnityEditor;
+using TMPro;
+using UnityEngine.UI;
+
+namespace UnityLibrary.Tools
+{
+    public class RectTransformCloner : EditorWindow
+    {
+        private GameObject source;
+        private GameObject target;
+        private bool requireIdenticalNames = true;
+        private bool cloneTMPAlignment = false;
+
+        [MenuItem("Tools/RectTransform Cloner")]
+        private static void ShowWindow()
+        {
+            var window = GetWindow<RectTransformCloner>();
+            window.titleContent = new GUIContent("RectTransform Cloner");
+            window.Show();
+        }
+
+        private void OnGUI()
+        {
+            GUILayout.Label("Clone RectTransform", EditorStyles.boldLabel);
+
+            source = (GameObject)EditorGUILayout.ObjectField("Source", source, typeof(GameObject), true);
+            target = (GameObject)EditorGUILayout.ObjectField("Target", target, typeof(GameObject), true);
+            requireIdenticalNames = EditorGUILayout.Toggle("Require Identical Names", requireIdenticalNames);
+            cloneTMPAlignment = EditorGUILayout.Toggle("Clone TMP Alignment", cloneTMPAlignment);
+
+            if (GUILayout.Button("Clone RectTransforms"))
+            {
+                if (source == null || target == null)
+                {
+                    Debug.LogError("Source and Target must be assigned.");
+                    return;
+                }
+
+                string errorMessage;
+                if (!CompareHierarchies(source.transform, target.transform, out errorMessage))
+                {
+                    Debug.LogError("Source and Target hierarchies do not match!\n" + errorMessage, target);
+                    return;
+                }
+
+                Undo.RegisterFullObjectHierarchyUndo(target, "Clone RectTransform Values");
+                CopyRectTransforms(source.transform, target.transform);
+
+                Debug.Log("RectTransform values cloned successfully.", target);
+
+                if (target.transform.parent != null)
+                {
+                    RectTransform parentRect = target.transform.parent as RectTransform;
+                    if (parentRect != null)
+                    {
+                        LayoutRebuilder.ForceRebuildLayoutImmediate(parentRect);
+                    }
+                    else
+                    {
+                        Debug.LogWarning("Target's parent is not a RectTransform, cannot force layout rebuild.", target);
+                    }
+                }
+                else
+                {
+                    Debug.LogWarning("Target has no parent, cannot force layout rebuild.", target);
+                }
+                EditorUtility.SetDirty(target);
+                SceneView.RepaintAll();
+            }
+        }
+
+        private bool CompareHierarchies(Transform source, Transform target, out string errorMessage)
+        {
+            errorMessage = "";
+
+            if (source.childCount != target.childCount)
+            {
+                errorMessage = $"Child count mismatch at {GetTransformPath(source)}: Source has {source.childCount}, Target has {target.childCount}";
+                return false;
+            }
+
+            for (int i = 0; i < source.childCount; i++)
+            {
+                var sourceChild = source.GetChild(i);
+                var targetChild = target.GetChild(i);
+
+                if (requireIdenticalNames && sourceChild.name != targetChild.name)
+                {
+                    errorMessage = $"Child name mismatch at {GetTransformPath(sourceChild)}: Source has '{sourceChild.name}', Target has '{targetChild.name}'";
+                    return false;
+                }
+
+                if (!CompareHierarchies(sourceChild, targetChild, out errorMessage))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private void CopyRectTransforms(Transform source, Transform target)
+        {
+            var sourceRect = source as RectTransform;
+            var targetRect = target as RectTransform;
+
+            if (sourceRect != null && targetRect != null)
+            {
+                CopyRectTransformValues(sourceRect, targetRect);
+
+                if (cloneTMPAlignment)
+                {
+                    var sourceTMP = source.GetComponent<TextMeshProUGUI>();
+                    var targetTMP = target.GetComponent<TextMeshProUGUI>();
+                    if (sourceTMP != null && targetTMP != null)
+                    {
+                        Undo.RecordObject(targetTMP, "Clone TMP Alignment");
+                        targetTMP.alignment = sourceTMP.alignment;
+                    }
+                }
+            }
+
+            for (int i = 0; i < source.childCount; i++)
+            {
+                CopyRectTransforms(source.GetChild(i), target.GetChild(i));
+            }
+        }
+
+        private void CopyRectTransformValues(RectTransform source, RectTransform target)
+        {
+            target.anchoredPosition = source.anchoredPosition;
+            target.sizeDelta = source.sizeDelta;
+            target.anchorMin = source.anchorMin;
+            target.anchorMax = source.anchorMax;
+            target.pivot = source.pivot;
+            target.localRotation = source.localRotation;
+            target.localScale = source.localScale;
+            target.localPosition = source.localPosition;
+        }
+
+        private string GetTransformPath(Transform t)
+        {
+            if (t.parent == null)
+                return t.name;
+            return GetTransformPath(t.parent) + "/" + t.name;
+        }
+    }
+}

From 4df3c80b60fc36d07a85ad241f2ca16c9aa277aa Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 15 May 2025 21:34:22 +0300
Subject: [PATCH 30/31] Create FindWhoReferencesThisGameObject.cs

---
 .../Tools/FindWhoReferencesThisGameObject.cs  | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs

diff --git a/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs b/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs
new file mode 100644
index 0000000..b2def9e
--- /dev/null
+++ b/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs
@@ -0,0 +1,123 @@
+// finds what scripts reference a given GameObject in the scene (in events, public fields..)
+
+using System.Collections.Generic;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.Events;
+
+namespace UnityLibrary.Editor
+{
+    public class FindWhoReferencesThisGameObject : EditorWindow
+    {
+        private GameObject target;
+        private Vector2 scroll;
+
+        private class ReferenceResult
+        {
+            public string message;
+            public GameObject owner;
+        }
+
+        private List<ReferenceResult> results = new List<ReferenceResult>();
+
+        [MenuItem("Tools/UnityLibrary/Find References To GameObject")]
+        public static void ShowWindow()
+        {
+            var win = GetWindow<FindWhoReferencesThisGameObject>("Find References");
+            win.minSize = new Vector2(500, 300);
+        }
+
+        private void OnGUI()
+        {
+            GUILayout.Label("Find scripts that reference this GameObject", EditorStyles.boldLabel);
+            target = EditorGUILayout.ObjectField("Target GameObject", target, typeof(GameObject), true) as GameObject;
+
+            if (GUILayout.Button("Find References"))
+            {
+                results.Clear();
+                if (target != null)
+                {
+                    FindReferences(target);
+                }
+                else
+                {
+                    Debug.LogWarning("Please assign a GameObject.");
+                }
+            }
+
+            if (results.Count > 0)
+            {
+                GUILayout.Label("Results:", EditorStyles.boldLabel);
+                scroll = GUILayout.BeginScrollView(scroll, GUILayout.Height(400));
+                foreach (var res in results)
+                {
+                    if (GUILayout.Button(res.message, GUILayout.ExpandWidth(true)))
+                    {
+                        EditorGUIUtility.PingObject(res.owner);
+                        Selection.activeGameObject = res.owner;
+                    }
+                }
+                GUILayout.EndScrollView();
+            }
+        }
+
+        private void FindReferences(GameObject target)
+        {
+            var allObjects = UnityEngine.Object.FindObjectsOfType<MonoBehaviour>(true);
+
+            foreach (var mono in allObjects)
+            {
+                if (mono == null || mono.gameObject == target) continue;
+
+                var type = mono.GetType();
+                var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+                foreach (var field in fields)
+                {
+                    if (typeof(UnityEventBase).IsAssignableFrom(field.FieldType))
+                    {
+                        var unityEvent = field.GetValue(mono) as UnityEventBase;
+                        if (unityEvent != null)
+                        {
+                            int count = unityEvent.GetPersistentEventCount();
+                            for (int i = 0; i < count; i++)
+                            {
+                                var listener = unityEvent.GetPersistentTarget(i);
+                                if (listener == target)
+                                {
+                                    results.Add(new ReferenceResult
+                                    {
+                                        message = $"{mono.name} ({type.Name}) -> UnityEvent '{field.Name}'",
+                                        owner = mono.gameObject
+                                    });
+                                }
+                            }
+                        }
+                    }
+                    else if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
+                    {
+                        var value = field.GetValue(mono) as UnityEngine.Object;
+                        if (value == target)
+                        {
+                            results.Add(new ReferenceResult
+                            {
+                                message = $"{mono.name} ({type.Name}) -> Field '{field.Name}'",
+                                owner = mono.gameObject
+                            });
+                        }
+                    }
+                }
+            }
+
+            if (results.Count == 0)
+            {
+                results.Add(new ReferenceResult
+                {
+                    message = "No references found.",
+                    owner = null
+                });
+            }
+        }
+    }
+}

From 540d629be475e7e984b893013a5634c725c3ad74 Mon Sep 17 00:00:00 2001
From: mika <unitycoder@users.noreply.github.com>
Date: Thu, 15 May 2025 21:35:50 +0300
Subject: [PATCH 31/31] Update FindWhoReferencesThisGameObject.cs

---
 Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs b/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs
index b2def9e..dd5b2c5 100644
--- a/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs
+++ b/Assets/Scripts/Editor/Tools/FindWhoReferencesThisGameObject.cs
@@ -1,4 +1,4 @@
-// finds what scripts reference a given GameObject in the scene (in events, public fields..)
+// find what scripts reference selected GameObject in the scene (in events, public fields..)
 
 using System.Collections.Generic;
 using System.Reflection;
