Overlays
An overlay plays a sequence of images over the screen, like a flipbook: blood splatter, cracked glass, rain on a visor, static. It's a track like any other, so it fades in and out, stacks, follows the effect's intensity and stops with the rest of the effect.
local Track = MotionFX.Track
MotionFX:Create("Splat", {
Tracks = {
Track.Overlay(splatterSheet, { Fps = 30, Loop = false }),
Track.Kick({ Pitch = 3 }),
},
})
The Overlay maker turns a short video into sprite sheets and writes the sheet table and the code for you. It can also make a black background transparent.
The Effect builder can't preview overlay tracks, because it can't load your images from Roblox. Write overlay tracks in code, mix them with tracks from the builder, and test them in Studio.
Sprite sheets
Every image in Roblox is a separate upload, so overlays read their frames from sprite sheets: one image holding a grid of frames. A 1024×1024 sheet holds 64 frames of 128×128, which means fewer uploads, one texture in memory, and no flicker between frames. Frames are read left to right, top to bottom, and continue into the next image.
local splatterSheet = {
Images = { "rbxassetid://111", "rbxassetid://222" }, -- in order
Columns = 8,
Rows = 8,
FrameSize = Vector2.new(128, 128), -- one frame, in pixels
Frames = 100, -- the second sheet isn't full
}
| Field | Type | Description |
|---|---|---|
Images | { string } | The sheets' asset ids, in order. |
Columns, Rows | number | How many frames across and down each sheet holds. |
FrameSize | Vector2? | One frame's size in pixels. Required when a sheet holds more than one frame. |
Frames | number? | The total number of frames, when the last sheet isn't full. Defaults to every cell. |
Fps | number? | The frame rate the sheet was made for. Used when the track doesn't set its own. |
Keep sheets at 1024×1024 or smaller: Roblox scales larger images down, which would put FrameSize out of step with the image.
Or a folder of numbered images
For a few frames, a folder can be simpler: put one image per frame in a Folder, named 1, 2, 3… Each child can be a Decal, a Texture, an ImageLabel or a StringValue holding an asset id. FromFolder sorts them as numbers, so 10 comes after 9.
local rainSheet = MotionFX.Overlay.FromFolder(ReplicatedStorage.RainFrames)
MotionFX:Create("Rain", {
FadeOut = 1,
Tracks = { Track.Overlay(rainSheet, { Fps = 12, Strength = 0.6 }) },
})
Overlay presets
MotionFX.Overlays is a frozen table of ready-to-use sheets, like MotionFX.Sounds is for sounds. Its names autocomplete. A preset sheet carries the frame rate it was made for, so Track.Overlay plays it at that speed unless you pass Fps.
| Name | Sheets | Frames | Used by |
|---|---|---|---|
FireOnScreen | 5, starting rbxassetid://79949598507374 | 29 × 512×288, 12 fps (2.4 s loop) | Fire over the screen. Example script: G |
SnowOnScreen | 4, starting rbxassetid://79051074501543 | 24 × 512×288, 12 fps (2 s loop) | Snow over the screen. Example script: J |
MotionFX:Create("Blizzard", {
FadeOut = 0.4,
Tracks = { Track.Overlay(MotionFX.Overlays.SnowOnScreen, { Strength = 0.8 }) },
})
Track.Overlay
Track.Overlay
functionTrack.Overlay(sheet: OverlaySheet, config: OverlayConfig?): OverlayTrack
Fades in over Time, holds, then fades out over Release. A looping overlay holds until the effect is stopped; one that plays once holds for one playthrough.
| Option | Type | Default | Description |
|---|---|---|---|
Fps | number? | sheet's, else 24 | Frames per second. Defaults to the sheet's own Fps when it has one. |
Loop | boolean? | true | false plays once and holds the last frame while it fades. |
Strength | number? | 1 | Opacity at full strength, 0 to 1. |
Color | Color3? | white | Tints the images. White leaves them unchanged. |
ScaleType | string? | "Stretch" | How a frame fills the screen: "Stretch", "Crop" (fill, keeping its shape) or "Fit" (whole frame visible). |
Time | number? | 0.1 | Seconds to fade in. |
Hold | number? | see above | Seconds at full strength. math.huge holds until stopped. |
Release | number? | 0.3 | Seconds to fade out after the hold. |
Delay, Weight | number? | 0, 1 | As for any track. |
Built from { Primitive = "Overlay", Sheet = sheet, Fps = fps, Loop = loop, Driver = Drive.Envelope({ Attack = time, Hold = hold, Release = release }, strength) }
The Overlay primitive
Built by hand, an Overlay track takes a Sheet and a Driver that produces its opacity, 0 to 1. Any number driver works, so an overlay can pulse, flicker or follow game state:
-- Static that gets stronger as health drops
{
Primitive = "Overlay",
Sheet = staticSheet,
Fps = 20,
Driver = Drive.Bind(function()
return 1 - humanoid.Health / humanoid.MaxHealth
end),
}
Each Overlay track gets its own image on screen, drawn above the vignette. Overlays aren't combined like the camera primitives: two overlays playing at once both show.
Decal ids vs Image ids
Uploading an image as a Decal creates two assets: the Decal, and the Image inside it, each with its own id. Overlays need the Image id. Given a Decal id, the overlay loads nothing and stays invisible, and MotionFX prints a warning in the Output when it can't load an image.
If you copied Decal ids, paste this into Studio's command bar (View → Command Bar) with your ids, press Enter, and copy the printed lines into Images:
-- Studio command bar: prints the Image id behind each Decal id, ready to paste
local InsertService = game:GetService("InsertService")
for _, decalId in { 111111111, 222222222 } do -- your Decal ids, in order
local ok, model = pcall(InsertService.LoadAsset, InsertService, decalId)
local decal = ok and model:FindFirstChildWhichIsA("Decal", true)
local imageId = decal and string.match(decal.Texture, "%d+$")
print(if imageId then `"rbxassetid://{imageId}",` else `-- {decalId}: not a Decal you can load`)
if ok then model:Destroy() end
end
You can also paste a Decal id into any ImageLabel's Image property in Studio: Studio swaps it for the Image id, which you can copy back out.
Preloading
Roblox only gets an image ready to draw once it's been downloaded and shown on screen. Without preloading, an overlay blinks the first time each of its sheets comes up. Preload sheets while your game loads:
task.spawn(MotionFX.Overlay.Preload, splatterSheet) -- yields until ready, so run it in its own thread
Preload downloads the sheet's images, keeps each one drawn as a single, nearly invisible pixel so Roblox has it ready, and returns once they all are. They stay ready between plays, until you free them:
MotionFX.Overlay.Unload(splatterSheet) -- e.g. when leaving the area that uses it
A preloaded sheet uses its memory the whole time, so preload the overlays a place actually uses. If a sheet still isn't ready when its turn comes, the overlay holds its current frame for a moment rather than blinking out.
Performance
Overlays are the heaviest thing MotionFX does. Every other primitive changes a few numbers each frame; an overlay keeps full-screen images in memory and draws one over the whole screen every frame. Most games can afford one or two at a time, but it's worth knowing where the cost goes, especially for phones.
Texture memory
Each sheet costs roughly its width × height × 4 bytes once it's loaded, whatever is on it. A full 1024×1024 sheet is about 4 MB, so an overlay's cost is mostly how many sheets it has:
| Overlay | Sheets | Memory |
|---|---|---|
| 60 frames of 128×72 | 1 (1024×576) | about 2.2 MB |
| 120 frames of 256×144 | 5 | about 17 MB |
29 frames of 512×288 (FireOnScreen) | 5 (1024×864 each) | about 17 MB |
24 frames of 512×288 (SnowOnScreen) | 4 | about 13.5 MB |
A preloaded sheet stays in memory until you call MotionFX.Overlay.Unload, even while nothing is playing. Phones have far less memory to spare than PCs, and going over it makes Roblox lower texture quality across your whole game or, on low-end devices, crash.
Drawing
A playing overlay draws one full-screen image every frame; the rest of its sheets wait, fully transparent, and cost nothing to draw. Full-screen, partly transparent images are what weaker GPUs struggle with most, and they add up with everything else on screen: two overlays, a couple of Edge layers and a Blur is a lot more work than any one of them.
Effects stack by default (Retrigger = "Stack", up to 8 copies), and every copy of an overlay effect draws its own full-screen overlay. Give overlay effects Retrigger = "Refresh" or "Ignore" so playing one again restarts or keeps the running copy instead.
Loading
Every sheet is a separate download the first time it's used, which takes time and data on a phone connection. Preload the overlays a place actually uses while it loads, and Unload ones you're done with, for example when leaving the area that uses them.
Keeping it light
- Fewer sheets beats anything else. Lower the frame rate (12 fps reads fine for fire, snow and rain), shorten the loop, or use a smaller frame size. The Overlay maker shows the sheet count and memory as you change settings.
- One overlay at a time. Design effects so two big overlays rarely play together, and stop one before starting another.
- A lighter version for phones. Make a second, smaller sheet and pick one per device:
local UserInputService = game:GetService("UserInputService")
-- a small, single-sheet version for touch devices; the full one everywhere else
local fireSheet = if UserInputService.TouchEnabled then fireSheetLow else MotionFX.Overlays.FireOnScreen
task.spawn(MotionFX.Overlay.Preload, fireSheet)
To see the real cost, open the Developer Console (F9) in a test game, go to Memory and look at GraphicsTexture, or use Studio's Device emulator to try a phone-sized screen. Test on a real low-end phone before shipping heavy overlays.
Tips
- Short loops. 1 to 3 seconds at 12 to 30 fps is plenty for most overlays.
- Small frames. Overlays are soft and stretched across the screen, so 128- or 256-pixel frames usually look fine. Every full 1024×1024 sheet uses about 4 MB of texture memory; see Performance.
- Black backgrounds. Roblox images can't blend additively, so an effect filmed on black shows as a black box unless the black is made transparent. The Overlay maker does this for you.