The newest version of LazyCam has arrived! Now the camera will focus on nearby interest points, like shops and monster spawners:
The camera is set to track the player from overhead at ~45 degree angle, and will follow slightly ahead of where they’re moving to get a better view of what’s directly ahead of them:
If the player isn’t moving, the camera will eventually center itself back to the player:
You can also zoom in and out by altering the FOV with the mouse wheel (and now -/+ keys):
Patch notes:
- Camera will now gravitate toward/focus on tagged points of interest while player is within range
- Most checks now done using values provided by function returns
- Camera speed/max distance now based on zoom level to better keep the player in frame
- Event handling that binds/unbinds camera controls depending on current state
(in a shop, dead, etc.) - Slightly improved overall script organization
Here’s the script:
local Players = game:GetService("Players")
local RunSVC = game:GetService("RunService")
local ContextActionSVC = game:GetService("ContextActionService")
local CollectionSVC = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Events = ReplicatedStorage:FindFirstChild("Events")
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera
local humanoid, rootPart
local camNearPlayer = false
local minZoom = 30
local maxZoom = 75
local currentZoom = 50
local minCamSpeed = 0.001
local maxCamSpeed = 0.025
local camGoal : CFrame
local focusPoint : Vector3
local camDistFromGoal : number
local currentCamSpeed : number
camera.CameraType = Enum.CameraType.Scriptable
camera.FieldOfView = currentZoom
local function getDist(object, goal) : number
return (object.CFrame.Position - goal).Magnitude
end
local function setCamSpeed() : number
return camNearPlayer and minCamSpeed
or math.clamp(0.002*camDistFromGoal, minCamSpeed, maxCamSpeed)
end
local function newPointAheadOfPlayer() : Vector3
return rootPart.CFrame.Position + (humanoid.MoveDirection * currentZoom/2)
end
local function pointIsNearby(point : CFrame) : CFrame
return getDist(point, rootPart.CFrame.Position) < currentZoom*0.85
end
local function getNearestInterestPoint() : Vector3
for _, point in pairs(CollectionSVC:GetTagged("PointOfInterest")) do
if not pointIsNearby(point) then continue end
return point.CFrame.Position
end
return nil
end
local function getNewFocusPoint() : Vector3
return camDistFromGoal < 1 and rootPart.CFrame.Position
or humanoid.MoveDirection.Magnitude == 0 and focusPoint
or getNearestInterestPoint()
or newPointAheadOfPlayer()
end
local function setCamGoal() : CFrame
return CFrame.new(focusPoint + Vector3.new(-30,75,-30), focusPoint)
end
local function invalidState(state) : boolean
return state == Enum.UserInputState.End
or state == Enum.UserInputState.Cancel
or state == Enum.UserInputState.None
end
local function isAtMaxZoom(isZoomingOut) : boolean
return isZoomingOut and currentZoom >= maxZoom
or not isZoomingOut and currentZoom <= minZoom
end
local function isZoomingOut(input : InputObject) : boolean
return input.UserInputType == Enum.UserInputType.MouseWheel and input.Position.Z < 0
or input.KeyCode == Enum.KeyCode.Minus
end
local function SetZoom(inputObject)
local zoomingOut = isZoomingOut(inputObject)
currentZoom += isAtMaxZoom(zoomingOut) and 0 or zoomingOut and 5 or -5
camera.FieldOfView = currentZoom
end
local function GetZoom(action, inputState, inputObject : InputObject?)
return invalidState(inputState) and Enum.ContextActionResult.Pass
or SetZoom(inputObject)
end
local function camIsMoving() : boolean
return camDistFromGoal > 0.1 and camDistFromGoal <= 10
end
local function OverheadCam()
camDistFromGoal = getDist(camera, camGoal.Position)
camera.CFrame = camera.CFrame:Lerp(camGoal, setCamSpeed())
if getDist(rootPart, focusPoint) < currentZoom/3 and camIsMoving() then return end
focusPoint = getNewFocusPoint()
camGoal = setCamGoal()
camNearPlayer = getDist(rootPart, focusPoint) < 5
end
local function BindCamera()
focusPoint = rootPart.CFrame.Position
camGoal = setCamGoal()
camera.CFrame = camGoal
RunSVC:BindToRenderStep("Camera", 0, OverheadCam)
ContextActionSVC:BindAction("Zoom", GetZoom, false,
Enum.KeyCode.Equals,
Enum.KeyCode.Minus,
Enum.UserInputType.MouseWheel
)
end
local function UnbindCamera(char)
RunSVC:UnbindFromRenderStep("Camera")
ContextActionSVC:UnbindAction("Zoom")
currentZoom = 50
camera.FieldOfView = currentZoom
if not char then return end
humanoid, rootPart = nil
end
local function InitCamera(char)
humanoid = char:WaitForChild("Humanoid")
rootPart = char:WaitForChild("HumanoidRootPart")
BindCamera()
humanoid.Died:Connect(UnbindCamera)
end
local function SwapCamShopState(state)
return state == 2 and BindCamera()
or state == 1 and UnbindCamera()
end
-- used in combination with values used by a shop system
-- state 1 = Entering Shop
-- state 2 = Exiting Shop
-- state 0 = Dead/Invalid (handled exclusively by shop system for now,
-- since death is already handled here)
player.CharacterAdded:Connect(InitCamera)
player.CharacterRemoving:Connect(UnbindCamera)
Events.ShopMenuChanged.OnClientEvent:Connect(SwapCamShopState)
I’d greatly appreciate any feedback y’all can give me on what I’ve written so far.
Older versions below:
LazyCam 1.2
-- [[CameraScript]]--
-- Updated zoom function to use ContextActionService
local Players = game:GetService("Players")
local RunSVC = game:GetService("RunService")
local ContextActionSVC = game:GetService("ContextActionService")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
local character, humanoid, rootPart
local focusPoint : CFrame
local camGoal : CFrame
local camDistFromGoal : number
local minZoom = 30
local maxZoom = 75
local currentZoom = 50
local camReturningToPlayer = false
camera.CameraType = Enum.CameraType.Scriptable
camera.FieldOfView = currentZoom
local function isAtMaxZoom(isZoomingOut) : boolean
return isZoomingOut and currentZoom >= maxZoom
or not isZoomingOut and currentZoom <= minZoom
end
local function checkIfZoomingOut(inputObject : InputObject) : boolean
return inputObject.UserInputType == Enum.UserInputType.MouseWheel and inputObject.Position.Z < 0
or inputObject.KeyCode == Enum.KeyCode.Minus
or false
end
local function zoom(action, inputState, inputObject : InputObject)
if inputState ~= Enum.UserInputState.Begin and inputState ~= Enum.UserInputState.Change then
return Enum.ContextActionResult.Pass
end
local isZoomingOut = checkIfZoomingOut(inputObject)
currentZoom += isAtMaxZoom(isZoomingOut) and 0 or isZoomingOut and 5 or -5
camera.FieldOfView = currentZoom
end
local function setCamGoal() : CFrame
return CFrame.new(focusPoint.Position + Vector3.new(-30,60,-30), focusPoint.Position)
end
local function getDist(object, goal) : number
return (object.CFrame.Position - goal).Magnitude
end
local function setNewFocusPoint() : CFrame
return camDistFromGoal < 1.1 and CFrame.new(rootPart.CFrame.Position)
or humanoid.MoveDirection.Magnitude == 0 and focusPoint
or CFrame.new(rootPart.CFrame.Position + (humanoid.MoveDirection * 20))
end
local function OverheadCam()
camDistFromGoal = getDist(camera, camGoal.Position)
camera.CFrame = camera.CFrame:Lerp(camGoal, camReturningToPlayer and .0005 or math.clamp(0.002*camDistFromGoal, 0.0005, 0.025))
if getDist(rootPart, focusPoint.Position) < 22 and camDistFromGoal <= 10 and camDistFromGoal >= 1 then return end
focusPoint = setNewFocusPoint()
camGoal = setCamGoal()
camReturningToPlayer = focusPoint == rootPart.CFrame
end
local function UnbindCamera()
character, humanoid, rootPart = nil
RunSVC:UnbindFromRenderStep("Camera")
ContextActionSVC:UnbindAction("Zoom")
end
player.CharacterAdded:Connect(function(char)
character = char
humanoid = char:WaitForChild("Humanoid")
rootPart = char:WaitForChild("HumanoidRootPart")
focusPoint = rootPart.CFrame
camGoal = setCamGoal()
camera.CFrame = camGoal
RunSVC:BindToRenderStep("Camera", 0, OverheadCam)
ContextActionSVC:BindAction("Zoom", zoom, false, Enum.KeyCode.Equals, Enum.KeyCode.Minus, Enum.UserInputType.MouseWheel)
humanoid.Died:Connect(UnbindCamera)
end)
player.CharacterRemoving:Connect(UnbindCamera)
LazyCam 1.1
--[[CameraScript]]--
local Players = game:GetService("Players")
local RunSVC = game:GetService("RunService")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
local character, humanoid, rootPart
local focusPoint : CFrame
local camGoal : CFrame
local camDistFromGoal : number
local minZoom = 30
local maxZoom = 75
local currentZoom = 50
local zoomIn, zoomOut
camera.CameraType = Enum.CameraType.Scriptable
camera.FieldOfView = currentZoom
-- determines whether the player is trying to zoom past the min/max zoom FOVs
local function isAtMaxZoom(isZoomingOut) : boolean
return isZoomingOut and currentZoom >= maxZoom or not isZoomingOut and currentZoom <= minZoom
end
-- To be used for adjusting FOV when called using a Connection
local function zoom(isZoomingOut)
return function()
currentZoom += isAtMaxZoom(isZoomingOut) and 0 or isZoomingOut and 5 or -5
camera.FieldOfView = currentZoom
end
end
-- Gives the camera a goal position and focus point to move to
local function setCamGoal() : CFrame
return CFrame.new(focusPoint.Position + Vector3.new(-30,60,-30), focusPoint.Position)
end
local function getDist(object, goal) : number
return (object.CFrame.Position - goal).Magnitude
end
-- sets new camera focus point depending on current camera/player behavior
local function setNewFocusPoint() : CFrame
return camDistFromGoal <= 3 and CFrame.new(rootPart.CFrame.Position)
or humanoid.MoveDirection.Magnitude == 0 and focusPoint
or CFrame.new(rootPart.CFrame.Position + (humanoid.MoveDirection * 20))
end
-- main function, lerps camera toward goal depending on player position and movement.
local function OverheadCam()
camDistFromGoal = getDist(camera, camGoal.Position)
camera.CFrame = camera.CFrame:Lerp(camGoal, math.clamp(0.002*camDistFromGoal, 0.0005, 0.025))
if getDist(rootPart, focusPoint.Position) < 22 and camDistFromGoal <= 2 then return end
focusPoint = setNewFocusPoint()
camGoal = setCamGoal()
end
-- to be called when the character dies/is removed
local function UnbindCamera()
character, humanoid, rootPart = nil
RunSVC:UnbindFromRenderStep("Camera")
zoomIn:Disconnect()
zoomOut:Disconnect()
end
player.CharacterAdded:Connect(function(char)
character = char
humanoid = char:WaitForChild("Humanoid")
rootPart = char:WaitForChild("HumanoidRootPart")
focusPoint = rootPart.CFrame -- camera focuses on character position when spawning
camGoal = setCamGoal()
camera.CFrame = camGoal
RunSVC:BindToRenderStep("Camera", 0, OverheadCam)
zoomIn = mouse.WheelForward:Connect(zoom(false))
zoomOut = mouse.WheelBackward:Connect(zoom(true))
humanoid.Died:Connect(UnbindCamera)
end)
player.CharacterRemoving:Connect(UnbindCamera)
LazyCam 1.0
local Players = game:GetService("Players")
local RunSVC = game:GetService("RunService")
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera
camera.CameraType = Enum.CameraType.Scriptable
local maxZoom = 35
local minZoom = 75
local currentZoom = 50
local character, humanoid, rootPart
local focusPoint : CFrame
local camGoal : CFrame
local camDistFromGoal : number
local function setCamGoal() : CFrame
return CFrame.new(focusPoint.Position + Vector3.new(-30,60,-30), focusPoint.Position)
end
local function getDist(object, goal) : number
return (object.CFrame.Position - goal).Magnitude
end
local function setNewFocusPoint() : CFrame
return camDistFromGoal <= 3.1 and CFrame.new(rootPart.CFrame.Position)
or humanoid.MoveDirection.Magnitude == 0 and focusPoint
or CFrame.new(rootPart.CFrame.Position + (humanoid.MoveDirection * 20))
end
local function OverheadCam()
camDistFromGoal = getDist(camera, camGoal.Position)
camera.CFrame = camera.CFrame:Lerp(camGoal, math.clamp(0.002*camDistFromGoal, 0.0005, 0.025))
if getDist(rootPart, focusPoint.Position) > 22 or camDistFromGoal > 3 then
focusPoint = setNewFocusPoint()
camGoal = setCamGoal()
end
end
local zoomIn, zoomOut
local function zoom(out, zoom)
return function()
currentZoom += (out and currentZoom >= zoom or not out and currentZoom <= zoom) and 0 or out and 5 or -5
camera.FieldOfView = currentZoom
end
end
player.CharacterAdded:Connect(function(char)
character = char
humanoid = char:WaitForChild("Humanoid")
rootPart = char:WaitForChild("HumanoidRootPart")
focusPoint = rootPart.CFrame
camGoal = setCamGoal()
camera.CFrame = camGoal
RunSVC:BindToRenderStep("Camera", 0, OverheadCam)
zoomIn = player:GetMouse().WheelForward:Connect(zoom(false, maxZoom))
zoomOut = player:GetMouse().WheelBackward:Connect(zoom(true, minZoom))
end)
player.CharacterRemoving:Connect(function(char)
character, humanoid, rootPart = nil
RunSVC:UnbindFromRenderStep("Camera")
zoomIn:Disconnect()
zoomOut:Disconnect()
end)