Forest Logo
search
package_2

camerakit

By @biotoxin495

Roblox

Mirrored from Wally

CameraKit — Lightweight client-side camera utility for Roblox

CameraKit is a lightweight, strictly typed Luau camera utility for Roblox. It handles temporary scripted camera ownership, cinematic movement, camera paths, subject changes, field-of-view transitions, additive camera shake, recoil, and restoration back to Roblox's normal camera controller.

CameraKit is client-only. Positional camera actions, FOV actions, and additive VFX use separate channels so effects can overlap intentionally—for example, a camera can follow a cinematic path while a shake and recoil effect are layered on top.

Quick example

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local CameraKit = require(ReplicatedStorage:WaitForChild("CameraKit"))
local cameraKit = CameraKit.new()

cameraKit:TweenTo(workspace.CameraPoints.Shop, {
	Speed = 30,
})

cameraKit:Shake({
	Duration = 0.45,
	Magnitude = 0.18,
	RotationMagnitude = 2,
})

cameraKit:PulseFieldOfView(8)

-- Return control to the captured gameplay camera later.
cameraKit:Restore(nil, {
	Duration = 0.45,
})

The first scripted positional action captures the current camera state by default. Restore() returns the camera type, subject, transform, focus, FOV, FOV mode, and player zoom limits to that captured state.

🚀 Features

  • Tween the camera to a CFrame, BasePart, or Attachment
  • Calculate transition duration automatically from distance and movement speed
  • Play ordered or looping cinematic paths
  • Configure duration, delay, and TweenInfo per path point
  • Run FOV effects independently from positional movement
  • Add procedural camera shake without taking over the base camera controller
  • Stack recoil impulses with shake and scripted camera movement
  • Play temporary FOV punches that automatically return to the starting FOV
  • Change camera subjects while preserving the previous state
  • Capture and restore camera state reliably
  • Cancel running actions without stale tweens reclaiming control
  • Receive Completed or Cancelled action results
  • Handle Workspace.CurrentCamera replacement
  • Recover when a previously captured camera subject is destroyed during respawn
  • Maintain Camera.Focus while CameraKit owns a scriptable camera
  • Strict Luau public API
  • No external runtime dependencies

🛠️ Installation

ModuleScript

Place init.luau in a client-accessible ModuleScript, such as ReplicatedStorage.CameraKit, then require it from a LocalScript:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CameraKit = require(ReplicatedStorage:WaitForChild("CameraKit"))

Wally

[dependencies]
CameraKit = "biotoxin495/camerakit@1.1.0"

CameraKit must be constructed on the client.

Basic usage

local cameraKit = CameraKit.new({
	DefaultSpeed = 24,
	MinDuration = 0.1,
	MaxDuration = 8,
	FocusDistance = 32,
})

Every constructor field is optional. One long-lived CameraKit instance per owning client controller is recommended.

When the controller is no longer needed:

cameraKit:Destroy()

By default, destruction restores the saved camera state.


API reference

CameraKit.new(config?)

Creates a CameraKit controller.

OptionTypeDefaultDescription
DefaultSpeednumber?24Default positional movement speed in studs per second.
MinDurationnumber?0.1Minimum automatically calculated movement duration.
MaxDurationnumber?8Maximum automatically calculated movement duration.
FocusDistancenumber?32Distance used while maintaining Camera.Focus during scripted movement.

TweenTo(target, options?) -> CameraAction

Tweens the current camera to a CFrame, BasePart, or Attachment. Attachments use WorldCFrame.

cameraKit:TweenTo(workspace.CameraPoint, {
	Duration = 1.2,
	EasingStyle = Enum.EasingStyle.Quint,
	EasingDirection = Enum.EasingDirection.Out,
})

Or let CameraKit calculate the duration from distance:

cameraKit:TweenTo(workspace.CameraPoint, {
	Speed = 28,
})
OptionTypeDescription
Durationnumber?Explicit duration in seconds. Overrides speed-based timing.
Speednumber?Movement speed when Duration is omitted.
MinDurationnumber?Per-action minimum calculated duration.
MaxDurationnumber?Per-action maximum calculated duration.
TweenInfoTweenInfo?Complete TweenInfo override.
EasingStyleEnum.EasingStyle?Defaults to Quint.
EasingDirectionEnum.EasingDirection?Defaults to Out.
CaptureStateboolean?Captures the current state if one has not already been saved. Defaults to true.

If TweenInfo is supplied, its timing and easing settings take precedence.

PlayPath(points, options?) -> CameraAction

Plays an ordered array of camera points.

cameraKit:PlayPath({
	workspace.CameraPoints.Start,
	workspace.CameraPoints.Middle,
	workspace.CameraPoints.End,
}, {
	Speed = 35,
	RestoreOnComplete = true,
})

A point can also carry per-segment timing:

cameraKit:PlayPath({
	workspace.CameraPoints.Start,
	{
		Target = workspace.CameraPoints.Middle,
		Duration = 2,
		Delay = 0.5,
	},
	{
		Target = workspace.CameraPoints.End,
		TweenInfo = TweenInfo.new(1.5, Enum.EasingStyle.Quint, Enum.EasingDirection.Out),
	},
})

Configured path points support:

OptionTypeDescription
TargetCFrame | BasePart | AttachmentSegment destination.
Durationnumber?Explicit segment duration.
Delaynumber?Delay after reaching the point.
TweenInfoTweenInfo?Complete TweenInfo override for the segment.

Path options:

OptionTypeDefaultDescription
Speednumber?constructor defaultSegment speed when no duration is supplied.
MinDurationnumber?constructor defaultMinimum calculated segment duration.
MaxDurationnumber?constructor defaultMaximum calculated segment duration.
TweenInfoTweenInfo?nilDefault TweenInfo for path segments.
EasingStyleEnum.EasingStyle?LinearDefault path easing style.
EasingDirectionEnum.EasingDirection?InOutDefault path easing direction.
Loopboolean?falseRepeats the path until cancelled.
StartFromCurrentboolean?trueWhen false, snaps to point one before continuing.
RestoreOnCompleteboolean?falseRestores the saved state after a non-looping path.
CaptureStateboolean?trueCaptures the current state if one is not already saved.
OnPointReachedfunction?nilInvoked asynchronously when a point is reached.

SetSubject(subject, options?)

Changes CameraSubject to a Humanoid or BasePart.

cameraKit:SetSubject(workspace.DisplayCharacter.Humanoid, {
	CameraType = Enum.CameraType.Custom,
	MinZoomDistance = 18,
	MaxZoomDistance = 35,
})
OptionTypeDefaultDescription
CameraTypeEnum.CameraType?CustomCamera type applied after changing the subject.
CaptureStateboolean?trueCaptures the previous state if needed.
MinZoomDistancenumber?unchangedOptional player minimum zoom distance.
MaxZoomDistancenumber?unchangedOptional player maximum zoom distance.

SetFieldOfView(fieldOfView, options?) -> CameraAction

Sets or tweens Camera.FieldOfView.

cameraKit:SetFieldOfView(55, {
	Duration = 0.3,
})

Valid values are 1 through 120.

OptionTypeDefaultDescription
Durationnumber?0.2Transition duration.
CaptureStateboolean?falseCaptures the full camera state if needed.
TweenInfoTweenInfo?nilComplete TweenInfo override.
EasingStyleEnum.EasingStyle?QuintEasing style when TweenInfo is omitted.
EasingDirectionEnum.EasingDirection?OutEasing direction when TweenInfo is omitted.

Starting another FOV action cancels the previous FOV action.

PulseFieldOfView(amount, options?) -> CameraAction

Creates a temporary FOV punch, then returns to the FOV that was active when the pulse started. The peak value is clamped to Roblox's 1120 FOV range.

cameraKit:PulseFieldOfView(10, {
	AttackDuration = 0.06,
	HoldDuration = 0.03,
	ReleaseDuration = 0.28,
})

Use a negative amount for a quick zoom-in punch:

cameraKit:PulseFieldOfView(-8)
OptionTypeDefaultDescription
AttackDurationnumber?0.08Time to reach the peak FOV.
HoldDurationnumber?0Optional time held at the peak.
ReleaseDurationnumber?0.22Time to return to the starting FOV.
CaptureStateboolean?falseCaptures the full camera state if needed.
AttackEasingStyleEnum.EasingStyle?QuadAttack easing style.
AttackEasingDirectionEnum.EasingDirection?OutAttack easing direction.
ReleaseEasingStyleEnum.EasingStyle?QuintRelease easing style.
ReleaseEasingDirectionEnum.EasingDirection?OutRelease easing direction.

PulseFieldOfView uses the same FOV channel as SetFieldOfView.

Shake(options?) -> CameraAction

Adds procedural local-space translation and rotation on top of the current camera. It does not change the camera type and can run over Roblox's normal player camera, TweenTo, or PlayPath.

local shake = cameraKit:Shake({
	Duration = 0.6,
	Magnitude = 0.2,
	RotationMagnitude = 2.5,
	Frequency = 22,
	FadeOut = 0.25,
})
OptionTypeDefaultDescription
Durationnumber?0.5Total shake duration.
Magnitudenumber?0.15Local positional shake magnitude in studs.
RotationMagnitudenumber?1.5Rotation magnitude in degrees.
Frequencynumber?18Noise sampling frequency; higher values feel rougher/faster.
FadeInnumber?0Fade-in duration.
FadeOutnumber?min(0.2, Duration)Fade-out duration.
Seednumber?randomOptional deterministic noise seed.

Shake uses smooth math.noise sampling rather than independent frame-by-frame randomness, avoiding jitter that changes character with frame rate.

Recoil(options?) -> CameraAction

Adds a one-shot local-space kick that eases back to zero. Recoil is an additive effect, so multiple recoil actions can overlap and can also stack with Shake().

cameraKit:Recoil({
	Rotation = Vector3.new(-5, 0.4, 0),
	Position = Vector3.new(0, 0, 0.14),
	Duration = 0.22,
})

Rotation is expressed in degrees around local X/Y/Z. Position is in local camera-space studs.

OptionTypeDefaultDescription
Durationnumber?0.25Time for the kick to settle back to zero.
PositionVector3?(0, 0, 0.12)Initial local positional kick.
RotationVector3?(-4, 0, 0)Initial local rotational kick in degrees.
EasingStyleEnum.EasingStyle?QuadDecay easing style.
EasingDirectionEnum.EasingDirection?OutDecay easing direction.

StopEffects()

Cancels all active additive Shake and Recoil actions and removes their currently applied camera offset.

cameraKit:StopEffects()

This does not cancel positional camera movement or FOV actions.

CaptureState() -> CameraState

Returns a snapshot of the current camera and player zoom state:

local state = cameraKit:CaptureState()

The state contains:

{
	CameraType,
	CameraSubject,
	CFrame,
	Focus,
	FieldOfView,
	FieldOfViewMode,
	CameraMinZoomDistance,
	CameraMaxZoomDistance,
}

When an additive VFX offset is currently applied, CameraKit captures the underlying base CFrame rather than baking the transient shake/recoil offset into the saved state.

GetSavedState() -> CameraState?

Returns CameraKit's automatically captured state, if one exists.

Restore(state?, options?) -> CameraAction

Restores an explicit CameraState, or the internally saved state when state is nil.

cameraKit:Restore(nil, {
	Duration = 0.5,
})

Restore cancels active positional, FOV, and additive VFX work before applying the target state.

OptionTypeDefaultDescription
Durationnumber?0Duration for CFrame and FOV restoration.
TweenInfoTweenInfo?nilComplete restoration TweenInfo override.
EasingStyleEnum.EasingStyle?QuintEasing style when TweenInfo is omitted.
EasingDirectionEnum.EasingDirection?OutEasing direction when TweenInfo is omitted.
ClearSavedStateboolean?trueClears the internal saved state when restoring that same state.

If a captured subject was destroyed, CameraKit attempts to use the current character's Humanoid instead.

Stop(options?) -> CameraAction?

Cancels active work.

cameraKit:Stop({
	Restore = true,
	RestoreOptions = {
		Duration = 0.4,
	},
})
OptionTypeDefaultDescription
Restoreboolean?falseRestores the saved camera state after stopping.
RestoreOptionsRestoreOptions?nilOptions forwarded to Restore().
StopFieldOfViewboolean?trueAlso cancels the active FOV action.
StopEffectsboolean?trueAlso cancels active shake/recoil effects.

Stopping without restoration intentionally leaves the camera at its current base state.

IsActive() -> boolean

Returns true while a positional action, FOV action, or additive VFX action is active.

Destroy(restoreCamera?)

Cancels all work, removes render-step bindings, disconnects internal listeners, and destroys the controller. By default it restores the saved state first.

cameraKit:Destroy()

To leave the current camera unchanged:

cameraKit:Destroy(false)

A destroyed CameraKit instance cannot be reused.


CameraAction

TweenTo, PlayPath, SetFieldOfView, PulseFieldOfView, Shake, Recoil, and Restore return a CameraAction handle.

local action = cameraKit:Shake()

action.Completed:Connect(function(result)
	print(result) -- "Completed" or "Cancelled"
end)

Available methods and signals:

  • action.Completed — fires once with "Completed" or "Cancelled"
  • action:Cancel() — cancels the action if still active
  • action:Await() — yields until completion and returns the result
  • action:IsPlaying() — returns whether the action is still active
  • action:GetResult() — returns the result or nil while running
  • action:Destroy() — cancels an unfinished action and destroys its completion event

Repeated cancellation is safe.

Action channels and VFX composition

CameraKit separates work into three categories.

Positional channel

Used by TweenTo, PlayPath, and animated Restore. Starting a new positional action cancels the previous positional action.

FOV channel

Used by SetFieldOfView and PulseFieldOfView. Starting a new FOV action cancels the previous FOV action.

Additive VFX layer

Used by Shake and Recoil. These effects are not exclusive: multiple effects are composed together every render frame after the base camera has been updated.

This is valid:

cameraKit:PlayPath(path, {
	Speed = 30,
})

cameraKit:Shake({
	Duration = 1.2,
	Magnitude = 0.1,
})

cameraKit:Recoil({
	Rotation = Vector3.new(-3, 0, 0),
})

cameraKit:PulseFieldOfView(6)

The path remains responsible for the base transform, shake and recoil add temporary transform offsets, and the FOV pulse runs independently.

CameraKit tracks the last VFX offset it applied. If the base camera did not change between frames—for example after a scripted tween reaches a stationary Scriptable camera—it removes the previous offset before applying the next one. This prevents shake/recoil offsets from accumulating over time.

Common patterns

Explosion impact

cameraKit:Shake({
	Duration = 0.75,
	Magnitude = 0.28,
	RotationMagnitude = 3,
	Frequency = 20,
	FadeOut = 0.4,
})

cameraKit:PulseFieldOfView(7, {
	AttackDuration = 0.04,
	ReleaseDuration = 0.35,
})

Weapon recoil

cameraKit:Recoil({
	Rotation = Vector3.new(-4.5, math.random(-10, 10) / 20, 0),
	Position = Vector3.new(0, 0, 0.1),
	Duration = 0.18,
})

Each shot can start another recoil action; the impulses overlap rather than cancelling each other.

Cinematic hit during a path

local pathAction = cameraKit:PlayPath({
	workspace.CutscenePoints.Intro,
	workspace.CutscenePoints.Hit,
	workspace.CutscenePoints.Exit,
}, {
	Speed = 24,
	OnPointReached = function(index)
		if index == 2 then
			cameraKit:Shake({ Duration = 0.5 })
			cameraKit:PulseFieldOfView(8)
		end
	end,
})

Looping lobby flyover

local flyover = cameraKit:PlayPath({
	workspace.LobbyCameraPoints.A,
	workspace.LobbyCameraPoints.B,
	workspace.LobbyCameraPoints.C,
}, {
	Loop = true,
	Speed = 45,
	EasingStyle = Enum.EasingStyle.Linear,
})

-- Player starts the game:
flyover:Cancel()
cameraKit:Restore(nil, {
	Duration = 0.4,
})

Notes

  • CameraKit is client-only.
  • Server systems should signal a client to begin camera work rather than attempting to control Workspace.CurrentCamera from the server.
  • BasePart and Attachment path targets are resolved when their segment begins.
  • A tween does not continuously follow a moving target.
  • Cancelling an action does not automatically restore the player camera unless restoration is explicitly requested.
  • Cancelling an FOV tween leaves the camera at its current FOV, matching normal Tween cancellation semantics.
  • Multiple CameraKit instances can technically exist, but they do not coordinate ownership with one another.
  • CameraKit does not currently provide spline/Bézier interpolation, shoulder-camera controllers, lock-on systems, first-person replacements, collision avoidance, or zone-based camera systems.

Project structure

CameraKit/
├── init.luau
├── README.md
├── wally.toml
├── sourcemap.json
└── LICENSE

License

MIT — see LICENSE.


made with ❤️ by biotoxin495

Package Details

Install command (Click to copy)


Version

1.1.0

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.