Skip to content

About

Custom particle engine for Unity uGUI

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

UI Particle Engine

A custom particle engine for Unity uGUI, written from scratch. Particles are simulated on the CPU and pushed straight to the CanvasRenderer โ€” no Shuriken, no Canvas rebuilds, zero GC allocations per frame.

Unity uGUI Tests License

๐ŸŽฎ Demo

Everything lives in a single scene: Assets/Scenes/SampleScene.unity โ€” a portrait mobile "reward screen" driving every engine feature at once.

Coin reward (attractor) Confetti (flipbook) Fireworks (trails)
Sample Shows off
UIParticleCoinReward Emit() burst + UIParticleAttractor + arrival callback driving a TMP counter
UIParticleConfettiDemo Texture sheet animation (3ร—3 debris flipbook, random start frame)
UIParticleFireworkShow World-space simulation, per-shell color tinting, tapered ribbon trails
UIParticleClickBurst Emitter teleporting to the pointer (heart bursts), world-space particles staying put
Falling stars (scene only) Rect shape emission, prewarm, rotation over lifetime

Note โ€” the demo art ships with the repository: a handful of particle sprites from Cartoon FX Remaster FREE by Jean Moreno (in Samples/Textures) plus two simple UI sprites. The engine itself has no dependencies on any of it.

๐Ÿ“ Description

Rendering particles inside a uGUI canvas is awkward: ParticleSystem lives in world space and fights the canvas (sorting, masking, scaling), while naive Graphic-based solutions rebuild the canvas mesh every frame through SetVerticesDirty, allocating and re-batching constantly.

This engine takes a third path: a UIParticleEmitter is a regular MaskableGraphic, but it bypasses the uGUI rebuild pipeline entirely. Geometry is written into a persistent NativeArray with a compact 24-byte vertex layout and handed to the renderer via CanvasRenderer.SetMesh on double-buffered meshes โ€” once per frame, only when something actually changed.

  • โœ… Renders inside the canvas: sorts, masks and scales like any other UI element
  • โœ… Familiar API: modules and playback controls mirror Unity's Shuriken ParticleSystem
  • โœ… Zero per-frame GC allocations, verified by tests and profiling

๐Ÿ“Œ Key Features

  • Direct mesh push โ€” CanvasRenderer.SetMesh with double-buffered meshes; no SetVerticesDirty, no canvas rebuild, no per-frame allocation
  • Shuriken-style modules โ€” main, emission (rate over time / distance + bursts), shape (cone / circle / rect / point), velocity & limit velocity, color / size / rotation over lifetime, texture sheet animation (flipbook), trails
  • Trails โ€” per-particle ribbon trails with width & color curves, fixed zero-alloc ring buffers
  • UI Particle Attractor โ€” pulls particles into a UI target (the classic coin-to-wallet effect) with a per-particle arrival callback
  • World / Local simulation space โ€” world-space particles stay put while the emitter moves, and survive screen / Game-view resizes
  • Flipbook animation โ€” texture sheet playback with cycles, random start frame and a frame-over-time curve
  • Edit-mode preview โ€” play particles in the Scene view without entering Play mode
  • Deterministic seeding โ€” replayable effects via explicit random seeds
  • Curve/gradient LUT baking โ€” AnimationCurve/Gradient lookups are baked to small LUTs per mesh build, replacing hundreds of thousands of managedโ†’native Evaluate calls
  • Shuriken-style playback API โ€” Play / Pause / Stop(StopBehavior) / Clear / Emit(count) / Simulate / IsAlive
  • Custom inspector โ€” boxed module editor styled after the built-in ParticleSystem inspector
  • Shuriken migrator โ€” Tools โ–ธ UI Particle Engine โ–ธ Particle Migrator converts existing ParticleSystem prefabs into UIParticleEmitter prefabs: modules mapped one-to-one, world units scaled to canvas pixels, material textures re-imported as sprites, and anything unsupported listed in a report

โš™ Installation

Via Package Manager (UPM)

Window โ–ธ Package Manager โ–ธ + โ–ธ Add package from git URL...

https://github.com/berkencami/ui-particle-engine.git?path=Assets/UIParticleEngine

Or add it to Packages/manifest.json directly:

{
  "dependencies": {
    "com.berkencami.ui-particle-engine": "https://github.com/berkencami/ui-particle-engine.git?path=Assets/UIParticleEngine"
  }
}

Manual

The engine is a self-contained folder with its own asmdefs โ€” copy Assets/UIParticleEngine/Runtime (and optionally Editor) into your project. There are no package dependencies; Samples additionally uses TextMeshPro.

Or clone the whole repository and open it as a Unity 6000.3+ project to explore the demo scene and tests.

๐Ÿš€ Usage

Add a UI Particle Emitter (UI/Effects/UI Particle Emitter) to any RectTransform under a canvas, assign a sprite, press Play โ€” or drive it from code:

using UIParticleEngine;

var emitter = gameObject.AddComponent<UIParticleEmitter>();
emitter.Sprite = mySprite;

// Configure modules like Shuriken:
emitter.Main.StartLifetime = new RangeF(0.8f, 1.4f);
emitter.Main.StartSpeed    = new RangeF(200f, 400f);
emitter.Main.GravityModifier = 0.3f;
emitter.Main.SimulationSpace = UIParticleEmitter.SimulationSpace.World;

emitter.Shape.ShapeType = UIParticleEmitter.ShapeType.Cone;
emitter.Shape.Angle = 25f;

emitter.ColorOverLifetime.Enabled = true;
emitter.Trails.Enabled = true;

// Playback:
emitter.Play();
emitter.Emit(30);                                            // manual burst
emitter.Stop(UIParticleEmitter.StopBehavior.StopEmitting);   // let alive particles finish

Attractor

var attractor = walletIcon.AddComponent<UIParticleAttractor>();
attractor.Emitter = coinEmitter;
attractor.OnAttracted.AddListener(() => coins++);  // fires once per consumed particle

Playback API

Member Behavior
Play() / Pause() Start emission / freeze simulation (resumes from pause)
Stop(StopBehavior) Stop emitting, optionally clearing alive particles
Clear() Remove all particles immediately
Emit(int count) Spawn particles now, ignoring emission rate
Simulate(float dt) Step the simulation manually (incremental โ€” documented deviation from Shuriken)
IsAlive() Any particles still alive or emission pending
ParticleCount, IsPlaying, IsPaused State queries

๐Ÿ” Migrating from Shuriken

Already have ParticleSystem effects? Tools โ–ธ UI Particle Engine โ–ธ Particle Migrator converts a prefab (or scene object) into a ready-to-use UIParticleEmitter hierarchy:

  • Every supported module is mapped one-to-one โ€” main, emission + bursts, shape, velocity, drag, color/size/rotation over lifetime, texture sheet, trails
  • World units are scaled to canvas pixels (Pixels Per Unit), radians to degrees, MinMaxGradient start colors to a two-color blend
  • The renderer's main texture is re-imported as a Sprite and assigned to the emitter
  • Anything Shuriken-only (noise, collision, sub-emitters, curves collapsed to ranges...) is listed in a migration report instead of failing silently

๐Ÿ“Š Performance

Measured with the built-in stress probe (simulation step + full mesh build per frame, averaged over 120 frames; Unity 6000.3 editor, Windows desktop):

Particles Trails Simulate / frame Mesh build / frame GC / frame
500 โ€“ 0.013 ms 0.19 ms 0 B
2,000 โ€“ 0.050 ms 0.71 ms 0 B
8,000 โ€“ 0.198 ms 2.73 ms 0 B
500 โœ” (12 pts) 0.025 ms 1.98 ms 0 B
1,000 โœ” (12 pts) 0.050 ms 3.97 ms 0 B

Compared to the initial SetVerticesDirty-based implementation, the SetMesh path made frame cost ~2โ€“2.5ร— cheaper across the board.

On device

Measured on a mid-range phone (Snapdragon 732G, Adreno 618, 1080ร—2400@60Hz), IL2CPP ARM64 development build, results printed by the on-device benchmark in the sample scene:

Particles Trails Simulate / frame Mesh build / frame GC / frame
500 โ€“ 0.002 ms 0.08 ms 0 B
2,000 โ€“ 0.011 ms 0.23 ms 0 B
8,000 โ€“ 0.041 ms 0.99 ms 0 B
500 โœ” (12 pts) 0.009 ms 0.87 ms 0 B
1,000 โœ” (12 pts) 0.019 ms 1.86 ms 0 B

IL2CPP comes out faster than the Mono editor numbers across the board, and a live sample with 8,000 on-screen particles renders at the panel's 60 Hz ceiling (avg 16.9 ms/frame, 59.3 fps) โ€” the practical mobile limit is GPU overdraw, not the simulation.

๐Ÿ›  How It Works

  • Bypassing the rebuild pipeline โ€” UIParticleEmitter never dirties the canvas for geometry. A hidden updater ([DefaultExecutionOrder(32000)]) advances all registered emitters in Update and flushes dirty meshes once in LateUpdate via CanvasRenderer.SetMesh.
  • Compact vertices โ€” a 24-byte vertex (Vector3 position, Color32, Vector2 uv) in a persistent NativeArray, with prebuilt index buffers that auto-switch from 16-bit to 32-bit past 65k vertices.
  • Double buffering โ€” two meshes flip at most once per frame so the renderer never reads a mesh mid-write.
  • Zero-GC simulation โ€” particles live in a plain struct array with swap-remove; trails use slot-indexed ring buffers; scratch buffers are grow-only statics.
  • LUT baking โ€” every mesh build bakes active curves/gradients into 64-entry LUTs, turning per-particle Evaluate calls into array reads.
  • Culling-aware โ€” mesh flushes are skipped while the CanvasRenderer is culled (e.g. under a RectMask2D).

๐Ÿงช Testing

33 EditMode tests cover the RNG, emission timing (bursts, cycles, probability, start delay), lifetime, playback state machine, prewarm, seeded determinism, the attractor, trails + flipbook smoke tests, and the Shuriken migrator's module mapping:

Window โ–ธ General โ–ธ Test Runner โ–ธ EditMode โ–ธ Run All

๐Ÿ“ Project Structure

Assets/UIParticleEngine/
โ”œโ”€ Runtime/            UIParticleEmitter, UIParticleAttractor, UIParticleUpdater, RangeF
โ”œโ”€ Editor/             Shuriken-style inspector, RangeF drawer, edit-mode preview, particle migrator
โ”œโ”€ Samples/            Coin reward, confetti, fireworks, click burst + debug probe + demo textures
โ””โ”€ Tests/EditMode/     33-test suite

About

Custom particle engine for Unity uGUI

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages