UNPKG

com.wallstop-studios.unity-helpers

Version:

Treasure chest of Unity developer tools

1,198 lines (852 loc) โ€ข 59.7 kB
# Unity Helpers <p align="center"> <img src="docs/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> **Reduce time spent on boilerplate and focus more on features.** Unity Helpers provides production-ready utilities designed to improve development speed. Key performance highlights: 10-15x faster random generation than Unity.Random (see benchmarks), O(log n) spatial queries, and declarative inspector attributes to reduce custom editor code. --- ## ๐Ÿ“ฆ 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. --- **What makes this different:** - ๐ŸŽจ **Inspector tooling** โ€” Grouping, buttons, conditional display, toggle grids. Covers common use cases similar to Odin Inspector; for advanced scenarios, see the [feature comparison](./docs/guides/odin-migration-guide.md) - โšก **10-15x faster** random generation than Unity.Random in benchmarks (see performance docs for details) - ๐Ÿ”Œ **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** ensuring production quality --- **๐Ÿ—บ๏ธ Roadmap Snapshot** โ€” See the [Roadmap](./docs/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](./docs/overview/getting-started.md) **๐Ÿ” Looking for something specific?** Check the [Feature Index](./docs/overview/index.md) **โ“ Need a definition?** See the [Glossary](./docs/overview/glossary.md) --- ## ๐Ÿ‘‹ First Time Here? **Pick your starting point based on your biggest pain point:** | Your Problem | Your Solution | Time to Value | | ------------------------------------ | ---------------------------------------------------------------------------------------------------- | ------------- | | ๐ŸŽจ Writing custom editors | [**Inspector Tooling**](#1--professional-inspector-tooling) - Inspector tooling for common use cases | ~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, extensive API | ~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](./docs/overview/getting-started.md) walks through the top 3 features in ~5 minutes. --- ## โšก Top Time-Savers These features address common categories of repetitive work. Pick one that solves your immediate pain: ### 1. ๐ŸŽจ Professional Inspector Tooling Reduce custom PropertyDrawer and EditorGUI code with declarative attributes: ```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](./docs/features/inspector/inspector-grouping-attributes.md)** - Boxed sections with auto-inclusion, collapsible headers, and animations when enabled - **[WButton](./docs/features/inspector/inspector-button.md)** - Method buttons with history, async support, cancellation - **[WShowIf](./docs/features/inspector/inspector-conditional-display.md)** - Conditional visibility (9 comparison operators) - **[WEnumToggleButtons](./docs/features/inspector/inspector-selection-attributes.md)** - Flag enums as visual toggle grids - **[SerializableDictionary](./docs/features/serialization/serialization-types.md)**, **[SerializableSet](./docs/features/serialization/serialization-types.md)**, **[WGuid](./docs/features/serialization/serialization-types.md)**, **[SerializableType](./docs/features/serialization/serialization-types.md)** - Collections Unity can't serialize [๐Ÿ“– Complete Inspector Guide](./docs/features/inspector/inspector-overview.md) | [๐Ÿ”„ Odin Migration Guide](./docs/guides/odin-migration-guide.md) --- ### 2. ๐Ÿ”Œ Auto-Wire Components Reduce GetComponent boilerplate with declarative attributes. 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](./docs/features/relational-components/relational-components.md) | [๐ŸŽฏ DI โ€“ VContainer](./Samples~/DI%20-%20VContainer/README.md) | [๐ŸŽฏ DI โ€“ Zenject](./Samples~/DI%20-%20Zenject/README.md) | [๐ŸŽฏ DI โ€“ Reflex](./Samples~/DI%20-%20Reflex/README.md) --- ### 3. ๐ŸŽฎ Data-Driven Effects Designers create buffs/debuffs as ScriptableObjects. Minimal programmer involvement after initial setup: ```csharp // 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:** - 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](./docs/features/effects/effects-system.md) | [๐Ÿš€ 5-Minute Tutorial](./docs/features/effects/effects-system-tutorial.md) --- ### 4. ๐Ÿ’พ Unity-Aware Serialization #### Handles Unity type serialization and helps prevent 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](./docs/features/serialization/serialization.md) --- ### 5. ๐ŸŽฑ Professional Pooling Reduces GC pressure in allocation-heavy scenarios. 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 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](./docs/features/editor-tools/editor-tools-guide.md) --- ## ๐ŸŽ Batteries-Included Extensions **Common operations available as extension methods.** Unity Helpers includes 200+ extension methods that handle common repetitive patterns: ### 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(); // Perfect 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 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](./docs/features/utilities/math-and-extensions.md) | [Reflection Helpers](./docs/features/utilities/reflection-helpers.md) --- ## ๐Ÿ’Ž Hidden Gems Worth Discovering These powerful utilities solve specific problems that waste hours if you implement them yourself: | Feature | What It Does | Benefit | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------- | | **[Predictive Targeting](./docs/features/utilities/helper-utilities.md#predictive-aiming)** | Perfect ballistics for turrets/missiles in one call | Simplifies implementation | | **[Coroutine Jitter](./docs/features/utilities/math-and-extensions.md#unity-extensions)** | Prevents 100 enemies polling on same frame | Reduces frame spikes | | **[IL-Emitted Reflection](./docs/features/utilities/reflection-helpers.md)** | 10-100x faster than System.Reflection (varies by operation), IL2CPP safe | Improves serialization performance | | **[SmartDestroy()](./docs/features/utilities/helper-utilities.md#smart-destruction)** | Editor/runtime safe destruction (no scene corruption) | Works across editor/runtime | | **[Convex/Concave Hulls](./docs/features/spatial/hulls.md)** | Generate territory borders from point clouds | Avoids manual hull calculation | | **[Logging Extensions](./docs/features/logging/logging-extensions.md)** | Rich tags, thread-aware logs, per-object toggles | Improves debugging | --- ## Why Teams Choose Unity Helpers 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:** - โœ… **Production-tested** in shipped commercial games - โœ… **11,000+ automated tests** catch edge cases before you hit them - โœ… **Zero external dependencies** โ€” protobuf-net is bundled 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 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 --- ## 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 **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 ### 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](./docs/features/serialization/serialization.md#-il2cpp-and-code-stripping-warning) - [Reflection Helpers: IL2CPP Warning](./docs/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 comprehensive [Getting Started Guide](./docs/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 extensive 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](./docs/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](./docs/performance/spatial-tree-2d-performance.md) | [๐Ÿ“Š 3D Benchmarks](./docs/performance/spatial-tree-3d-performance.md) For behavior details and edge cases, see: [Spatial Tree Semantics](./docs/features/spatial/spatial-tree-semantics.md) --- ### Relational Components 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 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](./docs/features/relational-components/relational-components.md). Performance snapshots: [Relational Component Performance Benchmarks](./docs/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 robust capability systems for invulnerability, AI decision-making, permission gates, state management, and elemental interactions. See [Advanced Scenarios](./docs/features/effects/effects-system.md#advanced-scenarios-beyond-buffs-and-debuffs) for patterns. Further reading: see the full guide [Effects System](./docs/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** โ€” 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 #### 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](./docs/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](./docs/features/utilities/data-structures.md) --- ### Core Math & Extensions Numeric helpers, geometry primitives, Unity extensions, colors, collections, strings, directions. See the guide: [Core Math & Extensions](./docs/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` โ€” 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. --- <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](./docs/features/utilities/singletons.md) and the tool: [ScriptableObject Singleton Creator](./docs/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 comprehensive 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](./docs/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 ### Professional-Grade 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](./docs/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;