$npx -y skills add TabooHarmony/roblox-brain --skill roblox-cameraUse when scripting Roblox camera behavior, CFrame placement, screen raycasts, first or third-person views, or cutscenes.
| 1 | ## When to Load |
| 2 | |
| 3 | Load when scripting custom camera behavior (cutscenes, third-person follow, custom rotation), manipulating `CFrame` for placement/rotation, raycasting from the screen, or building a non-default player view. Client-only. |
| 4 | |
| 5 | ## Quick Reference |
| 6 | |
| 7 | **The Camera**: `workspace.CurrentCamera` — one per client. Set `CameraType = Scriptable` to disable defaults and take full control. Without it, defaults overwrite your CFrame every frame. |
| 8 | |
| 9 | **CameraType**: `Fixed`, `Attach`/`Watch`/`Track`/`Follow` (subject-following), `Custom` (default), `Scriptable` (no default), `Orbital` (fixed Y, rotates around player). `CameraSubject` cannot be `nil` — setting it reverts. |
| 10 | |
| 11 | **Key properties**: `CFrame`, `CameraSubject`, `FieldOfView` (deg), `FieldOfViewMode` (`Vertical`/`Diagonal`), `NearPlaneZ`, `ViewportSize`, `HeadLocked`, `HeadScale`, `Focus`. |
| 12 | |
| 13 | **CFrame essentials**: |
| 14 | |
| 15 | ```luau |
| 16 | CFrame.new(pos) |
| 17 | CFrame.lookAt(at, lookAt, up?) -- preferred over deprecated CFrame.new(pos, lookAt) |
| 18 | CFrame.Angles(rx, ry, rz) -- XYZ Euler (radians) |
| 19 | cf * Vector3.new(0, 0, 10) -- local offset → world |
| 20 | cf:Lerp(goal, alpha) -- 0..1 linear interp |
| 21 | cf.LookVector / RightVector / UpVector -- world-space unit axes |
| 22 | ``` |
| 23 | |
| 24 | **Custom camera loop** — always `RenderStepped`, never `Heartbeat`: |
| 25 | |
| 26 | ```luau |
| 27 | camera.CameraType = Enum.CameraType.Scriptable |
| 28 | RunService.RenderStepped:Connect(function(dt) |
| 29 | local desired = CFrame.lookAt(head.Position - offset, head.Position) |
| 30 | camera.CFrame = camera.CFrame:Lerp(desired, math.min(dt * 10, 1)) |
| 31 | end) |
| 32 | ``` |
| 33 | |
| 34 | **Raycasting from camera**: `camera:ScreenPointToRay(mx, my)` (accounts for GUI inset) vs `camera:ViewportPointToRay(mx, my)` (raw, NO inset). `ScreenPointToRay` returns a unit Ray (1 stud) — multiply `Direction` by length for actual raycast. |
| 35 | |
| 36 | **Pitfalls**: |
| 37 | - Client-only. Server sets silently dropped. |
| 38 | - Without `Scriptable`, defaults overwrite your CFrame every frame. |
| 39 | - `RenderStepped` for camera (visual sync). `Heartbeat` adds 1-frame lag. |
| 40 | - `Camera.CFrame` lacks VR head rotation — use `GetRenderCFrame()` for true view. |
| 41 | - `SetRoll` is outdated — apply roll via `CFrame.Angles(0, 0, roll)` on CFrame. |
| 42 | - `CameraSubject = nil` reverts to previous. |
| 43 | - `CFrame.new(pos, lookAt)` is legacy (back-compat only) — use `CFrame.lookAt(at, lookAt)` for new code. |
| 44 | - `ScreenPointToRay` ≠ `ViewportPointToRay` (GUI inset). Use `ScreenPointToRay` for mouse input. |
| 45 | |
| 46 | See `references/full.md` for first/third-person recipes, cutscenes, screen shake, mouse-look, full API. |