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 fromAssets/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
; createsParticleSystem
. -
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 toAssets/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
toAssets/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()
createsAssets/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
No comments:
Post a Comment