Treasure chest of Unity developer tools. Professional inspector tooling, high-performance utilities, spatial queries, and 20+ editor tools.
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.
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.
✅ Use for:
❌ Don’t use for:
Oscillator component to any GameObjectusing 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
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
transform.localPosition in Update()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.
✅ Use for:
❌ Don’t use for:
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;
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.
When enabled:
Typical use case:
Scene 1: ChildSpawner spawns AnalyticsManager with DontDestroyOnLoad
Scene 2 loads: Same ChildSpawner detects existing AnalyticsManager, doesn't spawn duplicate
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.
✅ Use for:
❌ Don’t use for:
CollisionProxy to GameObject with Collider2Dusing 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;
}
Collision events (Collision2D parameter):
OnCollisionEnterOnCollisionStayOnCollisionExitTrigger events (Collider2D parameter):
OnTriggerEnterOnTriggerStayOnTriggerExit// 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
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.
✅ Use for:
❌ Don’t use for:
CircleLineRenderer to GameObject with CircleCollider2Dusing 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
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)
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.
✅ Use for:
❌ Don’t use for:
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
// Offset is added to target position
matcher.localOffset = new Vector3(1, 0, 0); // 1 unit to the right
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
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.
✅ Use for:
❌ Don’t use for:
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;
What to sync:
matchColor: Copy color tintmatchMaterial: Copy materialmatchSortingLayer: Copy sorting layermatchOrderInLayer: Copy order in layerDynamic source:
// Change what to match at runtime
syncer.DynamicToMatch = () => GetCurrentWeaponRenderer();
Sorting override:
// Override order in layer dynamically
syncer.DynamicSortingOrderOverride = () => characterRenderer.sortingOrder - 1; // Always behind
Syncs in LateUpdate() to ensure source renderer has updated first.
// 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
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.
✅ Use for:
❌ Don’t use for:
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
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
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.
Works identically for materials:
metadata.PushMaterial(this, glowMaterial);
// ... later
metadata.PopMaterial(this);
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;
Awake()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.
✅ Use for:
❌ Don’t use for:
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 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.
spriteUsesOffset is a boolean flag you can check in other systems:
if (center.spriteUsesOffset)
{
// Apply sprite-specific logic
}
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.
✅ Use for:
❌ Don’t use for:
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
}
Setting stateMachine.Value automatically:
falsetrueThis ensures exclusive state control (only one state active).
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
AnimatorEnumStateMachine<T> is serializable for debugging in Inspector.
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.
✅ Use for:
❌ Don’t use for:
using WallstopStudios.UnityHelpers.Utils;
// From anywhere
CoroutineHandler.Instance.StartCoroutine(MyCoroutine());
IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(1f);
Debug.Log("Done!");
}
CoroutineHandler persists across scene loads (DontDestroyOnLoad), so coroutines survive scene transitions.
Coroutine routine = CoroutineHandler.Instance.StartCoroutine(MyCoroutine());
// ... later
CoroutineHandler.Instance.StopCoroutine(routine);
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.
✅ Use for:
❌ Don’t use for:
using WallstopStudios.UnityHelpers.Utils;
// Add to GameObject
StartTracker tracker = gameObject.AddComponent<StartTracker>();
// Later, check if Start has been called
if (tracker.Started)
{
// Initialization complete
}
Automatically syncs PolygonCollider2D shape to sprite’s physics shape.
See: Editor Tools Guide - MatchColliderToSprite
Reduces PolygonCollider2D point count using Douglas-Peucker simplification.
See: Editor Tools Guide - PolygonCollider2DOptimizer