Skip to content

Features Editor Tools Editor Tools Guide

github-actions[bot] edited this page Sep 28, 2026 · 48 revisions

Editor Tools Guide

Batch tools for the asset work Unity makes you do one asset at a time. Crop a folder of sprites, build clips from a naming convention, apply one set of import settings to 400 textures, or find the serialized field Unity is silently throwing away — 30+ editor tools in all.

Everything here lives under Tools > Wallstop Studios > Unity Helpers unless stated otherwise. Two tools also expose a public C# API you can drive from your own editor scripts: Texture Settings Applier and Sprite Settings Applier.


Find the tool you need

I want to... Tool
Strip transparent padding off a folder of sprites Sprite Cropper
Apply one set of import settings to many textures Texture Settings Applier
Apply different sprite settings per folder or name Sprite Settings Applier
Stop guessing texture max sizes Fit Texture Size
Put pivots on the visual center of mass Sprite Pivot Adjuster
Upscale a batch of PNGs Texture Resizer
Blur backgrounds for a UI pause menu Image Blur Tool
Turn Run_0.png ... Run_7.png into a clip Animation Creator
Turn a sliced sheet into several clips Sprite Sheet Animation Creator
Split a sheet back into individual PNGs Sprite Sheet Extractor
Change the timing or frame order of a clip Sprite Animation Editor
Add footstep / hitbox events to a clip Animation Event Editor
Sync clips between two folders Animation Copier
Build a sprite atlas from a regex or a label Sprite Atlas Generator
Catch broken prefabs before they ship Prefab Checker
Catch a missing override on a lifecycle method Unity Method Analyzer
Configure Unity Helpers compiler diagnostics Analyzer Policies
Find a field Unity is silently not serializing Serialized Field Validator
See everything wrong with the project at once Asset Validation
React to assets being created or deleted Asset Change Detection
Recompile without touching a file Request Script Compilation
Keep failing test output after the run Failed Tests Exporter
Start a test run a script outside the editor polls Test Run Reporter

Inspector attributes ([WGroup], [WButton], [WEnumToggleButtons], [WNotNull] and friends) are covered in the Inspector documentation. The drawers documented here are the ones these tools use most.


Texture & Sprite Tools

Image Blur Tool

Tools > Wallstop Studios > Unity Helpers > Image Blur

You need a blurred copy of a background so the pause menu can sit on top of it without a render texture. This applies a Gaussian blur to a batch of images and writes the results next to the originals.

  1. Drag your background textures (or the folder holding them) onto Drag & Drop Images/Folders Here.
  2. Set Blur Radius — the slider runs 1 to 200; 10 is a soft haze, 50 is a heavy frost.
  3. Click Apply Blur.

Each source produces a new file beside it; the originals are left alone:

Assets/UI/Backgrounds/
  pause_bg.png                 # untouched
  pause_bg_blurred_24.png      # written by the tool

.jpg and .jpeg sources stay JPEG; every other format is written as PNG. Re-running at the same radius appends _1, _2 and so on rather than overwriting.

The tool temporarily enables Read/Write and uses uncompressed import data while processing each source. It restores both settings after that texture succeeds or fails, and shows one completion summary for the whole batch. Small images and one-row or one-column passes avoid worker scheduling; larger images retain parallel processing. The new blurred files are permanent project changes and are not covered by Unity's undo history.

Editor scripts can blur an already readable texture without opening the window. ImageBlurAPI.TryBlur returns a new texture owned by the caller; destroy it after encoding, saving, or displaying it:

if (ImageBlurAPI.TryBlur(source, 12, out Texture2D blurred, out string error))
{
    try
    {
        // Use blurred here.
    }
    finally
    {
        UnityEngine.Object.DestroyImmediate(blurred);
    }
}

The API accepts radii from 1 through 200 and reports invalid or unreadable sources through error. It does not change importer settings or write files.

For the same file output as the window, call ImageBlurAPI.TryWriteAsset with a project texture and radius. It temporarily makes the source readable, restores its importer, writes a uniquely named image beside it using the Unity project directory, and imports the result. An occupied output name is preserved and the next numbered name is used, including when another process creates the file while the blur is being written. New output files are published from a complete staged image in the same directory. A failed publication leaves the staged file for cleanup and does not expose a partial output. On Linux and macOS, the output filesystem must support hard links; otherwise the API reports an error. Publication shares destination ownership with DurableFile writes. If another writer holds that destination, publication fails and keeps the staged image for cleanup. On Linux and macOS, if staged-file cleanup fails after publication, the API still imports the output and returns its path, but returns false with the cleanup error. This operation writes a file; Unity Undo cannot remove that output file.

Visual Demo

Image Blur Tool showing before/after comparison as blur radius slider is adjusted

Adjusting blur radius from 0 to 200 pixels on a UI background texture


Sprite Cropper

Tools > Wallstop Studios > Unity Helpers > Sprite Cropper

Art tools export the whole canvas, so a 40-pixel-tall character often ships inside a 256x256 texture that is mostly transparency. Sprite Cropper trims each texture to its alpha bounds, moves the pivot so the sprite does not shift in the scene, and shrinks the 9-slice border by the same amount. Padding a sprite that has no 9-slice border keeps that border at zero. When padding grows the output beyond the source import size limit, the output limit grows to fit the new image. Large sprites scan alpha in parallel to keep batch cropping responsive.

  1. Drop Assets/Sprites/Characters into Input directories.
  2. Leave Sprite Name Regex at .*, or narrow it (^player_) to crop one character.
  3. Set Left/Right/Top/Bottom Padding to 2 if a shader samples outside the sprite (outlines, glow); otherwise leave them at 0.
  4. Tick Only Necessary so already-tight sprites are skipped.
  5. Click Find Sprites To Process, then Process N Sprites.

Results are written beside the source, or into Output Directory if you set one:

Assets/Sprites/Characters/
  player_idle_0.png            # 256x256, mostly transparency
  Cropped_player_idle_0.png    # 48x64, same pivot, sits in the same place in-scene

Overwrite Originals writes in place instead. Sprite Cropper stages each PNG before replacing its destination. A failed encoding or staging write reports an error and keeps the previous image bytes. The final swap has the platform limits of DurableFile.

Editor scripts can call SpriteCropperAPI.TryFind(inputFolders, spriteNameRegex, singlePaths, multiPaths, out error) with Assets/... folder paths. It returns project asset paths for single-sprite images and reports multi-sprite sheets separately. The window uses this same discovery path. Script paths may use backslashes or a differently cased Assets prefix; the API normalizes them before asset lookup. SpriteCropperAPI.Crop(assetPath, options) crops one sprite with explicit padding, destination, overwrite, readability, and platform-setting options, and returns the output path, status, and any error. The window uses the same crop operation for each selected sprite. SpriteCropperAPI.TryBuildReplacementMap(folders, mapping, out error) finds existing Cropped_* pairs, and SpriteCropperAPI.ReplaceReferences(folders, applyChanges) previews or applies reference changes across project assets. Both accept an optional output folder when cropped files were written away from their sources. Replacement is only for separate Cropped_* outputs; the Danger Zone button is disabled when Overwrite Originals is on. Script callers can pass overwriteOriginals: true to make ReplaceReferences reject that combination.

Before you run it:

  • Output is always PNG bytes. In overwrite mode a .jpg source keeps its .jpg name and holds PNG data.
  • Only Sprite Import Mode: Single textures are cropped. Multi-sprite sheets are reported and skipped — run Sprite Sheet Extractor first if you need them split.
  • There is no undo, so commit before you run it.
  • The collapsed Danger Zone at the bottom rewrites .prefab, .unity, .asset, .mat, .anim and .overrideController files to point at the Cropped_* sprites. It makes you tick "I understand the risks and want to proceed." first, and it is the one action here you cannot walk back without version control.

Visual Demo

Sprite Cropper showing original sprite with padding, then cropped result side-by-side

Before and after: transparent padding removed while preserving sprite content and pivot

Cropping first also makes Sprite Atlas Generator pack tighter. Crop output reuses dynamically sized pixel buffers, so repeated work does not retain one permanent pool bucket for every texture size. Folder and file-extension filtering scans directly without building temporary iterator chains during large asset searches.


Texture Settings Applier

Tools > Wallstop Studios > Unity Helpers > Texture Settings Applier

An artist drops 200 tiles into the project and every one arrives on Unity's defaults — bilinear filtering on pixel art, mipmaps you do not want, 8192 max size. This window applies one set of importer settings to every texture under a folder, with per-platform overrides.

  1. Drag Assets/Sprites/Tiles into Directory Sources (or add individual textures under Specific Textures).
  2. Tick Apply Filter Mode and choose Point; tick Apply MipMaps and leave Generate MipMaps off.
  3. Under Default Platform Settings, set Max Texture Size to 2048 and Compression to CompressedHQ. That group has no apply toggles — whatever it shows is written.
  4. Click Calculate Stats — it reports Textures to process and Textures that will change, and Preview (N) lists the first 200 paths.
  5. Click Apply Settings to Textures.

Settings that tend to travel together:

For Filter Mode Wrap Mode Generate MipMaps Compression
Pixel art and UI Point Clamp off CompressedHQ
Character sprites Bilinear Clamp off CompressedHQ
Tiling world textures Trilinear Repeat on CompressedHQ

Platform Overrides adds an entry per platform (Standalone, Android, iPhone, WebGL, Switch, ...). Each entry has its own apply toggles, so you can cap Android at 1024 while Standalone keeps 2048.

Source image bytes stay unchanged; Unity saves importer settings and reimports changed textures. The settings change is recorded as an Apply Texture Settings undo step. Require Changes Before Apply (on by default) skips the reimport entirely when nothing would differ.

Applying texture settings from a script

The same logic is public, so a build step or a custom importer can use it directly:

using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Editor.Sprites;

public static class TileImportStandard
{
    public static bool ApplyTo(IReadOnlyList<string> assetPaths)
    {
        TextureSettingsApplierAPI.Config config = new()
        {
            applyFilterMode = true,
            filterMode = FilterMode.Point,
            applyMipMaps = true,
            generateMipMaps = false,
            applyPlatformMaxTextureSize = true,
            platformMaxTextureSize = 2048,
            platformOverrides = new[]
            {
                new TextureSettingsApplierAPI.PlatformOverride
                {
                    name = "Android",
                    applyMaxTextureSize = true,
                    maxTextureSize = 1024,
                },
            },
        };

        if (
            !TextureSettingsApplierAPI.TryApplyTextureSettings(
                assetPaths,
                in config,
                out int changed,
                out bool canceled,
                out string error
            )
        )
        {
            Debug.LogError(error);
            return false;
        }

        Debug.Log($"Updated {changed} texture importers.");
        return !canceled;
    }
}

Config is a struct with no defaults, so every field you care about must be set explicitly, and the batch API reimports and saves changed textures. Its optional cancellation callback keeps completed changes and reports canceled; an error can also leave earlier textures changed. For one texture, WillTextureSettingsChange and TryUpdateTextureSettings remain available. The latter returns an importer for the caller to reimport. The default-platform name string is "DefaultTexturePlatform".

Visual Reference

Texture Settings Applier window showing configuration options

Texture Settings Applier with filter mode, wrap mode, and compression options


Sprite Pivot Adjuster

Tools > Wallstop Studios > Unity Helpers > Sprite Pivot Adjuster

A walk cycle where the character leans forward on some frames wobbles if every pivot is (0.5, 0.5), because the geometric center of the texture is not the visual center of the character. This computes the center of pixels above the alpha cutoff and writes it as a custom pivot.

  1. Add Assets/Sprites/Characters/Player to Input Directories.
  2. Leave Alpha Cutoff at 0.01 so anti-aliased fringe pixels do not drag the pivot outward.
  3. Leave Skip Unchanged (fuzzy) on — it avoids reimporting when the pivot moves less than 0.001.
  4. Click Find Sprites To Process, then Dry Run to see the counts, then Adjust Pivots in Directory.

Editor scripts can call SpritePivotAdjusterAPI.TryFind(folders, nameRegex, paths, out error) with Assets/... folders, then call SpritePivotAdjusterAPI.Run(paths, options) to preview or Run(paths, options, applyChanges: true) to apply. The result reports changes, skips, cancellation, warnings, and errors. These calls normalize backslashes, trailing folder slashes, and the Assets prefix casing. Invalid paths are counted as skipped and reported in the result errors. Neither call opens the window.

Each changed importer is recorded as an Adjust Sprite Pivot undo step. The reimport side effect may require regeneration after Undo. Center-of-mass scans run directly below 65,536 pixels and for one-row sprites. Larger scans use parallel row partitions, so small sprite batches avoid worker startup without slowing large art. Folder and file-extension filtering also scans directly without per-file predicate allocations.

Before you run it:

  • Single-sprite textures only.
  • Textures without Read/Write Enabled are skipped, not fixed. Turn Read/Write on with Texture Settings Applier first.
  • This window shares its remembered directory list with Sprite Cropper.

Visual Reference

Sprite Pivot Adjuster window showing cutoff-based pivot calculation

Sprite Pivot Adjuster with alpha cutoff slider and directory selection


Sprite Settings Applier

Tools > Wallstop Studios > Unity Helpers > Sprite Settings Applier

Same idea as Texture Settings Applier, but sprite-specific (pixels per unit, pivot, sprite mode, extrude) and driven by profiles — so ui_ sprites can import at 100 PPU with bilinear filtering while everything in Assets/Sprites/World imports at 16 PPU with point filtering, in one pass.

  1. Drag Assets/Sprites into Directory Sources.
  2. Under Sprite Settings Profiles, add one profile per rule. Each has Match By (Any, NameContains, PathContains, Regex, Extension), Match Pattern, and a Priority — the highest matching priority wins.
  3. Tick only the Apply ... toggles you want; each one reveals its value field.
  4. Calculate Stats, then Apply Settings to Sprites.

Save Profiles Asset writes the rule set to a SpriteSettingsProfileCollection asset so the rest of the team gets the same rules; Load Profiles Asset reads one back. There is no Assets > Create entry for that asset — the button is how you make one.

Editor scripts can save and load the same profile assets without opening the window:

using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Editor.Sprites;

public static class SpriteProfilePersistenceExample
{
    public static List<SpriteSettings> SaveAndLoad()
    {
        List<SpriteSettings> profiles = new() { new SpriteSettings { name = "World" } };
        if (!SpriteSettingsApplierAPI.TrySaveProfiles(
                "Assets/Settings/SpriteProfiles.asset", profiles, false, out string error))
        {
            Debug.LogError(error);
            return profiles;
        }
        if (SpriteSettingsApplierAPI.TryLoadProfiles(
                "Assets/Settings/SpriteProfiles.asset", out List<SpriteSettings> loaded, out error))
        {
            return loaded;
        }
        Debug.LogError(error);
        return profiles;
    }
}

The path must be a .asset file in an existing folder under Assets. Saving refuses an occupied path unless overwriteExisting is true, and never replaces an asset of another type. Saved and loaded profiles are separate copies. A new save stages its asset and refuses a destination that becomes occupied before the move. Overwrite refuses a file whose bytes changed before replacement. Noncooperating external writers can still race with replacement or staging cleanup (tracked in #863). Asset file writes and reimports cannot be fully reversed by Unity Undo; use version control to recover an earlier saved asset. A failed save leaves its staged asset in place because another writer may have changed it; check the reported path before retrying or removing it. If restoring an absent file fails, the error also identifies the staged restore bytes for inspection.

Applying sprite settings from a script

using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Editor.Sprites;

public static class SpriteImportStandard
{
    private static readonly List<SpriteSettings> Profiles = new()
    {
        new SpriteSettings
        {
            matchBy = SpriteSettings.MatchMode.PathContains,
            matchPattern = "sprites/world",
            priority = 1,
            applyPixelsPerUnit = true,
            pixelsPerUnit = 16,
            applyFilterMode = true,
            filterMode = FilterMode.Point,
        },
        new SpriteSettings
        {
            matchBy = SpriteSettings.MatchMode.NameContains,
            matchPattern = "ui_",
            priority = 5,
            applyPixelsPerUnit = true,
            pixelsPerUnit = 100,
            applyFilterMode = true,
            filterMode = FilterMode.Bilinear,
        },
    };

    public static bool ApplyTo(
        IReadOnlyList<string> assetPaths,
        out int changed,
        out bool canceled)
    {
        bool succeeded = SpriteSettingsApplierAPI.TryApplyProfiles(
            assetPaths,
            Profiles,
            out changed,
            out canceled,
            out string error);
        if (!succeeded)
        {
            Debug.LogError(error);
        }
        return succeeded;
    }
}

The batch API reimports and saves changed sprites. An optional cancellation callback keeps completed changes and reports canceled; an error can also leave earlier sprites changed. For one sprite, PrepareProfiles, WillTextureSettingsChange, and TryUpdateTextureSettings remain available; the latter returns an importer for the caller to reimport. NameContains matches the file name, PathContains and Regex match the full asset path, and Regex is always case-insensitive. Applying a pivot also forces Sprite Alignment to Custom.

Visual Reference

Sprite Settings Applier window showing profile configuration

Sprite Settings Applier with profile matching modes and import settings


Texture Resizer

Tools > Wallstop Studios > Unity Helpers > Texture Resizer

Scales PNGs up on disk. Reach for this when the source art is genuinely too small for the resolution you ship at and you want the bigger pixels baked into the file rather than paid for at runtime.

  1. Drag a folder into Texture Source Paths, or add textures to Textures.
  2. Pick Scaling Resize Algorithm: Point for pixel art (no blur), Bilinear for everything else.
  3. Set Num Resizes — each pass grows the texture by width / (Pixels Per Unit * Width Multiplier) (and the same for height), so several small passes scale more gently than one large one.
  4. Tick Dry Run and click Resize — the console logs [DryRun] Would resize <name> to [WxH] for every match. This reads texture dimensions without resizing the loaded texture, reading pixel data, or changing importer settings.
  5. Clear Dry Run and click Resize for real.

Editor scripts can call TextureResizerAPI.TryResizeTextures with selected textures, source asset-folder paths, sizing settings, an optional output asset-folder path, and a dry-run flag. The call does not open a window or ask for input. It returns false for invalid settings, a destination collision, a failed resize, a failed importer restore, or cancellation. Pass null for the output folder to overwrite the source PNGs; an empty string is invalid. For example, this Editor script previews one asset folder:

using WallstopStudios.UnityHelpers.Editor.Sprites;

public static class TextureResizeExample
{
    public static bool Preview()
    {
        return TextureResizerAPI.TryResizeTextures(
            null,
            new[] { "Assets/Art/Sprites" },
            1,
            TextureResizerWizard.ResizeAlgorithm.Point,
            100,
            1f,
            1f,
            "Assets/Art/Resized",
            true
        );
    }
}

Blank source folder entries are ignored; other invalid source or output folders are rejected before any texture changes. Overwrite mode accepts only textures under Assets/; package textures can be copied into an explicit Assets/ output folder. Textures are processed in natural name order, so Sprite2 precedes Sprite10. The dry run reports target sizes without changing files or importer settings. A real run writes PNG files, so Unity Undo cannot fully reverse it. A failed or canceled run may leave earlier textures resized.

Before you run it:

  • Only .png files are processed; anything else is counted as skipped.
  • With Output Folder empty this overwrites the originals in place and there is no undo. Set Output Folder to write copies instead.
  • Invalid sizing settings and output-name collisions are rejected before any source, importer, or destination file changes. Texture names are compared without regard to case because Unity asset paths must remain portable across project platforms.
  • PNG writes are staged before replacement. A failed write reports an error rather than leaving a partly written PNG.
  • A failed PNG encode reports an error and leaves the original image unchanged.
  • If PNG encoding fails, the original image remains unchanged.
  • The stock multipliers (0.54 width, 0.245 height) grow a texture non-uniformly — a 128x128 becomes 130x133 in one pass. Set both to the same value if you want a square scale.
  • The final size is clamped to 16384 on each axis.

Visual Reference

Texture Resizer window showing resize algorithm options

Texture Resizer with bilinear/point algorithm selection and multiplier settings


Fit Texture Size

Tools > Wallstop Studios > Unity Helpers > Fit Texture Size

Every texture in a fresh project imports at Max Size: 2048, whatever its real dimensions. A 64x64 icon wastes an import slot; a 4096-wide sky is silently halved. This reads each source file's actual dimensions and sets Max Size to the power of two that fits.

Closing the window releases its serialized editor state; reopening binds fresh state.

  1. Add Assets/Sprites to the source list (the window pre-fills it if that folder exists).

  2. Choose a Fit Mode:

    Mode Effect
    GrowAndShrink Smallest power of two that fits the source. The usual choice.
    GrowOnly Raise Max Size when the source is bigger; never lower it.
    ShrinkOnly Lower Max Size to the tightest fit; never raise it.
    RoundToNearest Nearest power of two to the source size, ties rounding up.
  3. Narrow the run if you want to: Only Current Selection, Only Sprites, Name Filter (substring, or a regex with Use Regex), Label Filter (CSV).

  4. Set Min Allowed Size / Max Allowed Size to keep results inside a range (defaults 32 and 8192).

  5. Click Calculate Potential Changes. You get N textures would be modified ... Grows: G, Shrinks: S, Unchanged: U.

  6. Click Run Fit Texture Size.

A 1920x1080 source under GrowAndShrink lands on 2048; a 64x64 source under ShrinkOnly lands on 64.

Import settings only, recorded as a Fit Texture Size undo step per asset, and cancellable from the progress bar. Apply to Standalone / Android / iOS additionally writes a platform override with the same size. Fit Mode is not persisted across a domain reload — re-select it after a recompile.

Scripts and batch-mode jobs can run the same operation without opening the window. Folder paths accept forward or back slashes:

List<string> textureGuids = new();
if (FitTextureSizeAPI.TryFindTextures(new[] { "Assets/Sprites" }, true, textureGuids, out string error))
{
    FitTextureSizeAPI.Options options = new() { FitMode = FitMode.GrowAndShrink, OnlySprites = true };
    FitTextureSizeAPI.Result preview = FitTextureSizeAPI.Run(textureGuids, options, false);
    if (!preview.Succeeded)
    {
        Debug.LogError(preview.Error);
    }
    else if (preview.Changed > 0)
    {
        FitTextureSizeAPI.Result applied = FitTextureSizeAPI.Run(textureGuids, options, true);
        if (!applied.Succeeded)
        {
            Debug.LogError(applied.Error);
        }
    }
}
else
{
    Debug.LogError(error);
}

Run reports counts, cancellation, and errors; a failed apply may report partial changes. Import settings writes and reimports cannot be fully reversed by Unity Undo alone. Supply explicit asset paths or GUIDs instead of relying on the current editor selection.

Visual Reference

Fit Texture Size window showing fit mode options and preview

Fit Texture Size with GrowAndShrink/GrowOnly/ShrinkOnly mode selection


Animation Tools

Sprite Animation Editor (Animation Viewer Window)

Tools > Wallstop Studios > Unity Helpers > Sprite Animation Editor

Unity's Animation window is built for curves, and reordering four sprite keyframes in it is more work than it should be. This opens a clip as a list of frames you can drag, with the animation playing next to it.

  1. Click Browse Clips (Multi)... and pick PlayerWalk.anim (and any other clips you want open at the same time).
  2. Click the clip in the left panel to make it active. The preview starts playing immediately.
  3. Drag a frame in the Frames panel to move it, or type a new position into its Order: field and press Enter.
  4. Set Preview FPS and click Apply Preview FPS to see the new speed.
  5. Click Save Active Clip.

The Preview FPS field works in Unity 2021.3 and later supported editors.

Before you save: saving writes the preview FPS onto the clip and re-spaces every keyframe to match. That is usually what you want after changing the speed of a walk cycle, but it means you cannot save a reorder while leaving the original timing alone.

Editor scripts can update an existing clip without opening the window:

bool saved = AnimationClipFrameSaveAPI.TrySaveFrames(
    clip,
    new[] { idle0, idle1, idle2 },
    12f,
    "Character",
    out bool usedFallbackBinding,
    out string error
);

The preferred path selects a SpriteRenderer sprite curve; an empty string selects a curve on the root object. If the path is absent, the method uses the first sprite curve and sets usedFallbackBinding. Pass null to select the first curve without a path preference. The method requires a standalone .anim asset, rejects invalid frames or FPS before editing, preserves other curves, events, and clip settings, records Undo, and saves the clip asset. Undo restores the clip in memory; the asset write itself is not fully reversible through Unity Undo.

This is an editor for frame order and speed. Adding or removing frames is Animation Creator's job; the X next to a loaded clip closes it in this window rather than deleting anything. One clip previews at a time.

Visual Demo

Sprite Animation Editor showing animation preview playing while frames are dragged to reorder

Drag-and-drop frame reordering with real-time preview updates

Sprite Animation Editor FPS adjustment showing animation speed changing live

Adjusting FPS and seeing immediate preview speed change


Animation Creator

Tools > Wallstop Studios > Unity Helpers > Animation Creator

An artist hands you Player_Idle_0.png through Player_Attack_11.png — sixty files, eight animations. Making those clips by hand is an hour of dragging, and Unity's default sort puts frame 10 before frame 2. This groups the sprites by name and writes the clips.

  1. Add Assets/Sprites/Player as a source folder.
  2. Leave Sprite Name Regex at .*, or narrow it (^Player_Attack) to build one clip.
  3. Click Generate Auto-Parse Preview to see the groups it found before anything is created.
  4. Click Auto-Parse Matched Sprites into Animations. You get one entry per group under Animation Data.
  5. Set FPS and Loop per clip.
  6. Click Create Animations.

These naming patterns are detected without any configuration:

Player_Idle_0.png,  Player_Idle_1.png     -> Player_Idle,  frames 0..N
slime-walk-01.png,  slime-walk-02.png     -> slime-walk,   frames 1..N
Mage/Attack (0).png, Mage/Attack (1).png  -> Attack,       frames 0..N

@2x density suffixes are stripped before grouping.

If your files do not fit any of those, tick Enable Custom Group Regex and supply one with base and index named groups. Use the Regex Tester box to check it against a real file name before applying:

^(?<base>.*?)(?:_|\s|-)?(?<index>\d+)$    # base plus trailing digits
^Enemy_(?<base>Walk)_(?<index>\d+)$        # only Enemy_Walk_N

Where clips land: each clip is written next to the first sprite in its group, with a unique name. There is no output folder picker — use Animation Copier to move a generated set into Assets/Animations.

Editor scripts can call AnimationCreatorAPI.TryCreateClip(data, frames, out clip, out error) to build a clip in the supplied frame order without writing an asset. Call AnimationCreatorAPI.TryCreateAsset(data, out path, out error) to ignore null frames, naturally sort the remaining sprites, and save a uniquely named .anim beside the first sprite. The sprite must already be an asset under Assets. When creating several clips inside an AssetDatabaseBatchHelper scope, pass saveAssets: false and call AssetDatabase.SaveAssets() after the batch. If saving fails after asset creation, TryCreateAsset returns false with the created path so the caller can inspect or remove it.

Also worth knowing:

  • Prefix Leaf Folder Name / Prefix Full Folder Path keep Idle from four different characters from colliding, and Resolve Duplicate Animation Names (on by default) renames any that still do.
  • Frames are ordered by Unity's natural sort at creation time, so 2 sorts before 10.
  • Framerate Mode is Constant (one FPS, the default at 12) or Curve, where an AnimationCurve drives FPS across the clip — the Flat / Ease In / Ease Out / Sync buttons give you a starting shape. Use Curve for an attack that snaps and then holds.
  • Preview plays a clip entry before you create anything.
  • Configuration Persistence saves the whole window state to .animation-creator.json in a source folder, so the next person to import that folder gets your settings. A staging failure keeps the previous config; on platforms without File.Replace, a failed fallback swap can still lose it.

Visual Demo

Animation Creator auto-parse workflow showing sprites being grouped and clips generated

One-click auto-parse: sprites grouped by naming pattern, clips generated instantly

Next: adjust the results in Sprite Animation Editor, or add events with Animation Event Editor.


Animation Copier

Tools > Wallstop Studios > Unity Helpers > Animation Copier

Animation Creator leaves clips beside their sprites, but you keep clips in Assets/Animations. Re-running it after an art update produces a mix of genuinely new clips, genuinely changed clips, and dozens that are byte-for-byte what you already have. This tells the three apart and moves only what matters.

  1. Set Source Path (defaults to Assets/Sprites) and Destination Path (defaults to Assets/Animations).
  2. Click Analyze Source & Destination.
  3. Read the counts: - New:, - Changed:, - Unchanged (Duplicates):.
  4. Expand New and Changed to review, using Filter (plus Regex) and Select All / Select None to pick what moves.
  5. Tick Dry Run (no changes) and run the copy once to see what it would do.
  6. Clear it, then click Copy New (N) or Copy Changed (N).

Dry runs leave destination files and folders untouched, including subfolders that a real copy would create for nested source clips.

Scripts and batch jobs can use AnimationCopierAPI.TryAnalyze with explicit source and destination folders to receive source entries and destination orphans. Pass selected source paths to Run for CopyNew, CopyChanged, CopyAll, or DeleteUnchangedSource; pass selected destination paths for DeleteDestinationOrphans. Set applyChanges: false to preview eligible operations and applyChanges: true to perform them. The result reports processed, skipped, and failed counts, cancellation, and per-path diagnostics. The API makes no prompts, rechecks each selected clip before an operation, accepts standalone .anim files under distinct non-overlapping Assets folders, and never creates folders during a preview.

Copying a changed clip preserves its GUID, so every Animator that already references it keeps working.

Enable Include Unchanged in Copy All (force replace) when you need to refresh selected clips that the last analysis classified as unchanged. Copy All then replaces those destination files too and preserves their GUIDs.

"Changed" is decided by comparing clip contents field by field — frame rate, length, wrap mode, every curve key, every event and its parameters — not by an asset hash, so a re-import that produces an identical clip does not show up as a change.

Cleanup is where the destructive buttons live: Delete N Unchanged Source Duplicates removes the redundant copies left behind in the source folder, and Mirror Delete Destination Orphans (N) deletes destination clips with no source any more. Both honour Dry Run. Export Preview Report writes the analysis to a file if you would rather review it outside the editor. The complete report is staged before replacing an existing file. Copy, delete, and reimport operations change files on disk and cannot be fully reversed through Unity Undo.

Visual Reference

Animation Copier window showing source/destination analysis

Animation Copier with new/changed/unchanged/orphan groups and copy actions


To read authored sprite keyframes or find the last bounds movement in a clip, see Sprite animation motion.

Sprite Sheet Animation Creator

Tools > Wallstop Studios > Unity Helpers > Sprite Sheet Animation Creator

Your character is one hero.png sliced into 48 sprites in the Sprite Editor, and frames 0-7 are the idle, 8-15 the run, 16-23 the attack. This lets you select those ranges visually and turn each into a clip.

  1. Assign the sliced texture to Sprite Sheet, or click Load Sprites and pick the file.
  2. Drag across the thumbnails to select frames 0-7.
  3. Click Add Animation Definition. Name it Hero_Idle.
  4. Set Default FPS:, Looping: and Cycle Offset:, then hit Preview This and use ▶ Play to check it.
  5. Repeat for the run and attack ranges — the Start Idx: / End Idx: fields let you correct a selection by hand.
  6. Click Generate Animation Files and choose an output folder.

FPS Curve: takes an AnimationCurve instead of a flat rate, so an attack can hold on its impact frame. The curve changes how far apart keyframes are placed; the generated clip's own frame rate is always 60, which is what lets fractional timings land cleanly.

Editor scripts can call SpriteSheetAnimationAPI.TryCreate(outputFolder, name, frames, defaultFrameRate, frameRateCurve, loop, cycleOffset, dryRun, out assetPath, out error) with ordered sprite frames and an Assets/ output folder or an existing Packages/ folder. It generates a unique .anim name and normalizes the Assets prefix casing. Set dryRun: true to preview the path without creating folders or assets. A successful creation saves the asset; Unity Undo cannot reverse the file write. For several clips, pass saveAssets: false and call AssetDatabase.SaveAssets() once after the batch. If saving fails after creation, the call returns false and retains the created asset path in assetPath so the caller can inspect or remove it.

Call SpriteSheetAnimationAPI.TryDiscoverFrames(texture, configureImporter, frames, out error) to load sprite frames into a caller-owned list without opening the window. It clears the list first and returns frames sorted by name. Pass configureImporter: false to leave a texture with unsuitable import settings untouched and receive an error. Pass true to change the texture to Sprite type and Multiple sprite mode and reimport it; that asset change cannot be fully undone.

The texture must already be sliced (Sprite Mode: Multiple) — this window reads Unity's sprites, it does not slice for you. Declining the window's importer prompt leaves its settings unchanged.

Visual Demo

Sprite Sheet Animation Creator showing drag-select across sprite thumbnails with live preview

Drag-select frame ranges on sprite sheet thumbnails with instant preview playback


Sprite Sheet Extractor

Tools > Wallstop Studios > Unity Helpers > Sprite Sheet Extractor

The reverse of the tool above: take a packed sheet and write each sprite out as its own PNG. Needed whenever something downstream wants files rather than sub-assets — a third-party animation tool, or Sprite Cropper, which only handles single-sprite textures.

  1. Add the folder holding your sheets to Input Directories.

  2. Pick a Mode:

    Mode Use when
    FromMetadata The sheet is already sliced in Unity. Keeps names, rects, pivots and borders.
    GridBased Even cells. Set Columns/Rows or leave Grid Size Mode on Auto.
    PaddedGrid Even cells with gutters. Set Left/Right/Top/Bottom padding.
    AlphaDetection Sprites are scattered irregularly. Tune Alpha Threshold (default 0.01).
  3. Click Find Sprite Sheets, then Preview Slicing to see the detected rects drawn over the texture (Show Overlay).

  4. Choose a Pivot Mode — Center, the eight edge and corner presets, or Custom with explicit X / Y.

  5. Set Output Directory, then click Extract N Sprite(s).

Scripts and batch jobs can call SpriteSheetExtractionAPI.Extract with explicit source and output asset paths, a pixel rectangle, normalized pivot, and border. The output folder must already exist under Assets or a writable local Packages path. The API returns extracted and skipped counts plus errors, and never opens a window or displays a prompt. Set dryRun: true to preview the count without changing files or importers. With the default overwriteExisting: false, an output that appears during extraction is skipped and left unchanged. With overwriteExisting: true, the encoded PNG is staged before replacing the existing file. New outputs are published from a complete staged image in the same directory. New output publication shares destination ownership with DurableFile writes. If another writer holds that destination, extraction reports an error without publishing the staged image. Extraction attempts to remove its temporary file if publishing fails; a failed publication does not expose a partial new output. On Linux and macOS, the output filesystem must support hard links. If temporary-file cleanup fails, the result reports that error alongside the extraction failure. On Linux and macOS, if cleanup fails after publication, the result still counts and imports the output and includes the cleanup error in Errors.

Use SpriteSheetExtractionAPI.Discover with folder asset paths and an optional filename regex to get the same sprite texture list as the window. Its result includes warnings for invalid folders and an error for an invalid or timed-out regex. Folder paths accept forward or back slashes.

SpriteSheetReferenceReplacementAPI.Run accepts an explicit map from source sprites to extracted sprites and explicit asset paths. It previews matching references by default; pass applyChanges: true to write them. The window uses this API after its Danger Zone confirmation. Unity records object changes for Undo, but saved asset edits should still be protected by version control.

using UnityEngine;
using WallstopStudios.UnityHelpers.Editor.Sprites;

SpriteSheetExtractionRequest request = new(
    "Assets/Sprites/characters.png",
    "Assets/Extracted/hero.png",
    new Rect(0, 0, 32, 32),
    new Vector2(0.5f, 0.5f),
    Vector4.zero
);
SpriteSheetExtractionResult result = SpriteSheetExtractionAPI.Extract(new[] { request });

Auto-detection: with Grid Size Mode on Auto, the Algorithm dropdown picks how cell size is inferred: AutoBest (tries each and stops once one reaches 90% confidence), UniformGrid, BoundaryScoring, ClusterCentroid, DistanceTransform or RegionGrowing. Filling in Expected Sprite Count dramatically improves the result, and Snap to Divisor keeps the cell size an exact divisor of the texture.

Alpha detection rejects NaN and infinite thresholds without producing sprite bounds or a grid. Transparency-based grid detection requires a threshold in [0, 1).

Also worth knowing:

  • Dry Run turns the extract button into Dry Run: Preview N Sprite(s) and writes nothing.
  • Per-sheet settings override the global ones; Apply Global to All pushes yours down, and Save Config writes a <texture>.spritesheet.json beside the sheet so a re-extraction is reproducible. A staging failure keeps the previous config; on platforms without File.Replace, a failed fallback swap can still lose it. A Config Stale badge appears when the texture has changed since.
  • Preserve Import Settings (on by default) copies the source's importer settings to each output.
  • Preview and extracted output reuse dynamically sized pixel buffers instead of retaining one permanent pool bucket for every sprite size.
  • The Danger Zone: Reference Replacement section points existing assets at the extracted sprites. Like the one in Sprite Cropper, it requires ticking "I understand the risks and want to proceed." and it is not undoable.

Animation Event Editor

Tools > Wallstop Studios > Unity Helpers > AnimationEvent Editor

Editor scripts can save events without opening the window. Call AnimationEventSaveAPI.TrySave(clip, events, frameRate, out error) with an explicit clip and event array. Pass null for frameRate to keep the clip's current rate. The call records Unity Undo, marks the clip dirty, and saves the asset. It returns false with an error for invalid input or a failed save. Unity Undo covers clip changes; external asset save side effects may not be fully reversible.

Putting a footstep sound on frame 4 of a run cycle in Unity's Animation window means finding the right time value and typing a method name from memory. This shows the sprite at each event, lists the methods that are actually callable, and edits the parameter with the right field type.

Animation Creator and Animation Event Editor retain at most 128 sprite previews per window, within a 32 MiB estimated texture-storage budget. The estimate includes mip levels, CPU/GPU copies and a per-texture allowance; it is not a measurement of total editor or driver memory. Recently used previews stay cached, and Unity-destroyed previews are rebuilt when needed.

Changing clips or closing Animation Event Editor releases copied previews. Eviction releases only textures the window created; Unity's shared previews remain owned by Unity. A readable sprite too large to copy within the budget is drawn directly from its source rectangle at the same resolution. A failed preview copy releases its temporary texture.

First, mark the methods you want to fire:

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public enum Stance { Idle, Crouched, Guarding }

public sealed class PlayerAnimationEvents : MonoBehaviour
{
    [AnimationEvent]
    public void PlayFootstep() { }

    [AnimationEvent]
    public void SpawnSlashVfx(string socketName) { }

    [AnimationEvent]
    public void EnableHitbox(int hitboxIndex) { }

    [AnimationEvent]
    public void SetStance(Stance stance) { }
}

Then:

  1. Drag the GameObject holding the Animator into Animator Object.
  2. Pick the clip from Animation (use Animation Search to filter a long list).
  3. Type 4 into FrameIndex and click Add Event.
  4. Choose TypeName PlayerAnimationEvents, then MethodName PlayFootstep.
  5. Click Save.

Method signatures Unity accepts — and therefore the only ones this window lists — return void and take either no parameter or exactly one of: int, float, string, UnityEngine.Object, or an enum. UnityEngine.Object means that exact type; a method taking GameObject or Sprite will not appear.

Explicit Mode is on by default and lists only [AnimationEvent] methods, which is what keeps the dropdown readable in a real project. Turn it off to see every signature-valid method on every MonoBehaviour (private ones included) and use Type Search / Method Search to narrow.

By default an [AnimationEvent] method is offered on its declaring class only. Use [AnimationEvent(ignoreDerived = false)] when subclasses should offer it too.

Also worth knowing:

  • Control Frame Time off (the default) means you work in whole frames; on, you edit the raw time value.
  • The sprite preview needs Read/Write Enabled on the texture. When it is missing you get a Fix button that turns it on.
  • Move Up / Move Down swap events that sit at the same time; Re-Order sorts everything by time. Duplicate copies an event, and Reset reverts to the last saved state.
  • The Save / Reset / Re-Order row only appears once you have unsaved changes; otherwise the window says No changes detected....
  • Shortcuts: Delete removes the focused event, Ctrl+D duplicates it, arrow keys navigate. The duplicate shortcut is Ctrl only, including on macOS.

Sprite Atlas Tools

Sprite Atlas Generator

Tools > Wallstop Studios > Unity Helpers > Sprite Atlas Generator

A .spriteatlas asset holds a hand-maintained list of sprites, so every new sprite an artist adds is one someone has to remember to drag in. This drives the atlas from a rule instead — a regex, an asset label, or both — and rescans on demand.

Project changes rebuild the serialized editor state and drop removed configurations from the foldout cache while preserving surviving scan results. Refreshes sort configurations in place, and source-sprite checks reuse temporary storage, so large atlas lists do not build query pipelines. Closing the window releases its cached state.

  1. Click Create New Config in 'Assets/Data'. That makes a ScriptableSpriteAtlas asset (you can also use Assets > Create > Wallstop Studios > Unity Helpers > Scriptable Sprite Atlas Config).

  2. Set Output Sprite Atlas Directory and Output Sprite Atlas Name — the atlas is written to <directory>/<name>.spriteatlas.

  3. Click Add New Source Folder Entry and point it at Assets/Sprites/Characters.

  4. Choose how sprites are selected:

    Selection Mode What it matches
    Regex Every pattern in Regexes (AND logic) must match the asset path.
    Labels Asset labels, combined by Label Selection Mode: All (every label) or AnyOf (any one).
    Regex | Labels Both, joined by Regex & Tags Logic (And / Or).
  5. Click Scan Folders for ''. New folder entries start with Regex selection and no patterns, so they include sprites until you narrow them. The window reports To Add: N sprites. and To Remove: N sprites. before anything changes.

  6. Click Sync List To Scan Result (N add, N remove).

  7. Click Generate/Update '.spriteatlas' ONLY, then Pack All Generated Sprite Atlases — or Generate + Pack to do both.

Scripts and batch jobs can use the same configuration without opening the window. Call ScriptableSpriteAtlasGenerator.TryCreateConfig("Assets/Data/CharacterAtlas.asset", out config, out error) to create a config at an explicit path outside an active asset batch. The parent folder must exist; the API refuses an occupied path at preflight. External writers are not locked out between that check and Unity asset creation. The window chooses a unique path before calling this API. Creation writes an asset file, which Unity Undo cannot fully reverse. If finalization fails after creation, the returned config remains at that path for inspection. Call ScriptableSpriteAtlasGenerator.Scan(config, toAdd, toRemove) to preview folder changes, then Synchronize(config, toAdd, toRemove) to add found sprites while keeping existing manual entries. Source folder paths in the config accept forward or back slashes. If a source folder or filter is invalid, Scan returns false with empty results; fix the config before synchronizing. Pass removeUnmatchedSprites: true to remove every sprite absent from the scanned folders, including manual entries. The window's Sync List To Scan Result button uses that removal mode. Generate(config) writes one atlas and returns whether it changed; GenerateAll() writes all configured atlases in one asset batch and returns the number changed, or -1 if any config failed. TryGenerateAll(out changed) also reports whether every config was valid. PackAll(target) packs for a chosen build target. A CI check can call TryFindDrift(config, differences) and fail if it returns false or adds any differences. It checks packables and generated settings without changing the atlas. To regenerate and pack in Unity batch mode, use -executeMethod WallstopStudios.UnityHelpers.Editor.Sprites.ScriptableSpriteAtlasGenerator.GenerateAndPackAllForBatch. That command skips packing and exits with code 1 if a config has an invalid or occupied output path. Generation and packing write assets and may trigger imports; Unity Undo cannot reverse all file and import effects.

ScriptableSpriteAtlasGenerator.TrySetSourceTexturesUncompressed(config, applyChanges, out count, out error) previews how many distinct source textures need uncompressed importer settings when applyChanges is false, and applies those settings without a window or prompt when it is true. It disables crunch and compression, sets the default platform to RGB24 or RGBA32 based on source alpha, and leaves PNG bytes untouched. Applying reimports textures, so Unity Undo cannot fully reverse the import effects. If a later texture fails, the call returns false with an error and the count of textures whose final reimport succeeded. Earlier changes can remain in place, and a failed import may need inspection. The API also reports failure if Unity reimports a texture but its resulting settings do not match the requested values.

A character atlas that picks up every new idle frame automatically:

Folder Path:          Assets/Sprites/Characters
Selection Mode:       Regex
Regexes (AND logic):  ["player_", "_idle_"]
Max Texture Size:     2048
Padding:              4
Compression:          CompressedHQ

A UI atlas driven by labels, so artists opt sprites in from the Inspector rather than by filename:

Folder Path:            Assets/Sprites/UI
Selection Mode:         Labels
Label Selection Mode:   AnyOf
Labels:                 ["icon", "hud"]
Max Texture Size:       1024
Padding:                2

The Labels fields are dropdowns populated from every label in the project — see Sprite Label Processor for what keeps that list current.

Also worth knowing:

  • Each source folder entry has an exclusion block — Exclude Regexes (OR logic), Exclude Labels and Exclude Path Prefixes — for keeping work-in-progress or reference art out of a shipped atlas.
  • Platform Overrides on the config set per-platform max size and compression for Standalone, iPhone and Android independently.
  • Force Uncompressed for N Source Sprites in '' sets the source sprites to uncompressed so the packer has full-quality input. It asks for confirmation first, and it changes those source assets.
  • Regex matching is case-insensitive. A pattern that fails to compile is logged and matches nothing.

Visual Reference

Sprite Atlas Generator window showing regex configuration

Sprite Atlas Generator with regex-based sprite selection and packing settings

Sprite Atlas Generator scan results showing sprites to add

Scanning folders and previewing which sprites will be added to the atlas


Validation & Quality Tools

Asset Validation

Tools > Wallstop Studios > Unity Helpers > Asset Validation

A dockable window over your own project checks. Write an IValidationRule -- "every enemy prefab has a spawn point", "no AudioClip is set to Decompress On Load" -- and this runs them across the whole project a few milliseconds per editor tick, rather than freezing for thirty seconds.

  1. Click Validate Project. A counter beside the button shows progress; the button becomes Cancel.
  2. Findings are colored by severity. Click one to select and ping the asset.
  3. Narrow the list: the search box matches the rule, path, discriminator and message; At least cycles the severity floor through Info, Warning and Error; Show suppressed is on by default.
  4. Suppress Selected appends the finding to ValidationSuppressions.txt, the same file the headless run reads.

Tick Re-check on import and an import re-validates only the assets it touched. It is off by default and stored per user, because whether the cost is worth paying is a fact about a workstation rather than about a repository.

The summary line distinguishes nothing checked yet from checked, and clean -- an empty list alone would report a project as healthy on the strength of never having looked at it.

Writing rules, the headless -executeMethod run, and the suppression file format are covered in Asset Validation.

Prefab Checker

Tools > Wallstop Studios > Unity Helpers > Prefab Checker

Someone deletes a script, and forty prefabs quietly acquire a "Missing (Mono Script)" slot that only shows up when a scene loads. Prefab Checker walks every prefab under a folder and logs the problems with clickable links.

  1. Click Add Folder and pick Assets/Prefabs (already added for you if that folder exists).
  2. Leave the default checks on and click Run Checks.
  3. Click a console line to select the offending prefab.
Check Reports Default
Missing Scripts Components whose script asset is gone On
Nulls in Lists/Arrays null elements inside a serialized list or array On
Missing Required Components A [RequireComponent] dependency that is not on the prefab On
Null Object References Unassigned UnityEngine.Object fields On
Only if [ValidateAssignment] Narrows the check above to annotated fields only On
Disabled Root GameObject Prefab roots saved inactive On
Empty String Fields Serialized string fields left empty Off
Disabled Components Behaviour components saved disabled Off

Everything except Empty String Fields is logged as an error; empty strings are warnings.

Editor scripts can scan without opening the window. Pass project-relative folder paths and explicit options to PrefabChecker.ScanFolders; the returned result includes counts, any input error, and findings grouped by prefab path. This call shows no dialogs, records no folder history, and makes no asset changes. It uses the same default checks as the window; set the options you need before the call. The window still logs findings and can export its last report.

using WallstopStudios.UnityHelpers.Editor;

PrefabChecker.ScanOptions options = new PrefabChecker.ScanOptions
{
    CheckDisabledComponents = true,
};
PrefabChecker.ScanResult result = PrefabChecker.ScanFolders(
    new[] { "Assets/Prefabs" },
    options
);
if (result.Error == null)
{
    foreach (PrefabChecker.ScanReport.Item item in result.Report.items)
    {
        UnityEngine.Debug.Log($"{item.path}: {string.Join("; ", item.messages)}");
    }
}

Narrowing a run: Include Labels (comma) and Exclude Labels (comma) filter by asset label, and Deny Component Types (comma names) skips those component types during checks (a debug-only behaviour, say).

Fixing and reporting: Fix Missing Scripts strips dead component slots, but it stays disabled until you tick Enable Auto-fix options — the gate is deliberate, because the fix deletes data. Editor scripts can call PrefabChecker.TryRemoveMissingScripts with explicit Assets folders. A dry run counts affected prefabs and missing scripts without changing assets; an apply run removes missing slots from prefab roots and children. The call opens no window or dialog, and reports an error through its final output parameter. Folder paths accept forward or back slashes. It may leave earlier prefabs repaired if a later prefab fails. Applying the repair writes prefab files, which Unity Undo cannot fully reverse.

bool repaired = PrefabChecker.TryRemoveMissingScripts(
    new[] { "Assets/Prefabs" },
    false,
    out int changedPrefabs,
    out int removedScripts,
    out string repairError
);

Export Report (JSON) and Export Report (CSV) write the same findings to a file for a build step or a review. The complete report is staged before replacing an existing file.

Annotate the fields you actually care about so the null check stays useful:

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public sealed class Turret : MonoBehaviour
{
    [ValidateAssignment]
    public Transform muzzle;

    [ValidateAssignment]
    public GameObject projectilePrefab;

    // Not annotated: optional, and a null here is not a bug.
    public AudioClip idleHum;
}

Visual Reference

Prefab Checker window showing validation check options

Prefab Checker with configurable validation checks and folder selection

Prefab Checker results in console showing detected issues

Console output showing detected prefab issues with clickable links


Analyzer Policies

Tools > Wallstop Studios > Unity Helpers > Analyzer Policies

Use this window to make the package's compiler guidance explicit for scripts under Assets:

  • Enable All writes every WUH001 through WUH018 rule as Warning.
  • Disable All writes every rule as None, including diagnostics that are on by default.
  • Refresh re-reads Assets/Default.ruleset and reports missing, duplicate, mixed, malformed, or unknown Unity Helpers policy entries.

The tool preserves rule groups owned by other analyzers. It refuses malformed XML instead of overwriting it. Ruleset writes are staged before replacement, and failures are reported without leaving a partly written ruleset. The ruleset is a project file change outside Unity's undo system, so commit it when the policy should be shared with the team. The window lists every diagnostic with a short explanation; the Analyzer reference has examples and fixes.

Editor scripts and batch mode can call AnalyzerPolicyAPI.TrySetEnabled(true, out string message) to enable all Unity Helpers policies in Assets/Default.ruleset, or pass false to disable them. The overload accepting an asset path manages another .ruleset under Assets. Both calls import the written ruleset immediately and return false with a message when the path, ruleset, write, or import fails. Ruleset file changes cannot be reversed through Unity Undo.

Unity Method Analyzer

Tools > Wallstop Studios > Unity Helpers > Unity Method Analyzer

A private void Start() in a subclass whose base class also has private void Start() compiles cleanly, and Unity calls only one of them. Roslyn reports that family of mistake during compilation; this window presents the compiler diagnostics.

  1. Compile the project, then select Assets/Scripts in the report directories.
  2. Click Refresh Report; use Recompile Scripts when a fresh compiler snapshot is needed.
  3. Double-click a result to jump to the line.
It finds Example
A missing override A derived method hiding the base one instead of overriding it
A wrong lifecycle signature OnCollisionEnter(Collider c) where Unity calls OnCollisionEnter(Collision)
A shadowed lifecycle method Base and derived both declare private void Start()
A static lifecycle method static void Awake(), which Unity never calls

Results group by file, severity or category, and export as JSON or Markdown. Editor scripts can use UnityMethodAnalyzerReportExportAPI.TryExportMarkdown or TryExportJson with explicit findings, coverage status, and output path. The status identifies missing coverage or compiler errors; an empty partial report is not a passing CI gate.

Silence a deliberate case — a test fixture that exists precisely to be wrong — with [SuppressAnalyzer]:

[SuppressAnalyzer("Test fixture for analyzer validation")]
public sealed class DeliberatelyShadowedStart : BaseBehaviour
{
    private void Start() { }
}

Full reference: Unity Method Analyzer Guide

Visual Demo

Unity Method Analyzer window showing detected issues

The report displays categorized compiler issues; refresh controls replace the former source scan


Serialized Field Validator

Tools > Wallstop Studios > Unity Helpers > Validate Serialized Fields In Selection

Unity declines to serialize some types and declines silently. Select the script and run the command; it names every field that will be empty after the next domain reload, and what to use instead.

public sealed class Loot : ScriptableObject
{
    public Dictionary<string, int> drops;   // gone on the next domain reload
    public (int, float) weightedRoll;       // gone, and it IS [Serializable]
}

SerializedObject.FindProperty("drops") returns null, JsonUtility.ToJson omits the field, and nothing is logged. Whatever a designer authored into it is gone, usually discovered from a build. [Serializable] is not the discriminator, which is what makes the rule hard to work out from outside: ValueTuple<int, float> carries it and is dropped anyway.

The console tells you the fix:

Loot.drops is declared as Dictionary<string, int>, which Unity does not serialize. Anything
authored into it is gone on the next domain reload. Use SerializableDictionary<string, int>
instead.
Declared as Use instead
Dictionary<TKey, TValue> SerializableDictionary<TKey, TValue>
SortedDictionary<TKey, TValue> SerializableSortedDictionary<TKey, TValue>
HashSet<T> SerializableHashSet<T>
SortedSet<T> SerializableSortedSet<T>
Nullable<T> SerializableNullable<T>
ValueTuple<...>, Tuple<...> SerializableValueTuple<...>
KeyValuePair<TKey, TValue> SerializableValueTuple<TKey, TValue>
Queue<T>, Stack<T>, LinkedList<T> List<T>

How it decides: it constructs the type, wraps it in a SerializedObject, and asks which fields arrived. That is Unity's own answer rather than a model of its rules, so it cannot misreport the generic user types Unity has serialized since 2020. Every public or [SerializeField] field with no SerializedProperty is reported, inherited ones included.

Silencing a field: mark it [NonSerialized]. That is the standard way to say "runtime only", Unity honours it, and so does this.

Why the selection and not the whole project: validating a type means constructing one, and constructing every type in a project runs the startup half of the project. Select what you just wrote — a MonoScript, a prefab, a scene object or an asset. A prefab contributes every component on it.


Custom Component Editors

MatchColliderToSprite Editor

MatchColliderToSprite reshapes a PolygonCollider2D through its synchronous RebuildCollider() command. Inspector edits and the MatchColliderToSprite button run that command with a Match Collider To Sprite undo step for the component and selected collider, including prefab overrides. Its runtime update loop also rebuilds when the sprite or trace settings change.

Editor OnValidate() only resolves component references. Editor tools that previously called it to regenerate geometry must call RebuildCollider() and record Undo for both objects first. This keeps Undo/Redo validation from replacing the polygon Unity just restored. See MatchColliderToSprite for exact-art settings and programmatic use.


PolygonCollider2DOptimizer Editor

An auto-generated PolygonCollider2D from a detailed sprite can carry hundreds of points, and Physics 2D pays for every one of them. This inspector shows a single Tolerance field and an Optimize button that simplifies the outline in place.

Raise Tolerance until the silhouette starts to visibly change, then back off one step. 0.1–0.5 is the usual working range; above 2.0 you are trading shape for point count. Set Tolerance to 0 and click Optimize to restore the original outline — the component keeps it. Optimize is also recorded as an Optimize Polygon Collider undo step.

See PolygonCollider2DOptimizer for the component.


EnhancedImage Editor

EnhancedImage extends Unity's Image with an HDR tint and a shape mask, both of which need the package's BackgroundMask material to do anything. The custom inspector is mostly there to notice when that material is missing.

  • If the component is still on Unity's Default UI Material, a yellow Incorrect Material Detected - Try Fix? button appears. Clicking it finds and assigns Shaders/Materials/BackgroundMask-Material.mat as a Fix EnhancedImage Material undo step.
  • HDR Color multiplies the image; push intensity above 1.0 to make it bloom under post-processing.
  • Shape Mask takes a Texture2D. It only does something if the assigned material's shader exposes a _ShapeMask texture property — the inspector says so in the tooltip rather than failing silently.

See EnhancedImage for the component and its runtime API.


Property Drawers & Attributes

These are the inspector attributes the tools above lean on most. The Inspector documentation covers the full set. Collection inspectors release their cached button textures on script reload and editor exit; the next draw rebuilds the styles as needed.

WInLineEditor Property Drawer

Tuning an ability means selecting the AbilityConfig asset, editing it, then selecting the character again to see the result. [WInLineEditor] draws the referenced asset's own inspector underneath the field so you never leave.

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public sealed class AbilityConfig : ScriptableObject { }

public sealed class AbilityLoadout : MonoBehaviour
{
    // Inherits the project-wide default from Unity Helpers Settings.
    [WInLineEditor]
    public AbilityConfig primary;

    // Always open, no object picker, taller body.
    [WInLineEditor(WInLineEditorMode.AlwaysExpanded, inspectorHeight: 260f, drawObjectField: false)]
    public AbilityConfig secondary;

    // Collapsed by default, with the texture preview turned on.
    [WInLineEditor(WInLineEditorMode.FoldoutCollapsed, drawPreview: true, previewHeight: 96f)]
    public Texture2D icon;
}
Mode Behaviour
UseSettings Default. Follows the Inline Editors setting in Project Settings.
AlwaysExpanded Inspector is always drawn.
FoldoutExpanded Foldout, open on first draw.
FoldoutCollapsed Foldout, closed on first draw.

Constructor parameters, in positional order: mode, inspectorHeight (default 200, floored at 160), drawPreview (false), previewHeight (64, floored at 40), drawObjectField (true), drawHeader (true), enableScrolling (true), minInspectorWidth (520; below this width a horizontal scrollbar appears, 0 disables that). Prefer named arguments — the order is easy to get wrong.

Field-only. Unlike the other attributes here, [WInLineEditor] targets fields, so on an auto-property you need [field: WInLineEditor].

It reuses Unity's own editor for the target, so custom inspectors, validation and undo all still work. Foldout animation speed lives in Inspector Settings.

Visual Reference

WInLineEditor showing embedded ScriptableObject inspector and expansion

WInLineEditor with embedded inspector for a ScriptableObject reference with foldout and collapse transitions


WShowIf Property Drawer

Half the fields on a spawner only matter when it is set to burst mode, and showing them the rest of the time is how designers set the wrong one. [WShowIf] hides a field until its condition holds.

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public sealed class EnemySpawner : MonoBehaviour
{
    public enum SpawnMode
    {
        Continuous = 1,
        Burst = 2,
    }

    public SpawnMode mode = SpawnMode.Continuous;

    [WShowIf(nameof(mode), expectedValues = new object[] { SpawnMode.Burst })]
    public int burstSize = 5;

    [WShowIf(nameof(mode), inverse: true, expectedValues: new object[] { SpawnMode.Burst })]
    public float spawnsPerSecond = 1f;

    public bool useSpawnBudget;

    [WShowIf(nameof(useSpawnBudget))]
    public int maxAlive = 20;

    [WShowIf(nameof(maxAlive), WShowIfComparison.GreaterThan, 50)]
    public bool warnAboutPerformance;

    public string overrideLayerName;

    [WShowIf(nameof(overrideLayerName), WShowIfComparison.IsNotNullOrEmpty)]
    public int sortingOrder;
}

inverse and comparison are constructor arguments (inverse: true), not object-initializer assignments — inverse = true will not compile. expectedValues accepts either form.

Comparison Use for
Equal, NotEqual Any equatable value; the default is Equal
GreaterThan, GreaterThanOrEqual Numbers and any IComparable
LessThan, LessThanOrEqual Numbers and any IComparable
IsNull, IsNotNull Object references
IsNullOrEmpty, IsNotNullOrEmpty Strings and collections

WShowIfComparison.Unknown exists only for backward compatibility and is marked obsolete.

The condition can be a field, a property (including a [field: SerializeField] auto-property), a parameterless method, or a dotted path into a nested serialized type:

[WShowIf(nameof(damage) + "." + nameof(DamageProfile.isCritical))]
public float criticalMultiplier;

Visual Reference

WShowIf showing field visibility changing based on toggle

Field appears/disappears based on enum toggle state


StringInList Property Drawer

A string field that has to match an animator state name or an asset label is a typo waiting to happen. [StringInList] turns it into a dropdown of the values that are actually valid.

using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
using WallstopStudios.UnityHelpers.Core.Helper;

public sealed class SpawnRule : MonoBehaviour
{
    // Fixed set.
    [StringInList("Idle", "Run", "Jump")]
    public string startState = "Idle";

    // Every asset label in the project, kept current by the Sprite Label Processor.
    [StringInList(typeof(Helpers), nameof(Helpers.GetAllSpriteLabelNames))]
    public List<string> requiredLabels = new();

    // A provider on this same type.
    [StringInList(nameof(GetPoolNames))]
    public string poolName;

    private IEnumerable<string> GetPoolNames()
    {
        yield return "Grunt";
        yield return "Archer";
        yield return "Brute";
    }
}

That second field is how the package itself populates the label pickers in Sprite Atlas Generator.

A provider method takes no parameters and returns string[] or IEnumerable<string>. It may be private, and it may be static or an instance method. [StringInList(typeof(T), "Method")] looks for a static method on T first, then an instance one; [StringInList("Method")] looks on the decorated object's own type.

  • On an int field the dropdown selects by index.
  • On an array or list you get a UI Toolkit list with a dropdown per element, plus add, remove and drag-to-reorder.
  • Typing filters the list. Tab accepts the highlighted match, Enter just fills the search box.
  • Long lists paginate. The page size is Project Settings ▸ Wallstop Studios ▸ Unity Helpers ▸ StringInList Page Size (default 25), shared with the SerializableType drawer.

Visual Reference

StringInList dropdown with search and pagination

StringInList dropdown showing search filtering and pagination

StringInList with list field showing add/remove/reorder

StringInList on a List field with per-element dropdowns and drag reordering


IntDropDown Property Drawer

For an int that only has a handful of legal values, a free text field lets someone type 3000 into a texture size. [IntDropDown] restricts it to the list.

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public sealed class AtlasSettings : MonoBehaviour
{
    [IntDropDown(32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384)]
    public int maxTextureSize = 2048;

    [IntDropDown(0, 2, 4, 8, 16, 32)]
    public int padding = 4;
}

Those two fields are lifted from ScriptableSpriteAtlas, which is exactly how Sprite Atlas Generator keeps its packing settings legal.

The attribute is spelled IntDropDown with a capital D. Like StringInList it also accepts a provider: [IntDropDown(typeof(T), nameof(T.Method))] or [IntDropDown(nameof(Method))], returning int[] or IEnumerable<int>.

Visual Reference

IntDropDown showing texture size options

IntDropDown for texture sizes showing power-of-two options


WValueDropDown Property Drawer

StringInList and IntDropDown for everything else — floats, bools, enums, Unity structs, and your own serializable types.

using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public sealed class WeaponTuning : MonoBehaviour
{
    [WValueDropDown(0.5f, 1.0f, 1.5f, 2.0f)]
    public float damageMultiplier = 1.0f;

    [WValueDropDown("Easy", "Normal", "Hard", "Insane")]
    public string difficulty = "Normal";

    // Instance provider: the options depend on this component's own state.
    [WValueDropDown(nameof(GetMuzzleOffsets), typeof(Vector2))]
    public Vector2 muzzleOffset;

    public List<Vector2> configuredMuzzles = new();

    private IEnumerable<Vector2> GetMuzzleOffsets()
    {
        return configuredMuzzles;
    }
}

There is a params overload for every primitive (bool, char, all the integer widths, float, double, string). For anything else, use a provider:

  • [WValueDropDown(typeof(Library), nameof(Library.GetPresets))] — static provider, element type inferred from the return type.
  • [WValueDropDown(nameof(GetOptions), typeof(Vector2))] — instance provider on the decorated type, value type stated.
  • [WValueDropDown(typeof(Preset), presetA, presetB)] — an inline list of custom-typed values.

Providers follow the same rules as StringInList: parameterless, may be private, returning an array or IEnumerable. Labels come from ToString(), so give a custom type a readable one.

Full constructor reference: Inspector Selection Attributes.

Visual Reference

WValueDropDown with predefined values

WValueDropDown showing predefined integer, float, and string values


WReadOnly Property Drawer

Shows a serialized value in the inspector without letting anyone edit it — useful for a number your code derives and a designer should only ever read.

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;

public sealed class Health : MonoBehaviour
{
    public int baseHealth = 100;
    public int equipmentBonus;

    [WReadOnly]
    public int totalHealth;

    private void OnValidate()
    {
        totalHealth = baseHealth + equipmentBonus;
    }
}

No parameters and nothing to configure. It works on any serialized type and keeps the field's normal height and layout.

For attributes that reject a bad value rather than just displaying it, see Inspector Validation Attributes.

Visual Reference

WReadOnly showing grayed-out calculated value in inspector

WReadOnly field showing totalHealth as a non-editable calculated value


Automation & Utilities

ScriptableObject Singleton Creator

Runs automatically on editor load. No menu item.

ScriptableObjectSingleton<T> loads its asset from Resources, which means a new teammate's first run of the game fails on a settings asset nobody committed. This watches for singleton types with no asset and creates them, and relocates any that end up in the wrong folder.

using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
using WallstopStudios.UnityHelpers.Utils;

public sealed class GameSettings : ScriptableObjectSingleton<GameSettings>
{
    public float masterVolume = 1f;
    public bool enableVSync = true;
}

[ScriptableSingletonPath("Settings/Audio")]
public sealed class MusicSettings : ScriptableObjectSingleton<MusicSettings>
{
    public float musicVolume = 0.8f;
}

Write those two files, let the editor reload, and you have:

Assets/
  Resources/
    GameSettings.asset                   # no [ScriptableSingletonPath]
    Settings/
      Audio/
        MusicSettings.asset              # [ScriptableSingletonPath("Settings/Audio")]

Then GameSettings.Instance.masterVolume works in play mode and in a build, with nothing to remember.

Move GameSettings.asset somewhere else by hand and the next domain reload moves it back — the attribute is the source of truth for where it lives.

See Singleton Utilities for the runtime base class, its lookup order and Odin compatibility.


Sprite Label Processor

Runs automatically on sprite import. No menu item.

Asset labels are a good way to say "this sprite belongs in the UI atlas", but there is no cheap way to ask Unity for every label in the project — you would have to load every asset. This AssetPostprocessor maintains the list as sprites are imported, so a dropdown can be populated instantly.

That is what makes this one line work:

using System.Collections.Generic;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Attributes;
using WallstopStudios.UnityHelpers.Core.Helper;

public sealed class AtlasRule : MonoBehaviour
{
    [StringInList(typeof(Helpers), nameof(Helpers.GetAllSpriteLabelNames))]
    public List<string> labels = new();
}

Helpers.GetAllSpriteLabelNames() returns the cached array; the GetAllSpriteLabelNames(List<string> destination) overload fills a list you own instead of allocating. Sprite Atlas Generator uses exactly this for its label pickers.

Editor-only. The cache is built by an AssetPostprocessor, so both methods return empty in a player build, in batch mode, and in CI. Do not build runtime behaviour on them.

Only .png, .jpg and .jpeg assets under Assets whose importer type is Sprite contribute labels, and the result is sorted so a dropdown is stable between sessions.


Request Script Compilation

Tools > Wallstop Studios > Unity Helpers > Request Script Compilation — Ctrl/Cmd + Alt + R

You generated a .cs file from outside Unity — a codegen step, a script pulled by a package tool — and the editor has not noticed. Rather than touching a file to force a rebuild, press the shortcut.

It runs a forced synchronous AssetDatabase.Refresh (so the new file is imported first) and then CompilationPipeline.RequestScriptCompilation(). If Unity is already compiling it logs Script compilation already in progress; manual request skipped. and does nothing, so leaning on the shortcut is harmless.

Rebind it under Edit > Shortcuts in Wallstop Studios ▸ Request Script Compilation.

Visual Reference

Request Script Recompilation menu item

Request Script Recompilation shortcut


Project Settings: Unity Helpers

Edit > Project Settings > Wallstop Studios > Unity Helpers

One panel for the package's project-wide defaults: inspector pagination, inline editor behaviour, button and toggle colours, and the coroutine wait buffers.

Coroutine wait buffers. yield return new WaitForSeconds(0.25f) allocates every time it runs. The package pools those instructions, and this panel controls the pool: a quantization step (how finely wait times are bucketed), a cap on distinct cached entries, and whether the cap evicts least-recently-used. Settings are written to Assets/Resources/Wallstop Studios/Unity Helpers/UnityHelpersBufferSettings.asset and applied on domain reload and player start, so they ship with the build.

Inspector pagination. StringInListPageSize (default 25) sets how many options a StringInList dropdown shows per page; EnumToggleButtonsPageSize (default 15) does the same for [WEnumToggleButtons] grids. Raise them if lists feel cramped, lower them if a big enum makes the inspector sluggish. Individual fields can override both.

Full reference: Inspector Settings.

Visual Reference

Project Settings panel for Unity Helpers

Centralized configuration panel in Unity's Project Settings


Attribute Metadata Cache Generator

Runs automatically on editor load and after every recompile. No menu item.

The Effects system and the relational component attributes resolve fields by reflection. Doing that on the first frame is a visible stall, and on IL2CPP some of it cannot be done at all. This walks your types at edit time and bakes the answers into an AttributeMetadataCache asset that ships with the build.

The scan filters each discovered type once and avoids additional temporary query stages during reloads.

You do not call the generator. It runs on load, and the AttributeMetadataCache asset's inspector has a Purge & Refresh Cache button for when you want to force it.

Read the baked data if you are building tooling of your own:

using System;
using UnityEngine;
using WallstopStudios.UnityHelpers.Tags;

public static class AttributeInspectorSupport
{
    public static void Dump(Type componentType)
    {
        AttributeMetadataCache cache = AttributeMetadataCache.Instance;

        // Every attribute name declared anywhere in the project.
        string[] all = cache.AllAttributeNames;

        if (cache.TryGetFieldNames(componentType, out string[] fieldNames))
        {
            Debug.Log($"{componentType.Name} declares {fieldNames.Length} of {all.Length} attributes.");
        }

        if (
            cache.TryGetRelationalFields(
                componentType,
                out AttributeMetadataCache.RelationalFieldMetadata[] relational
            )
        )
        {
            foreach (AttributeMetadataCache.RelationalFieldMetadata field in relational)
            {
                Debug.Log($"{field.fieldName} is a {field.attributeKind} field.");
            }
        }
    }
}

What gets baked: the attribute fields on every AttributesComponent type, the [ParentComponent] / [ChildComponent] / [SiblingComponent] metadata, assembly-qualified type names so they resolve at runtime, whether each field is a single value, array, List or HashSet, and whether its element type is an interface.


Editor Utilities

WallstopStudios.UnityHelpers.Editor.Utils.EditorUtilities

A wizard that creates an asset should create it where the user is looking, not in Assets/. Unity does not expose the Project window's selected folder, so this does.

using UnityEditor;
using UnityEngine;
using WallstopStudios.UnityHelpers.Editor.Utils;

public static class CreateAbilityAsset
{
    [MenuItem("Assets/Create/Game/Ability Config")]
    private static void Create()
    {
        AbilityConfig asset = ScriptableObject.CreateInstance<AbilityConfig>();

        string folder = EditorUtilities.GetCurrentPathOfProjectWindow();
        string directory = string.IsNullOrEmpty(folder) ? "Assets" : folder;

        AssetDatabase.CreateAsset(
            asset,
            AssetDatabase.GenerateUniqueAssetPath($"{directory}/NewAbilityConfig.asset")
        );
        AssetDatabase.SaveAssets();
        Selection.activeObject = asset;
    }
}

GetCurrentPathOfProjectWindow() returns an asset-relative path such as Assets/Data/Abilities, or an empty string when it cannot determine one — always have a fallback, as above. It reaches an internal Unity API by reflection, so treat the empty-string case as normal rather than exceptional.

IsInvokedByTestRunner() reports whether this editor process was launched with Unity Test Runner command-line arguments, by looking for runTests, testResults or testPlatform in the command line. A run started from the in-editor Test Runner window does not carry those arguments and is not detected — this answers "was this editor launched to run tests", not "is a test session running right now". A window's OnEnable runs during a command-line test session too, so work that consumes fixtures or writes logs should be gated on it — together with Application.isBatchMode and Helpers.IsRunningInContinuousIntegration, the other two legs of the same question:

using UnityEditor;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Helper;
using WallstopStudios.UnityHelpers.Editor.Utils;

public sealed class AssetReportWindow : EditorWindow
{
    private void OnEnable()
    {
        if (
            Application.isBatchMode
            || EditorUtilities.IsInvokedByTestRunner()
            || Helpers.IsRunningInContinuousIntegration
        )
        {
            return;
        }

        ImportReports();
    }

    private void ImportReports() { /* fixture-consuming work */ }
}

It answers false rather than throwing, even on a host that refuses to share its command line. If all you want is to skip dialogs, EditorUi already folds this in behind EditorUi.Confirm/EditorUi.Info; reach for this predicate when the work itself, not just the prompt, should be skipped.

IndentLevelScope.Indent() and IndentLevelScope.AtLevel(level) change EditorGUI.indentLevel without per-scope garbage after the shared owner has warmed to the maximum concurrent nesting depth. Copies are safe to dispose more than once. Nested scopes also restore the newest still-active level when copies are disposed out of order, rather than replaying a stale global value over an inner drawer.


Failed Tests Exporter

Tools > Wallstop Studios > Unity Helpers > Export Failed Tests / Clear Failed Tests

A flaky test that fails once in twenty runs is invisible if the only record is the Test Runner window you already closed. Enable this in Edit > Project Settings > Wallstop Studios > Unity Helpers and every failure — name, message, stack trace — is captured and written to failed-tests-YYYY-MM-DD-HHmmss.txt in a folder you choose (project root by default).

Disabled by default. Both menu items are greyed out until there are failures to act on.

Full setup and API: Failed Tests Exporter.


Test Run Reporter

Tools > Wallstop Studios > Unity Helpers > Run EditMode Tests With Summary / Run PlayMode Tests With Summary

Starts a test run and returns immediately, having first opened <project>/Temp/unity-helpers-test-run-<mode>.txt with a running marker. When the run ends the file is rewritten with one summary line, one line per assembly, and one line per failure carrying its message and source location. A process driving an editor it does not own polls that file instead of scraping the console, which can be cleared out from under it.

EditMode and PlayMode write different files, a second run is refused while one is in flight, and the callbacks are re-registered after the domain reload a PlayMode run causes.

Format, poller example and recovery from a lost run: Test Run Reporter.


MultiFile Selector

MultiFileSelectorElement is the UI Toolkit control the tools above use for "pick several files or folders". It is public, so your own editor windows can use it too.

In the editor it remembers selections through EditorPrefs and offers Reveal in Finder / Show in Explorer. In a player build it falls back to PlayerPrefs and enumerates files under the application's data root, dropping the editor-only affordances.

Persistence is opt-in per element, via a persistenceKey. Stored scopes are managed from Tools > Wallstop Studios > Unity Helpers > Multi-File Selector Persistence, which can Run Cleanup Now or drop scopes unused for more than Max age (days) (default 30) on editor startup.

There is a runnable example in the UI Toolkit - MultiFile Selector (Editor) sample, importable from the Package Manager.


Quick Reference

Menu items

Everything under Tools > Wallstop Studios > Unity Helpers:

Menu item Section
Animation Copier Animation Copier
Animation Creator Animation Creator
AnimationEvent Editor Animation Event Editor
Clear Failed Tests Failed Tests Exporter
Export Failed Tests Failed Tests Exporter
Fit Texture Size Fit Texture Size
Image Blur Image Blur Tool
Multi-File Selector Persistence MultiFile Selector
Prefab Checker Prefab Checker
Proto Schema Exporter Serialization
Request Script Compilation Request Script Compilation
Run EditMode Tests With Summary Test Run Reporter
Run PlayMode Tests With Summary Test Run Reporter
Sprite Animation Editor Sprite Animation Editor
Sprite Atlas Generator Sprite Atlas Generator
Sprite Cropper Sprite Cropper
Sprite Pivot Adjuster Sprite Pivot Adjuster
Sprite Settings Applier Sprite Settings Applier
Sprite Sheet Animation Creator Sprite Sheet Animation Creator
Sprite Sheet Extractor Sprite Sheet Extractor
Texture Resizer Texture Resizer
Texture Settings Applier Texture Settings Applier
Unity Method Analyzer Unity Method Analyzer
Validate Serialized Fields In Selection Serialized Field Validator

Elsewhere:

  • Assets > Create > Wallstop Studios > Unity Helpers > Scriptable Sprite Atlas Config — Sprite Atlas Generator
  • Edit > Project Settings > Wallstop Studios > Unity Helpers — Project Settings

Runs with no menu item: ScriptableObject Singleton Creator, Sprite Label Processor, Attribute Metadata Cache Generator, Asset Change Detection.

Shared across the windows: folder fields accept dragged Project-window folders, the folders you pick are remembered per tool and offered again next time, and long operations report progress in the console rather than blocking silently.

Progress bars close even when an operation, asset save, or batch cleanup fails. This includes prefab checks, sprite atlas generation and source settings, sprite and texture settings, texture resizing, sprite reference replacement, and animation copy or delete operations. Canceling a prefab scan keeps findings already collected; canceling a settings batch saves the importer changes already processed. A processing error still stops the operation and may leave earlier changes applied. Reimport and generated-file side effects are not a transaction and cannot be fully reversed with Undo.


What writes to disk

Most of these tools only change import settings, which Unity records as an undo step and which you can revert by reverting the .meta file. These are the ones that write real files:

Tool Effect
Image Blur Tool Writes new files; restores temporary source import-setting changes
Sprite Cropper Writes PNGs; overwrites sources when Overwrite Originals is on
Texture Resizer Overwrites source PNGs unless Output Folder is set
Sprite Sheet Extractor Writes new PNGs; the Danger Zone rewrites referencing assets
Sprite Cropper Danger Zone Rewrites .prefab, .unity, .asset, .mat, .anim, .overrideController
Animation Copier Copies and (in Cleanup) deletes .anim assets
Prefab Checker Read-only unless you enable auto-fix and click Fix Missing Scripts

Everything else — Texture Settings Applier, Sprite Settings Applier, Sprite Pivot Adjuster, Fit Texture Size — touches import settings only and records a named undo step.

None of the tools that rewrite image data have an undo. Commit before running them.


Common workflows

A folder of new character art:

  1. Sprite Sheet Extractor if it arrived as sheets.
  2. Sprite Cropper to trim the padding.
  3. Sprite Settings Applier for PPU and filter mode.
  4. Sprite Pivot Adjuster so the pivots agree.
  5. Animation Creator to build the clips.
  6. Animation Event Editor for footsteps and hitboxes.
  7. Sprite Atlas Generator to pack them.

Before you open a pull request:

  1. Prefab Checker over the prefab folders you touched.
  2. Unity Method Analyzer over Assets/Scripts.
  3. Serialized Field Validator on any new MonoBehaviour or ScriptableObject.

Trimming build size:

  1. Fit Texture Size in GrowAndShrink mode across Assets/Sprites.
  2. Sprite Cropper on anything still padded.
  3. Texture Settings Applier with crunch compression and per-platform max sizes.

When something does not work

Symptom Likely cause
A sprite tool skips files silently It is a multi-sprite texture, or Read/Write Enabled is off. Cropper and Pivot Adjuster need Single sprites.
Scan Folders finds nothing The regex does not match, or the folder path is outside Assets/. Invalid regexes are logged and match nothing.
An atlas looks worse than the source Run Force Uncompressed for N Source Sprites before packing.
Clips landed in the wrong folder Animation Creator writes beside the first sprite. Move them with Animation Copier.
An [AnimationEvent] method is not listed It must return void and take zero or one int / float / string / UnityEngine.Object / enum parameter.
Prefab Checker reports nothing The relevant checks may be off — Empty String Fields and Disabled Components are off by default.
Fix Missing Scripts is greyed out Tick Enable Auto-fix options first.
A tool did nothing in a CI or batch run Dialogs and folder pickers are suppressed in batch mode, so anything that needs one is skipped.

Related documentation

Clone this wiki locally