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 }),
	},
})
Start from a video.

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.

Not in the Effect builder yet.

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
}
FieldTypeDescription
Images{ string }The sheets' asset ids, in order.
Columns, RowsnumberHow many frames across and down each sheet holds.
FrameSizeVector2?One frame's size in pixels. Required when a sheet holds more than one frame.
Framesnumber?The total number of frames, when the last sheet isn't full. Defaults to every cell.
Fpsnumber?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.

NameSheetsFramesUsed by
FireOnScreen5, starting rbxassetid://7994959850737429 × 512×288, 12 fps (2.4 s loop)Fire over the screen. Example script: G
SnowOnScreen4, starting rbxassetid://7905107450154324 × 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

function
Track.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.

OptionTypeDefaultDescription
Fpsnumber?sheet's, else 24Frames per second. Defaults to the sheet's own Fps when it has one.
Loopboolean?truefalse plays once and holds the last frame while it fades.
Strengthnumber?1Opacity at full strength, 0 to 1.
ColorColor3?whiteTints the images. White leaves them unchanged.
ScaleTypestring?"Stretch"How a frame fills the screen: "Stretch", "Crop" (fill, keeping its shape) or "Fit" (whole frame visible).
Timenumber?0.1Seconds to fade in.
Holdnumber?see aboveSeconds at full strength. math.huge holds until stopped.
Releasenumber?0.3Seconds to fade out after the hold.
Delay, Weightnumber?0, 1As 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:

OverlaySheetsMemory
60 frames of 128×721 (1024×576)about 2.2 MB
120 frames of 256×1445about 17 MB
29 frames of 512×288 (FireOnScreen)5 (1024×864 each)about 17 MB
24 frames of 512×288 (SnowOnScreen)4about 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.

Watch out for stacking.

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

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