正在加载完整 README。 以下为已保存的摘录。
Recent versions of this project have used AI assistance for feature development, bug detection, performance optimization, and documentation.
The original codebase was developed entirely by humans over several years.
Reduce time spent on boilerplate and focus more on features.
Unity Helpers provides tested utilities for common Unity patterns. Benchmarks show 10-15x faster random generation than Unity.Random (see performance docs), O(log n) spatial queries, and declarative inspector attributes that reduce custom editor code.查看完整 README ↗🤖 AI Assistance Disclosure:
Recent versions of this project have used AI assistance for feature development, bug detection, performance optimization, and documentation.
The original codebase was developed entirely by humans over several years.
Reduce time spent on boilerplate and focus more on features.
Unity Helpers provides tested utilities for common Unity patterns. Benchmarks show 10-15x faster random generation than Unity.Random (see performance docs), O(log n) spatial queries, and declarative inspector attributes that reduce custom editor code.
| 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 or clone repo |
👉 Full installation instructions with step-by-step guides for each method.
What makes this different:
🗺️ Roadmap Snapshot: See the Roadmap for prioritized details.
📚 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 custom editors | Inspector Tooling - Inspector tooling for common use cases | ~2 minutes |
🐌 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 in benchmarks | ~1 minute |
| 💾 Need save/load system | Serialization - Unity types just work | ~10 minutes |
| 🛠️ Manual sprite workflows | Editor Tools - 30+ automation tools | ~3 minutes |
Not sure where to start? → Getting Started Guide walks through the top 3 features in ~5 minutes.
These features address common categories of repetitive work. Pick one that solves your immediate pain:
Reduce custom PropertyDrawer and EditorGUI code with declarative attributes:
// ❌ 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:
📖 Complete Inspector Guide | 🔄 Odin Migration Guide
Reduce GetComponent boilerplate with declarative attributes. 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/Reflex for automatic DI + relational wiring!
📖 Learn More | 🎯 DI – VContainer | 🎯 DI – Zenject | 🎯 DI – Reflex
Designers create buffs/debuffs as ScriptableObjects. Minimal programmer involvement after initial 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.RemoveEffects(player.GetHandlesWithTag("Haste")); // Batch removal
What you get:
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 | 🚀 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 add
Real-world impact: Ship updates without worrying about corrupting player saves.
Reduces GC pressure in allocation-heavy scenarios. 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:
30+ tools that automate sprite cropping, animation creation, atlas generation, prefab validation:
Common workflows:
walk_0001.png) → ~1 minute (was: ~20 minutes)Unity Helpers includes 200+ extension methods for common Unity operations:
// 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;
// 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
These utilities solve specific problems that are time-consuming to implement yourself:
| Feature | What It Does | Benefit |
|---|---|---|
| Predictive Targeting | Accurate ballistics for turrets/missiles in one call | Simplifies implementation |
| Coroutine Jitter | Prevents 100 enemies polling on same frame | Reduces frame spikes |
| IL-Emitted Reflection | Faster than System.Reflection; the margin varies by operation, IL2CPP safe | Improves serialization performance |
| SmartDestroy() | Editor/runtime safe destruction (no scene corruption) | Works across editor and runtime |
| Convex/Concave Hulls | Generate territory borders from point clouds | Avoids manual hull calculation |
| Logging Extensions | Rich tags, thread-aware logs, per-object toggles | Keeps consoles readable and actionable |
Common Unity development patterns like GetComponent calls, spatial queries, and serialization often involve repetitive code. Unity Helpers provides utilities for these patterns.
Built for Real Projects:
Who This Is For:
Unity Helpers is available from multiple sources. Choose the one that best fits your workflow:
| Source | Best For | Auto-Updates |
|---|---|---|
| OpenUPM | Most users, easy version management | ✅ Yes |
| Git URL | Latest commits, CI/CD pipelines | ✅ Yes |
| NPM Registry | Teams already using NPM | ✅ Yes |
| Source | Offline, modifications needed | ❌ Manual |
OpenUPM is the recommended installation method for easy version management and updates.
OpenUPMhttps://package.openupm.comcom.wallstop-studiosUnity HelpersIf you have the OpenUPM CLI installed:
openupm add com.wallstop-studios.unity-helpers
Add to your Packages/manifest.json:
{
"scopedRegistries": [
{
"name": "OpenUPM",
"url": "https://package.openupm.com",
"scopes": ["com.wallstop-studios"]
}
],
"dependencies": {
"com.wallstop-studios.unity-helpers": "3.1.0"
}
}
Install directly from GitHub for the latest version:
https://github.com/wallstop/unity-helpers.gitOR add to your Packages/manifest.json:
{
"dependencies": {
"com.wallstop-studios.unity-helpers": "https://github.com/wallstop/unity-helpers.git"
}
}
Tip: To lock to a specific version, append
#3.1.0to the URL.
NPMhttps://registry.npmjs.orgcom.wallstop-studioscom.wallstop-studios.unity-helpers.unitypackage from GitHub Releases.unitypackage file and importAssets/ or Packages/ folder| 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 multiplatform compatible including:
Requirements:
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:
Stack<T> and Dictionary<T> instead of ConcurrentBag<T> and ConcurrentDictionary<T>ThreadLocal<T>How to enable:
Unity automatically defines UNITY_WEBGL for WebGL builds. To enable SINGLE_THREADED optimization:
SINGLE_THREADED for WebGL platformcsc.rsp file: -define:SINGLE_THREADEDPerformance impact: 10-20% faster hot path operations on single-threaded platforms by avoiding unnecessary synchronization overhead.
Unity Helpers enables WallstopProto for its runtime assembly by default in Package Manager,
.unitypackage, and source installs. Types annotated with [WProtoContract] and matching
[WProtoMember] field numbers use generated, reflection-free code; types it cannot serve fall back
to protobuf-net for compatibility.
For every consumer [ProtoContract], add [WProtoContract] beside it and a [WProtoMember(n)]
beside each [ProtoMember(n)], preserving field numbers. WPROTO030 identifies contracts that still
lack a generated formatter: [ProtoBuf.ProtoContract], a ProtoContractAttribute from a
vendored protobuf-net under a renamed namespace, and a [DataContract] whose members declare
[DataMember(Order = n)] in a project that references protobuf-net. Its message names which of the
three matched. In Unity, promote it from Info to Warning in Assets/Default.ruleset to turn the
remaining work into a Console-visible migration list. Suppress it only when the type is deliberately
served through a surrogate, root marshal, or hand-written formatter.
link.xml can preserve reflection targets, but it does not make protobuf-net's runtime model or
closed generic reflection AOT-safe. Treat every protobuf-net fallback as needing explicit IL2CPP
testing rather than assuming preservation solves it.
Best practices:
WPROTO030 during migration so new fallback types cannot arrive silentlyRelated documentation:
💡 First time? Skip to section #2 (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 Getting Started Guide.
Unity Helpers includes 20+ high-quality random number generators, all implementing a rich IRandom interface:
The tables below are auto-generated by the performance benchmark. Run
RandomPerformanceTests.Benchmarkin the Unity Test Runner to refresh them.
No benchmark data available yet. Run RandomPerformanceTests.Benchmark to populate these tables.
All generators implement IRandom with the following 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 noise
All 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 values
Threading:
PRNG.Instance for a thread-local default, or use each generator's TypeName.Instance (e.g., IllusionFlow.Instance, PcgRandom.Instance).Efficient spatial data structures for 2D and 3D games.
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.
// 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:
❌ Not ideal for:
📊 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[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 valuesQuick example:
using WallstopStudios.UnityHelpers.Core.Attributes;
public sealed class EnemySpawner : MonoBehaviour { }
public sealed class Weapon : MonoBehaviour { }
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. Performance snapshots: Relational Component Performance Benchmarks.
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:
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 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.
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:
[ProtoMember(n)] and never reuse or renumber.Features:
Runtime/Utils/LZMA.cs)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 (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"]
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: 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 from Resources/, auto‑created by the editor tool.Define WALLSTOP_UNITY_HELPERS_DISABLE_ODIN_BASES in Player Settings to use Unity bases for both singleton families and AttributeEffect.
See the guide: Singleton Utilities and the tool: ScriptableObject Singleton Creator.
Use ReferenceIdentityComparer<T>.Instance when keys must match the same managed reference, including destroyed Unity objects. UnityObjectMap<TKey, TValue> adds explicit destroyed-key pruning and optional scene-unload cleanup. See Reference Identity.
Unity Helpers includes 30+ editor tools to streamline your workflow:
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
Quick Access:
Tools > Wallstop Studios > Unity HelpersAssets > Create > Wallstop Studios > Unity HelpersZero-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:
Buffers<T>: pooled collections (List/Stack/Queue/HashSet) with PooledResource leases.
using var lease = Buffers<Foo>.List.Get(out List<Foo> list);using var lease = Buffers<Foo>.Stack.Get(out Stack<Foo> stack);using var lease = Buffers<Foo>.HashSet.Get(out HashSet<Foo> set);WallstopArrayPool<T>: rent arrays by length with automatic return on dispose.
using var lease = WallstopArrayPool<int>.Get(1024, out int[] buffer);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.
using var lease = WallstopFastArrayPool<int>.Get(count, out int[] buffer);How pooling + buffering help APIs:
List<T> to GetElementsInRange/GetElementsInBounds and iterate results without allocations.GetComponents(buffer) clears and fills your buffer instead of allocating arrays.Auto-detected packages:
com.extenject.zenject, com.modesttree.zenject, com.svermeulen.extenjectjp.cysharp.vcontainer, jp.hadashikick.vcontainercom.gustavopsantos.reflexManual or source imports (no UPM):
Project Settings > Player > Other Settings > Scripting Define Symbols:
ZENJECT_PRESENT when Zenject/Extenject is presentVCONTAINER_PRESENT when VContainer is presentREFLEX_PRESENT when Reflex is presentNotes:
Runtime/Integrations/* compile automatically and expose helpers like RelationalComponentsInstaller (Zenject/Reflex) and RegisterRelationalComponents() (VContainer).versionDefines in the asmdefs.Quick start:
LifetimeScope.Configure, call builder.RegisterRelationalComponents().RelationalComponentsInstaller to your SceneContext (toggle scene scan if desired).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.// 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
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 + assignZenject:
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 + assignReflex:
container.InjectWithRelations(component): inject + assign a single instancecontainer.InstantiateComponentWithRelations(prefab, parent): instantiate + inject + assigncontainer.InjectGameObjectWithRelations(root, includeInactiveChildren): inject hierarchy + assigncontainer.InstantiateGameObjectWithRelations(prefab, parent): instantiate GO + inject + assignRegisterRelationalComponents(..., enableAdditiveSceneListener: true) registers a listener that hydrates components in newly loaded scenes.RelationalComponentsInstaller exposes a toggle "Listen For Additive Scenes" to register the same behavior.RelationalComponentsInstaller exposes a toggle "Listen For Additive Scenes" to register the same behavior.
FindObjectsOfType calls by scanning once and checking type ancestry.
new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)Unity Helpers is built with performance as a top priority:
Random Number Generation:
Spatial Queries:
Memory Management:
Reflection:
System.Reflection: the committed Windows benchmarks measure up to 10x for boxed access and up to ~320x for typed access, with a few boxed property reads at parityReflectionHelpers.OverrideReflectionCapabilities) let tests force expression/IL fallbacksList Sorting:
Ghost, Meteor, Power, Grail, Pattern-Defeating QuickSort, Insertion, Tim, Jesse, Green, Ska, Ipn, Smooth, Block, IPS4o, Power+, Glide, Flux) tuned for IList<T>Start Here:
Core Guides:
Spatial Trees:
Performance & Reference:
Project Info:
Contributions are welcome! Please feel free to submit a Pull Request.
/format (aliases: /autofix, /lint-fix). Commands mentioned in prose do not run formatting.
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.SystemBinary is [Obsolete]. Use SerializationType.Json (System.Text.Json + Unity converters) or SerializationType.Protobuf (protobuf-net) for new work. Keep BinaryFormatter for trusted, non‑portable data only.GameObject JSON converter outputs structured JSON:
GameObjectConverter now writes a JSON object with name, type (assembly-qualified), and instanceId rather than a stringified placeholder.Build reliability improvements:
UnityEditor imports in runtime files to ensure clean player builds.See Serialization guide for AOT/IL2CPP guidance and Unity JSON options, and Editor tools guide for Editor tool usage details.