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 — Automatic circular/elliptical motion
- ChildSpawner — Conditional prefab instantiation
- CollisionProxy — Event-based collision detection
- CircleLineRenderer — Visual circle debugging
- MatchTransform — Follow another transform
- SpriteRendererSync — Mirror sprite renderer state
- SpriteRendererMetadata — Stacked visual modifications
- CenterPointOffset — Define logical center points
- AnimatorEnumStateMachine — Type-safe animator control
- CoroutineHandler — Singleton coroutine host
- StartTracker — Lifecycle tracking
- MatchColliderToSprite — Auto-sync colliders
- PolygonCollider2DOptimizer — Simplify collider shapes
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:
- Floating/hovering UI elements
- Pickup items that gently bob
- Decorative objects with idle motion
- Circular patrol paths
- Simple pendulum motion
❌ Don't use for:
- Complex animation sequences (use Animator)
- Physics-based motion (use Rigidbody)
- Player/enemy movement (too rigid)
How to Use¶
- Add
Oscillatorcomponent to any GameObject - Configure three parameters:
- speed: Rotation speed (radians per second)
- width: Horizontal amplitude (X-axis movement range)
- height: Vertical amplitude (Y-axis movement range)
| C# | |
|---|---|
Examples¶
Gentle hover (coin pickup):
Figure-8 motion:
Horizontal sway:
Important Notes¶
- Updates
transform.localPositionin Update() - Motion is relative to the original local position
- Starts from current time offset (unique per instance)
- Zero allocation per frame
- Works in 2D and 3D (only affects X and Y)
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:
- Debug UI overlays (FPS counters, console)
- Analytics managers (only in release builds)
- Development tools (cheat menus, level select)
- Platform-specific managers
- Scene-independent singleton spawners
❌ Don't use for:
- Regular gameplay objects (use Instantiate)
- One-time spawns (just call Instantiate)
- Objects that need complex initialization
How to Use¶
Add ChildSpawner to a GameObject (often on a scene manager or empty GameObject):
Inspector configuration:
- Prefabs: Always spawned
- Editor Only Prefabs: Only in Unity Editor
- Development Only Prefabs: Only in Development builds
- Spawn Method: When to spawn (Awake/OnEnable/Start)
- Dont Destroy On Load: Persist across scenes
Deduplication Behavior¶
ChildSpawner prevents duplicate instantiation:
Deduplication uses prefab asset path matching.
Spawn Methods¶
- Awake: Spawns before anything else (use for foundational systems)
- OnEnable: Spawns when a component is enabled (use for dynamic spawning)
- Start: Spawns after all Awake calls (use when dependencies are needed)
DontDestroyOnLoad¶
When enabled:
- Spawned objects persist across scene loads
- Deduplication works across scene transitions
- Objects aren't destroyed when loading new scenes
Typical use case:
| Text Only | |
|---|---|
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:
- Composition over inheritance designs
- Multiple systems reacting to the same collision
- Decoupling collision logic from GameObject code
- Testing collision responses
- Dynamic behavior attachment/detachment
❌ Don't use for:
- Simple single-handler cases (override is fine)
- 3D collisions (only supports 2D)
- High-frequency collisions (event overhead)
How to Use¶
- Add
CollisionProxyto GameObject with Collider2D - Subscribe to events from other scripts
Available Events¶
Collision events (Collision2D parameter):
OnCollisionEnterOnCollisionStayOnCollisionExit
Trigger events (Collider2D parameter):
OnTriggerEnterOnTriggerStayOnTriggerExit
Multiple Subscribers Example¶
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:
- Debug visualization of collision bounds
- Dynamic range indicators (attack range, detection radius)
- Area-of-effect visualization
- Circular UI elements
- Animated selection rings
❌ Don't use for:
- Production graphics (performance overhead)
- Static circles (use a sprite)
- Thousands of circles (expensive)
How to Use¶
- Add
CircleLineRendererto GameObject withCircleCollider2D - Component automatically:
- Adds LineRenderer if not present
- Syncs circle size to collider radius
- Randomizes line width for visual variety
| C# | |
|---|---|
Configuration¶
- minLineWidth / maxLineWidth: Random line thickness range
- numSegments: Circle smoothness (more segments = smoother, more expensive)
- baseSegments: Minimum segments (scaled by radius)
- updateRateSeconds: How often to randomize appearance
- color: Line color
Update Rate¶
Lower values = more frequent randomization = more visual variety but higher CPU cost
| Text Only | |
|---|---|
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:
- UI name plates following 3D objects
- Camera targets
- Object attachments (weapon to hand)
- Position constraints
- Simple parent-child alternatives
❌ Don't use for:
- Smooth following (use Vector3.Lerp in Update)
- Physics-based following (use joints/springs)
- Complex multi-axis constraints (use Unity Constraints)
How to Use¶
| C# | |
|---|---|
Update Modes¶
- Update: Standard update timing (most common)
- FixedUpdate: For physics-synced following
- LateUpdate: After all Updates (best for camera followers)
- Awake: Set once at startup, then never update
- Start: Set once after Awake, then never update
Local Offset¶
| C# | |
|---|---|
Self-Matching¶
If toMatch is the same GameObject, applies offset once then disables:
| C# | |
|---|---|
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:
- Shadow sprites (black silhouette following character)
- Duplicate renderers for effects (outlines, glows)
- Mirrored sprites (reflection effects)
- Synchronized sprite swapping
- VFX layers
❌ Don't use for:
- Single sprite rendering
- Particle effects (use ParticleSystem)
- Complex multi-layer rendering (use LayeredImage for UI)
How to Use¶
Configuration Options¶
What to sync:
matchColor: Copy color tintmatchMaterial: Copy materialmatchSortingLayer: Copy sorting layermatchOrderInLayer: Copy order in layer- Sprite, flipX, flipY are always copied
Dynamic source:
Sorting override:
| C# | |
|---|---|
Update Timing¶
Syncs in LateUpdate() to ensure source renderer has updated first.
Example: Shadow Effect¶
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:
- Damage flashes (red tint on hit)
- Status effects (poison = green, frozen = blue)
- Power-up visuals (glow effects)
- Multiple overlapping visual modifiers
- Temporary material swaps
❌ Don't use for:
- Single, exclusive color changes (just set color directly)
- Animations (use Animator)
- Permanent changes (just set the property)
How to Use¶
Stack Operations¶
Push/Pop (LIFO - Last In, First Out):
| C# | |
|---|---|
PushBack (add to bottom, lower priority):
| C# | |
|---|---|
Component Ownership¶
Each color/material is tagged with the Component that pushed it:
| C# | |
|---|---|
This prevents Component A from accidentally removing Component B's color.
Material Stacking¶
Works identically for materials:
Original State¶
| C# | |
|---|---|
Important Notes¶
- Automatically detects and stores original color/material in
Awake() - Survives enable/disable cycles
- Priority is determined by push order (last push wins)
- Cleanup happens automatically when a component is destroyed
- If a non-owner tries to pop, the operation is ignored (defensive)
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:
- Sprites with off-center pivots that need gameplay center
- Rotation pivots different from visual pivot
- Targeting reticles
- AI targeting points
- Center-of-mass definitions
❌ Don't use for:
- Centered sprites (just use transform.position)
- Complex multi-point definitions
- Physics center of mass (use Rigidbody2D.centerOfMass)
How to Use¶
Offset Scaling¶
Offset is multiplied by transform.localScale:
| Text Only | |
|---|---|
This ensures the center point scales with the object.
Sprite Flag¶
spriteUsesOffset is a boolean flag you can check in other systems:
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:
- Complex state machines (player states, enemy AI)
- Type-safe animation control
- State pattern implementations
- Refactor-friendly animation code
❌ Don't use for:
- Simple trigger-based animations (use animator.SetTrigger)
- Float/int parameters (only supports bools)
- Blend trees (use animator.SetFloat)
How to Use¶
1. Define an enum matching your Animator parameters:
| C# | |
|---|---|
2. Create the state machine:
| C# | |
|---|---|
3. Set state:
| C# | |
|---|---|
Automatic State Management¶
Setting stateMachine.Value automatically:
- Sets ALL enum-named bools to
false - 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:
| Text Only | |
|---|---|
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:
- Starting coroutines from static utility classes
- Coroutines in plain C# objects
- ScriptableObjects that need coroutines
- Global/scene-independent coroutines
❌ Don't use for:
- MonoBehaviours (just use StartCoroutine)
- Short-lived coroutines (might outlive the object)
- Frame-perfect timing (singleton has overhead)
How to Use¶
| C# | |
|---|---|
Lifetime¶
CoroutineHandler persists across scene loads (DontDestroyOnLoad), so coroutines survive scene transitions.
Stopping Coroutines¶
| C# | |
|---|---|
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:
- Initialization order checking
- Conditional setup logic
- Editor tools validating scene state
- Testing initialization
❌ Don't use for:
- Production gameplay logic (architectural smell)
- Most scenarios (rethink if you need this)
How to Use¶
| C# | |
|---|---|
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¶
- One utility per GameObject: Don't stack unrelated utilities on the same GameObject
- Configure in Awake/Start: Set properties before first Update
- Remove when done: Disable/destroy utilities that are no longer needed
- Test in builds: Some utilities behave differently in editor vs. builds (ChildSpawner)
Performance¶
- CircleLineRenderer: Use sparingly, each instance updates line vertices
- SpriteRendererSync: Updates every LateUpdate, don't use for hundreds of sprites
- MatchTransform: Choose an appropriate update mode (FixedUpdate for physics, LateUpdate for camera)
Architecture¶
- CollisionProxy: Great for composition, but don't overuse events everywhere
- SpriteRendererMetadata: Document ownership in team code (who can push/pop)
- AnimatorEnumStateMachine: Keep enum names matching Animator parameters
Related Documentation¶
- Math & Extensions - Extension methods used by utilities
- Editor Tools Guide - Editor components
- Helpers Guide - Non-component helper classes