com.wallstop-studios.unity-helpers
Version:
Treasure chest of Unity developer tools
1,218 lines (864 loc) • 59.1 kB
Markdown
# Unity Helpers
<p align="center">
<img src="images/unity-helpers-banner.svg" alt="Unity Helpers Banner" width="800"/>
</p>
<p align="center">
<a href="https://wallstop.github.io/unity-helpers/"><img src="https://img.shields.io/badge/📖_Full_Documentation-Visit_the_Docs_Site-2ea44f?style=for-the-badge" alt="Full Documentation" /></a>
<a href="https://github.com/wallstop/unity-helpers/wiki"><img src="https://img.shields.io/badge/📚_Wiki-Community_Resources-blue?style=for-the-badge" alt="Wiki" /></a>
</p>
---
> **🤖 AI Assistance Disclosure:**
>
> Recent versions of this project have utilized AI assistance for feature development, bug detection, performance optimization, and documentation.
>
> The original codebase was developed entirely by humans over several years.
---
<p align="left">
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg" /></a><br/>
<a href="https://docs.unity3d.com/2021.3/Documentation/Manual/UnityManual.html"><img alt="Unity 2021.3+" src="https://img.shields.io/badge/Unity-2021.3%2B-000000?logo=unity&logoColor=white" /></a><br/>
<a href="https://openupm.com/packages/com.wallstop-studios.unity-helpers/"><img alt="OpenUPM" src="https://img.shields.io/npm/v/com.wallstop-studios.unity-helpers?label=openupm&registry_uri=https://package.openupm.com" /></a>
<a href="https://www.npmjs.com/package/com.wallstop-studios.unity-helpers"><img alt="npm version" src="https://img.shields.io/npm/v/com.wallstop-studios.unity-helpers" /></a>
<a href="https://github.com/wallstop/unity-helpers/releases"><img alt="GitHub release version" src="https://img.shields.io/github/v/release/wallstop/unity-helpers" /></a><br/>
<a href="https://github.com/wallstop/unity-helpers/actions/workflows/csharpier-autofix.yml"><img alt="CSharpier" src="https://github.com/wallstop/unity-helpers/actions/workflows/csharpier-autofix.yml/badge.svg" /></a>
<a href="https://github.com/wallstop/unity-helpers/actions/workflows/markdown-json.yml"><img alt="Markdown & JSON Lint/Format" src="https://github.com/wallstop/unity-helpers/actions/workflows/markdown-json.yml/badge.svg" /></a>
<a href="https://github.com/wallstop/unity-helpers/actions/workflows/lint-doc-links.yml"><img alt="Lint Docs Links" src="https://github.com/wallstop/unity-helpers/actions/workflows/lint-doc-links.yml/badge.svg" /></a><br/>
<a href="https://www.npmjs.com/package/com.wallstop-studios.unity-helpers"><img alt="npm downloads" src="https://img.shields.io/npm/dw/com.wallstop-studios.unity-helpers" /></a>
<a href="https://github.com/wallstop/unity-helpers/releases"><img alt="GitHub downloads" src="https://img.shields.io/github/downloads/wallstop/unity-helpers/total" /></a>
</p>
**Reduces boilerplate code for common Unity patterns.**
Unity Helpers reduces repetitive work with tested utilities. Benchmarks show 10-15x faster random generation than Unity.Random and significant speedups for common reflection operations (see [performance docs](./performance/random-performance.md)). From auto-wiring components to efficient spatial queries, this toolkit provides tools for Unity development.
---
## 📦 Quick Install
| Source | Install Method |
| ------------------------- | ---------------------------------------------------------------------------------------------- |
| **OpenUPM** (Recommended) | `openupm add com.wallstop-studios.unity-helpers` |
| **Git URL** | Package Manager → Add from git URL → `https://github.com/wallstop/unity-helpers.git` |
| **NPM** | Add scoped registry `https://registry.npmjs.org` → search `com.wallstop-studios.unity-helpers` |
| **Source** | Import [`.unitypackage`](https://github.com/wallstop/unity-helpers/releases) or clone repo |
👉 [Full installation instructions](#installation) with step-by-step guides for each method.
---
**Key Features:**
- 🎨 **Inspector tooling** - Grouping, buttons, conditional display, toggle grids (free and open-source) — [Migration Guide](./guides/odin-migration-guide.md)
- ⚡ **10-15x faster** random generation than Unity.Random in benchmarks
- 🔌 **Reduced boilerplate** component wiring with attributes
- 🎮 **Designer-friendly** effects system (buffs/debuffs as ScriptableObjects)
- 🌳 **O(log n)** spatial queries instead of O(n) loops
- 🛠️ **20+ editor tools** that automate sprite/animation workflows
- ✅ **11,000+ tests**
---
**🗺️ Roadmap Snapshot** — See the [Roadmap](./overview/roadmap.md) for prioritized details.
- Inspector tooling: inline nested editors, tabbed navigation, live instrumentation, disable-if/layer attributes
- Editor automation: Animation Creator and Sprite Sheet Animation Creator enhancements, timeline-ready Event Editor upgrades, and new automation dashboards
- Random/statistics: CI statistical harness, automated quality reports, scenario samplers, job-safe stream schedulers
- Spatial trees: graduate the 3D variants, add incremental updates, physics-shape parity, and streaming builders
- UI Toolkit: control pack (dockable panes, data grids), theming samples, and performance patterns
- Utilities: cross-system bridges plus new math/combinatorics and service-pattern helpers
- Performance: automated benchmarks, Burst/Jobs rewrites of hot paths, and allocation analyzers
- Attributes & tags: effect visualization tools, attribute graphs, and migration/versioning helpers
- Relational components: cached reflection, source generators, editor-time validation, and interface-based resolution
---
**📚 New to Unity Helpers?** Start here: [Getting Started Guide](./overview/getting-started.md)
**🔍 Looking for something specific?** Check the [Feature Index](./overview/index.md)
**❓ Need a definition?** See the [Glossary](./overview/glossary.md)
---
## 👋 First Time Here?
**Choose your starting point:**
| Your Problem | Your Solution | Time to Value |
| ------------------------------------ | ----------------------------------------------------------------------------------- | ------------- |
| 🎨 Writing custom editors | [**Inspector Tooling**](#1--inspector-tooling) - Odin-level features, free | ~2 minutes |
| 🐌 Writing `GetComponent` everywhere | [**Relational Components**](#2--auto-wire-components) - Auto-wire with attributes | ~2 minutes |
| 🎮 Need buffs/debuffs system | [**Effects System**](#3--data-driven-effects) - Designer-friendly ScriptableObjects | ~5 minutes |
| 🔍 Slow spatial searches | [**Spatial Trees**](#spatial-trees) - O(log n) queries | ~5 minutes |
| 🎲 Random is too slow/limited | [**PRNG.Instance**](#random-number-generators) - 10-15x faster in benchmarks | ~1 minute |
| 💾 Need save/load system | [**Serialization**](#4--unity-aware-serialization) - Unity types just work | ~10 minutes |
| 🛠️ Manual sprite workflows | [**Editor Tools**](#editor-tools) - 20+ automation tools | ~3 minutes |
**Not sure where to start?** → [Getting Started Guide](./overview/getting-started.md) walks through the top 3 features in 5 minutes.
---
## ⚡ Top Time-Savers
These features reduce entire categories of repetitive work. Pick one that solves your immediate pain:
### 1. 🎨 Inspector Tooling
⏱️ **5-10 min/script × 200 scripts = ~20 hours saved** on custom editors
Declarative inspector attributes reduce the need for custom PropertyDrawers and EditorGUI code:
```csharp
// ❌ OLD WAY: 100+ lines of custom editor code
[CustomEditor(typeof(CharacterStats))]
public class CharacterStatsEditor : Editor {
// ... SerializedProperty declarations ...
// ... OnEnable setup ...
// ... OnInspectorGUI with EditorGUI.BeginFoldoutHeaderGroup ...
// ... Custom button rendering ...
// ... Conditional field display logic ...
}
// ✅ NEW WAY: Declarative attributes, zero custom editors
public class CharacterStats : MonoBehaviour
{
[WGroup("combat", "Combat Stats", collapsible: true)]
public float maxHealth = 100f;
[WGroupEnd("combat")] // defense IS included, then group closes
public float defense = 10f;
[WGroup("abilities", "Abilities", collapsible: true, startCollapsed: true)]
[System.Flags] public enum Powers { None = 0, Fly = 1, Strength = 2, Speed = 4 }
[WEnumToggleButtons(showSelectAll: true, buttonsPerRow: 3)]
[WGroupEnd("abilities")] // currentPowers IS included, then group closes
public Powers currentPowers;
public enum WeaponType { Melee, Ranged, Magic }
public WeaponType weaponType;
[WShowIf(nameof(weaponType), WShowIfComparison.Equal, WeaponType.Ranged)]
public int ammoCapacity = 30;
[WButton("Heal to Full", groupName: "Debug")]
private void HealToFull() { maxHealth = 100f; }
}
```
**Features:**
- **[WGroup](./features/inspector/inspector-grouping-attributes.md)** - Boxed sections with auto-inclusion, collapsible headers, and animations when enabled
- **[WButton](./features/inspector/inspector-button.md)** - Method buttons with history, async support, cancellation
- **[WShowIf](./features/inspector/inspector-conditional-display.md)** - Conditional visibility (9 comparison operators)
- **[WEnumToggleButtons](./features/inspector/inspector-selection-attributes.md)** - Flag enums as visual toggle grids
- **[SerializableDictionary](./features/serialization/serialization-types.md)**, **[SerializableSet](./features/serialization/serialization-types.md)**, **[WGuid](./features/serialization/serialization-types.md)**, **[SerializableType](./features/serialization/serialization-types.md)** - Collections Unity can't serialize
[📖 Complete Inspector Guide](./features/inspector/inspector-overview.md) | [🔄 Odin Migration Guide](./guides/odin-migration-guide.md)
---
### 2. 🔌 Auto-Wire Components
⏱️ **10-20 min/script × 100 scripts = ~20 hours saved**
Reduces GetComponent boilerplate with attribute-based auto-wiring. Replace 20+ lines with 3 attributes:
```csharp
// ❌ OLD WAY: 20+ lines per script
void Awake() {
sprite = GetComponent<SpriteRenderer>();
if (sprite == null) Debug.LogError("Missing SpriteRenderer!");
rigidbody = GetComponentInParent<Rigidbody2D>();
if (rigidbody == null) Debug.LogError("Missing Rigidbody2D!");
colliders = GetComponentsInChildren<Collider2D>();
// 15 more lines...
}
// ✅ NEW WAY: 4 lines total
[SiblingComponent] private SpriteRenderer sprite;
[ParentComponent] private Rigidbody2D rigidbody;
[ChildComponent] private Collider2D[] colliders;
void Awake() => this.AssignRelationalComponents();
```
**Bonus:** Works with VContainer/Zenject/Reflex for automatic DI + relational wiring!
[📖 Learn More](./features/relational-components/relational-components.md) | See `Samples~/DI - VContainer`, `Samples~/DI - Zenject`, and `Samples~/DI - Reflex` folders in the repository for DI examples
---
### 3. 🎮 Data-Driven Effects
⏱️ **2-4 hours/effect × 50 effects = ~150 hours saved**
Designers create buffs/debuffs as ScriptableObjects. Zero programmer time after 20-minute setup:
```csharp
// Create once (ScriptableObject in editor):
// - HasteEffect: Speed × 1.5, duration 5s, tag "Haste", particle effect
// Use everywhere:
player.ApplyEffect(hasteEffect); // Apply buff
if (player.HasTag("Stunned")) return; // Query state
player.RemoveEffects(player.GetHandlesWithTag("Haste")); // Batch removal
```
**What you get:**
- Automatic stacking & duration management
- Reference-counted tags for gameplay queries
- Cosmetic VFX/SFX that spawn/despawn automatically
- Designer-friendly iteration without code changes
**Beyond buffs:** Tags become a flexible capability system for AI decisions, permission gates, state management, and complex gameplay interactions (invulnerability, stealth, elemental systems).
[📖 Full Guide](./features/effects/effects-system.md) | [🚀 5-Minute Tutorial](./features/effects/effects-system-tutorial.md)
---
### 4. 💾 Unity-Aware Serialization
⏱️ **40+ hours on initial implementation** + prevents player data loss
JSON/Protobuf that understands `Vector3`, `GameObject`, `Color` - no custom converters needed:
```csharp
// Vector3, Color, GameObject references just work:
var saveData = new SaveData {
playerPosition = new Vector3(1, 2, 3),
playerColor = Color.cyan,
inventory = new List<GameObject>()
};
// One line to save:
byte[] data = Serializer.JsonSerialize(saveData);
// Schema evolution = never break old saves:
[ProtoMember(1)] public int gold;
[ProtoMember(2)] public Vector3 position;
// Adding new field? Old saves still load!
[ProtoMember(3)] public int level; // Safe to add
```
**Real-world impact:** Ship updates without worrying about corrupting player saves.
[📖 Serialization Guide](./features/serialization/serialization.md)
---
### 5. 🎱 Object Pooling
⏱️ **Reduces GC spikes** = 5-10 FPS improvement in complex scenes
Zero-allocation queries with automatic cleanup. Thread-safe pooling in one line:
```csharp
// Get pooled buffer - automatically returned on scope exit
void ProcessEnemies(QuadTree2D<Enemy> enemyTree) {
using var lease = Buffers<Enemy>.List.Get(out List<Enemy> buffer);
// Use it for spatial query - zero allocations!
enemyTree.GetElementsInRange(playerPos, 10f, buffer);
foreach (Enemy enemy in buffer) {
enemy.TakeDamage(5f);
}
// Buffer automatically returned to pool here - no cleanup needed
}
```
**Why this matters:**
- Stable 60 FPS under load (no GC spikes)
- AI systems querying hundreds of neighbors per frame
- Particle systems with thousands of particles
- Works for List, HashSet, Stack, Queue, and Arrays
[📖 Buffering Pattern](#buffering-pattern)
---
### 6. 🛠️ Editor Tools Suite
⏱️ **1-2 hours/operation × weekly use = ~100 hours/year**
20+ tools that automate sprite cropping, animation creation, atlas generation, prefab validation:
**Common workflows:**
- **Sprite Cropper**: Add or remove transparent pixels from 500 sprites → 1 click (was: 30 minutes in Photoshop)
- **Animation Creator**: Bulk-create clips from naming patterns (`walk_0001.png`) → 1 minute (was: 20 minutes)
- **Prefab Checker**: Validate 200 prefabs for missing references → 1 click (was: manual QA)
- **Atlas Generator**: Create sprite atlases from regex/labels → automated (was: manual setup)
[📖 Editor Tools Guide](./features/editor-tools/editor-tools-guide.md)
---
## 🎁 Batteries-Included Extensions
Unity Helpers includes 200+ extension methods for common Unity operations:
### Unity Type Extensions
```csharp
// Color averaging (4 methods: LAB, HSV, Weighted, Dominant)
Color teamColor = sprite.GetAverageColor(ColorAveragingMethod.LAB); // Perceptually accurate
// Collider auto-fitting
polygonCollider.UpdateShapeToSprite(); // Instant sprite → collider sync
// Smooth direction rotation (returns rotated direction vector)
Vector2 facing = Helpers.GetAngleWithSpeed(targetDirection, currentFacing, rotationSpeed);
// Safe destruction (works in editor AND runtime)
gameObject.SmartDestroy(); // No more #if UNITY_EDITOR everywhere
// Camera world bounds
Bounds visibleArea = Camera.main.OrthographicBounds(); // For culling/spawning
// Predictive targeting (intercept moving targets)
Vector2 aimPoint = target.PredictCurrentTarget(shooter.position, projectileSpeed, predictiveFiring: true, targetVelocity);
turret.transform.up = (aimPoint - (Vector2)shooter.position).normalized;
```
### Math That Should Be Built-In
```csharp
// Positive modulo (no more negative results!)
int index = (-1).PositiveMod(array.Length); // 4, not -1
// Wrapped add for ring buffers
index = index.WrappedAdd(2, capacity); // Handles overflow correctly
// Approximate equality with tolerance
if (transform.position.x.Approximately(target.x, 0.01f)) { /* close enough */ }
// Polyline simplification (Douglas–Peucker)
List<Vector2> simplified = LineHelper.Simplify(path, epsilon: 0.5f); // Reduce pathfinding waypoints
```
### Collection Utilities
```csharp
// Infinite iterator (no extra allocation)
foreach (var item in itemList.Infinite()) { /* cycles infinitely */ }
// Aggregate bounds from multiple renderers
Bounds? combined = renderers.Select(r => r.bounds).GetBounds();
// String similarity for fuzzy search
int distance = playerName.LevenshteinDistance("jon"); // "john" = 1, close match!
// Case conversions (6 styles: Pascal, Camel, Snake, Kebab, Title, Constant)
string apiKey = "user_name".ToPascalCase(); // "UserName"
```
**Full list:** [Math & Extensions Guide](./features/utilities/math-and-extensions.md) | [Reflection Helpers](./features/utilities/reflection-helpers.md)
---
## 💎 Additional Utilities
These utilities solve specific problems that waste hours if you implement them yourself:
| Feature | What It Does | Time Saved |
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------ |
| **[Predictive Targeting](./features/utilities/helper-utilities.md#predictive-aiming)** | Accurate ballistics for turrets/missiles in one call | 2-3 hours per shooting system |
| **[Coroutine Jitter](./features/utilities/math-and-extensions.md#unity-extensions)** | Prevents 100 enemies polling on same frame | Reduces frame spikes |
| **[IL-Emitted Reflection](./features/utilities/reflection-helpers.md)** | Up to 12x faster than System.Reflection for method invocations, IL2CPP safe | Critical for serialization/modding |
| **[SmartDestroy()](./features/utilities/helper-utilities.md#smart-destruction)** | Editor/runtime safe destruction (no scene corruption) | Prevents countless debugging hours |
| **[Convex/Concave Hulls](./features/spatial/hulls.md)** | Generate territory borders from point clouds | 4-6 hours per hull algorithm |
| **[Logging Extensions](./features/logging/logging-extensions.md)** | Rich tags, thread-aware logs, per-object toggles | Keeps consoles readable + actionable |
---
## Design Philosophy
Unity Helpers reduces repetitive work by providing tested utilities for common Unity patterns, including GetComponent boilerplate, spatial query loops, and save/load systems.
**Built for Real Projects:**
- ✅ **Tested** in shipped commercial games
- ✅ **11,000+ automated tests** catch edge cases before you hit them
- ✅ **Minimal external dependencies** - depends on protobuf-net for binary serialization
- ✅ **IL2CPP/WebGL ready** with optimized SINGLE_THREADED paths
- ✅ **MIT Licensed** - use freely in commercial projects
**Who This Is For:**
- **Indie devs** who need tools without enterprise overhead
- **Teams** who value performance and want their junior devs to use tested code
- **Senior engineers** who want to avoid re-implementing the same utilities every project
---
## Installation
Unity Helpers is available from multiple sources. Choose the one that best fits your workflow:
| Source | Best For | Auto-Updates |
| ------------------------------------ | ----------------------------------- | ------------ |
| [OpenUPM](#from-openupm-recommended) | Most users, easy version management | ✅ Yes |
| [Git URL](#from-git-url) | Latest commits, CI/CD pipelines | ✅ Yes |
| [NPM Registry](#from-npm-registry) | Teams already using NPM | ✅ Yes |
| [Source](#from-source) | Offline, modifications needed | ❌ Manual |
### From OpenUPM (Recommended)
[OpenUPM](https://openupm.com/packages/com.wallstop-studios.unity-helpers/) is the recommended installation method for easy version management and updates.
#### Option A: Via Package Manager UI
1. Open **Edit → Project Settings → Package Manager**
2. Under **Scoped Registries**, click **+** to add a new registry:
- **Name**: `OpenUPM`
- **URL**: `https://package.openupm.com`
- **Scope(s)**: `com.wallstop-studios`
3. Click **Save**
4. Open **Window → Package Manager**
5. Change the dropdown to **My Registries**
6. Find and install `Unity Helpers`
#### Option B: Via OpenUPM CLI
If you have the [OpenUPM CLI](https://openupm.com/docs/) installed:
```bash
openupm add com.wallstop-studios.unity-helpers
```
#### Option C: Manual manifest.json
Add to your `Packages/manifest.json`:
```json
{
"scopedRegistries": [
{
"name": "OpenUPM",
"url": "https://package.openupm.com",
"scopes": ["com.wallstop-studios"]
}
],
"dependencies": {
"com.wallstop-studios.unity-helpers": "3.1.0"
}
}
```
### From Git URL
Install directly from GitHub for the latest version:
1. Open **Window → Package Manager**
2. Click **+** → **Add package from git URL...**
3. Enter: `https://github.com/wallstop/unity-helpers.git`
**OR** add to your `Packages/manifest.json`:
```json
{
"dependencies": {
"com.wallstop-studios.unity-helpers": "https://github.com/wallstop/unity-helpers.git"
}
}
```
> **Tip:** To lock to a specific version, append `#3.1.0` to the URL.
### From NPM Registry
1. Open **Edit → Project Settings → Package Manager**
2. Under **Scoped Registries**, click **+** to add a new registry:
- **Name**: `NPM`
- **URL**: `https://registry.npmjs.org`
- **Scope(s)**: `com.wallstop-studios`
3. Click **Save**
4. Open **Window → Package Manager**
5. Change the dropdown to **My Registries**
6. Find and install `com.wallstop-studios.unity-helpers`
### From Source
#### Option A: Import Unity Package
1. [Download the latest `.unitypackage`](https://github.com/wallstop/unity-helpers/releases) from GitHub Releases
2. In Unity, go to **Assets → Import Package → Custom Package...**
3. Select the downloaded `.unitypackage` file and import
#### Option B: Clone or Download Repository
1. Clone or [download](https://github.com/wallstop/unity-helpers/archive/refs/heads/main.zip) the repository
2. Copy the contents to your project's `Assets/` or `Packages/` folder
3. Unity will automatically import the package
---
## Compatibility
| Unity Version | Built-In | URP | HDRP |
| ------------- | -------------------- | -------------------- | -------------------- |
| 2021 | Likely, but untested | Likely, but untested | Likely, but untested |
| 2022 | ✅ Compatible | ✅ Compatible | ✅ Compatible |
| 2023 | ✅ Compatible | ✅ Compatible | ✅ Compatible |
| Unity 6 | ✅ Compatible | ✅ Compatible | ✅ Compatible |
### Platform Support
Unity Helpers is **multiplatform compatible** including:
- ✅ **WebGL** - Full support with optimized SINGLE_THREADED hot paths
- ✅ **IL2CPP** - Tested and compatible with ahead-of-time compilation
- ✅ **Mobile** (iOS, Android) - Compatible with IL2CPP
- ✅ **Desktop** (Windows, macOS, Linux) - Full threading support
- ✅ **Consoles** - IL2CPP compatible
**Requirements:**
- **.NET Standard 2.1** - Required for core library features
### WebGL and Single-Threaded Optimization
Unity Helpers includes a `SINGLE_THREADED` scripting define symbol for WebGL and other single-threaded environments. When enabled, the library automatically uses optimized code paths that eliminate threading overhead:
**Optimized systems with SINGLE_THREADED:**
- **Buffers & Pooling** - Uses `Stack<T>` and `Dictionary<T>` instead of `ConcurrentBag<T>` and `ConcurrentDictionary<T>`
- **Random Number Generation** - Static instances instead of `ThreadLocal<T>`
- **Reflection Caches** - Non-concurrent dictionaries for faster lookups
- **Thread Pools** - SingleThreadedThreadPool disabled (not needed on WebGL)
**How to enable:**
Unity automatically defines `UNITY_WEBGL` for WebGL builds. To enable SINGLE_THREADED optimization:
1. Go to **Project Settings > Player > Other Settings > Scripting Define Symbols**
2. Add `SINGLE_THREADED` for WebGL platform
3. Or use in your `csc.rsp` file: `-define:SINGLE_THREADED`
**Performance impact:** 10-20% faster hot path operations on single-threaded platforms by avoiding unnecessary synchronization overhead.
### IL2CPP and Code Stripping Considerations
⚠️ **Important for IL2CPP builds (WebGL, Mobile, Consoles):**
Some features in Unity Helpers use reflection internally (particularly **Protobuf serialization** and **ReflectionHelpers**). IL2CPP's managed code stripping may remove types/members that are only accessed via reflection, causing runtime errors.
**Symptoms of stripping issues:**
- `NullReferenceException` or `TypeLoadException` during deserialization
- Missing fields after Protobuf deserialization
- Reflection helpers failing to find types at runtime
#### Solution: Use link.xml to preserve required types
Create a `link.xml` file in your `Assets` folder to prevent stripping:
```xml
<linker>
<!-- Preserve your serialized types -->
<assembly fullname="Assembly-CSharp">
<type fullname="MyNamespace.PlayerSave" preserve="all"/>
<type fullname="MyNamespace.InventoryData" preserve="all"/>
<!-- Add all Protobuf-serialized types here -->
</assembly>
<!-- Preserve Unity Helpers if needed -->
<assembly fullname="WallstopStudios.UnityHelpers.Runtime" preserve="all"/>
</linker>
```
**Best practices:**
- ✅ **Always test IL2CPP builds** - Development builds don't use stripping, so bugs only appear in release builds
- ✅ **Test on target platform** - WebGL stripping behaves differently than iOS/Android
- ✅ **Use link.xml for all Protobuf types** - Any type with `[ProtoContract]` should be preserved
- ✅ **Verify after every schema change** - Adding new serialized types requires updating link.xml
- ✅ **Check logs for stripping warnings** - Unity logs which types are stripped during build
**When you don't need link.xml:**
- JSON serialization (uses source-generated converters, not reflection)
- Spatial trees and data structures (no reflection used)
- Most helper methods (compiled ahead-of-time)
**Related documentation:**
- [Unity Manual: Managed Code Stripping](https://docs.unity3d.com/Manual/managed-code-stripping.html)
- [protobuf-net documentation](https://protobuf-net.github.io/protobuf-net/)
- [Serialization Guide: IL2CPP Warning](./features/serialization/serialization.md#il2cpp-and-code-stripping-warning)
- [Reflection Helpers: IL2CPP Warning](./features/utilities/reflection-helpers.md#il2cpp-code-stripping-considerations)
---
## Quick Start Guide
> 💡 **First time?** Skip to section #2 ([Relational Components](#2--auto-wire-components)) - it has the biggest immediate impact.
Already read the [Top 5 Time-Savers](#-top-time-savers)? Jump directly to the [Core Features](#core-features) reference below, or check out the [Getting Started Guide](./overview/getting-started.md).
---
## Core Features
### Random Number Generators
Unity Helpers includes **15+ high-quality random number generators**, all implementing a rich `IRandom` interface:
#### Available Generators
> The tables below are auto-generated by the performance benchmark. Run
> `RandomPerformanceTests.Benchmark` in the Unity Test Runner to refresh them.
<!-- RANDOM_BENCHMARKS_START -->
_No benchmark data available yet. Run `RandomPerformanceTests.Benchmark` to populate these tables._
<!-- RANDOM_BENCHMARKS_END -->
#### Rich API
All generators implement `IRandom` with the following functionality:
```csharp
IRandom random = PRNG.Instance;
// Basic types
int i = random.Next(); // int in [0, int.MaxValue]
int range = random.Next(10, 20); // int in [10, 20)
uint ui = random.NextUint(); // uint in [0, uint.MaxValue]
float f = random.NextFloat(); // float in [0.0f, 1.0f]
double d = random.NextDouble(); // double in [0.0d, 1.0d]
bool b = random.NextBool(); // true or false
// Unity types
Vector2 v2 = random.NextVector2(); // Random 2D vector
Vector3 v3 = random.NextVector3(); // Random 3D vector
Color color = random.NextColor(); // Random color
Quaternion rot = random.NextRotation(); // Random rotation
// Distributions
float gaussian = random.NextGaussian(mean: 0f, stdDev: 1f);
// Collections
T item = random.NextOf(collection); // Random element
T[] shuffled = random.Shuffle(array); // Fisher-Yates shuffle
int weightedIndex = random.NextWeightedIndex(weights);
// Special
Guid uuid = random.NextGuid(); // UUIDv4
T enumValue = random.NextEnum<T>(); // Random enum value
float[,] noise = random.NextNoiseMap(width, height); // Perlin noise
```
#### Deterministic Gameplay
All generators are **seedable** for replay systems:
```csharp
// Create seeded generator for deterministic behavior
IRandom seededRandom = new IllusionFlow(seed: 12345);
// Same seed = same sequence
IRandom replay = new IllusionFlow(seed: 12345);
// Both will generate identical values
```
**Threading:**
- Do not share a single RNG instance across threads.
- Use `PRNG.Instance` for a thread-local default, or use each generator's `TypeName.Instance` (e.g., `IllusionFlow.Instance`, `PcgRandom.Instance`).
- Alternatively, create one separate instance per thread.
[📊 Performance Comparison](./performance/random-performance.md)
---
### Spatial Trees
Efficient spatial data structures for 2D and 3D games.
#### 2D Spatial Trees
- **QuadTree2D** - Best general-purpose choice
- **KDTree2D** - Fast nearest-neighbor queries
- **RTree2D** - Optimized for bounding boxes
```csharp
using WallstopStudios.UnityHelpers.Core.DataStructure;
// Create from collection
GameObject[] objects = FindObjectsOfType<GameObject>();
QuadTree2D<GameObject> tree = new(objects, go => go.transform.position);
// Query by radius
List<GameObject> nearby = new();
tree.GetElementsInRange(playerPos, radius: 10f, nearby);
// Query by bounds
Bounds searchArea = new(center, size);
tree.GetElementsInBounds(searchArea, nearby);
// Find nearest neighbors (approximate, but fast)
tree.GetApproximateNearestNeighbors(playerPos, count: 5, nearby);
```
#### 3D Spatial Trees
Note: KdTree3D, OctTree3D, and RTree3D are under active development. SpatialHash3D is stable and production‑ready.
- **OctTree3D** - Best general-purpose choice for 3D
- **KDTree3D** - Fast 3D nearest-neighbor queries
- **RTree3D** - Optimized for 3D bounding volumes
- **SpatialHash3D** - Efficient for uniformly distributed moving objects (stable)
```csharp
// Same API as 2D, but with Vector3
Vector3[] positions = GetAllPositions();
OctTree3D<Vector3> tree = new(positions, p => p);
List<Vector3> results = new();
tree.GetElementsInRange(center, radius: 50f, results);
```
#### When to Use Spatial Trees
✅ **Good for:**
- Many objects (100+)
- Frequent spatial queries
- Static or slowly changing data
- AI awareness systems
- Visibility culling
- Collision detection optimization
❌ **Not ideal for:**
- Few objects (<50)
- Constantly moving objects
- Single queries
- Already using Unity's physics system
[📊 2D Benchmarks](./performance/spatial-tree-2d-performance.md) | [📊 3D Benchmarks](./performance/spatial-tree-3d-performance.md)
For behavior details and edge cases, see: [Spatial Tree Semantics](./features/spatial/spatial-tree-semantics.md)
---
### Relational Components
Auto-wire components using attributes to reduce GetComponent boilerplate.
**Key attributes:**
- `[SiblingComponent]` - Find components on same GameObject
- `[ParentComponent]` - Find components in parent hierarchy
- `[ChildComponent]` - Find components in children
- `[ValidateAssignment]` - Validate at edit time, show errors in inspector
- `[WNotNull]` - Must be assigned in inspector
- `[WReadOnly]` - Read-only display in inspector
- `[WInLineEditor]` - Inline inspector editing for object references
- `[WShowIf]` - Conditional display based on field values
**Quick example:**
```csharp
using WallstopStudios.UnityHelpers.Core.Attributes;
public class Enemy : MonoBehaviour
{
// Find on same GameObject
[SiblingComponent]
private Animator animator;
// Find in parent
[ParentComponent]
private EnemySpawner spawner;
// Find in children
[ChildComponent]
private List<Weapon> weapons;
// Optional component (no error if missing)
[SiblingComponent(Optional = true)]
private AudioSource audioSource;
// Only search direct children/parents
[ParentComponent(OnlyAncestors = true)]
private Transform[] parentHierarchy;
// Include inactive components
[ChildComponent(IncludeInactive = true)]
private ParticleSystem[] effects;
private void Awake()
{
this.AssignRelationalComponents();
}
}
```
See the in-depth guide: [Relational Components](./features/relational-components/relational-components.md).
Performance snapshots: [Relational Component Performance Benchmarks](./performance/relational-components-performance.md).
---
### Effects, Attributes, and Tags
Create data-driven gameplay effects that modify stats, apply tags, and drive cosmetics.
**Key pieces:**
- `AttributeEffect` — ScriptableObject that bundles stat changes, tags, cosmetics, and duration.
- `EffectHandle` — Unique ID for one application instance; remove/refresh specific stacks.
- `AttributesComponent` — Base class for components that expose modifiable `Attribute` fields.
- `TagHandler` — Counts and queries string tags for gating gameplay (e.g., "Stunned").
- `CosmeticEffectData` — Prefab-like container of behaviors shown while an effect is active.
**Quick example:**
```csharp
using WallstopStudios.UnityHelpers.Tags;
// 1) Define stats on a component
public class CharacterStats : AttributesComponent
{
public Attribute Health = 100f;
public Attribute Speed = 5f;
}
// 2) Author an AttributeEffect (ScriptableObject) in the editor
// - modifications: [ { attribute: "Speed", action: Multiplication, value: 1.5f } ]
// - durationType: Duration, duration: 5
// - effectTags: [ "Haste" ]
// - cosmeticEffects: [ a prefab with CosmeticEffectData + Particle/Audio components ]
// 3) Apply and later remove
GameObject player = ...;
AttributeEffect haste = ...; // ScriptableObject reference
EffectHandle? handle = player.ApplyEffect(haste);
if (handle.HasValue)
{
// Remove early if needed
player.RemoveEffect(handle.Value);
}
// Query tags anywhere
if (player.HasTag("Stunned")) { /* disable input */ }
```
**Details at a glance:**
- `ModifierDurationType.Instant` — applies permanently; returns null handle.
- `ModifierDurationType.Duration` — temporary; expires automatically; reapply can reset if enabled.
- `ModifierDurationType.Infinite` — persists until `RemoveEffect(handle)` is called.
- `AttributeModification` order: Addition → Multiplication → Override.
- `CosmeticEffectData.RequiresInstancing` — instance per application or reuse shared presenters.
**Power Pattern:** Tags aren't just for buffs—use them to build capability systems for invulnerability, AI decision-making, permission gates, state management, and elemental interactions. See [Advanced Scenarios](./features/effects/effects-system.md#advanced-scenarios-beyond-buffs-and-debuffs) for patterns.
Further reading: see the full guide [Effects System](./features/effects/effects-system.md).
---
### Serialization
Fast, compact serialization for save systems, config, and networking.
This package provides three serialization technologies:
- `Json` — Uses System.Text.Json with built‑in converters for Unity types.
- `Protobuf` — Uses protobuf-net for compact, fast, schema‑evolvable binary.
- `SystemBinary` — Uses .NET BinaryFormatter for legacy/ephemeral data only.
All are exposed via `WallstopStudios.UnityHelpers.Core.Serialization.Serializer`.
#### JSON Profiles
- **Normal** — default settings (case-insensitive, includes fields, comments/trailing commas allowed)
- **Pretty** — human-friendly, indented
- **Fast** — strict, minimal with Unity converters (case-sensitive, strict numbers, no comments/trailing commas, IncludeFields=false)
- **FastPOCO** — strict, minimal, no Unity converters; best for pure POCO graphs
#### When To Use What
- Use **Json** for:
- Player or tool settings, human‑readable saves, serverless workflows.
- Interop with tooling, debugging, or versioning in Git.
- Use **Protobuf** for:
- Network payloads, large save files, bandwidth/storage‑sensitive data.
- Situations where you expect schema evolution across versions.
- Use **SystemBinary** only for:
- Transient caches in trusted environments where data and code version match.
- Never for untrusted data or long‑term persistence.
#### JSON Example
```csharp
using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Serialization;
public class SaveData
{
public Vector3 position;
public Color playerColor;
public List<GameObject> inventory;
}
var data = new SaveData
{
position = new Vector3(1, 2, 3),
playerColor = Color.cyan,
inventory = new List<GameObject>()
};
// Serialize to UTF‑8 JSON bytes (Unity types supported via built‑in converters)
byte[] jsonBytes = Serializer.JsonSerialize(data);
// Deserialize from string
string jsonText = Serializer.JsonStringify(data, pretty: true);
SaveData fromText = Serializer.JsonDeserialize<SaveData>(jsonText);
// File helpers
Serializer.WriteToJsonFile(data, path: "save.json", pretty: true);
SaveData fromFile = Serializer.ReadFromJsonFile<SaveData>("save.json");
// Generic entry points (choose format at runtime)
byte[] bytes = Serializer.Serialize(data, SerializationType.Json);
SaveData loaded = Serializer.Deserialize<SaveData>(bytes, SerializationType.Json);
```
#### Protobuf Example
```csharp
using ProtoBuf; // protobuf-net
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Serialization;
[ProtoContract]
public class NetworkMessage
{
[ProtoMember(1)] public int playerId;
[ProtoMember(2)] public Vector3 position; // Vector3 works in Protobuf via built-in surrogates
}
var message = new NetworkMessage { playerId = 7, position = new Vector3(5, 0, -2) };
// Protobuf bytes (small + fast)
byte[] bytes = Serializer.ProtoSerialize(message);
NetworkMessage decoded = Serializer.ProtoDeserialize<NetworkMessage>(bytes);
// Generic entry points
byte[] bytes2 = Serializer.Serialize(message, SerializationType.Protobuf);
NetworkMessage decoded2 = Serializer.Deserialize<NetworkMessage>(bytes2, SerializationType.Protobuf);
// Buffer reuse (reduce GC for hot paths)
byte[] buffer = null;
int len = Serializer.Serialize(message, SerializationType.Protobuf, ref buffer);
NetworkMessage again = Serializer.Deserialize<NetworkMessage>(buffer.AsSpan(0, len).ToArray(), SerializationType.Protobuf);
```
**Notes:**
- Protobuf‑net requires stable field numbers. Annotate with `[ProtoMember(n)]` and never reuse or renumber.
- Unity types supported via surrogates: Vector2/3, Vector2Int/3Int, Quaternion, Color/Color32, Rect/RectInt, Bounds/BoundsInt, Resolution.
**Features:**
- Custom converters for Unity types (Vector2/3/4, Color, GameObject, Matrix4x4, Type)
- Protobuf (protobuf‑net) support for compact binary
- LZMA compression utilities (see `Runtime/Utils/LZMA.cs`)
- Type‑safe serialization and pooled buffers/writers to reduce GC
[Full guide: Serialization](./features/serialization/serialization.md)
---
### Data Structures
Additional high-performance data structures:
| Structure | Use Case |
| -------------------- | ------------------------------ |
| **CyclicBuffer<T>** | Ring buffer, sliding windows |
| **BitSet** | Compact boolean storage |
| **ImmutableBitSet** | Read-only bit flags |
| **Heap<T>** | Priority queue operations |
| **PriorityQueue<T>** | Event scheduling |
| **Deque<T>** | Double-ended queue |
| **DisjointSet** | Union-find operations |
| **Trie** | String prefix trees |
| **SparseSet** | Fast add/remove with iteration |
| **TimedCache<T>** | Auto-expiring cache |
```csharp
// Cyclic buffer for damage history
CyclicBuffer<float> damageHistory = new(capacity: 10);
damageHistory.Add(25f);
damageHistory.Add(30f);
float avgDamage = damageHistory.Average();
// Priority queue for event scheduling (priority determined by comparer)
PriorityQueue<GameEvent> eventQueue = new(
Comparer<GameEvent>.Create((a, b) => b.Priority.CompareTo(a.Priority)) // Higher first
);
eventQueue.Enqueue(spawnEvent);
eventQueue.Enqueue(bossEvent);
if (eventQueue.TryDequeue(out GameEvent next)) { /* process event */ }
// Trie for autocomplete
Trie commandTrie = new();
commandTrie.Insert("teleport");
commandTrie.Insert("tell");
commandTrie.Insert("terrain");
List<string> matches = commandTrie.GetWordsWithPrefix("tel");
// Returns: ["teleport", "tell"]
```
[Full guide: Data Structures](./features/utilities/data-structures.md)
---
### Core Math & Extensions
Numeric helpers, geometry primitives, Unity extensions, colors, collections, strings, directions.
See the guide: [Core Math & Extensions](./features/utilities/math-and-extensions.md).
#### At a Glance
- `PositiveMod`, `WrappedAdd` — Safe cyclic arithmetic for indices/angles.
- `LineHelper.Simplify` — Reduce polyline vertices with Douglas–Peucker.
- `Line2D.Intersects` — 2D segment intersection and closest-point helpers.
- `RectTransform.GetWorldRect` — Axis-aligned world bounds for rotated UI.
- `Camera.OrthographicBounds` — Compute visible world bounds for ortho cameras.
- `Color.GetAverageColor` — LAB/HSV/Weighted/Dominant color averaging.
- `IEnumerable.Infinite` — Cycle sequences without extra allocations.
- `StringExtensions.LevenshteinDistance` — Edit distance for fuzzy matching.
---
<a id="singleton-utilities-odin-compatible"></a>
### Singleton Utilities (ODIN‑compatible)
- `RuntimeSingleton<T>` — Global component singleton with optional cross‑scene persistence.
- `ScriptableObjectSingleton<T>` — Global settings/data singleton loaded from `Resources/`, auto‑created by the editor tool.
See the guide: [Singleton Utilities](./features/utilities/singletons.md) and the tool: [ScriptableObject Singleton Creator](./features/editor-tools/editor-tools-guide.md#scriptableobject-singleton-creator).
---
### Editor Tools
Unity Helpers includes 20+ editor tools to streamline your workflow:
- **Sprite Tools**: Cropper, Atlas Generator, Animation Editor, Pivot Adjuster
- **Texture Tools**: Blur, Resize, Settings Applier, Fit Texture Size
- **Animation Tools**: Event Editor, Creator, Copier, Sheet Animation Creator
- **Validation**: Prefab Checker with validation rules
- **Automation**: ScriptableObject Singleton Creator, Attribute Cache Generator
- **Compilation**: Request a manual script compilation via `Tools > Wallstop Studios > Unity Helpers > Request Script Compilation` or use the default shortcut (**Ctrl/Cmd + Alt + R**) registered with Unity’s Shortcut Manager (listed under _Wallstop / Request Script Compilation_). The shortcut now forces an `AssetDatabase.Refresh` before requesting compilation and logs whenever Unity is already compiling, so scripts added outside the editor are imported even while Unity is unfocused.
[📖 Complete Editor Tools Documentation](./features/editor-tools/editor-tools-guide.md)
**Quick Access:**
- Menu: `Tools > Wallstop Studios > Unity Helpers`
- Create Assets: `Assets > Create > Wallstop Studios > Unity Helpers`
---
## Buffering Pattern
### Object Pooling
Zero-allocation queries with automatic cleanup and thread-safe pooling.
```csharp
using WallstopStudios.UnityHelpers.Utils;
using WallstopStudios.UnityHelpers.Core.DataStructure;
// Example: Use pooled buffer for spatial query
void FindNearbyEnemies(QuadTree2D<Enemy> tree, Vector2 position)
{
// Get pooled list - automatically returned when scope exits
using var lease = Buffers<Enemy>.List.Get(out List<Enemy> buffer);
// Use it with spatial query - combines zero-alloc query + pooled buffer!
tree.GetElementsInRange(position, 10f, buffer);
foreach (Enemy enemy in buffer)
{
enemy.TakeDamage(5f);
}
// buffer automatically returned to pool here
}
// Array pooling example
void ProcessLargeDataset(int size)
{
using var lease = WallstopArrayPool<float>.Get(size, out float[] buffer);
// Use buffer for temporary processing
for (int i = 0; i < size; i++)
{
buffer[i] = ComputeValue(i);
}
// buffer automatically returned to pool here
}
```
**Do / Don'ts:**
- Do reuse buffers per system or component.
- Do treat buffers as temporary scratch space (APIs clear them first).
- Don't keep references to pooled lists beyond their lease lifetime.
- Don't share the same buffer across overlapping async/coroutine work.
<a id="pooling-utilities"></a>
## Pooling utilities
- `Buffers<T>` — pooled collections (List/Stack/Queue/HashSet) with `PooledResource` leases.
- Lists: `using var lease = Buffers<Foo>.List.Get(out List<Foo> list);`
- Stacks: `using var lease = Buffers<Foo>.Stack.Get(out Stack<Foo> stack);`
- HashSets: `using var lease = Buffers<Foo>.HashSet.Get(out HashSet<Foo> set);`
- Pattern: acquire → use → Dispose (returns to pool, clears collection).
- `WallstopArrayPool<T>` — rent arrays by length with automatic return on dispose.
- Example: `using var lease = WallstopArrayPool<int>.Get(1024, out int[] buffer);`
- Use for temporary processing buffers, sorting, or interop with APIs that require arrays.
- `WallstopFastArrayPool<T>` — fast array pool specialized for frequent short‑lived arrays (requires `T : unmanaged`), does not clear arrays. Returned arrays may have previous content in them.
- Example: `using var lease = WallstopFastArrayPool<int>.Get(count, out int[] buffer);`
- Used throughout Helpers for high‑frequency editor/runtime operations (e.g., asset searches).
**How pooling + buffering help APIs:**
- Spatial queries: pass a reusable `List<T>` to `GetElementsInRange/GetElementsInBounds` and iterate results without allocations.
- Component queries: `GetComponents(buffer)` clears and fills your buffer instead of allocating arrays.
- Editor utilities: temporary arrays/lists from pools keep import/scan tools snappy, especially inside loops.
---
## Dependency Injection Integrations
**Auto-detected packages:**
- Zenject/Extenject: `com.extenject.zenject`, `com.modesttree.zenject`, `com.svermeulen.extenject`
- VContainer: `jp.cysharp.vcontainer`, `jp.hadashikick.vcontainer`
- Reflex: `com.gustavopsantos.reflex`
**Manual or source imports (no UPM):**
- Add scripting defines in `Project Settings > Player > Other Settings > Scripting Define Symbols`:
- `ZENJECT_PRESENT` when Zenject/Extenject is present
- `VCONTAINER_PRESENT` when VContainer is present
- `REFLEX_PRESENT` when Reflex is present
- Add the define per target platform (e.g., Standalone, Android, iOS).
**Notes:**
- When the define is present, optional assemblies under `Runtime/Integrations/*` compile automatically and expose helpers like `RelationalComponentsInstaller` (Zenject/Reflex) and `RegisterRelationalComponents()` (VContainer).
- If you use UPM, no manual defines are required — the package IDs above trigger symbols via `versionDefines` in the asmdefs.
- For test scenarios without LifetimeScope (VContainer), SceneContext (Zenject), or SceneScope (Reflex), see [DI Integrations: Testing and Edge Cases](./features/relational-components/relational-components.md#di-integrations-testing-and-edge-cases) for step‑by‑step patterns.
**Quick start:**
- **VContainer**: in your `LifetimeScope.Configure`, call `builder.RegisterRelationalComponents()`.
- **Zenject/Extenject**: add `RelationalComponentsInstaller` to your `SceneContext` (toggle scene scan if desired).
- **Reflex**: place `RelationalComponentsInstaller` on the same GameObject as your `SceneScope` to bind the assigner, run the scene scan, and (optionally) listen for additive scenes. Use `container.InjectWithRelations(...)` / `InstantiateGameObjectWithRelations(...)` for DI-friendly hydration.
```csharp
// VContainer — LifetimeScope
using VContainer;
using VContainer.Unity;
using WallstopStudios.UnityHelpers.Integrations.VContainer;
protected override void Configure(IContainerBuilder builder)
{
// Register assigner + one-time scene scan + additive listener (default)
builder.RegisterRelationalComponents(
RelationalSceneAssignmentOptions.Default,
enableAdditiveSceneListener: true
);
}
// Zenject — prefab instantiation with DI + relations
using Zenject;
using WallstopStudios.UnityHelpers.Integrations.Zenject;
var enemy = Container.InstantiateComponentWithRelations(enemyPrefab, parent);
// Reflex — prefab instantiation with DI + relations
using Reflex.Core;
using WallstopStudios.UnityHelpers.Integrations.Reflex;
var enemy = container.InstantiateComponentWithRelations(enemyPrefab, parent);
```
See the full guide with scenarios, troubleshooting, and testing patterns: [Relational Components Guide](./features/relational-components/relational-components.md)
### Additional Helpers
- VContainer:
- `resolver.InjectWithRelations(component)` — inject + assign a single instance
- `resolver.InstantiateComponentWithRelations(prefab, pare