Unity Helpers

Logo

Treasure chest of Unity developer tools. Professional inspector tooling, high-performance utilities, spatial queries, and 20+ editor tools.

Utility Components Guide

TL;DR — Why Use These

Drop-in MonoBehaviour components that solve common game development problems without writing custom scripts. Add them to GameObjects for instant functionality like motion animation, collision forwarding, transform following, and visual state management.


Contents


Oscillator

What it does: Automatically moves a GameObject in a circular or elliptical pattern. Think “floating pickup” or “idle hover animation” without animators.

Problem it solves: Creating simple repetitive motion (hovering, bobbing, orbiting) usually requires animation curves or custom update loops. Oscillator handles it with three parameters.

When to Use

Use for:

Don’t use for:

How to Use

  1. Add Oscillator component to any GameObject
  2. Configure three parameters:
    • speed: Rotation speed (radians per second)
    • width: Horizontal amplitude (X-axis movement range)
    • height: Vertical amplitude (Y-axis movement range)
using WallstopStudios.UnityHelpers.Utils;

// Via code
Oscillator osc = gameObject.AddComponent<Oscillator>();
osc.speed = 2f;    // Two radians/second
osc.width = 1f;    // ±1 unit horizontally
osc.height = 0.5f; // ±0.5 units vertically

Examples

Gentle hover (coin pickup):

speed = 3
width = 0
height = 0.2

Figure-8 motion:

speed = 2
width = 1
height = 1

Horizontal sway:

speed = 1
width = 0.5
height = 0

Important Notes


ChildSpawner

What it does: Conditionally instantiates prefabs as children based on environment (editor/development/release) with automatic duplicate prevention.

Problem it solves: Managing debug overlays, analytics, or development tools that should only exist in certain builds. Handles deduplication across scene loads and DontDestroyOnLoad scenarios.

When to Use

Use for:

Don’t use for:

How to Use

Add ChildSpawner to a GameObject (often on a scene manager or empty GameObject):

Inspector configuration:

using WallstopStudios.UnityHelpers.Utils;

// Via code
ChildSpawner spawner = gameObject.AddComponent<ChildSpawner>();
spawner._prefabs = new[] { analyticsPrefab };
spawner._developmentOnlyPrefabs = new[] { debugMenuPrefab };
spawner._spawnMethod = ChildSpawnMethod.Awake;
spawner._dontDestroyOnLoad = true;

Deduplication Behavior

ChildSpawner prevents duplicate instantiation:

// Spawns DebugCanvas once
ChildSpawner spawner1 = obj1.AddComponent<ChildSpawner>();
spawner1._prefabs = new[] { debugCanvasPrefab };

// This will NOT spawn a second DebugCanvas (detects existing instance)
ChildSpawner spawner2 = obj2.AddComponent<ChildSpawner>();
spawner2._prefabs = new[] { debugCanvasPrefab };

Deduplication uses prefab asset path matching.

Spawn Methods

DontDestroyOnLoad

When enabled:

Typical use case:

Scene 1: ChildSpawner spawns AnalyticsManager with DontDestroyOnLoad
Scene 2 loads: Same ChildSpawner detects existing AnalyticsManager, doesn't spawn duplicate

CollisionProxy

What it does: Exposes Unity’s 2D collision callbacks as C# events, enabling composition-based collision handling without inheriting from MonoBehaviour.

Problem it solves: To receive collision events in Unity, you traditionally override OnCollisionEnter2D etc. in a MonoBehaviour subclass. CollisionProxy lets you subscribe to events instead, supporting multiple listeners and decoupled architectures.

When to Use

Use for:

Don’t use for:

How to Use

  1. Add CollisionProxy to GameObject with Collider2D
  2. Subscribe to events from other scripts
using WallstopStudios.UnityHelpers.Utils;

CollisionProxy proxy = gameObject.AddComponent<CollisionProxy>();

// Subscribe to enter event
proxy.OnCollisionEnter += HandleCollision;
proxy.OnTriggerEnter += HandleTrigger;

void HandleCollision(Collision2D collision)
{
    Debug.Log($"Hit {collision.gameObject.name}");
}

void HandleTrigger(Collider2D other)
{
    Debug.Log($"Triggered by {other.gameObject.name}");
}

// Cleanup
void OnDestroy()
{
    proxy.OnCollisionEnter -= HandleCollision;
    proxy.OnTriggerEnter -= HandleTrigger;
}

Available Events

Collision events (Collision2D parameter):

Trigger events (Collider2D parameter):

Multiple Subscribers Example

// Health system subscribes
healthSystem.OnDamageTaken += proxy.OnCollisionEnter;

// Sound system subscribes to same event
soundSystem.PlayImpactSound += proxy.OnCollisionEnter;

// Analytics subscribes
analytics.TrackCollision += proxy.OnCollisionEnter;

// All three systems react to the same collision independently

CircleLineRenderer

What it does: Visualizes CircleCollider2D with a dynamically drawn circle using LineRenderer, with randomized appearance for visual variety.

Problem it solves: Seeing collision bounds at runtime for debugging, or creating dynamic range indicators (ability ranges, explosion radii) without pre-made sprites.

When to Use

Use for:

Don’t use for:

How to Use

  1. Add CircleLineRenderer to GameObject with CircleCollider2D
  2. Component automatically:
    • Adds LineRenderer if not present
    • Syncs circle size to collider radius
    • Randomizes line width for visual variety
using WallstopStudios.UnityHelpers.Utils;

CircleLineRenderer circleVis = gameObject.AddComponent<CircleLineRenderer>();
circleVis.color = Color.red;
circleVis.minLineWidth = 0.05f;
circleVis.maxLineWidth = 0.15f;
circleVis.updateRateSeconds = 0.5f; // Refresh twice per second

Configuration

Update Rate

Lower values = more frequent randomization = more visual variety but higher CPU cost

0.1f = Very active (10 updates/sec)
0.5f = Moderate (2 updates/sec)
2.0f = Subtle (0.5 updates/sec)

MatchTransform

What it does: Makes one transform follow another with configurable update timing and offset.

Problem it solves: Following transforms (UI name plates, camera targets, position constraints) usually require custom scripts. MatchTransform handles it declaratively.

When to Use

Use for:

Don’t use for:

How to Use

using WallstopStudios.UnityHelpers.Utils;

MatchTransform matcher = uiPlate.AddComponent<MatchTransform>();
matcher.toMatch = enemyTransform;
matcher.localOffset = new Vector3(0, 2, 0); // 2 units above target
matcher.mode = MatchTransform.Mode.LateUpdate; // Update after camera

Update Modes

Local Offset

// Offset is added to target position
matcher.localOffset = new Vector3(1, 0, 0); // 1 unit to the right

Self-Matching

If toMatch is the same GameObject, applies offset once then disables:

matcher.toMatch = transform; // Self-reference
matcher.localOffset = new Vector3(5, 0, 0);
// GameObject moves 5 units right once, then MatchTransform disables itself

SpriteRendererSync

What it does: Mirrors one SpriteRenderer’s properties (sprite, color, material, sorting) to another, with selective property matching.

Problem it solves: Creating shadow sprites, duplicate renderers for effects, or layered rendering often requires manually keeping multiple SpriteRenderers in sync.

When to Use

Use for:

Don’t use for:

How to Use

using WallstopStudios.UnityHelpers.Utils;

// On the "follower" sprite renderer
SpriteRendererSync syncer = shadowRenderer.AddComponent<SpriteRendererSync>();
syncer.toMatch = characterRenderer;
syncer.matchColor = false; // Don't copy color (shadow should be black)
syncer.matchMaterial = true;
syncer.matchSortingLayer = true;
syncer.matchOrderInLayer = true;

Configuration Options

What to sync:

Dynamic source:

// Change what to match at runtime
syncer.DynamicToMatch = () => GetCurrentWeaponRenderer();

Sorting override:

// Override order in layer dynamically
syncer.DynamicSortingOrderOverride = () => characterRenderer.sortingOrder - 1; // Always behind

Update Timing

Syncs in LateUpdate() to ensure source renderer has updated first.

Example: Shadow Effect

// Create shadow GameObject
GameObject shadow = new GameObject("Shadow");
shadow.transform.parent = character.transform;
shadow.transform.localPosition = new Vector3(0.2f, -0.2f, 0); // Offset

SpriteRenderer shadowRenderer = shadow.AddComponent<SpriteRenderer>();
SpriteRendererSync syncer = shadow.AddComponent<SpriteRendererSync>();

syncer.toMatch = character.GetComponent<SpriteRenderer>();
syncer.matchColor = false;
shadowRenderer.color = new Color(0, 0, 0, 0.5f); // Semi-transparent black

SpriteRendererMetadata

What it does: Stack-based color and material management for SpriteRenderers, allowing multiple systems to modify visuals with automatic priority handling and restoration.

Problem it solves: When multiple systems want to modify a sprite’s color (damage flash, power-up glow, status effect) simultaneously, manually coordinating who “owns” the color is error-prone. This provides push/pop semantics with component-based ownership.

When to Use

Use for:

Don’t use for:

How to Use

using WallstopStudios.UnityHelpers.Utils;

SpriteRenderer renderer = GetComponent<SpriteRenderer>();
SpriteRendererMetadata metadata = renderer.GetComponent<SpriteRendererMetadata>();
if (metadata == null)
    metadata = renderer.gameObject.AddComponent<SpriteRendererMetadata>();

// Component A pushes red color
metadata.PushColor(this, Color.red);

// Component B pushes blue color (takes precedence)
metadata.PushColor(otherComponent, Color.blue);
// Renderer is now blue

// Component B pops its color
metadata.PopColor(otherComponent);
// Renderer reverts to red (Component A's color)

// Component A pops its color
metadata.PopColor(this);
// Renderer reverts to original color

Stack Operations

Push/Pop (LIFO - Last In, First Out):

metadata.PushColor(owner, Color.red);    // Add to top of stack
metadata.PopColor(owner);                 // Remove from top (must match owner)

PushBack (add to bottom, lower priority):

metadata.PushBackColor(owner, Color.yellow);  // Added to bottom, doesn't change current color unless stack is empty

Component Ownership

Each color/material is tagged with the Component that pushed it:

public class DamageFlash : MonoBehaviour
{
    void OnDamage()
    {
        metadata.PushColor(this, Color.red);
        Invoke(nameof(RemoveFlash), 0.1f);
    }

    void RemoveFlash()
    {
        metadata.PopColor(this); // Only removes if this component owns top of stack
    }
}

This prevents Component A from accidentally removing Component B’s color.

Material Stacking

Works identically for materials:

metadata.PushMaterial(this, glowMaterial);
// ... later
metadata.PopMaterial(this);

Original State

Color original = metadata.OriginalColor;     // Color before any modifications
Color current = metadata.CurrentColor;       // Current top-of-stack color

Material originalMat = metadata.OriginalMaterial;
Material currentMat = metadata.CurrentMaterial;

Important Notes


CenterPointOffset

What it does: Defines a logical center point for a GameObject that’s separate from the transform pivot, scaled by the object’s local scale.

Problem it solves: Sprites with off-center pivots (for animation reasons) need a separate “logical center” for gameplay (rotation point, targeting reticle, etc.). This provides that without changing the transform pivot.

When to Use

Use for:

Don’t use for:

How to Use

using WallstopStudios.UnityHelpers.Utils;

CenterPointOffset centerDef = gameObject.AddComponent<CenterPointOffset>();
centerDef.offset = new Vector2(0, 0.5f); // Center is 0.5 units above transform
centerDef.spriteUsesOffset = true; // Flag for sprite-specific logic

// Get world-space center point
Vector2 centerInWorld = centerDef.CenterPoint;

// Use for targeting
targetingSystem.AimAt(centerDef.CenterPoint);

Offset Scaling

Offset is multiplied by transform.localScale:

transform.position = (0, 0)
offset = (1, 0)
transform.localScale = (2, 2, 2)

CenterPoint = (0, 0) + (1, 0) * (2, 2) = (2, 0)

This ensures the center point scales with the object.

Sprite Flag

spriteUsesOffset is a boolean flag you can check in other systems:

if (center.spriteUsesOffset)
{
    // Apply sprite-specific logic
}

AnimatorEnumStateMachine

What it does: Type-safe, enum-based Animator state control. Maps enum values to Animator boolean parameters for exclusive state control.

Problem it solves: Setting Animator bools with magic strings (animator.SetBool("IsJumping", true)) is error-prone and hard to refactor. This provides compile-time safety and automatic cleanup of previous states.

When to Use

Use for:

Don’t use for:

How to Use

1. Define an enum matching your Animator parameters:

public enum PlayerState
{
    Idle,    // Maps to Animator bool "Idle"
    Running, // Maps to Animator bool "Running"
    Jumping, // Maps to Animator bool "Jumping"
    Falling  // Maps to Animator bool "Falling"
}

2. Create the state machine:

using WallstopStudios.UnityHelpers.Utils;

Animator animator = GetComponent<Animator>();
AnimatorEnumStateMachine<PlayerState> stateMachine;

void Awake()
{
    stateMachine = new AnimatorEnumStateMachine<PlayerState>(animator, PlayerState.Idle);
}

3. Set state:

void Jump()
{
    stateMachine.Value = PlayerState.Jumping;
    // Automatically sets Animator bools:
    //   Idle = false
    //   Running = false
    //   Jumping = true
    //   Falling = false
}

Automatic State Management

Setting stateMachine.Value automatically:

  1. Sets ALL enum-named bools to false
  2. Sets ONLY the matching bool to true

This ensures exclusive state control (only one state active).

Animator Setup

Your Animator needs bool parameters matching enum names:

Animator parameters:
- Idle (bool)
- Running (bool)
- Jumping (bool)
- Falling (bool)

Transitions:
- Any State → Idle: Idle == true
- Any State → Running: Running == true
- Any State → Jumping: Jumping == true
- Any State → Falling: Falling == true

Serialization

AnimatorEnumStateMachine<T> is serializable for debugging in Inspector.


CoroutineHandler

What it does: Singleton MonoBehaviour that provides a global coroutine host for non-MonoBehaviour classes.

Problem it solves: Coroutines require a MonoBehaviour to start. Static classes, plain C# objects, and ScriptableObjects can’t start coroutines directly.

When to Use

Use for:

Don’t use for:

How to Use

using WallstopStudios.UnityHelpers.Utils;

// From anywhere
CoroutineHandler.Instance.StartCoroutine(MyCoroutine());

IEnumerator MyCoroutine()
{
    yield return new WaitForSeconds(1f);
    Debug.Log("Done!");
}

Lifetime

CoroutineHandler persists across scene loads (DontDestroyOnLoad), so coroutines survive scene transitions.

Stopping Coroutines

Coroutine routine = CoroutineHandler.Instance.StartCoroutine(MyCoroutine());
// ... later
CoroutineHandler.Instance.StopCoroutine(routine);

StartTracker

What it does: Simple component that tracks whether MonoBehaviour.Start() has been called.

Problem it solves: Sometimes you need to know if initialization (Start) has completed, especially in the editor or during complex initialization orders.

When to Use

Use for:

Don’t use for:

How to Use

using WallstopStudios.UnityHelpers.Utils;

// Add to GameObject
StartTracker tracker = gameObject.AddComponent<StartTracker>();

// Later, check if Start has been called
if (tracker.Started)
{
    // Initialization complete
}

MatchColliderToSprite

Automatically syncs PolygonCollider2D shape to sprite’s physics shape.

See: Editor Tools Guide - MatchColliderToSprite


PolygonCollider2DOptimizer

Reduces PolygonCollider2D point count using Douglas-Peucker simplification.

See: Editor Tools Guide - PolygonCollider2DOptimizer


Best Practices

General

Performance

Architecture