Simple introduction to making a "Lock-On" Camera

Hello guys, I want to show you how to make a lock-on camera in roblox its fairly simple. It will utilize this formula i made.

V = B + ((A + Offset) - B).Unit * ((A - B).Magnitude + Extra)

(A - B).Unit gives you a direction vector of B pointing to A
(A - B).Magnitude gives the distance between A and B
Ill try to break these down into what they mean in the line ^^^
B = The object you want to look at but its the origin as well
A = is the object that B is shooting out towards

Edit:

  1. we create a variable

    • local V =
  2. we have to establish the origin which in this case will be defined as…

    • B
  3. So now that we have the origin we need to create a Vector that points towards another object(which is created with the positions in the .Unit) the other object is defined as A, so essentially we’re making B point to A, now the Offset is to have it off to move it around instead of the camera being directly behind the A

  4. now we want to multiply that Direction Vector we got from the .Unit by the distance between the two by using the magnitude and then we’ll add a little extra

So i’ve created a demonstration at the time of me making this

Basically the camera would represent the blue object, the player the red, and the target is green.emphasized text

https://cdn.discordapp.com/attachments/165202235226062848/617484312274534453/Download.mp4
Lock-on.rbxl (44.6 KB)

In there is the model i used in the 1st video also pressing R will lock on to the zombie in the game the script for it is located in StarterCharacterScripts

68 Likes

I made a similar system, but ended up using lots of trigonometry and such to do target swapping (so you press a key and it swaps to the next target on your left or right, depending on the key press, since your cursor was locked)

2 Likes

I don’t see why not just use CFrame.new(origin, lookAt) ?

If you want over your shoulder view, just use an Attachment at that position and use it’s worldposition for origin, right?

7 Likes

well to be honest i tried that but i did it wrong and i forgot what i did wrong so i scrapped the entire thing and thought to myself .Unit would be great for this

and i think mine is more optimizable if im fairly honest

The CFrame method is more efficient, IIRC. Doesn’t mean yours is any less functional.

I used CFrame heavily.

do you mind showing CFrame(origin, lookAt) vs your method?

2 Likes

I scrapped it but if i reattempted i bet it would work

1 Like

I can dig up the code, gimme a sec

My code
local entities = workspace.Entities
local players = game:GetService("Players")
local lP = players.LocalPlayer
local rS = game:GetService("ReplicatedStorage")
local input = require(script.Input)
input.start()
lP.CharacterAdded:Wait():WaitForChild("Humanoid")
lP.Character.Humanoid.CameraOffset = Vector3.new(0,1,0)
local uSettings = UserSettings():GetService("UserGameSettings")
local uIS = game:GetService("UserInputService")
local run = game:GetService("RunService")
workspace.CurrentCamera.FieldOfView = 85
local aspRatio = workspace.CurrentCamera.ViewportSize.X/workspace.CurrentCamera.ViewportSize.Y
print("Aspect Ratio: " .. aspRatio)
local tol = 0

do do -- compacting, double 'do end' to fix collapsing errors
lP.CharacterAdded:Connect(function(char)
		workspace.CurrentCamera.CameraSubject = char.Humanoid
end)
function wrapAround(meta,key) -- do looparound
	local elem
	if math.fmod(key,#meta) > 0 then
		elem = meta[key-math.fmod(key,#meta)*#meta]
		return elem
	else
		elem = meta[#meta - (math.abs(key)-math.fmod(key,#meta)*#meta)]
		return elem
	end
end
end
function roundTo(num, deg)
	return(math.floor(num*(1/deg)+.5)*deg)
end
function getTargetList(range)
	local rtrn = {}
	local cf = CFrame.new(lP.Character.PrimaryPart.CFrame.Position,
		lP.Character.PrimaryPart.CFrame.Position + (workspace.CurrentCamera.CFrame.LookVector * Vector3.new(1,0,1)).Unit
		)
	for _, e in pairs(entities:GetDescendants()) do
		if e:IsA("Model") and e ~= lP.Character then
			local ray = Ray.new(lP.Character.PrimaryPart.Position, e.PrimaryPart.Position - lP.Character.PrimaryPart.Position)
			local part = workspace:FindPartOnRayWithIgnoreList(ray, entities:GetDescendants())
			if (lP.Character.PrimaryPart.Position - e.PrimaryPart.Position).Magnitude <= range and not part then
				local ep = e.PrimaryPart.Position
				local p = cf:PointToObjectSpace(Vector3.new(ep.X,cf.Position.Y,ep.Z))
				local dist = p.Magnitude
				local ang = math.deg(math.asin(-p.X/dist))
				if -p.Z < 0 then
					ang = (math.asin(-p.X/dist) < 0 and -1 or 1)*180 - math.deg(math.asin(-p.X/dist))
				end
				if ang < 0 then
					ang = ang + 360
				end
				local k = 1
				local stop = false
				while ((not stop) and k <= #rtrn) do -- sort table
					if rtrn[k].angle > ang or (rtrn[k].angle == ang and rtrn[k].distance > dist) then
						stop = true
					else
						k = k + 1
					end
				end
				table.insert(rtrn,k,{angle = ang,distance = dist,entity = e})
			end
		end
	end
	local index = {}
	for i=1, #rtrn do
		index[rtrn[i].entity] = i
		rtrn[i] = {e = rtrn[i].entity, a = rtrn[i].angle}
	end
	local meta = setmetatable(rtrn,{__index = wrapAround})
	return meta, index
end
end	

local camTarget
local atkTarget
local lastAtk
local target = false
local oldTarget = target
local cFunc
local mark = script.TargetMarker
local rotCam = CFrame.new()
local lastCF = CFrame.new()
local offset = 0
local lastMouse = Vector2.new()
local cause = nil
run.Stepped:Connect(function()
	local entity, index = getTargetList(math.huge)
	if input.keysDown[Enum.KeyCode.V] and not input.previousKeysDown[Enum.KeyCode.V] then
		if target then cause = "Flee" end
		target = not target
	end
	if target then
		if target ~= oldTarget then
			lP.Character.Humanoid.AutoRotate = false
		end
		if camTarget and index[camTarget] then
			local mod = 
				((input.keysDown[Enum.KeyCode.Q] and not input.previousKeysDown[Enum.KeyCode.Q]) and 1 or 0) +
				((input.keysDown[Enum.KeyCode.E] and not input.previousKeysDown[Enum.KeyCode.E]) and -1 or 0)
			camTarget = entity[index[camTarget]+mod].e
			mark.Parent = camTarget.PrimaryPart
			lP.Character:SetPrimaryPartCFrame(CFrame.new(lP.Character.PrimaryPart.CFrame.Position, 
				atkTarget.PrimaryPart.Position*Vector3.new(1,0,1)+lP.Character.PrimaryPart.CFrame.Position*Vector3.new(0,1,0)))
		elseif not camTarget then
			if #entity > 0 then
				local min = {n = 1, a = 181}
				for i=1, #entity do
					if (min.a > entity[i].a and entity[i].a <= 180) or (min.a > 360-entity[i].a and entity[i].a > 180) then -- if min is not closest to forward
						min.n, min.a = i, entity[i].a > 180 and 360-entity[i].a or entity[i].a
					end
				end
				camTarget = entity[min.n].e
				atkTarget = camTarget
				mark.Parent = camTarget.PrimaryPart
				rS.combatEvent:FireServer("Retarget", atkTarget)
				cFunc = uIS.InputBegan:Connect(function(input)
					if input.UserInputType == Enum.UserInputType.MouseButton1 then
						atkTarget = camTarget
					end
				end)
			else
				print("No eligible targets")
				oldTarget = target
				camTarget = nil
				input.step()
				mark.Parent = script
				cFunc:Disconnect()
				return
			end
		elseif camTarget and not index[camTarget] then
			print("Lock broken")
			camTarget = nil
			input.step()
			cFunc:Disconnect()
			oldTarget = target
			return
		end
		workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
		local camAng = CFrame.new(
				(lP.Character.PrimaryPart.Position),
				atkTarget.PrimaryPart.Position
			):ToObjectSpace(CFrame.new(
				(lP.Character.PrimaryPart.Position),
				camTarget.PrimaryPart.Position
			))
		local x,y,z = camAng:ToEulerAnglesYXZ()
		local dR = (lP.Character.PrimaryPart.Position-camTarget.PrimaryPart.Position).Magnitude/(
			(lP.Character.PrimaryPart.Position-camTarget.PrimaryPart.Position).Magnitude+
			(lP.Character.PrimaryPart.Position-atkTarget.PrimaryPart.Position).Magnitude)
		offset = y*dR	
		rotCam = CFrame.fromEulerAnglesYXZ(0, offset, 0)
		local cf = CFrame.new(
				(lP.Character.UpperTorso.CFrame:ToWorldSpace(lP.Character.UpperTorso.Neck.C0).Position + lP.Character.Humanoid.CameraOffset),
				atkTarget.PrimaryPart.Position
			)
		cf = lastCF:Lerp(cf, .1)
		local camCF = cf*rotCam
		if atkTarget ~= camTarget then
		workspace.CurrentCamera.FieldOfView = math.min((math.max(85, math.deg(math.abs(y/(aspRatio*(6/8)))))+9*workspace.CurrentCamera.FieldOfView)/10,120)
		end
		local lV = (camCF.LookVector.Y > 0 and camCF.LookVector*Vector3.new(1,-1,1) or camCF.LookVector)
		local camPos = (camCF+camCF:VectorToWorldSpace(Vector3.new(0,0,15))).Position
		local refPos = ((lP.Character.UpperTorso.CFrame:ToWorldSpace(lP.Character.UpperTorso.Neck.C0).Position
		+ lP.Character.Humanoid.CameraOffset)
		- Vector3.new(0,tol,0))
		if camPos.Y <= refPos.Y then
			print(camPos.Y .. " : " .. refPos.Y , 2*refPos.Y-camPos.Y)
			camPos = Vector3.new(
				camPos.X,
				2*refPos.Y-camPos.Y,
				camPos.Z
			)
		end
		workspace.CurrentCamera.CFrame = workspace.CurrentCamera.CFrame:Lerp(CFrame.new(
			camPos,
			camPos + 
			(camCF.LookVector.Y > 0 and camCF.LookVector*Vector3.new(1,-1,1) or camCF.LookVector)
		),.1)
		if lastAtk ~= atkTarget then
		rS.combatEvent:FireServer("Retarget", atkTarget)
		end
		lastAtk = atkTarget
		lastCF = cf
	else
		if target ~= oldTarget then
			lP.Character.Humanoid.AutoRotate = true
			workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
			workspace.CurrentCamera.CameraSubject = lP.Character.Humanoid
			camTarget = nil
			atkTarget = nil
			mark.Parent = script
			cFunc:Disconnect()
			rS.combatEvent:FireServer("Retarget", nil, cause)
		end
	end
	oldTarget = target
	input.step()
end)

It’s very long and some of it relates to some stuff I’ve attached to the camera lock, but I encourage you to read it.

6 Likes

I meant like a GIF comparison of CFrame(origin, lookAt) and his lock-on camera, but thanks anyways.

Ah, I have a clip of mine somewhere… let me find that for you then

Edit: Here’s a reddit post I made about it:

4 Likes

This is very useful! Thanks you so much for helping.

1 Like

Is this optimized? It seems really long when I read from other people you can just use CFrame.lookAt() which if I recall right would require very few lines

Is there anything particularly superior about your method or something? Seems fairly unnecessary, I mean, I haven’t read it but that looks like that’s gotta be at least 200-300 lines

1 Like

I’ve been looking how to create a lock-on camera, but yours seems better than I could do. Can I use this?

1 Like

Is there any way you can keep the camera on the right side of the player at all times?

1 Like