Summary

Shape and timing decide whether a visual effect reads as an intentional spell or as incidental noise. Shape language governs what the effect looks like frozen in a single frame; timing governs how it unfolds across frames. In a busy competitive scene, both must be disciplined: simple hand-painted shapes with clear silhouettes, paired with a three-stage timeline that tells the player the effect is coming, happening, and ending.

Key ideas

Shape language

Riot’s guidance for VFX texture authoring:

  • Hand-paint all textures with concise, deliberate detail. Photographic textures or overly busy noise fields create visual clutter without adding information.
  • Mix soft and sharp shapes. Soft gradients carry atmosphere and energy; sharp edges define silhouettes and directional intent. Using only one produces either mush or hardness.
  • Well-defined silhouettes. The effect must read as a recognisable shape at a glance — circle, cone, beam, burst — not as a cloud of particles.
  • Simple shapes in busy moments. In teamfights, simpler shapes read faster than complex ones. Complexity belongs in calm moments or in ultimate-tier effects.

Creating movement with the shape layer

Motion blur is not a post-processing afterthought; it is part of the shape authoring. Fast-moving particles without blur look like dropped frames or jitter. With directional blur, the same particles read as purposeful motion. The blur also tells the player where the effect is going, which is gameplay information.

Glow, bloom and trails versus the hitbox

The shape a player reads is the whole bright area on screen, not the mesh or sprite underneath. Glow, Bloom, trails, halos and light falloff all make a projectile look bigger than it is. Riot’s style guide requires visuals to match hitboxes and names redesigns such as Sona’s ultimate, whose art covered more than its real area (Riot Games, League of Legends VFX Style Guide, see source-lol-vfx-style-guide); vfx-design-principles calls this over-representation, which confuses players when an apparent hit misses. Applied to a projectile, that gives a few working rules. They are practice derived from that principle, not quoted guidance, so check them in play:

  • The solid core is the hitbox. Make the brightest, most opaque part of the projectile the same size and shape as its collider. Everything outside it is a secondary element in Riot’s sense: lower value, lower saturation and lower opacity.
  • Power comes from value contrast and motion, not size. Push the value at the centre (see “Illumination as magic” in vfx-value-and-colour), add a hot white core, and use crisp anticipation and impact timing, rather than inflating the glow radius.
  • Keep Bloom from inflating the shape. Bloom spreads every HDR pixel outwards, so a very intense emissive core can read as a ball twice its size. Keep the core’s HDR intensity moderate, keep the Bloom Threshold around 1 so only the core blooms, and tune Bloom with the projectile on a dark and a bright background.
  • Trails show direction, not reach. Taper the trail and fade it quickly, so it reads behind the projectile and never beside it. A wide, bright trail reads as a wider hitbox.
  • Check with the collider visible. Turn on Gizmos in the Game view (or draw the collider in a debug build) and compare it with the effect at full speed and with Bloom on. If players in a playtest dodge “by a hair” and still get hit, or think they were hit when the projectile missed, the visual and the hitbox disagree.

Three-stage timing

Every effect is composed of three stages:

  1. Anticipation — a wind-up or tell that the effect is about to happen. Usually lower saturation, lower opacity, a charging or gathering motion. This is the player’s cue to react.
  2. Main — the payload. The moment of impact, burst, or peak expression. Highest value, saturation, opacity, and size.
  3. Dissipation — the fade out. The effect dies with lower value, saturation, and opacity than the main. Dissipation is a secondary element: it must never compete with the main stage, and it must never linger beyond its purpose.

Fading can be driven by any or all of value, hue, saturation, opacity, and size.

Where the peak goes on an impact. For anything that hits, the main stage (peak size, brightness and opacity) should land on the frame of contact. Anticipation belongs before contact, as the wind-up or tell; it is not a pause inserted after the hit. This is a practical rule derived from the three-stage model above rather than a quoted guideline: the hit flash and hit stop below also start on the contact frame, and a peak that arrives several frames after contact reads as lag rather than build-up, because Swink puts the limit for perceived responsiveness at about 100 ms (Swink, Game Feel, Ch. 17, see source-game-feel and game-feel). At 60 fps that is only six frames. If a hit needs more weight, lengthen the anticipation or the hit stop, not the gap between contact and peak.

Dynamic vs linear timing

Linear curves — constant velocity, constant growth, constant fade — produce mechanical, unsatisfying effects. Dynamic curves (ease-in/ease-out, anticipation overshoot, snappy attack followed by slow decay) produce impact. Riot cites Ekko’s R as a clear example: non-linear timing between cast and explosion blast creates a more powerful moment than a constant ramp would.

This is the same principle as Disney’s timing and “slow in, slow out”: bodies and forces in the real world do not move at constant speed, and game-feel follows the same rule (see game-feel).

Minimising linger time

The most common VFX failure in competitive games is over-persistence. An intensely opaque, long-lingering effect dominates everything around it, even after its gameplay moment has passed. Corrective practice:

  • Fade the main stage quickly after its gameplay window closes.
  • Use transparency even at the peak moment so other effects can still read through it.
  • Treat the dissipation as a suggestion of what happened, not a replay.

The Syndra W comparison in the guide: the version with long linger time blocks the teamfight; the version that fades off quickly lets the rest of the fight remain legible.

Sustained and defensive effects (shields, blocks, buffs)

The three-stage model above was written for one-shot spells. A shield, block or buff is different: its main stage lasts exactly as long as a gameplay state, so its timing must follow that state. Riot’s sources do not cover this case directly. The rules below apply the hitbox-accuracy principle in vfx-design-principles (the visual must match what the game is doing) and the dissipation rules above, so treat them as practice to tune in playtests rather than sourced fact:

  1. Hold full strength for the whole block window. Do not start fading while the block is still active: a shield that looks weaker reads as already breaking, and players stop trusting it. Equally, it must not stay at full strength after the window closes, or players think they are still protected.
  2. Mark each blocked hit with its own short pulse. Show a blocked hit as a brief brightening, ripple or spark burst at the contact point, on top of the steady shield (a hit-flash-length event of a few frames; see the hit flash section below and the ripple recipe in shader-graph-overview). Do not dip or change the shield’s base opacity to show a hit.
  3. When the window ends, fade with an ease-out curve. Drop most of the strength in the first few frames, so the change from “on” to “ending” is obvious at once, then let a faint remainder trail off. Roughly 0.1–0.25 s in total is a reasonable starting range (tentative). This keeps the outro a secondary element, as Riot asks of all dissipation.
  4. Never blink the alpha or toggle the renderer. Rapid on/off reads as flicker or a rendering bug, makes the shield look unreliable, and repeated large flashes run into the WCAG three-flashes limit discussed below. If a long buff needs an “about to expire” warning, use one steady change (a colour shift, a shrinking ring or a countdown arc) instead of blinking.
  5. Dissolve rather than fade uniformly. A uniform alpha fade makes the whole shield semi-transparent at once, which reads as “half a shield”. A dissolve (noise-driven alpha clipping with a bright edge) keeps the remaining surface fully solid while holes spread, so the shape stays readable to the last frame and the glowing edge says “ending”.

In Unity (URP Shader Graph). Enable Alpha Clipping in Graph Settings. Connect a Simple Noise node (UV input) to Alpha and a Float property _Dissolve (0 = whole, 1 = gone) to Alpha Clip Threshold, so pixels whose noise is below the threshold are discarded. For the edge, Add a small _EdgeWidth to _Dissolve, feed it as Edge into a Step whose In is the noise, take One Minus, multiply by an HDR edge colour and add it to Emission. Multiply the edge by Step(Edge 0.001, In _Dissolve) so it only appears while dissolving. A script then holds _Dissolve at 0 during the block and runs the ease-out when it ends:

public class ShieldVisual : MonoBehaviour
{
    [SerializeField] Renderer shield;
    [SerializeField] float fadeSeconds = 0.2f;
    // Ease-out: steep at the start, flat at the end.
    [SerializeField] AnimationCurve easeOut = new(new Keyframe(0f, 0f, 2f, 2f), new Keyframe(1f, 1f, 0f, 0f));
    static readonly int DissolveId = Shader.PropertyToID("_Dissolve");
    Material material;
    Coroutine fading;
 
    void Awake() { material = shield.material; shield.enabled = false; }
 
    public void BlockStart()   // full strength for the whole window
    {
        if (fading != null) { StopCoroutine(fading); fading = null; }
        material.SetFloat(DissolveId, 0f);
        shield.enabled = true;
    }
 
    public void BlockEnd() => fading = StartCoroutine(FadeOut());
 
    IEnumerator FadeOut()
    {
        for (float t = 0f; t < fadeSeconds; t += Time.deltaTime)
        {
            material.SetFloat(DissolveId, easeOut.Evaluate(t / fadeSeconds));
            yield return null;
        }
        shield.enabled = false;
        fading = null;
    }
}

(Needs using System.Collections; and using UnityEngine;.) Call BlockStart and BlockEnd from the code that opens and closes the block window, so the visual and the gameplay state cannot drift apart. For the per-hit pulse, drive a separate _HitPulse property the same way as the HitFlash script below. shield.material creates a per-object material instance, which is fine for one shield; because the fade uses scaled time, it pauses during a hit stop along with the rest of the action.

Hit flash and hit stop: impact timing

A hit flash is a brief switch of the struck object to a contrasting colour (usually solid white, sometimes red or the attack’s element colour). A hit stop (also called hitstop, hit pause, hitlag, freeze frames or “sleep”) briefly freezes or slows the action at the moment of contact. Both are the three-stage timing model compressed into a few frames: the flash is the main stage of an impact, so it must be short, high-contrast and then get out of the way.

What the sources actually give:

  • Hit stop scales with the hit. Sakurai, director of Super Smash Bros., writes that “in general, the more damage an attack inflicts, the longer the hitstop period”, that both fighters freeze for the same time, that special cases get modifiers (the tip of Marth’s sword, electric attacks, less for projectiles), and that he caps the maximum because long freezes slow a four-player game and let a third player strike (Sakurai, Famitsu column vol. 490–491, “Thoughts on Hitstop”, fan translation by Source Gaming, 2015).
  • Concrete numbers from Smash. Community frame-data research documents the hitlag formulas. In Melee hitlag is roughly ⌊damage ÷ 3 + 3⌋ frames, capped at 20; in Ultimate it is roughly ⌊damage × 0.65 + 6⌋ frames (before multipliers), capped at 30 (SmashWiki, “Hitlag”). At 60 fps, a 10% hit is therefore about 6 frames (100 ms) in Melee and about 12 frames (200 ms) in Ultimate. These are reverse-engineered community figures, not official Nintendo documentation.
  • Shake during the freeze. Sakurai makes the struck character vibrate during hitstop (side to side on the ground, up and down in the air), starts the vibration large and shrinks it, keeps the hurtbox still while the sprite shakes, lets the attacker keep moving very slightly, and scales vibration by camera distance (Sakurai, Famitsu column vol. 490–491; the same points are summarised from his 2022 YouTube video on hit stop techniques by Fruzzetti, Nintendo Wire, 12 December 2022).
  • Freeze does not have to be total. The demo code for Jonasson and Purho’s Juicy Breakout treats freeze as a speed multiplier with a fade-in, a hold and a fade-out, tunable from 0 to 320 ms with ramps of up to 160 ms (Jonasson & Purho, Juice It or Lose It, see source-juice-it-or-lose-it).
  • The flash itself. In the same talk, the ball turns white whenever it hits something and then returns to normal; and, together with the ball’s scale and wobble effects, the speakers say “it already feels way better” (Jonasson & Purho, Juice It or Lose It). Neither they nor Riot’s style guide give a flash duration.

Practical starting points (tentative). No source read for this page gives an authoritative flash duration, so treat the following as starting values to tune in playtests, not rules:

EffectStarting value at 60 fpsNotes
Hit flash (solid white)1–3 frames (≈17–50 ms) at full strengthOptionally fade back over another 2–4 frames. Arguably, a flash held much longer than this starts to read as a status effect (poisoned, invulnerable) rather than an impact.
Hit stop, light hit≈2–5 frames (≈33–83 ms)Shorter for fast, frequent hits (rapid fire, combos) so the game does not stutter.
Hit stop, heavy or finishing hit≈6–15 frames (≈100–250 ms)Within the range the Smash formulas produce for typical hits; fighting games cap their longest freezes at 20–30 frames.

Pair the two: start the flash on the same frame as the stop so that the frozen frame shows the flash, and let the flash end on real (unscaled) time. Scale both with the importance of the hit, as Sakurai does, rather than giving every hit the same value.

Accessibility. A single short flash on a small sprite is a low risk, but repeated large-area flashes are not. WCAG 2.3.1 says content should not flash “more than three times in any one second period” unless the flashing is below the general and red flash thresholds, with the safe area defined as roughly 25% of any 10° of visual field (about 341 × 256 CSS pixels at typical viewing distance) (W3C, Understanding Success Criterion 2.3.1: Three Flashes or Below Threshold). The Game Accessibility Guidelines advise avoiding flicker wherever possible and, where flashing is essential, giving a clear warning and investigating an option to disable it (Game Accessibility Guidelines, “Avoid flickering images and repetitive patterns”, see source-game-accessibility-guidelines). For full-screen white flashes, rapid-fire weapons that flash on every hit, or red damage vignettes, provide a “reduce flashing” option that swaps the flash for a smaller tint or outline. See accessibility-and-localisation.

In practice

Unity specifics:

  • Unity Particle System — use Colour over Lifetime, Size over Lifetime, and Velocity over Lifetime curves to shape all three timing stages in data. Mark the attack portion as fast ease-out, the decay as slow ease-in.
  • Stretched Billboard render mode gives cheap directional motion blur on fast-moving particles.
  • Animation curves are authored as AnimationCurve assets and can be shared between particle properties for consistent feel.
  • VFX Graph (URP/HDRP) exposes age-based evaluation explicitly, but its Spawn → Initialize → Update → Output contexts are the per-particle pipeline (when particles are emitted, their starting values, their per-frame simulation, and how they render), not the three timing stages. Anticipation, main, and dissipation are authored instead through spawn timing (the Spawn context’s delay and loop settings, or Event contexts fired from script or Timeline), curves sampled over particle age/lifetime in Update and Output, and often separate systems for each stage (Unity Documentation, Visual Effect Graph: Contexts).
  • For hand-painted flipbooks, use Texture Sheet Animation on a particle system with a single painted sheet. Keep sheets 4×4 or 8×8 to stay in budget.
  • Shader Graph (see shader-graph-overview) can drive dissolve, erosion, or edge-bright shaders off particle age — an easy way to get sharp-yet-soft dissipation without painting 16 frames.
  • For motion blur as a scene effect, URP/HDRP post-processing Motion Blur volumes cover camera-driven motion; per-particle motion is best handled at the particle level.

Hit flash in Unity

SpriteRenderer.color multiplies the sprite’s texture, so it can darken or tint a sprite but cannot push it to pure white. A hit flash therefore needs a shader with a flash property: in Shader Graph, sample the sprite texture, Lerp its colour towards white by a Float property named _FlashAmount, and output to a Sprite (Lit or Unlit) target (see shader-graph-overview). Then set that property per renderer with a MaterialPropertyBlock, so only the struck enemy flashes rather than every object sharing the material:

public class HitFlash : MonoBehaviour
{
    [SerializeField] SpriteRenderer target;
    [SerializeField, Range(0f, 0.2f)] float flashSeconds = 0.05f; // ~3 frames at 60 fps
    static readonly int FlashAmount = Shader.PropertyToID("_FlashAmount");
    MaterialPropertyBlock block;
    Coroutine running;
 
    void Awake() => block = new MaterialPropertyBlock();
 
    public void Flash()
    {
        if (running != null) StopCoroutine(running); // restart, don't stack
        running = StartCoroutine(FlashRoutine());
    }
 
    IEnumerator FlashRoutine()
    {
        SetFlash(1f);
        yield return new WaitForSecondsRealtime(flashSeconds); // still ends during hit stop
        SetFlash(0f);
        running = null;
    }
 
    void SetFlash(float amount)
    {
        target.GetPropertyBlock(block);  // keep other per-renderer overrides
        block.SetFloat(FlashAmount, amount);
        target.SetPropertyBlock(block);
    }
}

(Needs using System.Collections; and using UnityEngine;.) Unity notes that MaterialPropertyBlock is not compatible with the SRP Batcher and can cost performance in URP and HDRP (Unity Technologies, Scripting API: MaterialPropertyBlock). For a handful of objects flashing briefly this is usually acceptable; if hundreds of renderers flash at once, profile with the unity-profiler and consider a material swap instead. On a 3D mesh using the URP Lit shader, the same pattern can drive _EmissionColor or _BaseColor.

Hit stop in Unity

Setting Time.timeScale to 0 freezes everything driven by scaled time. Unity’s documentation notes that FixedUpdate and coroutines waiting on WaitForSeconds do not run while timeScale is 0, so the restore must wait on real time (Unity Technologies, Scripting API: Time.timeScale):

public class HitStop : MonoBehaviour
{
    public static HitStop Instance { get; private set; }
    float restoreScale = 1f;
    Coroutine running;
 
    void Awake() => Instance = this;
 
    public void Stop(float seconds, float slowScale = 0f)
    {
        if (running == null) restoreScale = Time.timeScale; // don't capture an in-progress stop's 0
        else StopCoroutine(running);                         // a new hit restarts the timer
        running = StartCoroutine(StopRoutine(seconds, slowScale));
    }
 
    IEnumerator StopRoutine(float seconds, float slowScale)
    {
        Time.timeScale = slowScale;
        yield return new WaitForSecondsRealtime(seconds); // WaitForSeconds would never finish at 0
        Time.timeScale = restoreScale;
        running = null;
    }
 
    void OnDisable() // coroutines die with the object, so restore time here
    {
        if (running != null) { Time.timeScale = restoreScale; running = null; }
    }
}

Call it as HitStop.Instance.Stop(0.08f); on a heavy hit, or Stop(0.1f, 0.1f) for a slow-motion “almost freeze” in the style of Juicy Breakout.

Pitfalls:

  • Never leave timeScale at 0. Restore it on real time, guard against overlapping stops (the naive version captures 0 as the “previous” scale when two hits land together and freezes the game permanently), and restore in OnDisable because a disabled or destroyed object’s coroutines stop running.
  • Pause menus. If the player can pause during a hit stop, decide which system owns timeScale. A simple rule: the pause menu cancels any running hit stop, sets its own value, and remembers the scale to restore.
  • Use unscaled time for things that must keep moving. UI animations, menu timers and the flash itself should use Time.unscaledDeltaTime or WaitForSecondsRealtime; set UI Animators to AnimatorUpdateMode.UnscaledTime (“updates independently of Time.timeScale”) (Unity Technologies, Scripting API: AnimatorUpdateMode). Conversely, gameplay timers (cooldowns, combo windows) should use scaled time so that they pause with the action.
  • Physics. FixedUpdate does not run at timeScale 0, so rigidbodies freeze in place, which is usually the desired effect. If you use a slow slowScale instead of 0, physics steps less often per rendered frame and can look choppy; Unity’s documentation says that to keep the number of FixedUpdate callbacks per frame constant you must also multiply Time.fixedDeltaTime by timeScale (and restore it afterwards) (Unity Technologies, Scripting API: Time.timeScale).
  • Audio keeps playing. Changing timeScale is not how Unity pauses sound; audio is paused separately with AudioListener.pause (Unity Technologies, Scripting API: AudioListener.pause). An impact sound therefore plays through the hit stop, which is usually what you want.
  • Local freezes. For hit stop that affects only the two fighters (Sakurai’s approach), freeze those characters’ own animation speed and movement instead of the global clock, so that the rest of the scene keeps moving.

Evidence

“A combination of hand-painted textures with a combination of soft and hard defining lines work best. Avoid using photographs texuture or visuals with superfluous detail because it creates unnecessary noise.” (Riot Games, League of Legends VFX Style Guide, see source-lol-vfx-style-guide)

“All effects should have anticipation and dissipation. Outros should be considered as a secondary effect; with a lower value, saturation, and opacity.” (Riot, see source-lol-vfx-style-guide)

“Fast moving particles without motion blur end up creating visual noise and illusion of frame drop.” (Riot, see source-lol-vfx-style-guide)

“We intentionally minimize an effect’s linger duration to reduce visual noise for team fights.” (Riot, on Syndra W comparison, see source-lol-vfx-style-guide)

“In general, the more damage an attack inflicts, the longer the hitstop period.” (Sakurai, Famitsu column vol. 490–491, Source Gaming translation, 2015)

“Regardless of how much damage an attack inflicts, the hitstop period will never exceed this limit.” (Sakurai, on capping hitstop in Smash, same column)

“The ball will go white every time it hits something and then it’s going to scale back to its normal.” (Jonasson & Purho, Juice It or Lose It, see source-juice-it-or-lose-it)

Implications

  • VFX timing is design information, not just art polish. Anticipation length is effectively the player’s reaction window; it belongs in combat tuning, not only in art review.
  • Linger time should be treated as a budget, not a default. Every on-screen effect is spending from a shared readability budget.
  • Dynamic curves take more authoring effort than linear ones but pay back in perceived power. Linear curves are usually a sign that no one has passed on the effect with timing in mind.
  • Motion blur on particles is one of the cheapest upgrades in a VFX pass and is almost always worth doing.
  • Impact timing should be data, not constants: expose flash and hit-stop durations per attack (or per damage band) so that designers can scale them with the hit and tune them in playtests, as Sakurai’s damage-scaled, capped hitstop suggests.
  • A flash or freeze that fires on every rapid hit stops being emphasis and becomes noise, and in the case of large flashes a photosensitivity risk. Reserve the strongest impact timing for the hits that matter.

Open questions

  • How should linger times be tuned for spectator modes and replays, where slower timing might help viewers read what happened?
  • What flash duration do players actually perceive as a hit rather than a flicker? No controlled study was found for this page; the frame ranges above are tuning starting points.
  • At what point does heavy anticipation become telegraphing that breaks competitive balance (e.g. windows so long that high-level players always dodge)?
  • How do these rules change in VR, where motion blur can induce cybersickness and anticipation/dissipation timings interact with embodied perception?