Stop writing boilerplate. Start shipping features.
Unity Helpers eliminates entire categories of repetitive work with production-ready utilities that are 10-100x faster than writing it yourself. From auto-wiring components to blazing-fast spatial queries, this is the toolkit that pays for itself in the first hour.
What makes this different:
- ⚡ 10-15x faster random generation than Unity.Random
- 🔌 Zero 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
- ✅ 4,000+ tests ensuring production quality
📚 New to Unity Helpers? Start here: Getting Started Guide
🔍 Looking for something specific? Check the Feature Index
❓ Need a definition? See the Glossary
Pick your starting point based on your biggest pain point:
| Your Problem | Your Solution | Time to Value |
|---|---|---|
🐌 Writing GetComponent everywhere |
Relational Components - Auto-wire with attributes | 2 minutes |
| 🎮 Need buffs/debuffs system | Effects System - Designer-friendly ScriptableObjects | 5 minutes |
| 🔍 Slow spatial searches | Spatial Trees - O(log n) queries | 5 minutes |
| 🎲 Random is too slow/limited | PRNG.Instance - 10-15x faster, extensive API | 1 minute |
| 💾 Need save/load system | Serialization - Unity types just work | 10 minutes |
| 🛠️ Manual sprite workflows | Editor Tools - 20+ automation tools | 3 minutes |
Not sure where to start? → Getting Started Guide walks through the top 3 features in 5 minutes.
These features eliminate entire categories of repetitive work. Pick one that solves your immediate pain:
Stop writing GetComponent boilerplate forever. Replace 20+ lines with 3 attributes:
// ❌ 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 for automatic DI + relational wiring!
📖 Learn More | 🎯 DI – VContainer | 🎯 DI – Zenject | 🎯 DI – Reflex
Designers create buffs/debuffs as ScriptableObjects. Zero programmer time after 20-minute setup:
// Create once (ScriptableObject in editor):
// - HasteEffect: Speed × 1.5, duration 5s, tag "Haste", particle effect
// Use everywhere (zero boilerplate):
player.ApplyEffect(hasteEffect); // Apply buff
if (player.HasTag("Stunned")) return; // Query state
player.RemoveAllEffectsWithTag("Haste"); // Batch removalWhat 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 powerful capability system for AI decisions, permission gates, state management, and complex gameplay interactions (invulnerability, stealth, elemental systems).
📖 Full Guide | 🚀 5-Minute Tutorial
JSON/Protobuf that understands Vector3, GameObject, Color - no custom converters needed:
// 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 addReal-world impact: Ship updates without worrying about corrupting player saves.
Zero-allocation queries with automatic cleanup. Thread-safe pooling in one line:
// 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
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)
Stop Googling "Unity how to..." for the 100th time.
Unity Helpers includes 200+ extension methods that handle the tedious stuff you're tired of writing:
// Get all ancestors without allocating
transform.GetAncestors(buffer); // 10x faster than recursive GetComponentInParent loops
// Find specific ancestor
Canvas canvas = transform.GetAncestor<Canvas>(); // Stops at first match
// Breadth-first child search with depth control
transform.GetDescendants(buffer, maxDepth: 2); // Avoid traversing entire treeWhy this matters: The naive way allocates arrays on every call. These methods use buffering and early-exit for hot paths.
// 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 rotation in one line
transform.rotation = transform.GetAngleWithSpeed(target, rotationSpeed, Time.deltaTime);
// Safe destruction (works in editor AND runtime)
gameObject.SmartDestroy(); // No more #if UNITY_EDITOR everywhere
// Camera world bounds
Bounds visibleArea = Camera.main.OrthographicBounds(); // Perfect for culling/spawning
// Predictive targeting (intercept moving targets)
if (Ballistics.TryGetInterceptVelocity(shooter, target, projectileSpeed, out Vector3 velocity)) {
Instantiate(projectile, shooter, Quaternion.LookRotation(velocity));
}// 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// Infinite iterator (no extra allocation)
foreach (var item in itemList.Infinite()) { /* cycles forever */ }
// 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 | Reflection Helpers
💎 Hidden Gems Worth Discovering
These powerful utilities solve specific problems that waste hours if you implement them yourself:
| Feature | What It Does | Time Saved |
|---|---|---|
| Predictive Targeting | Perfect ballistics for turrets/missiles in one call | 2-3 hours per shooting system |
| Coroutine Jitter | Prevents 100 enemies polling on same frame | Eliminates frame spikes |
| IL-Emitted Reflection | 100x faster than System.Reflection, IL2CPP safe | Critical for serialization/modding |
| SmartDestroy() | Editor/runtime safe destruction (no scene corruption) | Prevents countless debugging hours |
| Convex/Concave Hulls | Generate territory borders from point clouds | 4-6 hours per hull algorithm |
The Reality: You're spending 30-40% of your time writing the same GetComponent boilerplate, spatial query loops, and save/load plumbing over and over. Unity Helpers gives you that time back.
Built for Real Projects:
- ✅ Production-tested in shipped commercial games
- ✅ 4,000+ automated tests catch edge cases before you hit them
- ✅ Zero dependencies - drop it in any project
- ✅ IL2CPP/WebGL ready with optimized SINGLE_THREADED paths
- ✅ MIT Licensed - use freely in commercial projects
Who This Is For:
- Indie devs who need professional tools without enterprise overhead
- Teams who value performance and want their junior devs to use battle-tested code
- Senior engineers who are tired of re-implementing the same utilities every project
- Open Unity Package Manager
- (Optional) Enable Pre-release packages for cutting-edge builds
- Click the + dropdown → Add package from git URL...
- Enter:
https://github.com/wallstop/unity-helpers.git
OR add to your manifest.json:
{
"dependencies": {
"com.wallstop-studios.unity-helpers": "https://github.com/wallstop/unity-helpers.git"
}
}- Open Unity Package Manager
- (Optional) Enable Pre-release packages
- Open Advanced Package Settings (gear icon)
- Add a new Scoped Registry:
- Name:
NPM - URL:
https://registry.npmjs.org - Scope(s):
com.wallstop-studios.unity-helpers
- Name:
- Search for and install
com.wallstop-studios.unity-helpers
- Download the latest release or clone this repository
- Copy the contents to your project's
Assetsfolder - Unity will automatically import the package
| 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 |
Unity Helpers is fully multiplatform compatible including:
- ✅ WebGL - Full support with optimized SINGLE_THREADED hot paths
- ✅ IL2CPP - Tested and compatible with ahead-of-time compilation
- ✅ Mobile (iOS, Android) - Production-ready with IL2CPP
- ✅ Desktop (Windows, macOS, Linux) - Full threading support
- ✅ Consoles - IL2CPP compatible
Requirements:
- .NET Standard 2.1 - Required for core library features
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>andDictionary<T>instead ofConcurrentBag<T>andConcurrentDictionary<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:
- Go to Project Settings > Player > Other Settings > Scripting Define Symbols
- Add
SINGLE_THREADEDfor WebGL platform - Or use in your
csc.rspfile:-define:SINGLE_THREADED
Performance impact: 10-20% faster hot path operations on single-threaded platforms by avoiding unnecessary synchronization overhead.
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:
NullReferenceExceptionorTypeLoadExceptionduring deserialization- Missing fields after Protobuf deserialization
- Reflection helpers failing to find types at runtime
Create a link.xml file in your Assets folder to prevent stripping:
<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
- Protobuf-net and IL2CPP
- Serialization Guide: IL2CPP Warning
- Reflection Helpers: IL2CPP Warning
💡 First time? Skip to section #1 (Relational Components) - it has the biggest immediate impact.
Already read the Top 5 Time-Savers? Jump directly to the Core Features reference below, or check out the comprehensive Getting Started Guide.
Unity Helpers includes 12 high-quality random number generators, all implementing a rich IRandom interface:
| Generator | Speed | Quality | Use Case |
|---|---|---|---|
| IllusionFlow ⭐ | Fast | Good | Default choice (via PRNG.Instance) |
| PcgRandom | Very Fast | Excellent | Deterministic gameplay; explicit seeding |
| RomuDuo | Fastest | Good | Maximum performance needed |
| LinearCongruentialGenerator | Fastest | Fair | Simple, fast generation |
| XorShiftRandom | Very Fast | Good | General purpose |
| XoroShiroRandom | Very Fast | Good | General purpose |
| SplitMix64 | Very Fast | Good | Initialization, hashing |
| SquirrelRandom | Moderate | Good | Hash-based generation |
| WyRandom | Moderate | Good | Hashing applications |
| DotNetRandom | Moderate | Good | .NET compatibility |
| SystemRandom | Slow | Good | Backward compatibility |
| UnityRandom | Very Slow | Good | Unity compatibility |
⭐ Recommended: Use PRNG.Instance (currently IllusionFlow)
All generators implement IRandom with extensive functionality:
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 noiseAll generators are seedable for replay systems:
// 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 valuesThreading:
- Do not share a single RNG instance across threads.
- Use
PRNG.Instancefor a thread-local default, or use each generator'sTypeName.Instance(e.g.,IllusionFlow.Instance,PcgRandom.Instance). - Alternatively, create one separate instance per thread.
Efficient spatial data structures for 2D and 3D games.
- QuadTree2D - Best general-purpose choice
- KDTree2D - Fast nearest-neighbor queries
- RTree2D - Optimized for bounding boxes
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);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)
// 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);✅ 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 | 📊 3D Benchmarks
For behavior details and edge cases, see: Spatial Tree Semantics
Stop writing GetComponent boilerplate. Auto-wire components using attributes.
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[NotNull]- Must be assigned in inspector[DxReadOnly]- Read-only display in inspector[WShowIf]- Conditional display based on field values
Quick example:
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.
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 modifiableAttributefields.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:
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 untilRemoveEffect(handle)is called.AttributeModificationorder: Addition → Multiplication → Override.CosmeticEffectData.RequiresInstancing— instance per application or reuse shared presenters.
Power Pattern: Tags aren't just for buffs—use them to build robust capability systems for invulnerability, AI decision-making, permission gates, state management, and elemental interactions. See Advanced Scenarios for patterns.
Further reading: see the full guide Effects System.
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.
- Normal — robust defaults (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
- 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.
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);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
Additional high-performance data structures:
| Structure | Use Case |
|---|---|
| CyclicBuffer | Ring buffer, sliding windows |
| BitSet | Compact boolean storage |
| ImmutableBitSet | Read-only bit flags |
| Heap | Priority queue operations |
| PriorityQueue | Event scheduling |
| Deque | Double-ended queue |
| DisjointSet | Union-find operations |
| Trie | String prefix trees |
| SparseSet | Fast add/remove with iteration |
| TimedCache | Auto-expiring cache |
// 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
PriorityQueue<GameEvent> eventQueue = new();
eventQueue.Enqueue(spawnEvent, priority: 1);
eventQueue.Enqueue(bossEvent, priority: 10);
GameEvent next = eventQueue.Dequeue(); // Highest priority
// Trie for autocomplete
Trie commandTrie = new();
commandTrie.Insert("teleport");
commandTrie.Insert("tell");
commandTrie.Insert("terrain");
List<string> matches = commandTrie.GetWordsWithPrefix("tel");
// Returns: ["teleport", "tell"]Numeric helpers, geometry primitives, Unity extensions, colors, collections, strings, directions.
See the guide: Core Math & Extensions.
PositiveMod,WrappedAdd— Safe cyclic arithmetic for indices/angles.LineHelper.Simplify— Reduce polyline vertices with Douglas–Peucker.Line2D.Intersects— Robust 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.
RuntimeSingleton<T>— Global component singleton with optional cross‑scene persistence.ScriptableObjectSingleton<T>— Global settings/data singleton loaded fromResources/, auto‑created by the editor tool.
See the guide: Singleton Utilities and the tool: ScriptableObject Singleton Creator.
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 comprehensive validation rules
- Automation: ScriptableObject Singleton Creator, Attribute Cache Generator
📖 Complete Editor Tools Documentation
Quick Access:
- Menu:
Tools > Wallstop Studios > Unity Helpers - Create Assets:
Assets > Create > Wallstop Studios > Unity Helpers
Zero-allocation queries with automatic cleanup and thread-safe pooling.
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.
Pooling utilities:
-
Buffers<T>— pooled collections (List/Stack/Queue/HashSet) withPooledResourceleases.- 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).
- Lists:
-
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.
- Example:
-
WallstopFastArrayPool<T>— fast array pool specialized for frequent short‑lived arrays.- Example:
using var lease = WallstopFastArrayPool<string>.Get(count, out string[] buffer); - Used throughout Helpers for high‑frequency editor/runtime operations (e.g., asset searches).
- Example:
How pooling + buffering help APIs:
- Spatial queries: pass a reusable
List<T>toGetElementsInRange/GetElementsInBoundsand 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.
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_PRESENTwhen Zenject/Extenject is presentVCONTAINER_PRESENTwhen VContainer is presentREFLEX_PRESENTwhen 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 likeRelationalComponentsInstaller(Zenject/Reflex) andRegisterRelationalComponents()(VContainer). - If you use UPM, no manual defines are required — the package IDs above trigger symbols via
versionDefinesin the asmdefs. - For test scenarios without LifetimeScope (VContainer) or SceneContext (Zenject), see DI Integrations: Testing and Edge Cases for step‑by‑step patterns.
Quick start:
- VContainer: in your
LifetimeScope.Configure, callbuilder.RegisterRelationalComponents(). - Zenject/Extenject: add
RelationalComponentsInstallerto yourSceneContext(toggle scene scan if desired). - Reflex: place
RelationalComponentsInstalleron the same GameObject as yourSceneScopeto bind the assigner, run the scene scan, and (optionally) listen for additive scenes. Usecontainer.InjectWithRelations(...)/InstantiateGameObjectWithRelations(...)for DI-friendly hydration.
// 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);See the full guide with scenarios, troubleshooting, and testing patterns: Relational Components Guide
-
VContainer:
resolver.InjectWithRelations(component)— inject + assign a single instanceresolver.InstantiateComponentWithRelations(prefab, parent)— instantiate + inject + assignresolver.InjectGameObjectWithRelations(root, includeInactiveChildren)— inject hierarchy + assignresolver.InstantiateGameObjectWithRelations(prefab, parent)— instantiate GO + inject + assign
-
Zenject:
container.InjectWithRelations(component)— inject + assign a single instancecontainer.InstantiateComponentWithRelations(prefab, parent)— instantiate + assigncontainer.InjectGameObjectWithRelations(root, includeInactiveChildren)— inject hierarchy + assigncontainer.InstantiateGameObjectWithRelations(prefab, parent)— instantiate GO + inject + assign
- VContainer:
RegisterRelationalComponents(..., enableAdditiveSceneListener: true)registers a listener that hydrates components in newly loaded scenes. - Zenject:
RelationalComponentsInstallerexposes a toggle “Listen For Additive Scenes” to register the same behavior.- Only the newly loaded scene is processed; other loaded scenes are not re‑scanned.
- One-time scene scan runs after container build; additive scenes are handled incrementally.
- Single-pass scan (default) reduces
FindObjectsOfTypecalls by scanning once and checking type ancestry.- VContainer:
new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true) - Zenject:
new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)
- VContainer:
- Per-object paths (instantiate/inject helpers, pools) avoid global scans entirely for objects created via DI.
Unity Helpers is built with performance as a top priority:
Random Number Generation:
- 10-15x faster than Unity.Random (655-885M ops/sec vs 65-85M ops/sec)
- Zero GC pressure with thread-local instances
- 📊 Full Random Performance Benchmarks
Spatial Queries:
- O(log n) tree queries vs O(n) linear search
- 100-1000x faster for large datasets
- QuadTree2D: 10,000 objects = ~13 checks vs 10,000 checks
- 📊 2D Performance Benchmarks
- 📊 3D Performance Benchmarks
Memory Management:
- Zero-allocation buffering pattern eliminates GC spikes
- Professional-grade pooling for List, HashSet, Stack, Queue, Arrays
- 5-10 FPS improvement in complex scenes from stable GC
Reflection:
- IL-emitted delegates 10-100x faster than System.Reflection
- Safe for IL2CPP and AOT platforms
- 📊 Reflection Performance
Start Here:
- 🚀 Getting Started — Getting Started Guide
- 🔍 Feature Index — Complete A-Z Index
- 📖 Glossary — Term Definitions
Core Guides:
- Serialization Guide — Serialization
- Editor Tools Guide — Editor Tools
- Math & Extensions — Core Math & Extensions
- Singletons — Singleton Utilities
- Relational Components — Relational Components
- Effects System — Effects System
- Data Structures — Data Structures
Spatial Trees:
- 2D Spatial Trees Guide — 2D Spatial Trees Guide
- 3D Spatial Trees Guide — 3D Spatial Trees Guide
- Spatial Tree Semantics — Spatial Tree Semantics
- Spatial Tree 2D Performance — Spatial Tree 2D Performance
- Spatial Tree 3D Performance — Spatial Tree 3D Performance
- Hulls (Convex vs Concave) — Hulls
Performance & Reference:
- Random Performance — Random Performance
- Reflection Helpers — Reflection Helpers
Project Info:
- Changelog — Changelog
- License — License
- Third‑Party Notices — Third‑Party Notices
- Contributing — Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Dependabot PRs: Formatting fixes (CSharpier + Prettier + markdownlint) are applied automatically by CI.
- Contributor PRs: Opt-in formatting is available.
- Comment on the PR with
/format(aliases:/autofix,/lint-fix).- If the PR branch is in this repo, the bot pushes a commit with fixes.
- If the PR is from a fork, the bot opens a formatting PR targeting the base branch.
- The commenter must be the PR author or a maintainer/collaborator.
- Or run the Actions workflow manually: Actions → "Opt-in Formatting" → Run workflow → enter the PR number.
- Comment on the PR with
- Not everything is auto-fixable: link checks and YAML linting may still require manual changes.
See more details in CONTRIBUTING.
This project is licensed under the MIT License - see the LICENSE file for details.
-
BinaryFormatter deprecated, still functional for trusted/legacy data:
SerializationType.SystemBinaryis[Obsolete]. UseSerializationType.Json(System.Text.Json + Unity converters) orSerializationType.Protobuf(protobuf-net) for new work. Keep BinaryFormatter for trusted, non‑portable data only.
-
GameObject JSON converter outputs structured JSON:
GameObjectConverternow writes a JSON object withname,type(assembly-qualified), andinstanceIdrather than a stringified placeholder.
-
Minor robustness improvements:
- Guarded stray
UnityEditorimports in runtime files to ensure clean player builds.
- Guarded stray
See Serialization guide for AOT/IL2CPP guidance and Unity JSON options, and Editor tools guide for Editor tool usage details.