Basic Isometric CameraScript

Hey all, I’ve been working on a top-down camera system for a brawler/RPG I’m working on.

The camera is set to follow slightly ahead of where the player is going to give them a bit of a better view of what’s ahead. If they’ve stayed still, or within range of the last focus point, the camera will slowly pan back to the player after reaching its last goal.

Everything works pretty well so far, and y’all can use it for yourselves in whatever you’re working on, but I’m also looking for some input on a few things – namely my formatting and whether or not I’m handling my returns correctly (and anything else that might be helpful tbh).

Here’s the script (I added some comments to it, hopefully someone finds them helpful lol):

local Players = game:GetService("Players")
local RunSVC = game:GetService("RunService")
local ContextActionSVC = game:GetService("ContextActionService")

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 focusPoint : CFrame -- point which the camera will look at
local camGoal : CFrame -- where the camera will be
local camDistFromGoal : number
local currentCamSpeed : number

camera.CameraType = Enum.CameraType.Scriptable
camera.FieldOfView = currentZoom

local function getDist(object, goal : Vector3) : number
	return (object.CFrame.Position - goal).Magnitude
end

local function setCamSpeed() : number
	return camNearPlayer and minCamSpeed
		or math.clamp(0.002*camDistFromGoal, minCamSpeed, maxCamSpeed)
		
		-- camera moves faster the further away it is from its goal,
		-- staying within speed range.
		
		-- slow pan when focusPoint is near/returning to player
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))

		-- camera set to follow slightly ahead of where the player is running
		
		-- if the player is not moving,
		-- the camera will continue toward its current destination
		
		-- if the camera is very close to its goal,
		-- it will change direction and pan back toward the player
end

local function setCamGoal() : CFrame
	return CFrame.new(focusPoint.Position + Vector3.new(-30,60,-30), focusPoint.Position)

	-- positions the camera overhead,
	-- viewing the area surrounding the player at an angle
end

local function invalidState(state) : boolean
	return state == Enum.UserInputState.End
		or state == Enum.UserInputState.Cancel
		or state == Enum.UserInputState.None

		-- all InputStates to be ignored when zooming in/out
end

local function isAtMaxZoom(isZoomingOut) : boolean
	return isZoomingOut and currentZoom >= maxZoom
		or not isZoomingOut and currentZoom <= minZoom

		-- makes sure the player doesn't zoom in/out past min/max values
end

local function isZoomingOut(input : InputObject) : boolean
	return input.UserInputType == Enum.UserInputType.MouseWheel and input.Position.Z < 0
		or input.KeyCode == Enum.KeyCode.Minus

		-- when zooming, checks to see if player is zooming out specifically
		-- if not, assume they're zooming in since that's the only other option anyway
end

local function camIsMoving() : boolean
	return camDistFromGoal >= 1 and camDistFromGoal <= 10

	-- a wider distance allows the player to stop the camera more quickly and easily
	-- a shorter distance will make the camera more reactive to movement
end

local function setZoom(action, inputState, inputObject : InputObject?)
	if invalidState(inputState) then
		return Enum.ContextActionResult.Pass
	end
	local zoomingOut = isZoomingOut(inputObject)
	currentZoom += isAtMaxZoom(zoomingOut) and 0 or zoomingOut and 5 or -5
	camera.FieldOfView = currentZoom
end

local function OverheadCam()
	camDistFromGoal = getDist(camera, camGoal.Position)
	camera.CFrame = camera.CFrame:Lerp(camGoal, setCamSpeed())
	
	if getDist(rootPart, focusPoint.Position) < 22 and camIsMoving() then return end

	-- if the player is currently moving and not within range of the previous focusPoint,
	-- update the point for the camera to lerp to
	
	focusPoint = setNewFocusPoint()
	camGoal = setCamGoal()
	camNearPlayer = getDist(rootPart, focusPoint.Position) < 5
end

local function UnbindCamera() -- called when player character dies/is removed
	humanoid, rootPart = nil
	RunSVC:UnbindFromRenderStep("Camera")
	ContextActionSVC:UnbindAction("Zoom")
	
	-- stops the player from moving the camera after death
end

player.CharacterAdded:Connect(function(char)
	humanoid = char:WaitForChild("Humanoid")
	rootPart = char:WaitForChild("HumanoidRootPart")
	
	focusPoint = rootPart.CFrame -- focus on player when spawning
	camGoal = setCamGoal()
	camera.CFrame = camGoal
	
	RunSVC:BindToRenderStep("Camera", 0, OverheadCam)
	
	-- camera begins tracking the player once their character spawns
	
	ContextActionSVC:BindAction("Zoom", setZoom, false,
		Enum.KeyCode.Equals,
		Enum.KeyCode.Minus,
		Enum.UserInputType.MouseWheel
	)

	-- once character spawns, let them zoom in/out using the given inputs
	
	humanoid.Died:Connect(UnbindCamera)
end)

player.CharacterRemoving:Connect(UnbindCamera)
1 Like