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.
- package/.github/workflows/format-on-demand.yml +2 -2
- package/.github/workflows/markdown-json.yml +1 -1
- package/.github/workflows/npm-publish.yml +1 -1
- package/.github/workflows/prettier-autofix.yml +4 -4
- package/.github/workflows/yaml-format-lint.yml +1 -1
- package/Docs/EFFECTS_SYSTEM.md +1316 -0
- package/{EFFECTS_SYSTEM_TUTORIAL.md → Docs/EFFECTS_SYSTEM_TUTORIAL.md} +1 -1
- package/{GETTING_STARTED.md → Docs/GETTING_STARTED.md} +10 -8
- package/{GLOSSARY.md → Docs/GLOSSARY.md} +4 -4
- package/Docs/HELPER_UTILITIES.md +885 -0
- package/Docs/HELPER_UTILITIES.md.meta +7 -0
- package/{INDEX.md → Docs/INDEX.md} +107 -62
- package/Docs/MATH_AND_EXTENSIONS.md +1039 -0
- package/{RANDOM_PERFORMANCE.md → Docs/RANDOM_PERFORMANCE.md} +15 -15
- package/{RELATIONAL_COMPONENTS.md → Docs/RELATIONAL_COMPONENTS.md} +21 -3
- package/{SPATIAL_TREES_2D_GUIDE.md → Docs/SPATIAL_TREES_2D_GUIDE.md} +2 -2
- package/{SPATIAL_TREES_3D_GUIDE.md → Docs/SPATIAL_TREES_3D_GUIDE.md} +1 -1
- package/{SPATIAL_TREE_2D_PERFORMANCE.md → Docs/SPATIAL_TREE_2D_PERFORMANCE.md} +64 -64
- package/{SPATIAL_TREE_3D_PERFORMANCE.md → Docs/SPATIAL_TREE_3D_PERFORMANCE.md} +64 -64
- package/Docs/UTILITY_COMPONENTS.md +906 -0
- package/Docs/UTILITY_COMPONENTS.md.meta +7 -0
- package/Docs/VISUAL_COMPONENTS.md +337 -0
- package/Docs/VISUAL_COMPONENTS.md.meta +7 -0
- package/Editor/Sprites/AnimationCopier.cs +3 -3
- package/README.md +69 -62
- package/Runtime/AssemblyInfo.cs +2 -0
- package/Runtime/Core/DataStructure/KDTree3D.cs +1 -1
- package/Runtime/Core/DataStructure/OctTree3D.cs +1 -1
- package/Runtime/Core/Extension/AsyncOperationExtensions.cs +122 -0
- package/Runtime/Core/Serialization/ProtobufUnitySurrogates.cs +24 -29
- package/Runtime/Integrations/Reflex/AssemblyInfo.cs +7 -0
- package/Runtime/Integrations/Reflex/AssemblyInfo.cs.meta +11 -0
- package/Runtime/Integrations/Reflex/ContainerRelationalExtensions.cs +198 -0
- package/Runtime/Integrations/Reflex/ContainerRelationalExtensions.cs.meta +11 -0
- package/Runtime/Integrations/Reflex/RelationalComponentsInstaller.cs +86 -0
- package/Runtime/Integrations/Reflex/RelationalComponentsInstaller.cs.meta +11 -0
- package/Runtime/Integrations/Reflex/RelationalReflexSceneBootstrapper.cs +316 -0
- package/Runtime/Integrations/Reflex/RelationalReflexSceneBootstrapper.cs.meta +11 -0
- package/Runtime/Integrations/Reflex/RelationalSceneAssignmentOptions.cs +86 -0
- package/Runtime/Integrations/Reflex/RelationalSceneAssignmentOptions.cs.meta +11 -0
- package/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Integration.Reflex.asmdef +20 -0
- package/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Integration.Reflex.asmdef.meta +7 -0
- package/Runtime/Integrations/Reflex.meta +8 -0
- package/Runtime/Utils/ScriptableObjectSingleton.cs +1 -1
- package/Samples~/DI - Reflex/README.md +527 -0
- package/Samples~/DI - Reflex/README.md.meta +7 -0
- package/Samples~/DI - Reflex/Scripts/ReflexPaletteService.cs +36 -0
- package/Samples~/DI - Reflex/Scripts/ReflexPaletteService.cs.meta +11 -0
- package/Samples~/DI - Reflex/Scripts/ReflexRelationalConsumer.cs +79 -0
- package/Samples~/DI - Reflex/Scripts/ReflexRelationalConsumer.cs.meta +11 -0
- package/Samples~/DI - Reflex/Scripts/ReflexSampleInstaller.cs +30 -0
- package/Samples~/DI - Reflex/Scripts/ReflexSampleInstaller.cs.meta +11 -0
- package/Samples~/DI - Reflex/Scripts/ReflexSpawner.cs +79 -0
- package/Samples~/DI - Reflex/Scripts/ReflexSpawner.cs.meta +11 -0
- package/Samples~/DI - Reflex/Scripts/Samples.UnityHelpers.DI.Reflex.asmdef +26 -0
- package/Samples~/DI - Reflex/Scripts/Samples.UnityHelpers.DI.Reflex.asmdef.meta +9 -0
- package/Samples~/DI - Reflex/Scripts.meta +8 -0
- package/Samples~/DI - Reflex.meta +8 -0
- package/Samples~/DI - VContainer/README.md +6 -5
- package/Samples~/DI - Zenject/README.md +6 -5
- package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +29 -31
- package/Tests/Editor/Integrations/Reflex/ReflexIntegrationCompilationTests.cs +41 -0
- package/Tests/Editor/Integrations/Reflex/ReflexIntegrationCompilationTests.cs.meta +11 -0
- package/Tests/Editor/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Editor.Reflex.asmdef +27 -0
- package/Tests/Editor/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Editor.Reflex.asmdef.meta +7 -0
- package/Tests/Editor/Integrations/Reflex.meta +8 -0
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +15 -16
- package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +7 -13
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +7 -11
- package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +19 -21
- package/Tests/Editor/PersistentDirectorySettingsTests.cs +0 -1
- package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +0 -1
- package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +2 -2
- package/Tests/Editor/Tools/ImageBlurToolTests.cs +1 -1
- package/Tests/Editor/Utils/CommonTestBase.cs +17 -0
- package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +1 -1
- package/Tests/Runtime/Extensions/AsyncOperationExtensionsTests.cs +179 -0
- package/Tests/Runtime/Extensions/RandomExtensionTests.cs +55 -0
- package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +445 -0
- package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs.meta +11 -0
- package/Tests/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Runtime.Reflex.asmdef +28 -0
- package/Tests/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Runtime.Reflex.asmdef.meta +7 -0
- package/Tests/Runtime/Integrations/Reflex.meta +8 -0
- package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +24 -29
- package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +8 -3
- package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +10 -20
- package/Tests/Runtime/Performance/RandomPerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +1 -1
- package/Tests/Runtime/Serialization/JsonRoundtripComprehensiveTests.cs +4 -9
- package/Tests/Runtime/Serialization/ProtoRoundtripComprehensiveTests.cs +13 -13
- package/Tests/Runtime/TestUtils/CommonTestBase.cs +11 -0
- package/Tests/Runtime/TestUtils/ReflexTestSupport.cs +111 -0
- package/Tests/Runtime/TestUtils/ReflexTestSupport.cs.meta +12 -0
- package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +4 -4
- package/Tests/TestUtils.meta +8 -0
- package/package.json +6 -1
- package/EFFECTS_SYSTEM.md +0 -242
- package/MATH_AND_EXTENSIONS.md +0 -316
- /package/{CHANGELOG.md → Docs/CHANGELOG.md} +0 -0
- /package/{CHANGELOG.md.meta → Docs/CHANGELOG.md.meta} +0 -0
- /package/{CONTRIBUTING.md → Docs/CONTRIBUTING.md} +0 -0
- /package/{CONTRIBUTING.md.meta → Docs/CONTRIBUTING.md.meta} +0 -0
- /package/{DATA_STRUCTURES.md → Docs/DATA_STRUCTURES.md} +0 -0
- /package/{DATA_STRUCTURES.md.meta → Docs/DATA_STRUCTURES.md.meta} +0 -0
- /package/{EDITOR_TOOLS_GUIDE.md → Docs/EDITOR_TOOLS_GUIDE.md} +0 -0
- /package/{EDITOR_TOOLS_GUIDE.md.meta → Docs/EDITOR_TOOLS_GUIDE.md.meta} +0 -0
- /package/{EFFECTS_SYSTEM.md.meta → Docs/EFFECTS_SYSTEM.md.meta} +0 -0
- /package/{EFFECTS_SYSTEM_TUTORIAL.md.meta → Docs/EFFECTS_SYSTEM_TUTORIAL.md.meta} +0 -0
- /package/{GETTING_STARTED.md.meta → Docs/GETTING_STARTED.md.meta} +0 -0
- /package/{GLOSSARY.md.meta → Docs/GLOSSARY.md.meta} +0 -0
- /package/{HULLS.md → Docs/HULLS.md} +0 -0
- /package/{HULLS.md.meta → Docs/HULLS.md.meta} +0 -0
- /package/{INDEX.md.meta → Docs/INDEX.md.meta} +0 -0
- /package/{LICENSE.md → Docs/LICENSE.md} +0 -0
- /package/{LICENSE.md.meta → Docs/LICENSE.md.meta} +0 -0
- /package/{MATH_AND_EXTENSIONS.md.meta → Docs/MATH_AND_EXTENSIONS.md.meta} +0 -0
- /package/{RANDOM_PERFORMANCE.md.meta → Docs/RANDOM_PERFORMANCE.md.meta} +0 -0
- /package/{REFLECTION_HELPERS.md → Docs/REFLECTION_HELPERS.md} +0 -0
- /package/{REFLECTION_HELPERS.md.meta → Docs/REFLECTION_HELPERS.md.meta} +0 -0
- /package/{RELATIONAL_COMPONENTS.md.meta → Docs/RELATIONAL_COMPONENTS.md.meta} +0 -0
- /package/{SERIALIZATION.md → Docs/SERIALIZATION.md} +0 -0
- /package/{SERIALIZATION.md.meta → Docs/SERIALIZATION.md.meta} +0 -0
- /package/{SINGLETONS.md → Docs/SINGLETONS.md} +0 -0
- /package/{SINGLETONS.md.meta → Docs/SINGLETONS.md.meta} +0 -0
- /package/{SPATIAL_TREES_2D_GUIDE.md.meta → Docs/SPATIAL_TREES_2D_GUIDE.md.meta} +0 -0
- /package/{SPATIAL_TREES_3D_GUIDE.md.meta → Docs/SPATIAL_TREES_3D_GUIDE.md.meta} +0 -0
- /package/{SPATIAL_TREE_2D_PERFORMANCE.md.meta → Docs/SPATIAL_TREE_2D_PERFORMANCE.md.meta} +0 -0
- /package/{SPATIAL_TREE_3D_PERFORMANCE.md.meta → Docs/SPATIAL_TREE_3D_PERFORMANCE.md.meta} +0 -0
- /package/{SPATIAL_TREE_SEMANTICS.md → Docs/SPATIAL_TREE_SEMANTICS.md} +0 -0
- /package/{SPATIAL_TREE_SEMANTICS.md.meta → Docs/SPATIAL_TREE_SEMANTICS.md.meta} +0 -0
- /package/{THIRD_PARTY_NOTICES.md → Docs/THIRD_PARTY_NOTICES.md} +0 -0
- /package/{THIRD_PARTY_NOTICES.md.meta → Docs/THIRD_PARTY_NOTICES.md.meta} +0 -0
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
# Core Math & Extensions
|
|
2
|
+
|
|
3
|
+
## TL;DR — Why Use These
|
|
4
|
+
|
|
5
|
+
- Small helpers that fix everyday math and Unity annoyances: safe modulo, wrapped indices, robust equality, fast bounds math, color utilities, and more.
|
|
6
|
+
- Copy/paste examples and diagrams show intent; use as building blocks in hot paths.
|
|
7
|
+
|
|
8
|
+
This guide summarizes the math primitives and extension helpers in this package and shows how to apply them effectively, with examples, performance notes, and practical scenarios.
|
|
9
|
+
|
|
10
|
+
Contents
|
|
11
|
+
|
|
12
|
+
- [Numeric helpers](#numeric-helpers) — Positive modulo, wrapped arithmetic, approximate equality, clamping
|
|
13
|
+
- [Geometry](#geometry) — Lines, ranges, parabolas, point-in-polygon, polyline simplification
|
|
14
|
+
- [Unity extensions](#unity-extensions) — Rect/Bounds conversions, RectTransform bounds, camera bounds, bounds aggregation
|
|
15
|
+
- [Color utilities](#color-utilities) — Averaging (LAB/HSV/Weighted/Dominant), hex conversion
|
|
16
|
+
- [Collections](#collections) — IEnumerable helpers, buffering, infinite sequences
|
|
17
|
+
- [Strings](#strings) — Casing, encoding/decoding, distance
|
|
18
|
+
- [Direction helpers](#directions) — Enum conversions and operations
|
|
19
|
+
- [Enum helpers](#enum-helpers) — Zero-allocation flag checks, cached names, display names
|
|
20
|
+
- [Random generators](#random-generators) — Weighted selection, vector generation, subset sampling
|
|
21
|
+
- [Async/Coroutine interop](#async-coroutine-interop) — Bridge Unity AsyncOperation with async/await
|
|
22
|
+
- [Best Practices](#best-practices)
|
|
23
|
+
|
|
24
|
+
<a id="numeric-helpers"></a>
|
|
25
|
+
|
|
26
|
+
## Numeric Helpers
|
|
27
|
+
|
|
28
|
+
- Positive modulo and wrap-around arithmetic
|
|
29
|
+
- Use `PositiveMod` to ensure non-negative modulo results for indices and cyclic counters.
|
|
30
|
+
- Use `WrappedAdd`/`WrappedIncrement` for ring buffer indexes and cursor navigation.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
|
|
34
|
+
```csharp
|
|
35
|
+
using WallstopStudios.UnityHelpers.Core.Helper;
|
|
36
|
+
|
|
37
|
+
int i = -1;
|
|
38
|
+
i = i.PositiveMod(5); // 4
|
|
39
|
+
i = i.WrappedAdd(2, 5); // 1
|
|
40
|
+
|
|
41
|
+
float angle = -30f;
|
|
42
|
+
float normalized = angle.PositiveMod(360f); // 330f
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Diagram (wrap-around on a ring of size 5):
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Index: 0 1 2 3 4
|
|
49
|
+
↖ ↙
|
|
50
|
+
\ +2 from 4 => 1
|
|
51
|
+
|
|
52
|
+
Start at 4, add 2 → 6 → 6 % 5 = 1
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- Approximate equality
|
|
56
|
+
- `float.Approximately(rhs, tolerance)` and `double.Approximately` add a magnitude-scaled fudge factor.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
|
|
60
|
+
```csharp
|
|
61
|
+
bool close = 0.1f.Approximately(0.10001f, 0.0001f); // true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Generic `Clamp`
|
|
65
|
+
- `Clamp<T>(min, max)` works for any `IComparable<T>`.
|
|
66
|
+
|
|
67
|
+
<a id="geometry"></a>
|
|
68
|
+
|
|
69
|
+
## Geometry
|
|
70
|
+
|
|
71
|
+
### Line2D — 2D line segment operations
|
|
72
|
+
|
|
73
|
+
**Why it exists:** Provides fast, battle-tested 2D line segment math for collision detection, ray-casting, and geometric queries.
|
|
74
|
+
|
|
75
|
+
**When to use:**
|
|
76
|
+
|
|
77
|
+
- Ray-casting for bullets, lasers, or line-of-sight checks
|
|
78
|
+
- Detecting if paths cross obstacles
|
|
79
|
+
- Click detection near edges or borders
|
|
80
|
+
- Finding closest points on paths or walls
|
|
81
|
+
|
|
82
|
+
**When NOT to use:**
|
|
83
|
+
|
|
84
|
+
- For 3D geometry (use Line3D instead)
|
|
85
|
+
- For curves or arcs (lines are always straight)
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
|
|
89
|
+
```csharp
|
|
90
|
+
using WallstopStudios.UnityHelpers.Core.Math;
|
|
91
|
+
var a = new Line2D(new Vector2(0,0), new Vector2(2,0));
|
|
92
|
+
var b = new Line2D(new Vector2(1,-1), new Vector2(1,1));
|
|
93
|
+
bool hit = a.Intersects(b); // true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Diagram (segment intersection):
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
y↑ b.to (1,1)
|
|
100
|
+
| │
|
|
101
|
+
| │ b
|
|
102
|
+
| a ────────┼────────▶ x
|
|
103
|
+
| (1,0)× ← intersection
|
|
104
|
+
| │
|
|
105
|
+
| │
|
|
106
|
+
+─────────────┼────────
|
|
107
|
+
b.from (1,-1)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Getting the exact intersection point:**
|
|
111
|
+
|
|
112
|
+
```csharp
|
|
113
|
+
var wall = new Line2D(new Vector2(0, 0), new Vector2(10, 0));
|
|
114
|
+
var ray = new Line2D(playerPos, targetPos);
|
|
115
|
+
|
|
116
|
+
if (wall.TryGetIntersectionPoint(ray, out Vector2 hitPoint))
|
|
117
|
+
{
|
|
118
|
+
// Spawn bullet impact effect at exact hitPoint
|
|
119
|
+
Instantiate(sparksPrefab, hitPoint, Quaternion.identity);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Circle intersection (bullets hitting circular enemies):**
|
|
124
|
+
|
|
125
|
+
```csharp
|
|
126
|
+
var bulletPath = new Line2D(bulletStart, bulletEnd);
|
|
127
|
+
var enemy = new Circle(enemyPosition, enemyRadius);
|
|
128
|
+
|
|
129
|
+
if (bulletPath.Intersects(enemy))
|
|
130
|
+
{
|
|
131
|
+
// Bullet hit the enemy
|
|
132
|
+
enemy.TakeDamage(bulletDamage);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Closest point on line (snapping to paths):**
|
|
137
|
+
|
|
138
|
+
```csharp
|
|
139
|
+
var path = new Line2D(pathStart, pathEnd);
|
|
140
|
+
Vector2 snappedPosition = path.ClosestPointOnLine(mouseWorldPos);
|
|
141
|
+
// Use for UI snapping, path following, or grid alignment
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Performance tip:** Use `DistanceSquaredToPoint` instead of `DistanceToPoint` when comparing distances (avoids expensive square root):
|
|
145
|
+
|
|
146
|
+
```csharp
|
|
147
|
+
// Fast distance comparison (no sqrt)
|
|
148
|
+
float distSq = line.DistanceSquaredToPoint(point);
|
|
149
|
+
if (distSq < thresholdSquared)
|
|
150
|
+
{
|
|
151
|
+
// Point is within threshold
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Line3D — 3D line segment operations
|
|
158
|
+
|
|
159
|
+
**Why it exists:** Extends Line2D concepts to 3D space with sophisticated algorithms for sphere intersection, bounding box clipping, and skew line distance.
|
|
160
|
+
|
|
161
|
+
**When to use:**
|
|
162
|
+
|
|
163
|
+
- 3D ray-casting for weapons, lasers, or grappling hooks
|
|
164
|
+
- Visibility checks between 3D objects
|
|
165
|
+
- Cable/rope collision detection
|
|
166
|
+
- Finding closest approach between moving objects
|
|
167
|
+
|
|
168
|
+
**When NOT to use:**
|
|
169
|
+
|
|
170
|
+
- For 2D games (use Line2D for better performance)
|
|
171
|
+
- For complex curved paths (lines are always straight)
|
|
172
|
+
|
|
173
|
+
Basic operations:
|
|
174
|
+
|
|
175
|
+
```csharp
|
|
176
|
+
using WallstopStudios.UnityHelpers.Core.Math;
|
|
177
|
+
|
|
178
|
+
var ray = new Line3D(gunBarrel.position, hitPoint);
|
|
179
|
+
var enemyBounds = new BoundingBox3D(enemy.bounds);
|
|
180
|
+
|
|
181
|
+
// Check if ray hits enemy bounding box
|
|
182
|
+
if (ray.Intersects(enemyBounds))
|
|
183
|
+
{
|
|
184
|
+
enemy.TakeDamage(bulletDamage);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Closest points between two 3D lines (skew lines):**
|
|
189
|
+
|
|
190
|
+
_Problem:_ In 3D, two lines might not actually intersect (imagine two pipes that pass by each other). This finds the closest approach.
|
|
191
|
+
|
|
192
|
+
```csharp
|
|
193
|
+
var ropeA = new Line3D(ropeAStart, ropeAEnd);
|
|
194
|
+
var ropeB = new Line3D(ropeBStart, ropeBEnd);
|
|
195
|
+
|
|
196
|
+
if (ropeA.TryGetClosestPoints(ropeB, out Vector3 pointOnA, out Vector3 pointOnB))
|
|
197
|
+
{
|
|
198
|
+
float separation = Vector3.Distance(pointOnA, pointOnB);
|
|
199
|
+
if (separation < 0.1f)
|
|
200
|
+
{
|
|
201
|
+
// Ropes are touching or tangled
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Sphere intersection (force fields, explosions):**
|
|
207
|
+
|
|
208
|
+
```csharp
|
|
209
|
+
var laserBeam = new Line3D(laserStart, laserEnd);
|
|
210
|
+
var shield = new Sphere(shieldCenter, shieldRadius);
|
|
211
|
+
|
|
212
|
+
if (laserBeam.Intersects(shield))
|
|
213
|
+
{
|
|
214
|
+
float distance = laserBeam.DistanceToSphere(shield);
|
|
215
|
+
// distance == 0 means line passes through sphere
|
|
216
|
+
// distance > 0 means line misses sphere
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### Range<T> — Numeric ranges with flexible boundaries
|
|
223
|
+
|
|
224
|
+
**Why it exists:** Solves the "is this value in a valid range" problem with clear, readable code and support for different boundary conditions.
|
|
225
|
+
|
|
226
|
+
**When to use:**
|
|
227
|
+
|
|
228
|
+
- Validating user input (is health between 0-100?)
|
|
229
|
+
- Time windows (is this event during business hours?)
|
|
230
|
+
- Array bounds checking with custom inclusivity
|
|
231
|
+
- Overlap detection (do these time slots conflict?)
|
|
232
|
+
|
|
233
|
+
**When NOT to use:**
|
|
234
|
+
|
|
235
|
+
- For single comparisons (just use `if (x >= min && x <= max)`)
|
|
236
|
+
- When you don't care about boundary inclusivity
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
|
|
240
|
+
```csharp
|
|
241
|
+
using WallstopStudios.UnityHelpers.Core.Math;
|
|
242
|
+
var r = Range<int>.Inclusive(0, 10);
|
|
243
|
+
bool inside = r.Contains(10); // true (10 is included)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Choosing the right inclusivity:**
|
|
247
|
+
|
|
248
|
+
```csharp
|
|
249
|
+
// [0, 10] - both endpoints included (closed interval)
|
|
250
|
+
var healthRange = Range<int>.Inclusive(0, 10);
|
|
251
|
+
healthRange.Contains(0); // true
|
|
252
|
+
healthRange.Contains(10); // true
|
|
253
|
+
|
|
254
|
+
// [0, 10) - start included, end excluded (common for indices)
|
|
255
|
+
var arrayRange = Range<int>.InclusiveExclusive(0, 10);
|
|
256
|
+
arrayRange.Contains(0); // true
|
|
257
|
+
arrayRange.Contains(10); // false (typical for array[0..10))
|
|
258
|
+
|
|
259
|
+
// (0, 1) - neither endpoint included (open interval)
|
|
260
|
+
var normalized = Range<float>.Exclusive(0f, 1f);
|
|
261
|
+
normalized.Contains(0f); // false
|
|
262
|
+
normalized.Contains(0.5f); // true
|
|
263
|
+
normalized.Contains(1f); // false
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Overlap detection:**
|
|
267
|
+
|
|
268
|
+
```csharp
|
|
269
|
+
var morningShift = Range<int>.Inclusive(9, 13); // 9am-1pm
|
|
270
|
+
var afternoonShift = Range<int>.Inclusive(13, 17); // 1pm-5pm
|
|
271
|
+
|
|
272
|
+
bool conflict = morningShift.Overlaps(afternoonShift); // true (overlap at 1pm)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Date ranges:**
|
|
276
|
+
|
|
277
|
+
```csharp
|
|
278
|
+
var january = Range<DateTime>.Inclusive(
|
|
279
|
+
new DateTime(2025, 1, 1),
|
|
280
|
+
new DateTime(2025, 1, 31)
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (january.Contains(someDate))
|
|
284
|
+
{
|
|
285
|
+
// Event happened in January
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### Parabola — Projectile trajectories and smooth curves
|
|
292
|
+
|
|
293
|
+
**Why it exists:** Provides easy-to-use parabolic math for projectile motion, jump arcs, and smooth animation curves without writing quadratic equations by hand.
|
|
294
|
+
|
|
295
|
+
**When to use:**
|
|
296
|
+
|
|
297
|
+
- Throwing/shooting projectiles (grenades, arrows, basketballs)
|
|
298
|
+
- Character jump arcs
|
|
299
|
+
- Camera dolly movements along smooth paths
|
|
300
|
+
- Particle fountain effects
|
|
301
|
+
|
|
302
|
+
**When NOT to use:**
|
|
303
|
+
|
|
304
|
+
- For straight-line motion (use Vector3.Lerp)
|
|
305
|
+
- For complex curves with multiple peaks (parabola has only one peak)
|
|
306
|
+
- When gravity/physics simulation is already handling it
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
|
|
310
|
+
```csharp
|
|
311
|
+
using WallstopStudios.UnityHelpers.Core.Math;
|
|
312
|
+
|
|
313
|
+
var p = new Parabola(maxHeight: 5f, length: 10f);
|
|
314
|
+
if (p.TryGetValueAtNormalized(0.5f, out float y))
|
|
315
|
+
{
|
|
316
|
+
// y == 5 (at the peak)
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Diagram (normalized parabola):
|
|
321
|
+
|
|
322
|
+
```text
|
|
323
|
+
y↑ * vertex (0.5, 5)
|
|
324
|
+
| *
|
|
325
|
+
| *
|
|
326
|
+
| *
|
|
327
|
+
| *
|
|
328
|
+
|* *
|
|
329
|
+
+────────*────────▶ x (t from 0..1)
|
|
330
|
+
0 0.5 1
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Custom coefficients (when you have a specific equation):**
|
|
334
|
+
|
|
335
|
+
```csharp
|
|
336
|
+
// Create parabola from equation y = -0.5x² + 5x
|
|
337
|
+
var p = Parabola.FromCoefficients(a: -0.5f, b: 5f, length: 10f);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Performance tip:** Use `GetValueAtUnchecked` when you know the input is in range (skips bounds checking):
|
|
341
|
+
|
|
342
|
+
```csharp
|
|
343
|
+
// In a tight loop updating many projectiles
|
|
344
|
+
for (int i = 0; i < projectiles.Length; i++)
|
|
345
|
+
{
|
|
346
|
+
float x = projectiles[i].distanceTraveled;
|
|
347
|
+
if (x >= 0 && x <= parabola.Length)
|
|
348
|
+
{
|
|
349
|
+
float y = parabola.GetValueAtUnchecked(x); // No bounds check
|
|
350
|
+
projectiles[i].position.y = y;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Normalized vs Absolute coordinates:**
|
|
356
|
+
|
|
357
|
+
```csharp
|
|
358
|
+
// Normalized: Use when working with 0-1 interpolation (animations)
|
|
359
|
+
float t = animationTime / totalDuration; // 0-1
|
|
360
|
+
parabola.TryGetValueAtNormalized(t, out float y);
|
|
361
|
+
|
|
362
|
+
// Absolute: Use when working with world-space coordinates
|
|
363
|
+
float worldX = transform.position.x;
|
|
364
|
+
parabola.TryGetValueAt(worldX, out float worldY);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
### Point-in-Polygon — Test if points are inside shapes
|
|
370
|
+
|
|
371
|
+
**Why it exists:** Detects whether a point lies inside an irregular polygon, solving the "did the player click this shape" problem.
|
|
372
|
+
|
|
373
|
+
**When to use:**
|
|
374
|
+
|
|
375
|
+
- Click detection in irregular UI shapes or game zones
|
|
376
|
+
- Testing if characters are inside territory boundaries
|
|
377
|
+
- Checking if waypoints are in walkable areas
|
|
378
|
+
- Testing if 3D points project inside mesh faces
|
|
379
|
+
|
|
380
|
+
**When NOT to use:**
|
|
381
|
+
|
|
382
|
+
- For circles (use `Vector2.Distance(point, center) <= radius`)
|
|
383
|
+
- For rectangles (use `Rect.Contains`)
|
|
384
|
+
- For complex 3D volumes (use Collider.bounds or raycasts)
|
|
385
|
+
|
|
386
|
+
**Important:** This uses the ray-casting algorithm — it counts how many times a ray from the point crosses polygon edges. Odd count = inside, even count = outside.
|
|
387
|
+
|
|
388
|
+
2D polygon test:
|
|
389
|
+
|
|
390
|
+
```csharp
|
|
391
|
+
using WallstopStudios.UnityHelpers.Core.Math;
|
|
392
|
+
|
|
393
|
+
Vector2[] zoneShape = new Vector2[]
|
|
394
|
+
{
|
|
395
|
+
new(0, 0), new(10, 0), new(10, 5), new(5, 10), new(0, 5)
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
Vector2 clickPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
|
399
|
+
|
|
400
|
+
if (PointPolygonCheck.IsPointInsidePolygon(clickPos, zoneShape))
|
|
401
|
+
{
|
|
402
|
+
Debug.Log("Clicked inside the zone!");
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
3D polygon with plane projection:
|
|
407
|
+
|
|
408
|
+
```csharp
|
|
409
|
+
// Test if 3D point is inside a 3D triangle (projects onto plane)
|
|
410
|
+
Vector3[] triangleFace = new Vector3[]
|
|
411
|
+
{
|
|
412
|
+
new(0, 0, 0), new(5, 0, 0), new(2.5f, 5, 0)
|
|
413
|
+
};
|
|
414
|
+
Vector3 faceNormal = Vector3.forward; // Must be normalized
|
|
415
|
+
|
|
416
|
+
Vector3 testPoint = new Vector3(2.5f, 2f, 1f); // Will project onto z=0 plane
|
|
417
|
+
|
|
418
|
+
if (PointPolygonCheck.IsPointInsidePolygon(testPoint, triangleFace, faceNormal))
|
|
419
|
+
{
|
|
420
|
+
Debug.Log("Point projects inside triangle");
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Zero-allocation version for hot paths:**
|
|
425
|
+
|
|
426
|
+
```csharp
|
|
427
|
+
// Use ReadOnlySpan to avoid heap allocations
|
|
428
|
+
Span<Vector2> vertices = stackalloc Vector2[4]
|
|
429
|
+
{
|
|
430
|
+
new(0, 0), new(1, 0), new(1, 1), new(0, 1)
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
bool inside = PointPolygonCheck.IsPointInsidePolygon(clickPos, vertices);
|
|
434
|
+
// No GC allocations, great for per-frame checks
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Edge cases to know:**
|
|
438
|
+
|
|
439
|
+
- Points exactly on polygon edges may return inconsistent results (floating-point precision issues)
|
|
440
|
+
- Assumes simple (non-self-intersecting) polygons
|
|
441
|
+
- Winding order (clockwise vs counter-clockwise) doesn't matter
|
|
442
|
+
- For 3D: all polygon vertices must be coplanar for accurate results
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
- Polyline simplification (Douglas–Peucker)
|
|
447
|
+
- `Simplify` (float epsilon) and `SimplifyPrecise` (double tolerance) reduce vertex count while preserving shape.
|
|
448
|
+
|
|
449
|
+
Example:
|
|
450
|
+
|
|
451
|
+
```csharp
|
|
452
|
+
using WallstopStudios.UnityHelpers.Core.Helper;
|
|
453
|
+
List<Vector2> simplified = LineHelper.Simplify(points, epsilon: 0.1f);
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Diagram (original vs simplified):
|
|
457
|
+
|
|
458
|
+
```text
|
|
459
|
+
Original: *----*--*---*--*-----*
|
|
460
|
+
Simplified: *-----------*--------*
|
|
461
|
+
|
|
462
|
+
Fewer vertices within epsilon of the original polyline.
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Visual:
|
|
466
|
+

|
|
467
|
+
|
|
468
|
+
Convex hull (monotone chain / Jarvis examples used by helpers):
|
|
469
|
+
|
|
470
|
+
```text
|
|
471
|
+
Points: · · ·
|
|
472
|
+
· · ·
|
|
473
|
+
· ·
|
|
474
|
+
|
|
475
|
+
Hull: ┌───────────┐
|
|
476
|
+
│ │
|
|
477
|
+
└───────┬───┘
|
|
478
|
+
└─┐
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Visual:
|
|
482
|
+

|
|
483
|
+
|
|
484
|
+
Edge Cases Gallery
|
|
485
|
+
|
|
486
|
+

|
|
487
|
+
|
|
488
|
+
<a id="unity-extensions"></a>
|
|
489
|
+
|
|
490
|
+
## Unity Extensions
|
|
491
|
+
|
|
492
|
+
- Rect/Bounds conversions, RectTransform world bounds
|
|
493
|
+
- Camera `OrthographicBounds`
|
|
494
|
+
- Bounds aggregation from collections
|
|
495
|
+
|
|
496
|
+
Example:
|
|
497
|
+
|
|
498
|
+
```csharp
|
|
499
|
+
Rect r = rectTransform.GetWorldRect();
|
|
500
|
+
Bounds view = Camera.main.OrthographicBounds();
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Diagrams:
|
|
504
|
+
|
|
505
|
+
- RectTransform world rect (axis-aligned bounds of rotated UI):
|
|
506
|
+
|
|
507
|
+
```text
|
|
508
|
+
• corner ┌───────────────┐
|
|
509
|
+
╲ │ AABB (r) │
|
|
510
|
+
╲ rotated │ ┌──────┐ │
|
|
511
|
+
╲ rectangle │ ╱│ UI ╱│ │
|
|
512
|
+
• │ ╱ └────╱─┘ │
|
|
513
|
+
└───────────────┘
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
- Orthographic camera bounds (centered on camera):
|
|
517
|
+
|
|
518
|
+
```text
|
|
519
|
+
┌──────── view (Bounds) ────────┐
|
|
520
|
+
│ height=2*size │
|
|
521
|
+
│ ┌────────────────┐ │
|
|
522
|
+
near ───▶│ │ camera FOV │ │◀── far
|
|
523
|
+
│ └────────────────┘ │
|
|
524
|
+
└────────────────────────────────┘
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
<a id="color-utilities"></a>
|
|
528
|
+
|
|
529
|
+
## Color Utilities
|
|
530
|
+
|
|
531
|
+
- Averaging methods:
|
|
532
|
+
- LAB: perceptually accurate
|
|
533
|
+
- HSV: preserves vibrancy
|
|
534
|
+
- Weighted: luminance-aware
|
|
535
|
+
- Dominant: bucket-based mode
|
|
536
|
+
|
|
537
|
+
Example:
|
|
538
|
+
|
|
539
|
+
```csharp
|
|
540
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
541
|
+
Color avg = sprite.GetAverageColor(ColorAveragingMethod.LAB);
|
|
542
|
+
string html = avg.ToHex();
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Dominant color example (bucket-based):
|
|
546
|
+
|
|
547
|
+
```csharp
|
|
548
|
+
// Emphasize palette extraction (posterized sprites, UI swatches)
|
|
549
|
+
var dominant = pixels.GetAverageColor(ColorAveragingMethod.Dominant, alphaCutoff: 0.05f);
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Diagram (dominant buckets):
|
|
553
|
+
|
|
554
|
+
```text
|
|
555
|
+
RGB space buckets → counts
|
|
556
|
+
[R][G][B] … [R+Δ][G][B] … [R][G+Δ][B] …
|
|
557
|
+
↑ pick max bucket centroid as dominant
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
<a id="collections"></a>
|
|
561
|
+
|
|
562
|
+
## Collections
|
|
563
|
+
|
|
564
|
+
### IEnumerable Helpers
|
|
565
|
+
|
|
566
|
+
**Infinite cycling:**
|
|
567
|
+
|
|
568
|
+
```csharp
|
|
569
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
570
|
+
|
|
571
|
+
// Cycle through elements endlessly (great for repeating patterns)
|
|
572
|
+
var colors = new[] { Color.red, Color.blue, Color.green };
|
|
573
|
+
foreach (var color in colors.Infinite())
|
|
574
|
+
{
|
|
575
|
+
// Loops forever: red, blue, green, red, blue, green...
|
|
576
|
+
if (shouldStop) break;
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Partition into chunks:**
|
|
581
|
+
|
|
582
|
+
```csharp
|
|
583
|
+
// Split large collections into fixed-size batches
|
|
584
|
+
var items = Enumerable.Range(0, 100);
|
|
585
|
+
foreach (var batch in items.Partition(10))
|
|
586
|
+
{
|
|
587
|
+
// Process 10 items at a time
|
|
588
|
+
ProcessBatch(batch); // batch is a List<int> of size 10
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Zero-allocation version for hot paths
|
|
592
|
+
using (var batchBuffer = items.PartitionPooled(10))
|
|
593
|
+
{
|
|
594
|
+
foreach (var batch in batchBuffer)
|
|
595
|
+
{
|
|
596
|
+
// batch is reused from pool, no allocations
|
|
597
|
+
}
|
|
598
|
+
} // Automatically returns buffer to pool
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Shuffled (non-destructive):**
|
|
602
|
+
|
|
603
|
+
```csharp
|
|
604
|
+
// Get shuffled copy without modifying original
|
|
605
|
+
var shuffled = items.Shuffled();
|
|
606
|
+
// Original list unchanged
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### IList Operations
|
|
610
|
+
|
|
611
|
+
**Remove O(1) by swapping with last element:**
|
|
612
|
+
|
|
613
|
+
```csharp
|
|
614
|
+
// Fast removal when order doesn't matter (particle systems, entity lists)
|
|
615
|
+
List<Enemy> enemies = GetActiveEnemies();
|
|
616
|
+
enemies.RemoveAtSwapBack(3); // Swaps enemy[3] with last enemy, then removes
|
|
617
|
+
// Much faster than List.RemoveAt which shifts all elements
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**Partition (split by predicate):**
|
|
621
|
+
|
|
622
|
+
```csharp
|
|
623
|
+
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
|
|
624
|
+
var (evens, odds) = numbers.Partition(n => n % 2 == 0);
|
|
625
|
+
// evens: [2, 4, 6]
|
|
626
|
+
// odds: [1, 3, 5]
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Custom sorting:**
|
|
630
|
+
|
|
631
|
+
```csharp
|
|
632
|
+
// GhostSort: Faster hybrid sort for medium-sized lists
|
|
633
|
+
largeList.GhostSort(); // Uses IComparable<T>
|
|
634
|
+
|
|
635
|
+
// Custom comparison function
|
|
636
|
+
list.Sort((a, b) => a.priority.CompareTo(b.priority));
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Dictionary Helpers
|
|
640
|
+
|
|
641
|
+
**Thread-safe get-or-create:**
|
|
642
|
+
|
|
643
|
+
```csharp
|
|
644
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
645
|
+
|
|
646
|
+
// Thread-safe for ConcurrentDictionary
|
|
647
|
+
var value = dict.GetOrAdd(key, () => new ExpensiveObject());
|
|
648
|
+
|
|
649
|
+
// Read-only version (doesn't modify dict)
|
|
650
|
+
var value = readOnlyDict.GetOrElse(key, defaultValue);
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
**Merge dictionaries:**
|
|
654
|
+
|
|
655
|
+
```csharp
|
|
656
|
+
var defaults = new Dictionary<string, int> { ["health"] = 100, ["mana"] = 50 };
|
|
657
|
+
var overrides = new Dictionary<string, int> { ["health"] = 150 };
|
|
658
|
+
|
|
659
|
+
var merged = defaults.Merge(overrides);
|
|
660
|
+
// Result: { ["health"] = 150, ["mana"] = 50 }
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Deep equality:**
|
|
664
|
+
|
|
665
|
+
```csharp
|
|
666
|
+
// Compare dictionary contents (not just references)
|
|
667
|
+
bool same = dict1.ContentEquals(dict2); // Compares all key-value pairs
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Bounds from Collections
|
|
671
|
+
|
|
672
|
+
Bounds from points example:
|
|
673
|
+
|
|
674
|
+
```csharp
|
|
675
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
676
|
+
|
|
677
|
+
// Compute BoundsInt for occupied grid cells
|
|
678
|
+
Vector3Int[] positions = GetOccupiedCells();
|
|
679
|
+
BoundsInt? area = positions.GetBounds(inclusive: false);
|
|
680
|
+
if (area is BoundsInt b)
|
|
681
|
+
{
|
|
682
|
+
// b contains all positions
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Bounds aggregation example:
|
|
687
|
+
|
|
688
|
+
```csharp
|
|
689
|
+
// Merge many Bounds (e.g., from Renderers)
|
|
690
|
+
Renderer[] renderers = GetComponentsInChildren<Renderer>();
|
|
691
|
+
Bounds? merged = renderers.Select(r => r.bounds).GetBounds();
|
|
692
|
+
if (merged is Bounds totalBounds)
|
|
693
|
+
{
|
|
694
|
+
// totalBounds encompasses all renderers
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
<a id="strings"></a>
|
|
699
|
+
|
|
700
|
+
## Strings
|
|
701
|
+
|
|
702
|
+
### Case Conversions
|
|
703
|
+
|
|
704
|
+
**Why it exists:** Automatically convert between common programming case styles without writing regex or manual parsing.
|
|
705
|
+
|
|
706
|
+
```csharp
|
|
707
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
708
|
+
|
|
709
|
+
string input = "XMLHttpRequest";
|
|
710
|
+
|
|
711
|
+
input.ToPascalCase(); // "XmlHttpRequest"
|
|
712
|
+
input.ToCamelCase(); // "xmlHttpRequest"
|
|
713
|
+
input.ToSnakeCase(); // "xml_http_request"
|
|
714
|
+
input.ToKebabCase(); // "xml-http-request"
|
|
715
|
+
input.ToTitleCase(); // "Xml Http Request"
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
Smart tokenization handles mixed cases intelligently.
|
|
719
|
+
|
|
720
|
+
### String Utilities
|
|
721
|
+
|
|
722
|
+
**Levenshtein Distance (edit distance):**
|
|
723
|
+
|
|
724
|
+
```csharp
|
|
725
|
+
// Calculate how many edits to transform one string into another
|
|
726
|
+
string a = "kitten";
|
|
727
|
+
string b = "sitting";
|
|
728
|
+
int distance = a.LevenshteinDistance(b); // 3 edits
|
|
729
|
+
// Use for: fuzzy matching, spell correction, search suggestions
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
**Base64 encoding:**
|
|
733
|
+
|
|
734
|
+
```csharp
|
|
735
|
+
string text = "Hello, World!";
|
|
736
|
+
string encoded = text.ToBase64(); // "SGVsbG8sIFdvcmxkIQ=="
|
|
737
|
+
string decoded = encoded.FromBase64(); // "Hello, World!"
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
**String analysis:**
|
|
741
|
+
|
|
742
|
+
```csharp
|
|
743
|
+
bool isNum = "12345".IsNumeric(); // true
|
|
744
|
+
bool isAlpha = "Hello".IsAlphabetic(); // true
|
|
745
|
+
bool isAlphaNum = "Hello123".IsAlphanumeric(); // true
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Truncate with ellipsis:**
|
|
749
|
+
|
|
750
|
+
```csharp
|
|
751
|
+
string long = "This is a very long string";
|
|
752
|
+
string short = long.Truncate(10); // "This is a..."
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Encoding Helpers
|
|
756
|
+
|
|
757
|
+
```csharp
|
|
758
|
+
// Quick UTF-8 conversions
|
|
759
|
+
byte[] bytes = "Hello".GetBytes();
|
|
760
|
+
string text = bytes.GetString();
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
<a id="directions"></a>
|
|
764
|
+
|
|
765
|
+
## Directions
|
|
766
|
+
|
|
767
|
+
- Conversions between enum and vectors; splitting flag sets; combining
|
|
768
|
+
|
|
769
|
+
Example:
|
|
770
|
+
|
|
771
|
+
```csharp
|
|
772
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
773
|
+
Vector2Int v = Direction.NorthWest.AsVector2Int(); // (-1, 1)
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
<a id="enum-helpers"></a>
|
|
777
|
+
|
|
778
|
+
## Enum Helpers
|
|
779
|
+
|
|
780
|
+
**Why it exists:** Standard C# enum operations cause boxing allocations and are slow in hot paths. These helpers solve performance problems.
|
|
781
|
+
|
|
782
|
+
### Zero-Allocation Flag Checking
|
|
783
|
+
|
|
784
|
+
**The problem:** Standard `HasFlag()` boxes both enums, causing GC pressure.
|
|
785
|
+
|
|
786
|
+
```csharp
|
|
787
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
788
|
+
|
|
789
|
+
[Flags]
|
|
790
|
+
public enum Permissions
|
|
791
|
+
{
|
|
792
|
+
None = 0,
|
|
793
|
+
Read = 1,
|
|
794
|
+
Write = 2,
|
|
795
|
+
Execute = 4
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
Permissions userPerms = Permissions.Read | Permissions.Write;
|
|
799
|
+
|
|
800
|
+
// ❌ BAD: Causes boxing allocations
|
|
801
|
+
if (userPerms.HasFlag(Permissions.Write)) { }
|
|
802
|
+
|
|
803
|
+
// ✅ GOOD: Zero allocations
|
|
804
|
+
if (userPerms.HasFlagNoAlloc(Permissions.Write)) { }
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
Use `HasFlagNoAlloc` in:
|
|
808
|
+
|
|
809
|
+
- Per-frame checks
|
|
810
|
+
- Hot loops
|
|
811
|
+
- Frequently-called methods
|
|
812
|
+
- Performance-critical code paths
|
|
813
|
+
|
|
814
|
+
### Fast Enum-to-String Conversion
|
|
815
|
+
|
|
816
|
+
**The problem:** `enum.ToString()` is slow (reflection) and allocates every call.
|
|
817
|
+
|
|
818
|
+
```csharp
|
|
819
|
+
public enum GameState { MainMenu, Playing, Paused, GameOver }
|
|
820
|
+
|
|
821
|
+
GameState state = GameState.Playing;
|
|
822
|
+
|
|
823
|
+
// ❌ SLOW: Uses reflection every time
|
|
824
|
+
string name = state.ToString();
|
|
825
|
+
|
|
826
|
+
// ✅ FAST: Cached in array/dictionary after first call
|
|
827
|
+
string cached = state.ToCachedName();
|
|
828
|
+
// Subsequent calls are O(1) lookups with zero allocation
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
Performance: ToCachedName is ~100x faster after the first call.
|
|
832
|
+
|
|
833
|
+
### Display Names for UI
|
|
834
|
+
|
|
835
|
+
**The problem:** Enum values often need different names in UI than in code.
|
|
836
|
+
|
|
837
|
+
```csharp
|
|
838
|
+
using WallstopStudios.UnityHelpers.Core.Attribute;
|
|
839
|
+
|
|
840
|
+
public enum Difficulty
|
|
841
|
+
{
|
|
842
|
+
[EnumDisplayName("Easy Mode")]
|
|
843
|
+
Easy,
|
|
844
|
+
|
|
845
|
+
[EnumDisplayName("Normal")]
|
|
846
|
+
Medium,
|
|
847
|
+
|
|
848
|
+
[EnumDisplayName("NIGHTMARE MODE!!!")]
|
|
849
|
+
Hard
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
Difficulty current = Difficulty.Hard;
|
|
853
|
+
string displayName = current.ToDisplayName(); // "NIGHTMARE MODE!!!"
|
|
854
|
+
// Falls back to enum name if attribute not present
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Use for:
|
|
858
|
+
|
|
859
|
+
- Dropdown labels in UI
|
|
860
|
+
- Localization keys
|
|
861
|
+
- User-facing text that doesn't match code names
|
|
862
|
+
|
|
863
|
+
<a id="random-generators"></a>
|
|
864
|
+
|
|
865
|
+
## Random Generators
|
|
866
|
+
|
|
867
|
+
**Why it exists:** Unity's `Random` class is limited and not suitable for all scenarios. These extensions provide rich random generation.
|
|
868
|
+
|
|
869
|
+
### Weighted Random Selection
|
|
870
|
+
|
|
871
|
+
**The problem:** Selecting items based on probability weights (loot tables, spawn chances).
|
|
872
|
+
|
|
873
|
+
```csharp
|
|
874
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
875
|
+
|
|
876
|
+
// Items with different drop chances
|
|
877
|
+
var loot = new[]
|
|
878
|
+
{
|
|
879
|
+
(item: "Common Sword", weight: 50),
|
|
880
|
+
(item: "Rare Shield", weight: 30),
|
|
881
|
+
(item: "Epic Helmet", weight: 15),
|
|
882
|
+
(item: "Legendary Ring", weight: 5)
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
IRandom rng = PRNG.Instance;
|
|
886
|
+
string drop = rng.NextWeighted(loot); // More likely to get Common Sword
|
|
887
|
+
|
|
888
|
+
// Get index instead of value
|
|
889
|
+
int dropIndex = rng.NextWeightedIndex(loot.Select(x => x.weight));
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Vector and Quaternion Generation
|
|
893
|
+
|
|
894
|
+
**Uniform random vectors:**
|
|
895
|
+
|
|
896
|
+
```csharp
|
|
897
|
+
// Random point in rectangle
|
|
898
|
+
Vector2 point = rng.NextVector2(minX, maxX, minY, maxY);
|
|
899
|
+
|
|
900
|
+
// Random point inside circle
|
|
901
|
+
Vector2 inCircle = rng.NextVector2InRange(radius);
|
|
902
|
+
|
|
903
|
+
// Random point ON sphere surface (uniform distribution)
|
|
904
|
+
Vector3 onSphere = rng.NextVector3OnSphere(radius);
|
|
905
|
+
// Uses Marsaglia's method for true uniform distribution
|
|
906
|
+
|
|
907
|
+
// Random rotation (uniform distribution)
|
|
908
|
+
Quaternion rotation = rng.NextQuaternion();
|
|
909
|
+
// Uses Shoemake's algorithm
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
### Color Generation
|
|
913
|
+
|
|
914
|
+
```csharp
|
|
915
|
+
// Random opaque color
|
|
916
|
+
Color color = rng.NextColor();
|
|
917
|
+
|
|
918
|
+
// Random color in HSV range (for similar hues)
|
|
919
|
+
Color tint = rng.NextColorInRange(
|
|
920
|
+
baseColor: Color.red,
|
|
921
|
+
hueVariance: 0.1f,
|
|
922
|
+
saturationVariance: 0.2f,
|
|
923
|
+
valueVariance: 0.2f
|
|
924
|
+
);
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### Subset Sampling
|
|
928
|
+
|
|
929
|
+
**Reservoir sampling** — Pick k random items from a large collection without loading it all into memory:
|
|
930
|
+
|
|
931
|
+
```csharp
|
|
932
|
+
// Select 5 random enemies from potentially huge list
|
|
933
|
+
IEnumerable<Enemy> allEnemies = GetAllEnemiesInWorld();
|
|
934
|
+
List<Enemy> randomFive = rng.NextSubset(allEnemies, k: 5);
|
|
935
|
+
// O(n) time, uses reservoir sampling for uniform probability
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
### Random Utilities
|
|
939
|
+
|
|
940
|
+
```csharp
|
|
941
|
+
bool coinFlip = rng.NextBool(); // 50/50
|
|
942
|
+
bool biasedFlip = rng.NextBool(0.7f); // 70% true
|
|
943
|
+
int sign = rng.NextSign(); // Randomly -1 or +1
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
<a id="async-coroutine-interop"></a>
|
|
947
|
+
|
|
948
|
+
## Async/Coroutine Interop
|
|
949
|
+
|
|
950
|
+
**Why it exists:** Unity's `AsyncOperation` and coroutines don't natively support modern async/await patterns. This bridges the gap.
|
|
951
|
+
|
|
952
|
+
### Await AsyncOperation (Unity < 2023.1)
|
|
953
|
+
|
|
954
|
+
**The problem:** Unity's AsyncOperations (scene loading, asset loading) don't support `await`.
|
|
955
|
+
|
|
956
|
+
```csharp
|
|
957
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
958
|
+
using UnityEngine.SceneManagement;
|
|
959
|
+
|
|
960
|
+
// ✅ Now you can await scene loading
|
|
961
|
+
async Task LoadGameScene()
|
|
962
|
+
{
|
|
963
|
+
var operation = SceneManager.LoadSceneAsync("GameLevel");
|
|
964
|
+
await operation; // Just works!
|
|
965
|
+
|
|
966
|
+
Debug.Log("Scene loaded!");
|
|
967
|
+
}
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
**Note:** Unity 2023.1+ has built-in await support, but this works in older versions.
|
|
971
|
+
|
|
972
|
+
### Convert AsyncOperation to Task
|
|
973
|
+
|
|
974
|
+
```csharp
|
|
975
|
+
// As Task
|
|
976
|
+
Task task = asyncOperation.AsTask();
|
|
977
|
+
await task;
|
|
978
|
+
|
|
979
|
+
// As ValueTask (better performance for short operations)
|
|
980
|
+
ValueTask valueTask = asyncOperation.AsValueTask();
|
|
981
|
+
await valueTask;
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### Run Task as Coroutine
|
|
985
|
+
|
|
986
|
+
**The problem:** You have async/await code (from a library, or your own), but need to run it in a Unity coroutine context.
|
|
987
|
+
|
|
988
|
+
```csharp
|
|
989
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
990
|
+
|
|
991
|
+
async Task<string> DownloadDataAsync()
|
|
992
|
+
{
|
|
993
|
+
// Some async operation (HttpClient, database, etc.)
|
|
994
|
+
await Task.Delay(1000);
|
|
995
|
+
return "Downloaded data";
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// In MonoBehaviour
|
|
999
|
+
IEnumerator Start()
|
|
1000
|
+
{
|
|
1001
|
+
// ✅ Convert Task to IEnumerator
|
|
1002
|
+
return DownloadDataAsync().AsCoroutine();
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
### Chain Continuations
|
|
1007
|
+
|
|
1008
|
+
```csharp
|
|
1009
|
+
// Chain operations on ValueTask
|
|
1010
|
+
await myValueTask.WithContinuation(() => Debug.Log("Done!"));
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
**When to use:**
|
|
1014
|
+
|
|
1015
|
+
- Integrating third-party async libraries with Unity
|
|
1016
|
+
- Mixing async/await code with existing coroutine systems
|
|
1017
|
+
- Background operations that need to update Unity objects on completion
|
|
1018
|
+
- Modernizing legacy coroutine code
|
|
1019
|
+
|
|
1020
|
+
**When NOT to use:**
|
|
1021
|
+
|
|
1022
|
+
- Unity 2023.1+ (use built-in await support)
|
|
1023
|
+
- Simple fire-and-forget operations (just use coroutines)
|
|
1024
|
+
- When you have control over both ends (just use all-async or all-coroutines)
|
|
1025
|
+
|
|
1026
|
+
## Best Practices
|
|
1027
|
+
|
|
1028
|
+
- Use `PositiveMod` instead of `%` for indices and angles when negatives are possible.
|
|
1029
|
+
- Prefer `SimplifyPrecise` for offline tooling; use `Simplify` during gameplay for speed.
|
|
1030
|
+
- Choose color averaging method per goal: LAB for perceptual palette, Weighted for speed, Dominant for swatches.
|
|
1031
|
+
- Favor IReadOnlyList/HashSet specializations to minimize allocations; pooled buffers are used where applicable.
|
|
1032
|
+
- Run Unity-dependent extensions (e.g., `RectTransform`, `Camera`, `Grid`) on the main thread.
|
|
1033
|
+
|
|
1034
|
+
## Related Docs
|
|
1035
|
+
|
|
1036
|
+
- Random performance details — [Random Performance](RANDOM_PERFORMANCE.md)
|
|
1037
|
+
- Serialization formats — [Serialization Guide](SERIALIZATION.md)
|
|
1038
|
+
- Effects system — [Effects System](EFFECTS_SYSTEM.md)
|
|
1039
|
+
- Relational Components — [Relational Components](RELATIONAL_COMPONENTS.md)
|