Primitives & Tracks
An effect can change eight things. Each one has its own value type and its own rule for combining contributions from effects that run at the same time.
You're at the deepest level. Every primitive and every driver is open here, and nothing is hidden underneath: the Effect builder and the ready-made tracks are made from these same pieces. You can still drop ready-made tracks into any effect where they fit.
Tracks
A track is a flat table tagged by Primitive. Every track accepts these two fields:
| Field | Type | Description |
|---|---|---|
Weight | number? | A per-track scale applied on top of the effect's intensity. Default 1. |
Delay | number? | Seconds after Play before the track starts. Must be 0 or more. |
Every primitive except Sound also needs a Driver that produces its value type:
| Primitive | Driver value | Meaning | Combine |
|---|---|---|---|
FOV | number | Degrees added to the base FOV | sum |
Position | Vector3 | Camera-space offset in studs | sum |
Rotation | Vector3 | Degrees of (pitch, yaw, roll) | sum |
Blur | number | BlurEffect.Size | max |
Color | ColorValue | Colour correction | multiply / sum |
Vignette | number | VignetteLayer | Edge darkening or a coloured ring | layered |
Sound | — | A one-shot or looping sound | independent |
Overlay | number | Opacity of a flipbook over the screen | independent |
Which drivers fit
Drivers don't belong to a primitive. A driver fits a primitive when it produces that primitive's value type: FOV, Blur and Overlay (its opacity) take a number, Position and Rotation a Vector3, Color a ColorValue, and Vignette a number or a VignetteLayer. With --!strict, a track whose driver doesn't fit is underlined in your editor.
| Driver | FOV, Blur, Overlay | Position, Rotation | Color | Vignette |
|---|---|---|---|---|
Constant | ✓ | ✓ | ✓ | ✓ |
Tween, Cycle | ✓ | ✓ | ✓ ColorValues; a Color3 via Map | ✓ |
Spring | ✓ number Kick | ✓ Vector3 Kick | ✓ rarely useful | ✓ |
Noise | ✓ number Amplitude | ✓ Vector3 Amplitude | via Map | ✓; a layer via Map |
Envelope | ✓ | via Modulate | via Modulate | ✓; a layer via Map |
Bind | ✓ return the primitive's value type | |||
Modulate | ✓ when its first driver (the carrier) fits | |||
Sum, Sequence | ✓ when every driver inside fits | |||
Map | ✓ return the primitive's value type. It turns any driver into any other value. | |||
✓ fits directly. via … means wrap it first, for example Drive.Modulate(Drive.Constant(Vector3.new(0, 0, 4)), envelope) to give a Vector3 the envelope's shape. Sound takes no driver.
FOV
A delta in degrees, added to the camera's base FieldOfView. Positive values widen the view (a punch outward) and negative values narrow it (zoom in). Contributions add up. If other code changes the FOV, CameraRig adopts the new value as the base instead of fighting it.
{ Primitive = "FOV", Driver = Drive.Spring({ Kick = 7, Frequency = 3.5, Damping = 0.4 }) }
Drivers that fit
| For | Driver |
|---|---|
| A punch out or in | Drive.Spring({ Kick = 7, Frequency = 3.5, Damping = 0.4 }) |
| Zoom in and hold | Drive.Sequence(Drive.Tween(0, -10, 0.25), Drive.Constant(-10)) |
| Follow speed or another value | Drive.Bind(function() return speedFov end) |
| A slow wobble | Drive.Noise({ Frequency = 0.5, Amplitude = 2 }) |
Position
A camera-space offset in studs: X is right, Y is up and Z is back. Contributions add up.
-- Kick the camera back 0.18 studs
{ Primitive = "Position", Driver = Drive.Spring({ Kick = Vector3.new(0, 0, 0.18), Frequency = 9, Damping = 0.45 }) }
Drivers that fit
| For | Driver |
|---|---|
| A kick that settles | Drive.Spring({ Kick = Vector3.new(0, 0, 0.18), Frequency = 9, Damping = 0.45 }) |
| A rumble that dies out | Drive.Modulate(Drive.Noise({ Frequency = 12, Amplitude = Vector3.new(0.1, 0.1, 0) }), envelope) |
| A fixed offset | Drive.Constant(Vector3.new(0, -0.5, 0)) |
Rotation
An Euler offset in degrees as (pitch, yaw, roll). Contributions add up. This is the main ingredient of most shakes.
{ Primitive = "Rotation", Driver = Drive.Modulate(Drive.Noise({ Frequency = 14, Amplitude = Vector3.new(3, 3, 6) }), envelope) }
Drivers that fit
| For | Driver |
|---|---|
| A shake | Drive.Modulate(Drive.Noise({ Frequency = 14, Amplitude = Vector3.new(3, 3, 6) }), envelope) |
| Recoil | Drive.Spring({ Kick = Vector3.new(3.5, 0, 0), Frequency = 9, Damping = 0.4 }) |
| Tilt and hold | Drive.Sequence(Drive.Tween(Vector3.zero, Vector3.new(0, 0, 8), 0.3), Drive.Constant(Vector3.new(0, 0, 8))) |
| A constant jitter | Drive.Noise({ Frequency = 20, Amplitude = Vector3.new(0.12, 0.12, 0.25) }) |
Blur
A BlurEffect.Size. The strongest contribution wins instead of summing, so two blurs don't stack into a white-out. MotionFX creates its own MotionFX_Blur in Lighting when it is first needed and destroys it when idle.
Drivers that fit
| For | Driver |
|---|---|
| A flash that decays | Drive.Envelope({ Release = 0.9 }, 14) |
| A steady blur | Drive.Constant(6) |
| A blur that wobbles | Drive.Sum(Drive.Constant(8), Drive.Noise({ Frequency = 0.6, Amplitude = 4 })) |
Color
A ColorValue table, applied through MotionFX's own MotionFX_Color ColorCorrectionEffect, so it never fights your Lighting setup.
| Field | Type | Combine | Applied |
|---|---|---|---|
Tint | Color3? | multiplied together | TintColor, white if unset |
Saturation | number? | summed | clamped to -1..1 |
Contrast | number? | summed | clamped to -1..1 |
Brightness | number? | summed | clamped to -1..1 |
-- Flash: brighter and desaturated, fading over 0.7 s
{ Primitive = "Color", Driver = Drive.Modulate(Drive.Constant({ Brightness = 0.25, Saturation = -0.35 }), Drive.Envelope({ Release = 0.7 })) }
Drivers that fit
| For | Driver |
|---|---|
| A flash that decays | Drive.Modulate(Drive.Constant({ Brightness = 0.25 }), envelope) |
| Fade into a grade | Drive.Tween({ Saturation = 0 }, { Saturation = -0.8 }, 1) |
| A flicker | Drive.Map(Drive.Noise({ Frequency = 14, Amplitude = 0.08 }), function(n: number): Types.ColorValue return { Brightness = n } end) |
| A colour walk | Drive.Map(Drive.Cycle({ Color3.new(1, 0.6, 1), Color3.new(0.6, 0.8, 1) }, 1.6), function(c: Color3): Types.ColorValue return { Tint = c } end) |
Vignette
A plain number from 0 to 1 is a black vignette at the screen edge. A VignetteLayer gives you control over colour, placement and images:
| Field | Type | Description |
|---|---|---|
Intensity | number | Opacity at the layer's peak, 0..1. |
Color | Color3? | Black by default, or white for an Image layer so the image keeps its colours. |
Inset | number? | Where the layer peaks, as a fraction of the vignette's depth, 0..1. 0 is the screen edge. |
Size | number? | How far past the peak the layer fades out toward the centre, in the same units. Default 1. |
Image | string? | An image asset stretched over the screen instead of the gradient edges. Color tints it and Size is ignored. |
- Contributions with the same
InsetandImageshare a layer, and the strongest one wins, including its colour and size. - Different layers stack, with the outermost drawn on top. An Image layer is drawn above a gradient layer with the same Inset.
- A gradient layer with
Inset > 0is a soft ring. It is clear at the edge, peaks at its Inset, and fades toward the centre. - Gradient layers are built from four edge frames with
UIGradients, so they need no image asset. TheScreenGuiis created on first use and destroyed when idle.
-- Outer dark edge, plus a deep red ring inside it
{ Primitive = "Vignette", Driver = Drive.Modulate(Drive.Constant(0.85), flash) },
{
Primitive = "Vignette",
Driver = Drive.Map(flash, function(amount: number): Types.VignetteLayer
return { Intensity = amount * 0.7, Color = Color3.fromRGB(120, 0, 0), Inset = 0.3, Size = 0.9 }
end),
},
Drivers that fit
| For | Driver |
|---|---|
| A black pulse | Drive.Envelope({ Attack = 0.03, Release = 0.6 }, 0.5) |
| Follow health | Drive.Bind(function() return missingHealth end) |
| A coloured ring that decays | Drive.Map(envelope, function(a: number): Types.VignetteLayer return { Intensity = a, Color = RED, Inset = 0.3 } end) |
| A breathing layer | Drive.Map(Drive.Sum(Drive.Constant(0.35), Drive.Noise({ Frequency = 2.5, Amplitude = 0.1 })), toLayer) |
Sound
Sound is event-based rather than continuous, so it has no driver. Each Sound track spawns its own Sound in SoundService once its Delay has passed. Its volume follows Volume × intensity × Weight × fade.
| Field | Type | Description |
|---|---|---|
SoundId | string | Required. For example MotionFX.Sounds.Heartbeat. |
Volume | number? | Base volume. Default 1. |
PlaybackSpeed | number? | Playback rate. |
TimePosition | number? | Seconds into the audio to start from, to skip its intro. |
Looped | boolean? | A looped sound makes the effect open-ended. |
FadeIn | number? | Seconds to ramp up from silence once the sound starts. |
FadeOut | number? | Fades the sound over the end of a fixed-duration effect so it stops with the effect, and when the effect is stopped early. |
SoundGroup | SoundGroup? | Routes the sound through your mixer. |
If a fixed-duration effect ends naturally before a non-looped sound without FadeOut has finished, the sound keeps playing to its natural end. Stopping the effect with Stop() fades it out instead.
Overlay
A flipbook of images drawn over the screen, from sprite sheets or a folder of numbered images. Its driver is the opacity, 0 to 1, so any number driver works: an envelope to fade in and out, noise to flicker, a binding to follow game state. Like Sound, overlays aren't combined; each track shows its own image, above the vignette. See Overlays for sheets, Track.Overlay and the Overlay maker.
{ Primitive = "Overlay", Sheet = splatterSheet, Fps = 30, Driver = Drive.Envelope({ Attack = 0.05, Release = 0.6 }) }