Skip to main content

Preview

Plays emitters in Roblox Studio without starting a playtest, so you can change a property and see the result a few seconds later. It's a module inside Spray that nothing else requires, so it never runs in your game.

Select a ParticleEmitter in the Explorer, or a Frame or ScreenGui that has some inside, then paste this into Studio's command bar and press Enter:

require(game.ReplicatedStorage.Packages.Spray.Preview).Play()

The selected emitters play in bursts, over and over, right in the edit viewport. This works for UI in StarterGui, because Studio draws StarterGui on top of the viewport while you edit. Preview reads the emitters when it starts, so after changing a property, run Preview.Play again to see it.

To stop:

require(game.ReplicatedStorage.Packages.Spray.Preview).Stop()
Stop before you save

To make the emitters visible, Preview turns on any hidden UI above them (Visible on GuiObjects, Enabled on ScreenGuis), and Preview.Stop turns it back off. If you save the place before stopping, that UI is saved turned on.

Every function returns a one-line message saying what it did, such as how many emitters are playing. Wrap the call in print() to see it in the Output window.

The Spray Studio plugin has a Preview button that does the same job with a playback window, and picks up property changes on its own.

Types​

Options​

interface Options {
Count: number?--

Particles per burst. The default is 30. An emitter with a BurstCount number attribute uses that instead.

Interval: number?--

Seconds between bursts. The default is the longest Lifetime Max among the emitters plus 0.45, so each burst ends before the next one starts.

Seed: number?--

Uses this seed for every burst, so each loop looks the same. Handy for comparing an effect before and after a change.

Show: boolean?--

Turns on hidden UI above the emitters so you can see them. The default is true.

}

Settings for Preview.Play. Every field is optional, and so is the table itself.

local Preview = require(game.ReplicatedStorage.Packages.Spray.Preview)
print(Preview.Play(nil, { Count = 50, Seed = 3 }))

Functions​

Stop​

Preview.Stop() → string--

A message that says how many leftover Frames were deleted.

Stops the preview and puts the place back the way it was. It destroys every Spray the preview made, turns hidden UI back off, and deletes any leftover particle Frames it finds in StarterGui.

The leftovers come from requiring the module again after editing it while a preview was running. The new copy doesn't know about the old preview, so Stop finds the old Frames by name instead (Spray names them Spray_ plus the emitter's name) and deletes them.

CAUTION

That search deletes every Frame whose name starts with Spray_ in StarterGui (and in your player, during a playtest), including your own. Don't give your UI names that start that way.

Play​

Preview.Play(
Target: Instance?,--

What to play. Leave it out to use the selection.

Config: Options?--

Optional settings. See Preview.Options.

) → string--

A message saying how many emitters are playing, or why nothing is.

Starts looping the emitters in Target, or in whatever is selected in the Explorer if you don't pass anything.

Target can be a ParticleEmitter or anything that contains some: a Frame, a ScreenGui, even all of StarterGui. Every emitter inside it plays, all of them bursting at the same time. When it uses the selection, only the first selected object counts.

local Preview = require(game.ReplicatedStorage.Packages.Spray.Preview)

Preview.Play(game.StarterGui.Shop.BuyButton) -- every emitter in the button
Preview.Play(nil, { Count = 80 }) -- the selection, 80 particles per burst

Emitters that aren't inside a GuiObject are skipped, and the returned message lists them. If a preview is already running, it's stopped first.

Scrub​

Preview.Scrub(
Time: number--

Seconds into the burst.

) → string--

A message saying the moment and how many emitters were frozen.

Freezes every previewed emitter at Time seconds into a burst and stops the loop. The picture holds still, which helps when you're looking at a Size or Transparency curve and want to see one exact moment of it.

local Preview = require(game.ReplicatedStorage.Packages.Spray.Preview)

Preview.Play(nil, { Seed = 1 })
Preview.Scrub(0.15) -- the burst 0.15 seconds in

Each call starts a fresh burst, so without the Seed option you get different particles every time you scrub. After editing a property, call Preview.Play and then Scrub again to see the change.

Needs a preview started with Preview.Play first.

Step​

Preview.Step(
Delta: number--

Seconds to move. Negative values go back.

) → string--

A message saying how far it moved.

Moves the frozen moment by Delta seconds. Negative values go back. Use it after Preview.Scrub to go through a burst a little at a time, for example to find the exact moment a curve starts looking wrong.

Preview.Scrub(0)
Preview.Step(0.02) -- run this line a few times

Call Preview.Scrub first. Without it, Step stops new bursts from firing, but the burst already on screen keeps playing.

Resume​

Preview.Resume() → string--

A message saying the preview is looping again.

Unfreezes the preview after Preview.Scrub or Preview.Step. The frozen burst plays out from where it stopped, and new bursts start again after the usual interval.

Show raw api
{
    "functions": [
        {
            "name": "Stop",
            "desc": "Stops the preview and puts the place back the way it was. It destroys every Spray the\npreview made, turns hidden UI back off, and deletes any leftover particle Frames it finds\nin StarterGui.\n\nThe leftovers come from requiring the module again after editing it while a preview was\nrunning. The new copy doesn't know about the old preview, so `Stop` finds the old Frames\nby name instead (Spray names them `Spray_` plus the emitter's name) and deletes them.\n\n:::caution\nThat search deletes every Frame whose name starts with `Spray_` in StarterGui (and in your\nplayer, during a playtest), including your own. Don't give your UI names that start that\nway.\n:::",
            "params": [],
            "returns": [
                {
                    "desc": "A message that says how many leftover Frames were deleted.",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 211,
                "path": "Spray/Preview.luau"
            }
        },
        {
            "name": "Play",
            "desc": "Starts looping the emitters in `Target`, or in whatever is selected in the Explorer if you\ndon't pass anything.\n\n`Target` can be a ParticleEmitter or anything that contains some: a Frame, a ScreenGui,\neven all of StarterGui. Every emitter inside it plays, all of them bursting at the same\ntime. When it uses the selection, only the first selected object counts.\n\n```lua\nlocal Preview = require(game.ReplicatedStorage.Packages.Spray.Preview)\n\nPreview.Play(game.StarterGui.Shop.BuyButton) -- every emitter in the button\nPreview.Play(nil, { Count = 80 }) -- the selection, 80 particles per burst\n```\n\nEmitters that aren't inside a GuiObject are skipped, and the returned message lists them.\nIf a preview is already running, it's stopped first.",
            "params": [
                {
                    "name": "Target",
                    "desc": "What to play. Leave it out to use the selection.",
                    "lua_type": "Instance?"
                },
                {
                    "name": "Config",
                    "desc": "Optional settings. See [Preview.Options].",
                    "lua_type": "Options?"
                }
            ],
            "returns": [
                {
                    "desc": "A message saying how many emitters are playing, or why nothing is.",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 256,
                "path": "Spray/Preview.luau"
            }
        },
        {
            "name": "Scrub",
            "desc": "Freezes every previewed emitter at `Time` seconds into a burst and stops the loop. The\npicture holds still, which helps when you're looking at a Size or Transparency curve and\nwant to see one exact moment of it.\n\n```lua\nlocal Preview = require(game.ReplicatedStorage.Packages.Spray.Preview)\n\nPreview.Play(nil, { Seed = 1 })\nPreview.Scrub(0.15) -- the burst 0.15 seconds in\n```\n\nEach call starts a fresh burst, so without the `Seed` option you get different particles\nevery time you scrub. After editing a property, call [Preview.Play] and then `Scrub`\nagain to see the change.\n\nNeeds a preview started with [Preview.Play] first.",
            "params": [
                {
                    "name": "Time",
                    "desc": "Seconds into the burst.",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "A message saying the moment and how many emitters were frozen.",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 354,
                "path": "Spray/Preview.luau"
            }
        },
        {
            "name": "Step",
            "desc": "Moves the frozen moment by `Delta` seconds. Negative values go back. Use it after\n[Preview.Scrub] to go through a burst a little at a time, for example to find the exact\nmoment a curve starts looking wrong.\n\n```lua\nPreview.Scrub(0)\nPreview.Step(0.02) -- run this line a few times\n```\n\nCall [Preview.Scrub] first. Without it, `Step` stops new bursts from firing, but the burst\nalready on screen keeps playing.",
            "params": [
                {
                    "name": "Delta",
                    "desc": "Seconds to move. Negative values go back.",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "A message saying how far it moved.",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 387,
                "path": "Spray/Preview.luau"
            }
        },
        {
            "name": "Resume",
            "desc": "Unfreezes the preview after [Preview.Scrub] or [Preview.Step]. The frozen burst plays out\nfrom where it stopped, and new bursts start again after the usual interval.",
            "params": [],
            "returns": [
                {
                    "desc": "A message saying the preview is looping again.",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 407,
                "path": "Spray/Preview.luau"
            }
        }
    ],
    "properties": [],
    "types": [
        {
            "name": "Options",
            "desc": "Settings for [Preview.Play]. Every field is optional, and so is the table itself.\n\n```lua\nlocal Preview = require(game.ReplicatedStorage.Packages.Spray.Preview)\nprint(Preview.Play(nil, { Count = 50, Seed = 3 }))\n```",
            "fields": [
                {
                    "name": "Count",
                    "lua_type": "number?",
                    "desc": "Particles per burst. The default is 30. An emitter with a `BurstCount` number attribute uses that instead."
                },
                {
                    "name": "Interval",
                    "lua_type": "number?",
                    "desc": "Seconds between bursts. The default is the longest `Lifetime` Max among the emitters plus 0.45, so each burst ends before the next one starts."
                },
                {
                    "name": "Seed",
                    "lua_type": "number?",
                    "desc": "Uses this seed for every burst, so each loop looks the same. Handy for comparing an effect before and after a change."
                },
                {
                    "name": "Show",
                    "lua_type": "boolean?",
                    "desc": "Turns on hidden UI above the emitters so you can see them. The default is `true`."
                }
            ],
            "source": {
                "line": 101,
                "path": "Spray/Preview.luau"
            }
        }
    ],
    "name": "Preview",
    "desc": "Plays emitters in Roblox Studio without starting a playtest, so you can change a property\nand see the result a few seconds later. It's a module inside Spray that nothing else\nrequires, so it never runs in your game.\n\nSelect a ParticleEmitter in the Explorer, or a Frame or ScreenGui that has some inside,\nthen paste this into Studio's command bar and press Enter:\n\n```lua\nrequire(game.ReplicatedStorage.Packages.Spray.Preview).Play()\n```\n\nThe selected emitters play in bursts, over and over, right in the edit viewport. This works\nfor UI in StarterGui, because Studio draws StarterGui on top of the viewport while you\nedit. Preview reads the emitters when it starts, so after changing a property, run\n[Preview.Play] again to see it.\n\nTo stop:\n\n```lua\nrequire(game.ReplicatedStorage.Packages.Spray.Preview).Stop()\n```\n\n:::caution Stop before you save\nTo make the emitters visible, Preview turns on any hidden UI above them (`Visible` on\nGuiObjects, `Enabled` on ScreenGuis), and [Preview.Stop] turns it back off. If you save\nthe place before stopping, that UI is saved turned on.\n:::\n\nEvery function returns a one-line message saying what it did, such as how many emitters\nare playing. Wrap the call in `print()` to see it in the Output window.\n\nThe Spray Studio plugin has a Preview button that does the same job with a playback\nwindow, and picks up property changes on its own.",
    "source": {
        "line": 85,
        "path": "Spray/Preview.luau"
    }
}