{"id":"biotoxin495/camerakit","name":"camerakit","scope":"biotoxin495","platform":"roblox","description":"Lightweight client-side camera utility for Roblox","version":"1.1.0","latest":"1.1.0","versions":["1.0.0","1.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"4320b65adc2a18da03a7aa45042f5cfef831b857def3cb36b3b215a5ee5ec94d","likes":0,"downloads":0,"install":"forest install biotoxin495/camerakit","url":"https://forest.dev/p/roblox/biotoxin495/camerakit","files":"https://api.forest.dev/ai/package/roblox/biotoxin495/camerakit/files","readme":"# CameraKit — Lightweight client-side camera utility for Roblox\r\n\r\n**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.\r\n\r\nCameraKit 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.\r\n\r\n## Quick example\r\n\r\n```lua\r\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\r\n\r\nlocal CameraKit = require(ReplicatedStorage:WaitForChild(\"CameraKit\"))\r\nlocal cameraKit = CameraKit.new()\r\n\r\ncameraKit:TweenTo(workspace.CameraPoints.Shop, {\r\n\tSpeed = 30,\r\n})\r\n\r\ncameraKit:Shake({\r\n\tDuration = 0.45,\r\n\tMagnitude = 0.18,\r\n\tRotationMagnitude = 2,\r\n})\r\n\r\ncameraKit:PulseFieldOfView(8)\r\n\r\n-- Return control to the captured gameplay camera later.\r\ncameraKit:Restore(nil, {\r\n\tDuration = 0.45,\r\n})\r\n```\r\n\r\nThe 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.\r\n\r\n## 🚀 Features\r\n\r\n- Tween the camera to a `CFrame`, `BasePart`, or `Attachment`\r\n- Calculate transition duration automatically from distance and movement speed\r\n- Play ordered or looping cinematic paths\r\n- Configure duration, delay, and `TweenInfo` per path point\r\n- Run FOV effects independently from positional movement\r\n- Add procedural camera shake without taking over the base camera controller\r\n- Stack recoil impulses with shake and scripted camera movement\r\n- Play temporary FOV punches that automatically return to the starting FOV\r\n- Change camera subjects while preserving the previous state\r\n- Capture and restore camera state reliably\r\n- Cancel running actions without stale tweens reclaiming control\r\n- Receive `Completed` or `Cancelled` action results\r\n- Handle `Workspace.CurrentCamera` replacement\r\n- Recover when a previously captured camera subject is destroyed during respawn\r\n- Maintain `Camera.Focus` while CameraKit owns a scriptable camera\r\n- Strict Luau public API\r\n- No external runtime dependencies\r\n\r\n## 🛠️ Installation\r\n\r\n### ModuleScript\r\n\r\nPlace `init.luau` in a client-accessible `ModuleScript`, such as `ReplicatedStorage.CameraKit`, then require it from a `LocalScript`:\r\n\r\n```lua\r\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\r\nlocal CameraKit = require(ReplicatedStorage:WaitForChild(\"CameraKit\"))\r\n```\r\n\r\n### Wally\r\n\r\n```toml\r\n[dependencies]\r\nCameraKit = \"biotoxin495/camerakit@1.1.0\"\r\n```\r\n\r\nCameraKit must be constructed on the client.\r\n\r\n## Basic usage\r\n\r\n```lua\r\nlocal cameraKit = CameraKit.new({\r\n\tDefaultSpeed = 24,\r\n\tMinDuration = 0.1,\r\n\tMaxDuration = 8,\r\n\tFocusDistance = 32,\r\n})\r\n```\r\n\r\nEvery constructor field is optional. One long-lived CameraKit instance per owning client controller is recommended.\r\n\r\nWhen the controller is no longer needed:\r\n\r\n```lua\r\ncameraKit:Destroy()\r\n```\r\n\r\nBy default, destruction restores the saved camera state.\r\n\r\n---\r\n\r\n# API reference\r\n\r\n## `CameraKit.new(config?)`\r\n\r\nCreates a CameraKit controller.\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `DefaultSpeed` | `number?` | `24` | Default positional movement speed in studs per second. |\r\n| `MinDuration` | `number?` | `0.1` | Minimum automatically calculated movement duration. |\r\n| `MaxDuration` | `number?` | `8` | Maximum automatically calculated movement duration. |\r\n| `FocusDistance` | `number?` | `32` | Distance used while maintaining `Camera.Focus` during scripted movement. |\r\n\r\n## `TweenTo(target, options?) -> CameraAction`\r\n\r\nTweens the current camera to a `CFrame`, `BasePart`, or `Attachment`. Attachments use `WorldCFrame`.\r\n\r\n```lua\r\ncameraKit:TweenTo(workspace.CameraPoint, {\r\n\tDuration = 1.2,\r\n\tEasingStyle = Enum.EasingStyle.Quint,\r\n\tEasingDirection = Enum.EasingDirection.Out,\r\n})\r\n```\r\n\r\nOr let CameraKit calculate the duration from distance:\r\n\r\n```lua\r\ncameraKit:TweenTo(workspace.CameraPoint, {\r\n\tSpeed = 28,\r\n})\r\n```\r\n\r\n| Option | Type | Description |\r\n| --- | --- | --- |\r\n| `Duration` | `number?` | Explicit duration in seconds. Overrides speed-based timing. |\r\n| `Speed` | `number?` | Movement speed when `Duration` is omitted. |\r\n| `MinDuration` | `number?` | Per-action minimum calculated duration. |\r\n| `MaxDuration` | `number?` | Per-action maximum calculated duration. |\r\n| `TweenInfo` | `TweenInfo?` | Complete TweenInfo override. |\r\n| `EasingStyle` | `Enum.EasingStyle?` | Defaults to `Quint`. |\r\n| `EasingDirection` | `Enum.EasingDirection?` | Defaults to `Out`. |\r\n| `CaptureState` | `boolean?` | Captures the current state if one has not already been saved. Defaults to `true`. |\r\n\r\nIf `TweenInfo` is supplied, its timing and easing settings take precedence.\r\n\r\n## `PlayPath(points, options?) -> CameraAction`\r\n\r\nPlays an ordered array of camera points.\r\n\r\n```lua\r\ncameraKit:PlayPath({\r\n\tworkspace.CameraPoints.Start,\r\n\tworkspace.CameraPoints.Middle,\r\n\tworkspace.CameraPoints.End,\r\n}, {\r\n\tSpeed = 35,\r\n\tRestoreOnComplete = true,\r\n})\r\n```\r\n\r\nA point can also carry per-segment timing:\r\n\r\n```lua\r\ncameraKit:PlayPath({\r\n\tworkspace.CameraPoints.Start,\r\n\t{\r\n\t\tTarget = workspace.CameraPoints.Middle,\r\n\t\tDuration = 2,\r\n\t\tDelay = 0.5,\r\n\t},\r\n\t{\r\n\t\tTarget = workspace.CameraPoints.End,\r\n\t\tTweenInfo = TweenInfo.new(1.5, Enum.EasingStyle.Quint, Enum.EasingDirection.Out),\r\n\t},\r\n})\r\n```\r\n\r\nConfigured path points support:\r\n\r\n| Option | Type | Description |\r\n| --- | --- | --- |\r\n| `Target` | `CFrame \\| BasePart \\| Attachment` | Segment destination. |\r\n| `Duration` | `number?` | Explicit segment duration. |\r\n| `Delay` | `number?` | Delay after reaching the point. |\r\n| `TweenInfo` | `TweenInfo?` | Complete TweenInfo override for the segment. |\r\n\r\nPath options:\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `Speed` | `number?` | constructor default | Segment speed when no duration is supplied. |\r\n| `MinDuration` | `number?` | constructor default | Minimum calculated segment duration. |\r\n| `MaxDuration` | `number?` | constructor default | Maximum calculated segment duration. |\r\n| `TweenInfo` | `TweenInfo?` | `nil` | Default TweenInfo for path segments. |\r\n| `EasingStyle` | `Enum.EasingStyle?` | `Linear` | Default path easing style. |\r\n| `EasingDirection` | `Enum.EasingDirection?` | `InOut` | Default path easing direction. |\r\n| `Loop` | `boolean?` | `false` | Repeats the path until cancelled. |\r\n| `StartFromCurrent` | `boolean?` | `true` | When false, snaps to point one before continuing. |\r\n| `RestoreOnComplete` | `boolean?` | `false` | Restores the saved state after a non-looping path. |\r\n| `CaptureState` | `boolean?` | `true` | Captures the current state if one is not already saved. |\r\n| `OnPointReached` | `function?` | `nil` | Invoked asynchronously when a point is reached. |\r\n\r\n## `SetSubject(subject, options?)`\r\n\r\nChanges `CameraSubject` to a `Humanoid` or `BasePart`.\r\n\r\n```lua\r\ncameraKit:SetSubject(workspace.DisplayCharacter.Humanoid, {\r\n\tCameraType = Enum.CameraType.Custom,\r\n\tMinZoomDistance = 18,\r\n\tMaxZoomDistance = 35,\r\n})\r\n```\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `CameraType` | `Enum.CameraType?` | `Custom` | Camera type applied after changing the subject. |\r\n| `CaptureState` | `boolean?` | `true` | Captures the previous state if needed. |\r\n| `MinZoomDistance` | `number?` | unchanged | Optional player minimum zoom distance. |\r\n| `MaxZoomDistance` | `number?` | unchanged | Optional player maximum zoom distance. |\r\n\r\n## `SetFieldOfView(fieldOfView, options?) -> CameraAction`\r\n\r\nSets or tweens `Camera.FieldOfView`.\r\n\r\n```lua\r\ncameraKit:SetFieldOfView(55, {\r\n\tDuration = 0.3,\r\n})\r\n```\r\n\r\nValid values are `1` through `120`.\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `Duration` | `number?` | `0.2` | Transition duration. |\r\n| `CaptureState` | `boolean?` | `false` | Captures the full camera state if needed. |\r\n| `TweenInfo` | `TweenInfo?` | `nil` | Complete TweenInfo override. |\r\n| `EasingStyle` | `Enum.EasingStyle?` | `Quint` | Easing style when TweenInfo is omitted. |\r\n| `EasingDirection` | `Enum.EasingDirection?` | `Out` | Easing direction when TweenInfo is omitted. |\r\n\r\nStarting another FOV action cancels the previous FOV action.\r\n\r\n## `PulseFieldOfView(amount, options?) -> CameraAction`\r\n\r\nCreates a temporary FOV punch, then returns to the FOV that was active when the pulse started. The peak value is clamped to Roblox's `1`–`120` FOV range.\r\n\r\n```lua\r\ncameraKit:PulseFieldOfView(10, {\r\n\tAttackDuration = 0.06,\r\n\tHoldDuration = 0.03,\r\n\tReleaseDuration = 0.28,\r\n})\r\n```\r\n\r\nUse a negative amount for a quick zoom-in punch:\r\n\r\n```lua\r\ncameraKit:PulseFieldOfView(-8)\r\n```\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `AttackDuration` | `number?` | `0.08` | Time to reach the peak FOV. |\r\n| `HoldDuration` | `number?` | `0` | Optional time held at the peak. |\r\n| `ReleaseDuration` | `number?` | `0.22` | Time to return to the starting FOV. |\r\n| `CaptureState` | `boolean?` | `false` | Captures the full camera state if needed. |\r\n| `AttackEasingStyle` | `Enum.EasingStyle?` | `Quad` | Attack easing style. |\r\n| `AttackEasingDirection` | `Enum.EasingDirection?` | `Out` | Attack easing direction. |\r\n| `ReleaseEasingStyle` | `Enum.EasingStyle?` | `Quint` | Release easing style. |\r\n| `ReleaseEasingDirection` | `Enum.EasingDirection?` | `Out` | Release easing direction. |\r\n\r\n`PulseFieldOfView` uses the same FOV channel as `SetFieldOfView`.\r\n\r\n## `Shake(options?) -> CameraAction`\r\n\r\nAdds 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`.\r\n\r\n```lua\r\nlocal shake = cameraKit:Shake({\r\n\tDuration = 0.6,\r\n\tMagnitude = 0.2,\r\n\tRotationMagnitude = 2.5,\r\n\tFrequency = 22,\r\n\tFadeOut = 0.25,\r\n})\r\n```\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `Duration` | `number?` | `0.5` | Total shake duration. |\r\n| `Magnitude` | `number?` | `0.15` | Local positional shake magnitude in studs. |\r\n| `RotationMagnitude` | `number?` | `1.5` | Rotation magnitude in degrees. |\r\n| `Frequency` | `number?` | `18` | Noise sampling frequency; higher values feel rougher/faster. |\r\n| `FadeIn` | `number?` | `0` | Fade-in duration. |\r\n| `FadeOut` | `number?` | `min(0.2, Duration)` | Fade-out duration. |\r\n| `Seed` | `number?` | random | Optional deterministic noise seed. |\r\n\r\nShake uses smooth `math.noise` sampling rather than independent frame-by-frame randomness, avoiding jitter that changes character with frame rate.\r\n\r\n## `Recoil(options?) -> CameraAction`\r\n\r\nAdds 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()`.\r\n\r\n```lua\r\ncameraKit:Recoil({\r\n\tRotation = Vector3.new(-5, 0.4, 0),\r\n\tPosition = Vector3.new(0, 0, 0.14),\r\n\tDuration = 0.22,\r\n})\r\n```\r\n\r\n`Rotation` is expressed in degrees around local X/Y/Z. `Position` is in local camera-space studs.\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `Duration` | `number?` | `0.25` | Time for the kick to settle back to zero. |\r\n| `Position` | `Vector3?` | `(0, 0, 0.12)` | Initial local positional kick. |\r\n| `Rotation` | `Vector3?` | `(-4, 0, 0)` | Initial local rotational kick in degrees. |\r\n| `EasingStyle` | `Enum.EasingStyle?` | `Quad` | Decay easing style. |\r\n| `EasingDirection` | `Enum.EasingDirection?` | `Out` | Decay easing direction. |\r\n\r\n## `StopEffects()`\r\n\r\nCancels all active additive `Shake` and `Recoil` actions and removes their currently applied camera offset.\r\n\r\n```lua\r\ncameraKit:StopEffects()\r\n```\r\n\r\nThis does not cancel positional camera movement or FOV actions.\r\n\r\n## `CaptureState() -> CameraState`\r\n\r\nReturns a snapshot of the current camera and player zoom state:\r\n\r\n```lua\r\nlocal state = cameraKit:CaptureState()\r\n```\r\n\r\nThe state contains:\r\n\r\n```lua\r\n{\r\n\tCameraType,\r\n\tCameraSubject,\r\n\tCFrame,\r\n\tFocus,\r\n\tFieldOfView,\r\n\tFieldOfViewMode,\r\n\tCameraMinZoomDistance,\r\n\tCameraMaxZoomDistance,\r\n}\r\n```\r\n\r\nWhen 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.\r\n\r\n## `GetSavedState() -> CameraState?`\r\n\r\nReturns CameraKit's automatically captured state, if one exists.\r\n\r\n## `Restore(state?, options?) -> CameraAction`\r\n\r\nRestores an explicit `CameraState`, or the internally saved state when `state` is `nil`.\r\n\r\n```lua\r\ncameraKit:Restore(nil, {\r\n\tDuration = 0.5,\r\n})\r\n```\r\n\r\nRestore cancels active positional, FOV, and additive VFX work before applying the target state.\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `Duration` | `number?` | `0` | Duration for CFrame and FOV restoration. |\r\n| `TweenInfo` | `TweenInfo?` | `nil` | Complete restoration TweenInfo override. |\r\n| `EasingStyle` | `Enum.EasingStyle?` | `Quint` | Easing style when TweenInfo is omitted. |\r\n| `EasingDirection` | `Enum.EasingDirection?` | `Out` | Easing direction when TweenInfo is omitted. |\r\n| `ClearSavedState` | `boolean?` | `true` | Clears the internal saved state when restoring that same state. |\r\n\r\nIf a captured subject was destroyed, CameraKit attempts to use the current character's `Humanoid` instead.\r\n\r\n## `Stop(options?) -> CameraAction?`\r\n\r\nCancels active work.\r\n\r\n```lua\r\ncameraKit:Stop({\r\n\tRestore = true,\r\n\tRestoreOptions = {\r\n\t\tDuration = 0.4,\r\n\t},\r\n})\r\n```\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | ---: | --- |\r\n| `Restore` | `boolean?` | `false` | Restores the saved camera state after stopping. |\r\n| `RestoreOptions` | `RestoreOptions?` | `nil` | Options forwarded to `Restore()`. |\r\n| `StopFieldOfView` | `boolean?` | `true` | Also cancels the active FOV action. |\r\n| `StopEffects` | `boolean?` | `true` | Also cancels active shake/recoil effects. |\r\n\r\nStopping without restoration intentionally leaves the camera at its current base state.\r\n\r\n## `IsActive() -> boolean`\r\n\r\nReturns `true` while a positional action, FOV action, or additive VFX action is active.\r\n\r\n## `Destroy(restoreCamera?)`\r\n\r\nCancels all work, removes render-step bindings, disconnects internal listeners, and destroys the controller. By default it restores the saved state first.\r\n\r\n```lua\r\ncameraKit:Destroy()\r\n```\r\n\r\nTo leave the current camera unchanged:\r\n\r\n```lua\r\ncameraKit:Destroy(false)\r\n```\r\n\r\nA destroyed CameraKit instance cannot be reused.\r\n\r\n---\r\n\r\n# CameraAction\r\n\r\n`TweenTo`, `PlayPath`, `SetFieldOfView`, `PulseFieldOfView`, `Shake`, `Recoil`, and `Restore` return a `CameraAction` handle.\r\n\r\n```lua\r\nlocal action = cameraKit:Shake()\r\n\r\naction.Completed:Connect(function(result)\r\n\tprint(result) -- \"Completed\" or \"Cancelled\"\r\nend)\r\n```\r\n\r\nAvailable methods and signals:\r\n\r\n- `action.Completed` — fires once with `\"Completed\"` or `\"Cancelled\"`\r\n- `action:Cancel()` — cancels the action if still active\r\n- `action:Await()` — yields until completion and returns the result\r\n- `action:IsPlaying()` — returns whether the action is still active\r\n- `action:GetResult()` — returns the result or `nil` while running\r\n- `action:Destroy()` — cancels an unfinished action and destroys its completion event\r\n\r\nRepeated cancellation is safe.\r\n\r\n# Action channels and VFX composition\r\n\r\nCameraKit separates work into three categories.\r\n\r\n### Positional channel\r\n\r\nUsed by `TweenTo`, `PlayPath`, and animated `Restore`. Starting a new positional action cancels the previous positional action.\r\n\r\n### FOV channel\r\n\r\nUsed by `SetFieldOfView` and `PulseFieldOfView`. Starting a new FOV action cancels the previous FOV action.\r\n\r\n### Additive VFX layer\r\n\r\nUsed by `Shake` and `","readmeTruncated":true,"readmeFull":"https://api.forest.dev/v1/package/biotoxin495/roblox/camerakit/1.1.0/readme"}