Skip to main content

Shake

Create realistic shake effects, such as camera or object shakes.

Creating a shake is very simple with this module. For every shake, simply create a shake instance by calling Shake.new(). From there, configure the shake however desired. Once configured, call shake:Start() and then bind a function to it with either shake:OnSignal(...) or shake:BindToRenderStep(...).

The shake will output its values to the connected function, and then automatically stop and clean up its connections once completed.

Shake instances can be reused indefinitely. However, only one shake operation per instance can be running. If more than one is needed of the same configuration, simply call shake:Clone() to duplicate it.

Example of a simple camera shake:

local priority = Enum.RenderPriority.Last.Value

local shake = Shake.new()
shake.FadeInTime = 0
shake.Frequency = 0.1
shake.Amplitude = 5
shake.RotationInfluence = Vector3.new(0.1, 0.1, 0.1)

shake:Start()
shake:BindToRenderStep(Shake.NextRenderName(), priority, function(pos, rot, isDone)
	camera.CFrame *= CFrame.new(pos) * CFrame.Angles(rot.X, rot.Y, rot.Z)
end)

Shakes will automatically stop once the shake has been completed. Shakes can also be used continuously if the Sustain property is set to true.

Fixing drift

If you are not controlling the initial CFrame of the camera (i.e. using Roblox's camera scripts), then you might run into odd behavior with the above example. This is because Roblox's camera scripts are influenced by the current CFrame value of the camera. Thus, the shake effect causes odd side-effects, especially noticeable at higher FPS.

The solution is to simply store the camera's CFrame before applying the shake CFrame, and then reapplying this stored CFrame value after rendering is complete (e.g. on Heartbeat).

local camCf: CFrame

shake:BindToRenderStep(Shake.NextRenderName(), priority, function(pos, rot, isDone)
	-- Store the CFrame value that was set from Roblox's camera scripts:
	camCf = camera.CFrame

	camera.CFrame *= CFrame.new(pos) * CFrame.Angles(rot.X, rot.Y, rot.Z)
end)

RunService.Heartbeat:Connect(function()
	-- Reapply the camera script CFrame before they run again.
	-- Heartbeat runs AFTER Roblox has rendered, so it will not affect the camera this frame.
	camera.CFrame = camCf
end)

Configuration

Here are some more helpful configuration examples:

local shake = Shake.new()

-- The magnitude of the shake. Larger numbers means larger shakes.
shake.Amplitude = 5

-- The speed of the shake. Smaller frequencies mean faster shakes.
shake.Frequency = 0.1

 -- Fade-in time before max amplitude shake. Set to 0 for immediate shake.
shake.FadeInTime = 0

-- Fade-out time. Set to 0 for immediate cutoff.
shake.FadeOutTime = 0

-- How long the shake sustains full amplitude before fading out
shake.SustainTime = 1

-- Set to true to never end the shake. Call shake:StopSustain() to start the fade-out.
shake.Sustain = true

-- Multiplies against the shake vector to control the final amplitude of the position.
-- Can be seen internally as: position = shakeVector * fadeInOut * positionInfluence
shake.PositionInfluence = Vector3.one

-- Multiplies against the shake vector to control the final amplitude of the rotation.
-- Can be seen internally as: position = shakeVector * fadeInOut * rotationInfluence
shake.RotationInfluence = Vector3.new(0.1, 0.1, 0.1)

Types

UpdateCallbackFn

type UpdateCallbackFn = () → (
positionVector3,
rotationVector3,
completedboolean
)

Properties

Amplitude

Shake.Amplitude: number

Amplitude of the overall shake. For instance, an amplitude of 3 would mean the peak magnitude for the outputted shake vectors would be about 3.

Defaults to 1.

Frequency

Shake.Frequency: number

Frequency of the overall shake. This changes how slow or fast the shake occurs.

Defaults to 1.

FadeInTime

Shake.FadeInTime: number

How long it takes for the shake to fade in, measured in seconds.

Defaults to 1.

FadeOutTime

Shake.FadeOutTime: number

How long it takes for the shake to fade out, measured in seconds.

Defaults to 1.

SustainTime

Shake.SustainTime: number

How long it takes for the shake sustains itself after fading in and before fading out.

To sustain a shake indefinitely, set Sustain to true, and call the StopSustain() method to stop the sustain and fade out the shake effect.

Defaults to 0.

Sustain

Shake.Sustain: boolean

If true, the shake will sustain itself indefinitely once it fades in. If StopSustain() is called, the sustain will end and the shake will fade out based on the FadeOutTime.

Defaults to false.

PositionInfluence

Shake.PositionInfluence: Vector3

This is similar to Amplitude but multiplies against each axis of the resultant shake vector, and only affects the position vector.

Defaults to Vector3.one.

RotationInfluence

Shake.RotationInfluence: Vector3

This is similar to Amplitude but multiplies against each axis of the resultant shake vector, and only affects the rotation vector.

Defaults to Vector3.one.

TimeFunction

Shake.TimeFunction: () → number

The function used to get the current time. This defaults to time during runtime, and os.clock otherwise. Usually this will not need to be set, but it can be optionally configured if desired.

Functions

new

Shake.new() → Shake

Construct a new Shake instance.

InverseSquare

Shake.InverseSquare(
shakeVector3,
distancenumber
) → Vector3

Apply an inverse square intensity multiplier to the given vector based on the distance away from some source. This can be used to simulate shake intensity based on the distance the shake is occurring from some source.

For instance, if the shake is caused by an explosion in the game, the shake can be calculated as such:

local function Explosion(positionOfExplosion: Vector3)

	local cam = workspace.CurrentCamera
	local renderPriority = Enum.RenderPriority.Last.Value

	local shake = Shake.new()
	-- Set shake properties here

	local function ExplosionShake(pos: Vector3, rot: Vector3)
		local distance = (cam.CFrame.Position - positionOfExplosion).Magnitude
		pos = Shake.InverseSquare(pos, distance)
		rot = Shake.InverseSquare(rot, distance)
		cam.CFrame *= CFrame.new(pos) * CFrame.Angles(rot.X, rot.Y, rot.Z)
	end

	shake:BindToRenderStep("ExplosionShake", renderPriority, ExplosionShake)

end

NextRenderName

Shake.NextRenderName() → string

Returns a unique render name for every call, which can be used with the BindToRenderStep method optionally.

shake:BindToRenderStep(Shake.NextRenderName(), ...)

Start

Shake:Start() → ()

Start the shake effect.

NOTE

This must be called before calling Update. As such, it should also be called once before or after calling OnSignal or BindToRenderStep methods.

Stop

Shake:Stop() → ()

Stops the shake effect. If using OnSignal or BindToRenderStep, those bound functions will be disconnected/unbound.

Stop is automatically called when the shake effect is completed or when the Destroy method is called.

IsShaking

Shake:IsShaking() → boolean

Returns true if the shake instance is currently running, otherwise returns false.

StopSustain

Shake:StopSustain() → ()

Schedules a sustained shake to stop. This works by setting the Sustain field to false and letting the shake effect fade out based on the FadeOutTime field.

Update

Shake:Update() → ()

Calculates the current shake vector. This should be continuously called inside a loop, such as RunService.Heartbeat. Alternatively, OnSignal or BindToRenderStep can be used to automatically call this function.

Returns a tuple of three values:

  1. position: Vector3 - Position shake offset
  2. rotation: Vector3 - Rotation shake offset
  3. completed: boolean - Flag indicating if the shake is finished
local hb
hb = RunService.Heartbeat:Connect(function()
	local offsetPosition, offsetRotation, isDone = shake:Update()
	if isDone then
		hb:Disconnect()
	end
	-- Use `offsetPosition` and `offsetRotation` here
end)

OnSignal

Shake:OnSignal(
signalSignal | RBXScriptSignal,
callbackFnUpdateCallbackFn
) → Connection | RBXScriptConnection

Bind the Update method to a signal. For instance, this can be used to connect to RunService.Heartbeat.

All connections are cleaned up when the shake instance is stopped or destroyed.

local function SomeShake(pos: Vector3, rot: Vector3, completed: boolean)
	-- Shake
end

shake:OnSignal(RunService.Heartbeat, SomeShake)

BindToRenderStep

Shake:BindToRenderStep(
namestring,--

Name passed to RunService:BindToRenderStep

prioritynumber,--

Priority passed to RunService:BindToRenderStep

callbackFnUpdateCallbackFn
) → ()

Bind the Update method to RenderStep.

All bound functions are cleaned up when the shake instance is stopped or destroyed.

local renderPriority = Enum.RenderPriority.Camera.Value

local function SomeShake(pos: Vector3, rot: Vector3, completed: boolean)
	-- Shake
end

shake:BindToRenderStep("SomeShake", renderPriority, SomeShake)

Clone

Shake:Clone() → Shake

Creates a new shake with identical properties as this one. This does not clone over playing state, and thus the cloned instance will be in a stopped state.

A use-case for using Clone would be to create a module with a list of shake presets. These presets can be cloned when desired for use. For instance, there might be presets for explosions, recoil, or earthquakes.

--------------------------------------
-- Example preset module
local ShakePresets = {}

local explosion = Shake.new()
-- Configure `explosion` shake here
ShakePresets.Explosion = explosion

return ShakePresets
--------------------------------------

-- Use the module:
local ShakePresets = require(somewhere.ShakePresets)
local explosionShake = ShakePresets.Explosion:Clone()

Destroy

Shake:Destroy() → ()

Alias for Stop().

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Construct a new Shake instance.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Shake"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 230,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "InverseSquare",
            "desc": "Apply an inverse square intensity multiplier to the given vector based on the\ndistance away from some source. This can be used to simulate shake intensity\nbased on the distance the shake is occurring from some source.\n\nFor instance, if the shake is caused by an explosion in the game, the shake\ncan be calculated as such:\n\n```lua\nlocal function Explosion(positionOfExplosion: Vector3)\n\n\tlocal cam = workspace.CurrentCamera\n\tlocal renderPriority = Enum.RenderPriority.Last.Value\n\n\tlocal shake = Shake.new()\n\t-- Set shake properties here\n\n\tlocal function ExplosionShake(pos: Vector3, rot: Vector3)\n\t\tlocal distance = (cam.CFrame.Position - positionOfExplosion).Magnitude\n\t\tpos = Shake.InverseSquare(pos, distance)\n\t\trot = Shake.InverseSquare(rot, distance)\n\t\tcam.CFrame *= CFrame.new(pos) * CFrame.Angles(rot.X, rot.Y, rot.Z)\n\tend\n\n\tshake:BindToRenderStep(\"ExplosionShake\", renderPriority, ExplosionShake)\n\nend\n```",
            "params": [
                {
                    "name": "shake",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "distance",
                    "desc": "",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Vector3\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 281,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "NextRenderName",
            "desc": "Returns a unique render name for every call, which can\nbe used with the `BindToRenderStep` method optionally.\n\n```lua\nshake:BindToRenderStep(Shake.NextRenderName(), ...)\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 297,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Start",
            "desc": "Start the shake effect.\n\n:::note\nThis **must** be called before calling `Update`. As such, it should also be\ncalled once before or after calling `OnSignal` or `BindToRenderStep` methods.\n:::",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 310,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Stop",
            "desc": "Stops the shake effect. If using `OnSignal` or `BindToRenderStep`, those bound\nfunctions will be disconnected/unbound.\n\n`Stop` is automatically called when the shake effect is completed _or_ when the\n`Destroy` method is called.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 322,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "IsShaking",
            "desc": "Returns `true` if the shake instance is currently running,\notherwise returns `false`.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 340,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "StopSustain",
            "desc": "Schedules a sustained shake to stop. This works by setting the\n`Sustain` field to `false` and letting the shake effect fade out\nbased on the `FadeOutTime` field.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 349,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Update",
            "desc": "Calculates the current shake vector. This should be continuously\ncalled inside a loop, such as `RunService.Heartbeat`. Alternatively,\n`OnSignal` or `BindToRenderStep` can be used to automatically call\nthis function.\n\nReturns a tuple of three values:\n1. `position: Vector3` - Position shake offset\n2. `rotation: Vector3` - Rotation shake offset\n3. `completed: boolean` - Flag indicating if the shake is finished\n\n```lua\nlocal hb\nhb = RunService.Heartbeat:Connect(function()\n\tlocal offsetPosition, offsetRotation, isDone = shake:Update()\n\tif isDone then\n\t\thb:Disconnect()\n\tend\n\t-- Use `offsetPosition` and `offsetRotation` here\nend)\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 377,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "OnSignal",
            "desc": "Bind the `Update` method to a signal. For instance, this can be used\nto connect to `RunService.Heartbeat`.\n\nAll connections are cleaned up when the shake instance is stopped\nor destroyed.\n\n```lua\nlocal function SomeShake(pos: Vector3, rot: Vector3, completed: boolean)\n\t-- Shake\nend\n\nshake:OnSignal(RunService.Heartbeat, SomeShake)\n```",
            "params": [
                {
                    "name": "signal",
                    "desc": "",
                    "lua_type": "Signal | RBXScriptSignal"
                },
                {
                    "name": "callbackFn",
                    "desc": "",
                    "lua_type": "UpdateCallbackFn"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Connection | RBXScriptConnection"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 435,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "BindToRenderStep",
            "desc": "Bind the `Update` method to RenderStep.\n\nAll bound functions are cleaned up when the shake instance is stopped\nor destroyed.\n\n```lua\nlocal renderPriority = Enum.RenderPriority.Camera.Value\n\nlocal function SomeShake(pos: Vector3, rot: Vector3, completed: boolean)\n\t-- Shake\nend\n\nshake:BindToRenderStep(\"SomeShake\", renderPriority, SomeShake)\n```",
            "params": [
                {
                    "name": "name",
                    "desc": "Name passed to `RunService:BindToRenderStep`",
                    "lua_type": "string"
                },
                {
                    "name": "priority",
                    "desc": "Priority passed to `RunService:BindToRenderStep`",
                    "lua_type": "number"
                },
                {
                    "name": "callbackFn",
                    "desc": "",
                    "lua_type": "UpdateCallbackFn"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 465,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Clone",
            "desc": "Creates a new shake with identical properties as\nthis one. This does _not_ clone over playing state,\nand thus the cloned instance will be in a stopped\nstate.\n\nA use-case for using `Clone` would be to create a module\nwith a list of shake presets. These presets can be cloned\nwhen desired for use. For instance, there might be presets\nfor explosions, recoil, or earthquakes.\n\n```lua\n--------------------------------------\n-- Example preset module\nlocal ShakePresets = {}\n\nlocal explosion = Shake.new()\n-- Configure `explosion` shake here\nShakePresets.Explosion = explosion\n\nreturn ShakePresets\n--------------------------------------\n\n-- Use the module:\nlocal ShakePresets = require(somewhere.ShakePresets)\nlocal explosionShake = ShakePresets.Explosion:Clone()\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Shake"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 502,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Destroy",
            "desc": "Alias for `Stop()`.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 524,
                "path": "modules/shake/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Amplitude",
            "desc": "Amplitude of the overall shake. For instance, an amplitude of `3` would mean the\npeak magnitude for the outputted shake vectors would be about `3`.\n\nDefaults to `1`.",
            "lua_type": "number",
            "source": {
                "line": 150,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Frequency",
            "desc": "Frequency of the overall shake. This changes how slow or fast the\nshake occurs.\n\nDefaults to `1`.",
            "lua_type": "number",
            "source": {
                "line": 159,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "FadeInTime",
            "desc": "How long it takes for the shake to fade in, measured in seconds.\n\nDefaults to `1`.",
            "lua_type": "number",
            "source": {
                "line": 167,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "FadeOutTime",
            "desc": "How long it takes for the shake to fade out, measured in seconds.\n\nDefaults to `1`.",
            "lua_type": "number",
            "source": {
                "line": 175,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "SustainTime",
            "desc": "How long it takes for the shake sustains itself after fading in and\nbefore fading out.\n\nTo sustain a shake indefinitely, set `Sustain`\nto `true`, and call the `StopSustain()` method to stop the sustain\nand fade out the shake effect.\n\nDefaults to `0`.",
            "lua_type": "number",
            "source": {
                "line": 188,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "Sustain",
            "desc": "If `true`, the shake will sustain itself indefinitely once it fades\nin. If `StopSustain()` is called, the sustain will end and the shake\nwill fade out based on the `FadeOutTime`.\n\nDefaults to `false`.",
            "lua_type": "boolean",
            "source": {
                "line": 198,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "PositionInfluence",
            "desc": "This is similar to `Amplitude` but multiplies against each axis\nof the resultant shake vector, and only affects the position vector.\n\nDefaults to `Vector3.one`.",
            "lua_type": "Vector3",
            "source": {
                "line": 207,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "RotationInfluence",
            "desc": "This is similar to `Amplitude` but multiplies against each axis\nof the resultant shake vector, and only affects the rotation vector.\n\nDefaults to `Vector3.one`.",
            "lua_type": "Vector3",
            "source": {
                "line": 216,
                "path": "modules/shake/init.luau"
            }
        },
        {
            "name": "TimeFunction",
            "desc": "The function used to get the current time. This defaults to\n`time` during runtime, and `os.clock` otherwise. Usually this\nwill not need to be set, but it can be optionally configured\nif desired.",
            "lua_type": "() -> number",
            "source": {
                "line": 225,
                "path": "modules/shake/init.luau"
            }
        }
    ],
    "types": [
        {
            "name": "UpdateCallbackFn",
            "desc": "",
            "lua_type": "() -> (position: Vector3, rotation: Vector3, completed: boolean)",
            "source": {
                "line": 9,
                "path": "modules/shake/init.luau"
            }
        }
    ],
    "name": "Shake",
    "desc": "Create realistic shake effects, such as camera or object shakes.\n\nCreating a shake is very simple with this module. For every shake,\nsimply create a shake instance by calling `Shake.new()`. From\nthere, configure the shake however desired. Once configured,\ncall `shake:Start()` and then bind a function to it with either\n`shake:OnSignal(...)` or `shake:BindToRenderStep(...)`.\n\nThe shake will output its values to the connected function, and then\nautomatically stop and clean up its connections once completed.\n\nShake instances can be reused indefinitely. However, only one shake\noperation per instance can be running. If more than one is needed\nof the same configuration, simply call `shake:Clone()` to duplicate\nit.\n\nExample of a simple camera shake:\n```lua\nlocal priority = Enum.RenderPriority.Last.Value\n\nlocal shake = Shake.new()\nshake.FadeInTime = 0\nshake.Frequency = 0.1\nshake.Amplitude = 5\nshake.RotationInfluence = Vector3.new(0.1, 0.1, 0.1)\n\nshake:Start()\nshake:BindToRenderStep(Shake.NextRenderName(), priority, function(pos, rot, isDone)\n\tcamera.CFrame *= CFrame.new(pos) * CFrame.Angles(rot.X, rot.Y, rot.Z)\nend)\n```\n\nShakes will automatically stop once the shake has been completed. Shakes can\nalso be used continuously if the `Sustain` property is set to `true`.\n\n## Fixing drift\n\nIf you are not controlling the initial CFrame of the camera (i.e. using Roblox's camera scripts),\nthen you might run into odd behavior with the above example. This is because Roblox's camera\nscripts are influenced by the current CFrame value of the camera. Thus, the shake effect causes\nodd side-effects, especially noticeable at higher FPS.\n\nThe solution is to simply store the camera's CFrame before applying the shake CFrame, and then\nreapplying this stored CFrame value after rendering is complete (e.g. on Heartbeat).\n\n```lua\nlocal camCf: CFrame\n\nshake:BindToRenderStep(Shake.NextRenderName(), priority, function(pos, rot, isDone)\n\t-- Store the CFrame value that was set from Roblox's camera scripts:\n\tcamCf = camera.CFrame\n\n\tcamera.CFrame *= CFrame.new(pos) * CFrame.Angles(rot.X, rot.Y, rot.Z)\nend)\n\nRunService.Heartbeat:Connect(function()\n\t-- Reapply the camera script CFrame before they run again.\n\t-- Heartbeat runs AFTER Roblox has rendered, so it will not affect the camera this frame.\n\tcamera.CFrame = camCf\nend)\n```\n\n## Configuration\n\nHere are some more helpful configuration examples:\n\n```lua\nlocal shake = Shake.new()\n\n-- The magnitude of the shake. Larger numbers means larger shakes.\nshake.Amplitude = 5\n\n-- The speed of the shake. Smaller frequencies mean faster shakes.\nshake.Frequency = 0.1\n\n -- Fade-in time before max amplitude shake. Set to 0 for immediate shake.\nshake.FadeInTime = 0\n\n-- Fade-out time. Set to 0 for immediate cutoff.\nshake.FadeOutTime = 0\n\n-- How long the shake sustains full amplitude before fading out\nshake.SustainTime = 1\n\n-- Set to true to never end the shake. Call shake:StopSustain() to start the fade-out.\nshake.Sustain = true\n\n-- Multiplies against the shake vector to control the final amplitude of the position.\n-- Can be seen internally as: position = shakeVector * fadeInOut * positionInfluence\nshake.PositionInfluence = Vector3.one\n\n-- Multiplies against the shake vector to control the final amplitude of the rotation.\n-- Can be seen internally as: position = shakeVector * fadeInOut * rotationInfluence\nshake.RotationInfluence = Vector3.new(0.1, 0.1, 0.1)\n\n```",
    "source": {
        "line": 139,
        "path": "modules/shake/init.luau"
    }
}