Monday, 15 September 2025

Particle Tool Unity | code | WIP | Windsurf Coding

 

What:  Unity editor Tool 

Tools: Original written in Visual Studio. 

             Re-Written With Windsurf (It's Awesome!) 

A WIP Unity editor tool that standardizes particle presets, spawns prefabs from them, adds simple track keying, auto-captures thumbnail previews for any prefab, and includes a plain-English URP material maker for consistent rendering.


Architecture at a glance (what exists)

┌─────────────────────────────────────────────────────────────────┐
│                           FXTool (Editor)                       │
│                                                                 │
│  ┌───────────┐    ┌──────────────┐   ┌───────────────┐          │
│  │ FXPreset  │ →  │ FXPresetApplier │ → (Scene/Prefab)          │
│  └───────────┘    └──────────────┘   └───────────────┘          │
│         ↑                          ↑                             │
│         │                          │                             │
│  (listed/edited)             (invoked from)                      │
│         │                          │                             │
│  ┌──────────────┐     ┌──────────────────────┐      ┌──────────┐ │
│  │ FXToolWindow │ →→  │ FXTrackManager (API) │  →→  │ Animator │ │
│  └──────────────┘     └──────────────────────┘      └──────────┘ │
│         │                                                       │
│         ├── (calls)                                             │
│         │                                                       │
│  ┌─────────────────────┐        ┌──────────────────────┐        │
│  │ FXLibraryMaterial…  │  →→    │ FXURPMaterialFactory │        │
│  └─────────────────────┘        └──────────────────────┘        │
│                                                                 │
│   (PlayMode helper)                                             │
│        ┌─────────────────────────┐                              │
│        │ FXRuntimeThumbCapturer │  (GUID-keyed PNG cache)       │
│        └─────────────────────────┘                              │
└─────────────────────────────────────────────────────────────────┘
 

1) Folders & naming (shared foundation)

The tool enforces a predictable layout (Assets/FXTool/...) and naming helpers for roots/children, then guarantees all folders exist via EnsureAll(). This keeps presets, prefabs, thumbnails, materials, and textures tidy and script-discoverable.

public static string BuildChildName(string category, string preset, string role, int i = 0)

  => $"FX_{San(category)}_{San(preset)}_{role}_{i:D2}";

This naming scheme is used everywhere (presets to prefab names, child PS nodes, etc.).  


2) Presets (ScriptableObjects)

A preset stores identity (category/name/desc) and the key Shuriken knobs (duration, looping, lifetime, speed, size, emission, shape), plus renderer/sorting and optional flipbook info. It’s intentionally minimal but extendable.

[CreateAssetMenu(menuName="FXTool/Preset")]

public class FXPreset : ScriptableObject {

  public FXCategory category; public string presetName; public string description;

  public float duration; public bool looping; public float startLifetime, startSpeed, startSize;

  public int rateOverTime; public ParticleSystem.Burst[] bursts;

  public ParticleSystemShapeType shape; public float radius, angle;

  public Material material; public string sortingLayer; public int orderInLayer;

  public Texture2D flipbook; public int tilesX=1, tilesY=1; public float flipbookFPS=12f;

}

 ScriptableObject with callouts (“Main”, “Emission”, “Shape”, “Material/Sorting”, “Flipbook”).

 


3) Preset to Prefab 

The applier builds a rooted GameObject, adds a child with a ParticleSystem, and transfers preset values into Shuriken modules. It also wires flipbook settings when present, and can immediately save a prefab asset.

var root = new GameObject(FXNaming.BuildRootName(p.category.ToString(), p.presetName));

var psGo  = new GameObject(FXNaming.BuildChildName(p.category.ToString(), p.presetName, "PS"));

var ps    = psGo.AddComponent<ParticleSystem>();

// main

var main = ps.main;

main.duration = p.duration; main.loop = p.looping;

main.startLifetime = p.startLifetime; main.startSpeed = p.startSpeed; main.startSize = p.startSize;

// emission

ps.emission.rateOverTime = p.rateOverTime;

 

// renderer & sorting

var r = ps.GetComponent<ParticleSystemRenderer>();

r.sharedMaterial = p.material; r.sortingLayerName = p.sortingLayer; r.sortingOrder = p.orderInLayer;



4) Editor window (Library | Inspector | Tracks)

The tool window exposes three workflows:

  • Library: lists all FXPreset assets under Assets/FXTool/Presets so you can select, duplicate, or ping.
  • Inspector actions: “Create From Preset” to spawns in scene; “Save As Prefab” to writes to Assets/FXTool/Prefabs.
  • Tracks (MVP): a tiny keyframer for position on the spawned root. You set Clip name/FPS/Frame/Vector3 and it keys transform curves.

// “Key Position on Root at Frame”

var ctrl = FXTrackManager.EnsureAnimator(_lastRoot);

var clip = FXTrackManager.EnsureClip(ctrl, _clipName, Mathf.Max(2f, _frame/_fps+0.1f), _fps);

FXTrackManager.KeyTransformPosition(clip, _lastRoot.transform, _frame, _pos, _fps);


5) Tracks API (Animator + clips + transform curves)

Under the hood: if the spawned root doesn’t have an Animator, it creates one and stores a controller next to your prefabs. Then it ensures a clip by name and writes transform curves for m_LocalPosition.{x,y,z} at a given time = frame/fps.

static void AddKey(AnimationClip clip, Transform t, string prop, float v, float time) {

  var bind = EditorCurveBinding.FloatCurve(AnimationUtility.CalculateTransformPath(t, null), typeof(Transform), prop);

  var curve = AnimationUtility.GetEditorCurve(clip, bind) ?? new AnimationCurve();

  curve.AddKey(new Keyframe(time, v) { weightedMode = WeightedMode.Both });

  AnimationUtility.SetEditorCurve(clip, bind, curve);

}


6) Thumbnail capture (the big quality-of-life win!)

No need for Play Mode - just instantiate each requested prefab in front of Camera.main, warm up the particle systems, render to a square RT, reads back to Texture2D, and save as a PNG into a cache folder keyed by GUID.  

Particle pack used for testing: https://assetstore.unity.com/packages... 

instance.transform.position = cam.transform.position + cam.transform.forward * cfg.distance;

foreach (var ps in instance.GetComponentsInChildren<ParticleSystem>(true)) {

  ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);

  ps.Simulate(0f, true, true, true);

  ps.Play(true);

}

// render to RT to Texture2D to PNG in cache

The cache path is created defensively (Assets/FXTool/Cache/ParticleThumbs/...), and each PNG is named with the prefab’s GUID so the library can look up thumbnails deterministically later.

UX detail: the coroutine uses small warmup/capture delays so particles are “mid-motion” when snapped; it also shuts down Play Mode when done so the batch flow is quick.



7) URP Material Factory (plain-English, guard-railed)

A separate editor window lets you pick Usage (Sprite, Particle Billboard, Trails, UI), Blend (Alpha/Additive/Premultiplied/Multiply/Cutout), sidedness, ZWrite, and emission. It then creates/updates a URP material at a stable path (Assets/FXTool/Materials/<Category>/<Name>.mat).

On apply, it sets the URP surface/blend/keyword combo properly (including premultiplied and alpha-clip cases) and toggles common keywords. 

On masse layer switcher /  

There’s also a small “Tips” section to nudge best-practice choices (e.g., Sprite-Unlit-Default for 2D, Particles/Unlit + Additive for glows). The path utility ensures category subfolders exist.



How the pieces talk to each other

  • FXPreset is the source of truth for particle settings.
  • FXPresetApplier reads a preset and builds a scene object/prefab with those values (and flipbook, sorting).
  • FXToolWindow is the entry point: pick preset to create to (optionally) save prefab to (optionally) key position.
  • FXTrackManager owns animator/clip creation and low-level curve writing.
  • FXRuntimeThumbCapturer batch-renders thumbnails for any list of prefab paths and writes them to a cache (GUID-keyed).
  • URP Material Factory creates consistent materials that presets can reference.

 


Current limitations / “known” WIP

  • Track keying: only transform position is exposed; PS module animation is planned (Emission, Size, Speed, Color) either via safe bindings or a tiny runtime driver.
  • Thumbnail UX: batch list is passed by EditorPrefs (simple but brittle); a first-class “Library” grid with live cache state and folder filters is the next obvious polish.
  • Flipbook flow: presets carry rows/cols/FPS, but import helpers and sprite slicing UX are still to come.
  • Pooling & spawn rules: stubbed/roadmapped.

  • Visual library browser with thumbnail grid, folder dropdown, and Preview/Place actions.
  • Module animation (Emission/Size/Speed/Color) with robust 2021-friendly bindings.
  • Flipbook importer (rows/cols slicing + material hookup).
  • Pooling UI (prewarm/auto-despawn).
  • Strict structure validator and JSON import/export for presets. 

Here’s a concise script structure diagram outline for the Unity FX tool (Editor-side only). It maps responsibilities, key APIs, and dependencies between scripts so anyone can see how the pieces fit.


Top-level overview 


FXToolWindow (EditorWindow)

Role: Main entry point UI – browse presets, spawn prefabs, save prefabs, key basic motion.

  • Lists FXPreset assets from Assets/FXTool/Presets, duplicate/ping, pick active preset.

  • Buttons: Create From Preset, Save As Prefab (calls FXPresetApplier).

  • “Tracks” pane: keys position on the spawned root via FXTrackManager.

  • Depends on: FXPreset, FXPresetApplier, FXTrackManager, FXFolders.

  • Key code path (library + key):

[MenuItem("Tools/Particles/FXTool")] → ShowWindow()
DrawLibrary() → FindAssets("t:FXPreset") … select/duplicate
DrawInspectorAndTracks() → CreateFromPreset(), SaveAsPrefab()
→ FXTrackManager.EnsureAnimator/EnsureClip/KeyTransformPosition()


FXPreset (ScriptableObject)

Role: Source of truth for particle settings & identity.

  • Sections: Identity, Main, Emission, Shape, Renderer/Material/Sorting, Texture Sheet (Flipbook).

  • Referenced by FXPresetApplier to build a concrete PS.

  • Key fields: duration/looping/lifetime/speed/size, rateOverTime/bursts, shape/radius/angle, material/sorting, flipbook tiles & fps.

  • Snippet:


FXPresetApplier (static utility)

Role: Convert a FXPreset into a scene object (root + child PS) and save as prefab.

  • Builds named root & child using FXNaming; creates ParticleSystem.

  • Applies preset values to Main/Emission/Shape/Renderer; optional Texture Sheet Animation.

  • Saves to Assets/FXTool/Prefabs/<name>.prefab.

  • Snippet: create & apply + flipbook + save.


FXTrackManager (static API)

Role: Minimal “tracks” layer – ensures Animator/Clip; keys Transform.position curves.

  • Creates/assigns AnimatorController next to the prefab folder; ensures named clip.

  • Keys m_LocalPosition.{x,y,z} at time = frame/fps.

  • Extensible later for PS module animation.

  • Snippet: ensure & key.


FXRuntimeThumbCapturer (MonoBehaviour – Editor guarded)

Role: PlayMode helper that auto-captures thumbnails for any list of prefab paths.

  • Reads list from EditorPrefs (GUIDs or paths), spawns prefabs in front of Camera.main, warms PS, renders to RT, saves 256px PNG to Assets/FXTool/Cache/ParticleThumbs/<prefabGUID>.png, imports with sane settings.

  • Cleans up and stops Play Mode when done.

  • Snippet: instantiate → warm → capture → write PNG → stop.


FXLibraryMaterialTool (EditorWindow)

Role: Plain-English URP material builder for FX (independent of PS).

  • Inputs: Usage (Sprite, Billboard, Trails, UI), Blend (Alpha/Additive/Premultiplied/Multiply/Cutout), toggles (Double-Sided, ZWrite, Shadows, Emission), Base Map + Color.

  • Writes .mat to Assets/FXTool/Materials/<Category>/<Name>.mat, pings the asset.

  • Calls FXURPMaterialFactory.CreateOrUpdate(...) under the hood.

  • Snippet: UI → Create/Update call.


FXURPMaterialFactory (static)

Role: Centralizes URP surface/blend/keyword setup.

  • Applies Src/Dst blend, alpha variants, Cutout via _ALPHATEST_ON, Premultiplied via _ALPHAPREMULTIPLY_ON, toggles _SURFACE_TYPE_TRANSPARENT.

  • Called only by FXLibraryMaterialTool.

  • Snippet (keywords & blends):


FXNaming & FXFolders (static support)

Role: Consistent names and folder structure for everything.

  • FXNaming.BuildChildName(category,preset,role,i)FX_<Cat>_<Preset>_<Role>_##

  • FXFolders.EnsureAll() creates Assets/FXTool/{Presets,Prefabs,Thumbnails,Materials,Textures}.

  • Snippet: naming + ensure folders.


Dependency graph (editor)

FXToolWindow
 ├─ uses → FXPreset (list/select/duplicate)
 ├─ calls → FXPresetApplier (Create From Preset / Save As Prefab)
 └─ calls → FXTrackManager (EnsureAnimator, EnsureClip, KeyTransformPosition)

FXPresetApplier
 ├─ reads → FXPreset (values)
 ├─ uses → FXNaming (names), FXFolders (paths)
 └─ writes → Prefab asset

FXTrackManager
 └─ writes → AnimatorController + AnimationClip (curves on Transform)

FXLibraryMaterialTool
 └─ calls → FXURPMaterialFactory (CreateOrUpdate material)

FXRuntimeThumbCapturer (Play Mode, UNITY_EDITOR)
 ├─ reads → EditorPrefs capture list
 ├─ loads → Prefabs by path
 ├─ uses → Camera.main for RT capture
 └─ writes → PNG cache (GUID-keyed), reimports Texture