UNPKG

com.wallstop-studios.unity-helpers

Version:

Treasure chest of Unity developer tools

1,218 lines (864 loc) 59.1 kB
# 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> &nbsp;&nbsp; <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&amp;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&amp;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 &amp; 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