com.wallstop-studios.unity-helpers 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.github/workflows/format-on-demand.yml +2 -2
  2. package/.github/workflows/markdown-json.yml +1 -1
  3. package/.github/workflows/npm-publish.yml +1 -1
  4. package/.github/workflows/prettier-autofix.yml +4 -4
  5. package/.github/workflows/yaml-format-lint.yml +1 -1
  6. package/Docs/EFFECTS_SYSTEM.md +1316 -0
  7. package/{EFFECTS_SYSTEM_TUTORIAL.md → Docs/EFFECTS_SYSTEM_TUTORIAL.md} +1 -1
  8. package/{GETTING_STARTED.md → Docs/GETTING_STARTED.md} +10 -8
  9. package/{GLOSSARY.md → Docs/GLOSSARY.md} +4 -4
  10. package/Docs/HELPER_UTILITIES.md +885 -0
  11. package/Docs/HELPER_UTILITIES.md.meta +7 -0
  12. package/{INDEX.md → Docs/INDEX.md} +107 -62
  13. package/Docs/MATH_AND_EXTENSIONS.md +1039 -0
  14. package/{RANDOM_PERFORMANCE.md → Docs/RANDOM_PERFORMANCE.md} +15 -15
  15. package/{RELATIONAL_COMPONENTS.md → Docs/RELATIONAL_COMPONENTS.md} +21 -3
  16. package/{SPATIAL_TREES_2D_GUIDE.md → Docs/SPATIAL_TREES_2D_GUIDE.md} +2 -2
  17. package/{SPATIAL_TREES_3D_GUIDE.md → Docs/SPATIAL_TREES_3D_GUIDE.md} +1 -1
  18. package/{SPATIAL_TREE_2D_PERFORMANCE.md → Docs/SPATIAL_TREE_2D_PERFORMANCE.md} +64 -64
  19. package/{SPATIAL_TREE_3D_PERFORMANCE.md → Docs/SPATIAL_TREE_3D_PERFORMANCE.md} +64 -64
  20. package/Docs/UTILITY_COMPONENTS.md +906 -0
  21. package/Docs/UTILITY_COMPONENTS.md.meta +7 -0
  22. package/Docs/VISUAL_COMPONENTS.md +337 -0
  23. package/Docs/VISUAL_COMPONENTS.md.meta +7 -0
  24. package/Editor/Sprites/AnimationCopier.cs +3 -3
  25. package/README.md +69 -62
  26. package/Runtime/AssemblyInfo.cs +2 -0
  27. package/Runtime/Core/DataStructure/KDTree3D.cs +1 -1
  28. package/Runtime/Core/DataStructure/OctTree3D.cs +1 -1
  29. package/Runtime/Core/Extension/AsyncOperationExtensions.cs +122 -0
  30. package/Runtime/Core/Serialization/ProtobufUnitySurrogates.cs +24 -29
  31. package/Runtime/Integrations/Reflex/AssemblyInfo.cs +7 -0
  32. package/Runtime/Integrations/Reflex/AssemblyInfo.cs.meta +11 -0
  33. package/Runtime/Integrations/Reflex/ContainerRelationalExtensions.cs +198 -0
  34. package/Runtime/Integrations/Reflex/ContainerRelationalExtensions.cs.meta +11 -0
  35. package/Runtime/Integrations/Reflex/RelationalComponentsInstaller.cs +86 -0
  36. package/Runtime/Integrations/Reflex/RelationalComponentsInstaller.cs.meta +11 -0
  37. package/Runtime/Integrations/Reflex/RelationalReflexSceneBootstrapper.cs +316 -0
  38. package/Runtime/Integrations/Reflex/RelationalReflexSceneBootstrapper.cs.meta +11 -0
  39. package/Runtime/Integrations/Reflex/RelationalSceneAssignmentOptions.cs +86 -0
  40. package/Runtime/Integrations/Reflex/RelationalSceneAssignmentOptions.cs.meta +11 -0
  41. package/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Integration.Reflex.asmdef +20 -0
  42. package/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Integration.Reflex.asmdef.meta +7 -0
  43. package/Runtime/Integrations/Reflex.meta +8 -0
  44. package/Runtime/Utils/ScriptableObjectSingleton.cs +1 -1
  45. package/Samples~/DI - Reflex/README.md +527 -0
  46. package/Samples~/DI - Reflex/README.md.meta +7 -0
  47. package/Samples~/DI - Reflex/Scripts/ReflexPaletteService.cs +36 -0
  48. package/Samples~/DI - Reflex/Scripts/ReflexPaletteService.cs.meta +11 -0
  49. package/Samples~/DI - Reflex/Scripts/ReflexRelationalConsumer.cs +79 -0
  50. package/Samples~/DI - Reflex/Scripts/ReflexRelationalConsumer.cs.meta +11 -0
  51. package/Samples~/DI - Reflex/Scripts/ReflexSampleInstaller.cs +30 -0
  52. package/Samples~/DI - Reflex/Scripts/ReflexSampleInstaller.cs.meta +11 -0
  53. package/Samples~/DI - Reflex/Scripts/ReflexSpawner.cs +79 -0
  54. package/Samples~/DI - Reflex/Scripts/ReflexSpawner.cs.meta +11 -0
  55. package/Samples~/DI - Reflex/Scripts/Samples.UnityHelpers.DI.Reflex.asmdef +26 -0
  56. package/Samples~/DI - Reflex/Scripts/Samples.UnityHelpers.DI.Reflex.asmdef.meta +9 -0
  57. package/Samples~/DI - Reflex/Scripts.meta +8 -0
  58. package/Samples~/DI - Reflex.meta +8 -0
  59. package/Samples~/DI - VContainer/README.md +6 -5
  60. package/Samples~/DI - Zenject/README.md +6 -5
  61. package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +29 -31
  62. package/Tests/Editor/Integrations/Reflex/ReflexIntegrationCompilationTests.cs +41 -0
  63. package/Tests/Editor/Integrations/Reflex/ReflexIntegrationCompilationTests.cs.meta +11 -0
  64. package/Tests/Editor/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Editor.Reflex.asmdef +27 -0
  65. package/Tests/Editor/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Editor.Reflex.asmdef.meta +7 -0
  66. package/Tests/Editor/Integrations/Reflex.meta +8 -0
  67. package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +15 -16
  68. package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +7 -13
  69. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +7 -11
  70. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +19 -21
  71. package/Tests/Editor/PersistentDirectorySettingsTests.cs +0 -1
  72. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +0 -1
  73. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +2 -2
  74. package/Tests/Editor/Tools/ImageBlurToolTests.cs +1 -1
  75. package/Tests/Editor/Utils/CommonTestBase.cs +17 -0
  76. package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +1 -1
  77. package/Tests/Runtime/Extensions/AsyncOperationExtensionsTests.cs +179 -0
  78. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +55 -0
  79. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +445 -0
  80. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs.meta +11 -0
  81. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Runtime.Reflex.asmdef +28 -0
  82. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Runtime.Reflex.asmdef.meta +7 -0
  83. package/Tests/Runtime/Integrations/Reflex.meta +8 -0
  84. package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +24 -29
  85. package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +8 -3
  86. package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +10 -20
  87. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +1 -1
  88. package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +1 -1
  89. package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +1 -1
  90. package/Tests/Runtime/Serialization/JsonRoundtripComprehensiveTests.cs +4 -9
  91. package/Tests/Runtime/Serialization/ProtoRoundtripComprehensiveTests.cs +13 -13
  92. package/Tests/Runtime/TestUtils/CommonTestBase.cs +11 -0
  93. package/Tests/Runtime/TestUtils/ReflexTestSupport.cs +111 -0
  94. package/Tests/Runtime/TestUtils/ReflexTestSupport.cs.meta +12 -0
  95. package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +4 -4
  96. package/Tests/TestUtils.meta +8 -0
  97. package/package.json +6 -1
  98. package/EFFECTS_SYSTEM.md +0 -242
  99. package/MATH_AND_EXTENSIONS.md +0 -316
  100. /package/{CHANGELOG.md → Docs/CHANGELOG.md} +0 -0
  101. /package/{CHANGELOG.md.meta → Docs/CHANGELOG.md.meta} +0 -0
  102. /package/{CONTRIBUTING.md → Docs/CONTRIBUTING.md} +0 -0
  103. /package/{CONTRIBUTING.md.meta → Docs/CONTRIBUTING.md.meta} +0 -0
  104. /package/{DATA_STRUCTURES.md → Docs/DATA_STRUCTURES.md} +0 -0
  105. /package/{DATA_STRUCTURES.md.meta → Docs/DATA_STRUCTURES.md.meta} +0 -0
  106. /package/{EDITOR_TOOLS_GUIDE.md → Docs/EDITOR_TOOLS_GUIDE.md} +0 -0
  107. /package/{EDITOR_TOOLS_GUIDE.md.meta → Docs/EDITOR_TOOLS_GUIDE.md.meta} +0 -0
  108. /package/{EFFECTS_SYSTEM.md.meta → Docs/EFFECTS_SYSTEM.md.meta} +0 -0
  109. /package/{EFFECTS_SYSTEM_TUTORIAL.md.meta → Docs/EFFECTS_SYSTEM_TUTORIAL.md.meta} +0 -0
  110. /package/{GETTING_STARTED.md.meta → Docs/GETTING_STARTED.md.meta} +0 -0
  111. /package/{GLOSSARY.md.meta → Docs/GLOSSARY.md.meta} +0 -0
  112. /package/{HULLS.md → Docs/HULLS.md} +0 -0
  113. /package/{HULLS.md.meta → Docs/HULLS.md.meta} +0 -0
  114. /package/{INDEX.md.meta → Docs/INDEX.md.meta} +0 -0
  115. /package/{LICENSE.md → Docs/LICENSE.md} +0 -0
  116. /package/{LICENSE.md.meta → Docs/LICENSE.md.meta} +0 -0
  117. /package/{MATH_AND_EXTENSIONS.md.meta → Docs/MATH_AND_EXTENSIONS.md.meta} +0 -0
  118. /package/{RANDOM_PERFORMANCE.md.meta → Docs/RANDOM_PERFORMANCE.md.meta} +0 -0
  119. /package/{REFLECTION_HELPERS.md → Docs/REFLECTION_HELPERS.md} +0 -0
  120. /package/{REFLECTION_HELPERS.md.meta → Docs/REFLECTION_HELPERS.md.meta} +0 -0
  121. /package/{RELATIONAL_COMPONENTS.md.meta → Docs/RELATIONAL_COMPONENTS.md.meta} +0 -0
  122. /package/{SERIALIZATION.md → Docs/SERIALIZATION.md} +0 -0
  123. /package/{SERIALIZATION.md.meta → Docs/SERIALIZATION.md.meta} +0 -0
  124. /package/{SINGLETONS.md → Docs/SINGLETONS.md} +0 -0
  125. /package/{SINGLETONS.md.meta → Docs/SINGLETONS.md.meta} +0 -0
  126. /package/{SPATIAL_TREES_2D_GUIDE.md.meta → Docs/SPATIAL_TREES_2D_GUIDE.md.meta} +0 -0
  127. /package/{SPATIAL_TREES_3D_GUIDE.md.meta → Docs/SPATIAL_TREES_3D_GUIDE.md.meta} +0 -0
  128. /package/{SPATIAL_TREE_2D_PERFORMANCE.md.meta → Docs/SPATIAL_TREE_2D_PERFORMANCE.md.meta} +0 -0
  129. /package/{SPATIAL_TREE_3D_PERFORMANCE.md.meta → Docs/SPATIAL_TREE_3D_PERFORMANCE.md.meta} +0 -0
  130. /package/{SPATIAL_TREE_SEMANTICS.md → Docs/SPATIAL_TREE_SEMANTICS.md} +0 -0
  131. /package/{SPATIAL_TREE_SEMANTICS.md.meta → Docs/SPATIAL_TREE_SEMANTICS.md.meta} +0 -0
  132. /package/{THIRD_PARTY_NOTICES.md → Docs/THIRD_PARTY_NOTICES.md} +0 -0
  133. /package/{THIRD_PARTY_NOTICES.md.meta → Docs/THIRD_PARTY_NOTICES.md.meta} +0 -0
@@ -0,0 +1,906 @@
1
+ # Utility Components Guide
2
+
3
+ ## TL;DR — Why Use These
4
+
5
+ Drop-in MonoBehaviour components that solve common game development problems without writing custom scripts. Add them to GameObjects for instant functionality like motion animation, collision forwarding, transform following, and visual state management.
6
+
7
+ ---
8
+
9
+ ## Contents
10
+
11
+ - [Oscillator](#oscillator) — Automatic circular/elliptical motion
12
+ - [ChildSpawner](#childspawner) — Conditional prefab instantiation
13
+ - [CollisionProxy](#collisionproxy) — Event-based collision detection
14
+ - [CircleLineRenderer](#circlelinerenderer) — Visual circle debugging
15
+ - [MatchTransform](#matchtransform) — Follow another transform
16
+ - [SpriteRendererSync](#spriterenderersyncer) — Mirror sprite renderer state
17
+ - [SpriteRendererMetadata](#spriterenderermetadata) — Stacked visual modifications
18
+ - [CenterPointOffset](#centerpointoffset) — Define logical center points
19
+ - [AnimatorEnumStateMachine](#animatorenumstatemachine) — Type-safe animator control
20
+ - [CoroutineHandler](#coroutinehandler) — Singleton coroutine host
21
+ - [StartTracker](#starttracker) — Lifecycle tracking
22
+ - [MatchColliderToSprite](#matchcollidertosprite) — Auto-sync colliders
23
+ - [PolygonCollider2DOptimizer](#polygoncollider2doptimizer) — Simplify collider shapes
24
+
25
+ ---
26
+
27
+ <a id="oscillator"></a>
28
+
29
+ ## Oscillator
30
+
31
+ **What it does:** Automatically moves a GameObject in a circular or elliptical pattern. Think "floating pickup" or "idle hover animation" without animators.
32
+
33
+ **Problem it solves:** Creating simple repetitive motion (hovering, bobbing, orbiting) usually requires animation curves or custom update loops. Oscillator handles it with three parameters.
34
+
35
+ ### When to Use
36
+
37
+ ✅ **Use for:**
38
+
39
+ - Floating/hovering UI elements
40
+ - Pickup items that gently bob
41
+ - Decorative objects with idle motion
42
+ - Circular patrol paths
43
+ - Simple pendulum motion
44
+
45
+ ❌ **Don't use for:**
46
+
47
+ - Complex animation sequences (use Animator)
48
+ - Physics-based motion (use Rigidbody)
49
+ - Player/enemy movement (too rigid)
50
+
51
+ ### How to Use
52
+
53
+ 1. Add `Oscillator` component to any GameObject
54
+ 2. Configure three parameters:
55
+ - **speed**: Rotation speed (radians per second)
56
+ - **width**: Horizontal amplitude (X-axis movement range)
57
+ - **height**: Vertical amplitude (Y-axis movement range)
58
+
59
+ ```csharp
60
+ using WallstopStudios.UnityHelpers.Utils;
61
+
62
+ // Via code
63
+ Oscillator osc = gameObject.AddComponent<Oscillator>();
64
+ osc.speed = 2f; // Two radians/second
65
+ osc.width = 1f; // ±1 unit horizontally
66
+ osc.height = 0.5f; // ±0.5 units vertically
67
+ ```
68
+
69
+ ### Examples
70
+
71
+ **Gentle hover (coin pickup):**
72
+
73
+ ```text
74
+ speed = 3
75
+ width = 0
76
+ height = 0.2
77
+ ```
78
+
79
+ **Figure-8 motion:**
80
+
81
+ ```text
82
+ speed = 2
83
+ width = 1
84
+ height = 1
85
+ ```
86
+
87
+ **Horizontal sway:**
88
+
89
+ ```text
90
+ speed = 1
91
+ width = 0.5
92
+ height = 0
93
+ ```
94
+
95
+ ### Important Notes
96
+
97
+ - Updates `transform.localPosition` in Update()
98
+ - Motion is relative to original local position
99
+ - Starts from current time offset (unique per instance)
100
+ - Zero allocation per frame
101
+ - Works in 2D and 3D (only affects X and Y)
102
+
103
+ ---
104
+
105
+ <a id="childspawner"></a>
106
+
107
+ ## ChildSpawner
108
+
109
+ **What it does:** Conditionally instantiates prefabs as children based on environment (editor/development/release) with automatic duplicate prevention.
110
+
111
+ **Problem it solves:** Managing debug overlays, analytics, or development tools that should only exist in certain builds. Handles deduplication across scene loads and DontDestroyOnLoad scenarios.
112
+
113
+ ### When to Use
114
+
115
+ ✅ **Use for:**
116
+
117
+ - Debug UI overlays (FPS counters, console)
118
+ - Analytics managers (only in release builds)
119
+ - Development tools (cheat menus, level select)
120
+ - Platform-specific managers
121
+ - Scene-independent singleton spawners
122
+
123
+ ❌ **Don't use for:**
124
+
125
+ - Regular gameplay objects (use Instantiate)
126
+ - One-time spawns (just call Instantiate)
127
+ - Objects that need complex initialization
128
+
129
+ ### How to Use
130
+
131
+ Add `ChildSpawner` to a GameObject (often on a scene manager or empty GameObject):
132
+
133
+ **Inspector configuration:**
134
+
135
+ - **Prefabs**: Always spawned
136
+ - **Editor Only Prefabs**: Only in Unity Editor
137
+ - **Development Only Prefabs**: Only in Development builds
138
+ - **Spawn Method**: When to spawn (Awake/OnEnable/Start)
139
+ - **Dont Destroy On Load**: Persist across scenes
140
+
141
+ ```csharp
142
+ using WallstopStudios.UnityHelpers.Utils;
143
+
144
+ // Via code
145
+ ChildSpawner spawner = gameObject.AddComponent<ChildSpawner>();
146
+ spawner._prefabs = new[] { analyticsPrefab };
147
+ spawner._developmentOnlyPrefabs = new[] { debugMenuPrefab };
148
+ spawner._spawnMethod = ChildSpawner.SpawnMethod.Awake;
149
+ spawner._dontDestroyOnLoad = true;
150
+ ```
151
+
152
+ ### Deduplication Behavior
153
+
154
+ ChildSpawner prevents duplicate instantiation:
155
+
156
+ ```csharp
157
+ // Spawns DebugCanvas once
158
+ ChildSpawner spawner1 = obj1.AddComponent<ChildSpawner>();
159
+ spawner1._prefabs = new[] { debugCanvasPrefab };
160
+
161
+ // This will NOT spawn a second DebugCanvas (detects existing instance)
162
+ ChildSpawner spawner2 = obj2.AddComponent<ChildSpawner>();
163
+ spawner2._prefabs = new[] { debugCanvasPrefab };
164
+ ```
165
+
166
+ Deduplication uses prefab asset path matching.
167
+
168
+ ### Spawn Methods
169
+
170
+ - **Awake**: Spawns before anything else (use for foundational systems)
171
+ - **OnEnable**: Spawns when component enabled (use for dynamic spawning)
172
+ - **Start**: Spawns after all Awake calls (use when dependencies needed)
173
+
174
+ ### DontDestroyOnLoad
175
+
176
+ When enabled:
177
+
178
+ - Spawned objects persist across scene loads
179
+ - Deduplication works across scene transitions
180
+ - Objects aren't destroyed when loading new scenes
181
+
182
+ Typical use case:
183
+
184
+ ```text
185
+ Scene 1: ChildSpawner spawns AnalyticsManager with DontDestroyOnLoad
186
+ Scene 2 loads: Same ChildSpawner detects existing AnalyticsManager, doesn't spawn duplicate
187
+ ```
188
+
189
+ ---
190
+
191
+ <a id="collisionproxy"></a>
192
+
193
+ ## CollisionProxy
194
+
195
+ **What it does:** Exposes Unity's 2D collision callbacks as C# events, enabling composition-based collision handling without inheriting from MonoBehaviour.
196
+
197
+ **Problem it solves:** To receive collision events in Unity, you traditionally override `OnCollisionEnter2D` etc. in a MonoBehaviour subclass. CollisionProxy lets you subscribe to events instead, supporting multiple listeners and decoupled architectures.
198
+
199
+ ### When to Use
200
+
201
+ ✅ **Use for:**
202
+
203
+ - Composition over inheritance designs
204
+ - Multiple systems reacting to same collision
205
+ - Decoupling collision logic from GameObject code
206
+ - Testing collision responses
207
+ - Dynamic behavior attachment/detachment
208
+
209
+ ❌ **Don't use for:**
210
+
211
+ - Simple single-handler cases (override is fine)
212
+ - 3D collisions (only supports 2D)
213
+ - High-frequency collisions (event overhead)
214
+
215
+ ### How to Use
216
+
217
+ 1. Add `CollisionProxy` to GameObject with Collider2D
218
+ 2. Subscribe to events from other scripts
219
+
220
+ ```csharp
221
+ using WallstopStudios.UnityHelpers.Utils;
222
+
223
+ CollisionProxy proxy = gameObject.AddComponent<CollisionProxy>();
224
+
225
+ // Subscribe to enter event
226
+ proxy.OnCollisionEnter += HandleCollision;
227
+ proxy.OnTriggerEnter += HandleTrigger;
228
+
229
+ void HandleCollision(Collision2D collision)
230
+ {
231
+ Debug.Log($"Hit {collision.gameObject.name}");
232
+ }
233
+
234
+ void HandleTrigger(Collider2D other)
235
+ {
236
+ Debug.Log($"Triggered by {other.gameObject.name}");
237
+ }
238
+
239
+ // Cleanup
240
+ void OnDestroy()
241
+ {
242
+ proxy.OnCollisionEnter -= HandleCollision;
243
+ proxy.OnTriggerEnter -= HandleTrigger;
244
+ }
245
+ ```
246
+
247
+ ### Available Events
248
+
249
+ **Collision events** (Collision2D parameter):
250
+
251
+ - `OnCollisionEnter`
252
+ - `OnCollisionStay`
253
+ - `OnCollisionExit`
254
+
255
+ **Trigger events** (Collider2D parameter):
256
+
257
+ - `OnTriggerEnter`
258
+ - `OnTriggerStay`
259
+ - `OnTriggerExit`
260
+
261
+ ### Multiple Subscribers Example
262
+
263
+ ```csharp
264
+ // Health system subscribes
265
+ healthSystem.OnDamageTaken += proxy.OnCollisionEnter;
266
+
267
+ // Sound system subscribes to same event
268
+ soundSystem.PlayImpactSound += proxy.OnCollisionEnter;
269
+
270
+ // Analytics subscribes
271
+ analytics.TrackCollision += proxy.OnCollisionEnter;
272
+
273
+ // All three systems react to the same collision independently
274
+ ```
275
+
276
+ ---
277
+
278
+ <a id="circlelinerenderer"></a>
279
+
280
+ ## CircleLineRenderer
281
+
282
+ **What it does:** Visualizes CircleCollider2D with a dynamically-drawn circle using LineRenderer, with randomized appearance for visual variety.
283
+
284
+ **Problem it solves:** Seeing collision bounds at runtime for debugging, or creating dynamic range indicators (ability ranges, explosion radii) without pre-made sprites.
285
+
286
+ ### When to Use
287
+
288
+ ✅ **Use for:**
289
+
290
+ - Debug visualization of collision bounds
291
+ - Dynamic range indicators (attack range, detection radius)
292
+ - Area-of-effect visualization
293
+ - Circular UI elements
294
+ - Animated selection rings
295
+
296
+ ❌ **Don't use for:**
297
+
298
+ - Production graphics (performance overhead)
299
+ - Static circles (use a sprite)
300
+ - Thousands of circles (expensive)
301
+
302
+ ### How to Use
303
+
304
+ 1. Add `CircleLineRenderer` to GameObject with `CircleCollider2D`
305
+ 2. Component automatically:
306
+ - Adds LineRenderer if not present
307
+ - Syncs circle size to collider radius
308
+ - Randomizes line width for visual variety
309
+
310
+ ```csharp
311
+ using WallstopStudios.UnityHelpers.Utils;
312
+
313
+ CircleLineRenderer circleVis = gameObject.AddComponent<CircleLineRenderer>();
314
+ circleVis.color = Color.red;
315
+ circleVis.minLineWidth = 0.05f;
316
+ circleVis.maxLineWidth = 0.15f;
317
+ circleVis.updateRateSeconds = 0.5f; // Refresh twice per second
318
+ ```
319
+
320
+ ### Configuration
321
+
322
+ - **minLineWidth / maxLineWidth**: Random line thickness range
323
+ - **numSegments**: Circle smoothness (more segments = smoother, more expensive)
324
+ - **baseSegments**: Minimum segments (scaled by radius)
325
+ - **updateRateSeconds**: How often to randomize appearance
326
+ - **color**: Line color
327
+
328
+ ### Update Rate
329
+
330
+ Lower values = more frequent randomization = more visual variety but higher CPU cost
331
+
332
+ ```text
333
+ 0.1f = Very active (10 updates/sec)
334
+ 0.5f = Moderate (2 updates/sec)
335
+ 2.0f = Subtle (0.5 updates/sec)
336
+ ```
337
+
338
+ ---
339
+
340
+ <a id="matchtransform"></a>
341
+
342
+ ## MatchTransform
343
+
344
+ **What it does:** Makes one transform follow another with configurable update timing and offset.
345
+
346
+ **Problem it solves:** Following transforms (UI name plates, camera targets, position constraints) usually requires custom scripts. MatchTransform handles it declaratively.
347
+
348
+ ### When to Use
349
+
350
+ ✅ **Use for:**
351
+
352
+ - UI name plates following 3D objects
353
+ - Camera targets
354
+ - Object attachments (weapon to hand)
355
+ - Position constraints
356
+ - Simple parent-child alternatives
357
+
358
+ ❌ **Don't use for:**
359
+
360
+ - Smooth following (use Vector3.Lerp in Update)
361
+ - Physics-based following (use joints/springs)
362
+ - Complex multi-axis constraints (use Unity Constraints)
363
+
364
+ ### How to Use
365
+
366
+ ```csharp
367
+ using WallstopStudios.UnityHelpers.Utils;
368
+
369
+ MatchTransform matcher = uiPlate.AddComponent<MatchTransform>();
370
+ matcher.toMatch = enemyTransform;
371
+ matcher.localOffset = new Vector3(0, 2, 0); // 2 units above target
372
+ matcher.mode = MatchTransform.Mode.LateUpdate; // Update after camera
373
+ ```
374
+
375
+ ### Update Modes
376
+
377
+ - **Update**: Standard update timing (most common)
378
+ - **FixedUpdate**: For physics-synced following
379
+ - **LateUpdate**: After all Updates (best for camera followers)
380
+ - **Awake**: Set once at startup, then never update
381
+ - **Start**: Set once after Awake, then never update
382
+
383
+ ### Local Offset
384
+
385
+ ```csharp
386
+ // Offset is added to target position
387
+ matcher.localOffset = new Vector3(1, 0, 0); // 1 unit to the right
388
+ ```
389
+
390
+ ### Self-Matching
391
+
392
+ If `toMatch` is the same GameObject, applies offset once then disables:
393
+
394
+ ```csharp
395
+ matcher.toMatch = transform; // Self-reference
396
+ matcher.localOffset = new Vector3(5, 0, 0);
397
+ // GameObject moves 5 units right once, then MatchTransform disables itself
398
+ ```
399
+
400
+ ---
401
+
402
+ <a id="spriterenderersyncer"></a>
403
+
404
+ ## SpriteRendererSync
405
+
406
+ **What it does:** Mirrors one SpriteRenderer's properties (sprite, color, material, sorting) to another, with selective property matching.
407
+
408
+ **Problem it solves:** Creating shadow sprites, duplicate renderers for effects, or layered rendering often requires manually keeping multiple SpriteRenderers in sync.
409
+
410
+ ### When to Use
411
+
412
+ ✅ **Use for:**
413
+
414
+ - Shadow sprites (black silhouette following character)
415
+ - Duplicate renderers for effects (outlines, glows)
416
+ - Mirrored sprites (reflection effects)
417
+ - Synchronized sprite swapping
418
+ - VFX layers
419
+
420
+ ❌ **Don't use for:**
421
+
422
+ - Single sprite rendering
423
+ - Particle effects (use ParticleSystem)
424
+ - Complex multi-layer rendering (use LayeredImage for UI)
425
+
426
+ ### How to Use
427
+
428
+ ```csharp
429
+ using WallstopStudios.UnityHelpers.Utils;
430
+
431
+ // On the "follower" sprite renderer
432
+ SpriteRendererSyncer syncer = shadowRenderer.AddComponent<SpriteRendererSyncer>();
433
+ syncer.toMatch = characterRenderer;
434
+ syncer.matchColor = false; // Don't copy color (shadow should be black)
435
+ syncer.matchMaterial = true;
436
+ syncer.matchSortingLayer = true;
437
+ syncer.matchOrderInLayer = true;
438
+ ```
439
+
440
+ ### Configuration Options
441
+
442
+ **What to sync:**
443
+
444
+ - `matchColor`: Copy color tint
445
+ - `matchMaterial`: Copy material
446
+ - `matchSortingLayer`: Copy sorting layer
447
+ - `matchOrderInLayer`: Copy order in layer
448
+ - Sprite, flipX, flipY are always copied
449
+
450
+ **Dynamic source:**
451
+
452
+ ```csharp
453
+ // Change what to match at runtime
454
+ syncer.DynamicToMatch = () => GetCurrentWeaponRenderer();
455
+ ```
456
+
457
+ **Sorting override:**
458
+
459
+ ```csharp
460
+ // Override order in layer dynamically
461
+ syncer.DynamicSortingOrderOverride = () => characterRenderer.sortingOrder - 1; // Always behind
462
+ ```
463
+
464
+ ### Update Timing
465
+
466
+ Syncs in `LateUpdate()` to ensure source renderer has updated first.
467
+
468
+ ### Example: Shadow Effect
469
+
470
+ ```csharp
471
+ // Create shadow GameObject
472
+ GameObject shadow = new GameObject("Shadow");
473
+ shadow.transform.parent = character.transform;
474
+ shadow.transform.localPosition = new Vector3(0.2f, -0.2f, 0); // Offset
475
+
476
+ SpriteRenderer shadowRenderer = shadow.AddComponent<SpriteRenderer>();
477
+ SpriteRendererSyncer syncer = shadow.AddComponent<SpriteRendererSyncer>();
478
+
479
+ syncer.toMatch = character.GetComponent<SpriteRenderer>();
480
+ syncer.matchColor = false;
481
+ shadowRenderer.color = new Color(0, 0, 0, 0.5f); // Semi-transparent black
482
+ ```
483
+
484
+ ---
485
+
486
+ <a id="spriterenderermetadata"></a>
487
+
488
+ ## SpriteRendererMetadata
489
+
490
+ **What it does:** Stack-based color and material management for SpriteRenderers, allowing multiple systems to modify visuals with automatic priority handling and restoration.
491
+
492
+ **Problem it solves:** When multiple systems want to modify a sprite's color (damage flash, power-up glow, status effect) simultaneously, manually coordinating who "owns" the color is error-prone. This provides push/pop semantics with component-based ownership.
493
+
494
+ ### When to Use
495
+
496
+ ✅ **Use for:**
497
+
498
+ - Damage flashes (red tint on hit)
499
+ - Status effects (poison = green, frozen = blue)
500
+ - Power-up visuals (glow effects)
501
+ - Multiple overlapping visual modifiers
502
+ - Temporary material swaps
503
+
504
+ ❌ **Don't use for:**
505
+
506
+ - Single, exclusive color changes (just set color directly)
507
+ - Animations (use Animator)
508
+ - Permanent changes (just set the property)
509
+
510
+ ### How to Use
511
+
512
+ ```csharp
513
+ using WallstopStudios.UnityHelpers.Utils;
514
+
515
+ SpriteRenderer renderer = GetComponent<SpriteRenderer>();
516
+ SpriteRendererMetadata metadata = renderer.GetComponent<SpriteRendererMetadata>();
517
+ if (metadata == null)
518
+ metadata = renderer.gameObject.AddComponent<SpriteRendererMetadata>();
519
+
520
+ // Component A pushes red color
521
+ metadata.PushColor(this, Color.red);
522
+
523
+ // Component B pushes blue color (takes precedence)
524
+ metadata.PushColor(otherComponent, Color.blue);
525
+ // Renderer is now blue
526
+
527
+ // Component B pops its color
528
+ metadata.PopColor(otherComponent);
529
+ // Renderer reverts to red (Component A's color)
530
+
531
+ // Component A pops its color
532
+ metadata.PopColor(this);
533
+ // Renderer reverts to original color
534
+ ```
535
+
536
+ ### Stack Operations
537
+
538
+ **Push/Pop (LIFO - Last In, First Out):**
539
+
540
+ ```csharp
541
+ metadata.PushColor(owner, Color.red); // Add to top of stack
542
+ metadata.PopColor(owner); // Remove from top (must match owner)
543
+ ```
544
+
545
+ **PushBack (add to bottom, lower priority):**
546
+
547
+ ```csharp
548
+ metadata.PushBackColor(owner, Color.yellow); // Added to bottom, doesn't change current color unless stack is empty
549
+ ```
550
+
551
+ ### Component Ownership
552
+
553
+ Each color/material is tagged with the Component that pushed it:
554
+
555
+ ```csharp
556
+ public class DamageFlash : MonoBehaviour
557
+ {
558
+ void OnDamage()
559
+ {
560
+ metadata.PushColor(this, Color.red);
561
+ Invoke(nameof(RemoveFlash), 0.1f);
562
+ }
563
+
564
+ void RemoveFlash()
565
+ {
566
+ metadata.PopColor(this); // Only removes if this component owns top of stack
567
+ }
568
+ }
569
+ ```
570
+
571
+ This prevents Component A from accidentally removing Component B's color.
572
+
573
+ ### Material Stacking
574
+
575
+ Works identically for materials:
576
+
577
+ ```csharp
578
+ metadata.PushMaterial(this, glowMaterial);
579
+ // ... later
580
+ metadata.PopMaterial(this);
581
+ ```
582
+
583
+ ### Original State
584
+
585
+ ```csharp
586
+ Color original = metadata.OriginalColor; // Color before any modifications
587
+ Color current = metadata.CurrentColor; // Current top-of-stack color
588
+
589
+ Material originalMat = metadata.OriginalMaterial;
590
+ Material currentMat = metadata.CurrentMaterial;
591
+ ```
592
+
593
+ ### Important Notes
594
+
595
+ - Automatically detects and stores original color/material in `Awake()`
596
+ - Survives enable/disable cycles
597
+ - Priority is determined by push order (last push wins)
598
+ - Cleanup happens automatically when component destroyed
599
+ - If non-owner tries to pop, operation is ignored (defensive)
600
+
601
+ ---
602
+
603
+ <a id="centerpointoffset"></a>
604
+
605
+ ## CenterPointOffset
606
+
607
+ **What it does:** Defines a logical center point for a GameObject that's separate from the transform pivot, scaled by the object's local scale.
608
+
609
+ **Problem it solves:** Sprites with off-center pivots (for animation reasons) need a separate "logical center" for gameplay (rotation point, targeting reticle, etc.). This provides that without changing the transform pivot.
610
+
611
+ ### When to Use
612
+
613
+ ✅ **Use for:**
614
+
615
+ - Sprites with off-center pivots that need gameplay center
616
+ - Rotation pivots different from visual pivot
617
+ - Targeting reticles
618
+ - AI targeting points
619
+ - Center-of-mass definitions
620
+
621
+ ❌ **Don't use for:**
622
+
623
+ - Centered sprites (just use transform.position)
624
+ - Complex multi-point definitions
625
+ - Physics center of mass (use Rigidbody2D.centerOfMass)
626
+
627
+ ### How to Use
628
+
629
+ ```csharp
630
+ using WallstopStudios.UnityHelpers.Utils;
631
+
632
+ CenterPointOffset centerDef = gameObject.AddComponent<CenterPointOffset>();
633
+ centerDef.offset = new Vector2(0, 0.5f); // Center is 0.5 units above transform
634
+ centerDef.spriteUsesOffset = true; // Flag for sprite-specific logic
635
+
636
+ // Get world-space center point
637
+ Vector2 centerInWorld = centerDef.CenterPoint;
638
+
639
+ // Use for targeting
640
+ targetingSystem.AimAt(centerDef.CenterPoint);
641
+ ```
642
+
643
+ ### Offset Scaling
644
+
645
+ Offset is multiplied by `transform.localScale`:
646
+
647
+ ```text
648
+ transform.position = (0, 0)
649
+ offset = (1, 0)
650
+ transform.localScale = (2, 2, 2)
651
+
652
+ CenterPoint = (0, 0) + (1, 0) * (2, 2) = (2, 0)
653
+ ```
654
+
655
+ This ensures the center point scales with the object.
656
+
657
+ ### Sprite Flag
658
+
659
+ `spriteUsesOffset` is a boolean flag you can check in other systems:
660
+
661
+ ```csharp
662
+ if (center.spriteUsesOffset)
663
+ {
664
+ // Apply sprite-specific logic
665
+ }
666
+ ```
667
+
668
+ ---
669
+
670
+ <a id="animatorenumstatemachine"></a>
671
+
672
+ ## AnimatorEnumStateMachine
673
+
674
+ **What it does:** Type-safe, enum-based Animator state control. Maps enum values to Animator boolean parameters for exclusive state control.
675
+
676
+ **Problem it solves:** Setting Animator bools with magic strings (`animator.SetBool("IsJumping", true)`) is error-prone and hard to refactor. This provides compile-time safety and automatic cleanup of previous states.
677
+
678
+ ### When to Use
679
+
680
+ ✅ **Use for:**
681
+
682
+ - Complex state machines (player states, enemy AI)
683
+ - Type-safe animation control
684
+ - State pattern implementations
685
+ - Refactor-friendly animation code
686
+
687
+ ❌ **Don't use for:**
688
+
689
+ - Simple trigger-based animations (use animator.SetTrigger)
690
+ - Float/int parameters (only supports bools)
691
+ - Blend trees (use animator.SetFloat)
692
+
693
+ ### How to Use
694
+
695
+ **1. Define enum matching your Animator parameters:**
696
+
697
+ ```csharp
698
+ public enum PlayerState
699
+ {
700
+ Idle, // Maps to Animator bool "Idle"
701
+ Running, // Maps to Animator bool "Running"
702
+ Jumping, // Maps to Animator bool "Jumping"
703
+ Falling // Maps to Animator bool "Falling"
704
+ }
705
+ ```
706
+
707
+ **2. Create state machine:**
708
+
709
+ ```csharp
710
+ using WallstopStudios.UnityHelpers.Utils;
711
+
712
+ Animator animator = GetComponent<Animator>();
713
+ AnimatorEnumStateMachine<PlayerState> stateMachine;
714
+
715
+ void Awake()
716
+ {
717
+ stateMachine = new AnimatorEnumStateMachine<PlayerState>(animator, PlayerState.Idle);
718
+ }
719
+ ```
720
+
721
+ **3. Set state:**
722
+
723
+ ```csharp
724
+ void Jump()
725
+ {
726
+ stateMachine.Value = PlayerState.Jumping;
727
+ // Automatically sets Animator bools:
728
+ // Idle = false
729
+ // Running = false
730
+ // Jumping = true
731
+ // Falling = false
732
+ }
733
+ ```
734
+
735
+ ### Automatic State Management
736
+
737
+ Setting `stateMachine.Value` automatically:
738
+
739
+ 1. Sets ALL enum-named bools to `false`
740
+ 2. Sets ONLY the matching bool to `true`
741
+
742
+ This ensures exclusive state control (only one state active).
743
+
744
+ ### Animator Setup
745
+
746
+ Your Animator needs bool parameters matching enum names:
747
+
748
+ ```text
749
+ Animator parameters:
750
+ - Idle (bool)
751
+ - Running (bool)
752
+ - Jumping (bool)
753
+ - Falling (bool)
754
+
755
+ Transitions:
756
+ - Any State → Idle: Idle == true
757
+ - Any State → Running: Running == true
758
+ - Any State → Jumping: Jumping == true
759
+ - Any State → Falling: Falling == true
760
+ ```
761
+
762
+ ### Serialization
763
+
764
+ `AnimatorEnumStateMachine<T>` is serializable for debugging in Inspector.
765
+
766
+ ---
767
+
768
+ <a id="coroutinehandler"></a>
769
+
770
+ ## CoroutineHandler
771
+
772
+ **What it does:** Singleton MonoBehaviour that provides a global coroutine host for non-MonoBehaviour classes.
773
+
774
+ **Problem it solves:** Coroutines require a MonoBehaviour to start. Static classes, plain C# objects, and ScriptableObjects can't start coroutines directly.
775
+
776
+ ### When to Use
777
+
778
+ ✅ **Use for:**
779
+
780
+ - Starting coroutines from static utility classes
781
+ - Coroutines in plain C# objects
782
+ - ScriptableObjects that need coroutines
783
+ - Global/scene-independent coroutines
784
+
785
+ ❌ **Don't use for:**
786
+
787
+ - MonoBehaviours (just use StartCoroutine)
788
+ - Short-lived coroutines (might outlive object)
789
+ - Frame-perfect timing (singleton has overhead)
790
+
791
+ ### How to Use
792
+
793
+ ```csharp
794
+ using WallstopStudios.UnityHelpers.Utils;
795
+
796
+ // From anywhere
797
+ CoroutineHandler.Instance.StartCoroutine(MyCoroutine());
798
+
799
+ IEnumerator MyCoroutine()
800
+ {
801
+ yield return new WaitForSeconds(1f);
802
+ Debug.Log("Done!");
803
+ }
804
+ ```
805
+
806
+ ### Lifetime
807
+
808
+ CoroutineHandler persists across scene loads (`DontDestroyOnLoad`), so coroutines survive scene transitions.
809
+
810
+ ### Stopping Coroutines
811
+
812
+ ```csharp
813
+ Coroutine routine = CoroutineHandler.Instance.StartCoroutine(MyCoroutine());
814
+ // ... later
815
+ CoroutineHandler.Instance.StopCoroutine(routine);
816
+ ```
817
+
818
+ ---
819
+
820
+ <a id="starttracker"></a>
821
+
822
+ ## StartTracker
823
+
824
+ **What it does:** Simple component that tracks whether `MonoBehaviour.Start()` has been called.
825
+
826
+ **Problem it solves:** Sometimes you need to know if initialization (Start) has completed, especially in editor or during complex initialization orders.
827
+
828
+ ### When to Use
829
+
830
+ ✅ **Use for:**
831
+
832
+ - Initialization order checking
833
+ - Conditional setup logic
834
+ - Editor tools validating scene state
835
+ - Testing initialization
836
+
837
+ ❌ **Don't use for:**
838
+
839
+ - Production gameplay logic (architectural smell)
840
+ - Most scenarios (rethink if you need this)
841
+
842
+ ### How to Use
843
+
844
+ ```csharp
845
+ using WallstopStudios.UnityHelpers.Utils;
846
+
847
+ // Add to GameObject
848
+ StartTracker tracker = gameObject.AddComponent<StartTracker>();
849
+
850
+ // Later, check if Start has been called
851
+ if (tracker.Started)
852
+ {
853
+ // Initialization complete
854
+ }
855
+ ```
856
+
857
+ ---
858
+
859
+ <a id="matchcollidertosprite"></a>
860
+
861
+ ## MatchColliderToSprite
862
+
863
+ Automatically syncs `PolygonCollider2D` shape to sprite's physics shape.
864
+
865
+ **See:** [Editor Tools Guide - MatchColliderToSprite](EDITOR_TOOLS_GUIDE.md#matchcollidertosprite-editor)
866
+
867
+ ---
868
+
869
+ <a id="polygoncollider2doptimizer"></a>
870
+
871
+ ## PolygonCollider2DOptimizer
872
+
873
+ Reduces PolygonCollider2D point count using Douglas-Peucker simplification.
874
+
875
+ **See:** [Editor Tools Guide - PolygonCollider2DOptimizer](EDITOR_TOOLS_GUIDE.md#polygoncollider2doptimizer-editor)
876
+
877
+ ---
878
+
879
+ ## Best Practices
880
+
881
+ ### General
882
+
883
+ - **One utility per GameObject**: Don't stack unrelated utilities on the same GameObject
884
+ - **Configure in Awake/Start**: Set properties before first Update
885
+ - **Remove when done**: Disable/destroy utilities that are no longer needed
886
+ - **Test in builds**: Some utilities behave differently in editor vs builds (ChildSpawner)
887
+
888
+ ### Performance
889
+
890
+ - **CircleLineRenderer**: Use sparingly, each instance updates line vertices
891
+ - **SpriteRendererSync**: Updates every LateUpdate, don't use for hundreds of sprites
892
+ - **MatchTransform**: Choose appropriate update mode (FixedUpdate for physics, LateUpdate for camera)
893
+
894
+ ### Architecture
895
+
896
+ - **CollisionProxy**: Great for composition, but don't overuse events everywhere
897
+ - **SpriteRendererMetadata**: Document ownership in team code (who can push/pop)
898
+ - **AnimatorEnumStateMachine**: Keep enum names matching Animator parameters
899
+
900
+ ---
901
+
902
+ ## Related Documentation
903
+
904
+ - [Math & Extensions](MATH_AND_EXTENSIONS.md) - Extension methods used by utilities
905
+ - [Editor Tools Guide](EDITOR_TOOLS_GUIDE.md) - Editor components
906
+ - [Helpers Guide](HELPER_UTILITIES.md) - Non-component helper classes