Issues with lock-on targeting system with custom third person camera

I am currently attempting to create a lock-on targeting system for my game, the third person camera is fairly basic but works. I am currently attempting to create a “coherent” lock-on system, and for the most part it works, I just have a couple of quirks I need help solving. One of these quirks is when the player walks close to the dummy the camera bugs out, especially if they jump on them. Another quirk is attempting to transition from lock-on to normal third person camera, currently I just basically record the X and Y axis of the lookat CFrame and so when it is disabled, the camera will “start” at that angle. The issue is, it seems the lock-on is slightly lower and to the left offset-wise compared to my third person camera system, even though they are using the same vector variable for offset, this causes a small jump when toggling the lock-on system.

One small fix to the first issue of the camera bugging out as you get closer or jump on the dummy was to basically put a min (and max) magnitude, this somewhat solves the issue, but can look janky as players can still move the third-person camera when they reach that threshold, then it just instantly locks on as they leave. This also makes the second issue more visible, bc it “transitions” from lockon camera angles to the third-person camera one.

Not really a bug issue, but It would also be nice to get some pointers on how to implement focus_offset, so it doesnt have to be the center of the rootpart.

Another fix I tried with the lock-on not being the exact angle was to basically use the X and Y axis angles I “record” and use it instead, but the lock-on is now less consistent (the mouse is no longer at the center of the root part, and is at some “random” spot on the dummy).

Footage of the issues. The video maybe too low quality to see the issue relating to toggling on and off the lock-on, and as such I also include the place file, this is exactly “one-to-one” the code I am using in the video. This actual “issue” is inside the module script inside of replicatedstorage.
Place file, open module inside replicated storage:
DevelopmentSmallThings.rbxl (104.9 KB)

The “relavent” code.

local itpmod={
	enabled=false,
	focus=nil :: BasePart?,
	angles={
		current=Vector2.zero,
		min=Vector2.new(-math.huge,-75),
		max=Vector2.new(math.huge,75),
	},
	sensitivity=Vector2.new(1,.4),
	focus_offset=Vector3.new(2,2,8),
	offset=Vector3.new(2,2,8),
}
local imod={
	enabled=false,
}


RunService:BindToRenderStep("CamController",Enum.RenderPriority.Camera.Value+1,function(delta:number)
	if not imod.enabled then return end
	if itpmod.enabled then
		repeat
			local character=LOCALPLAYER.Character
			if not character then break end
			local rootpart=character:FindFirstChild("HumanoidRootPart") :: BasePart?
			if not rootpart or not rootpart:IsA("BasePart") then break end
			if itpmod.auto_rotate then rootpart.CFrame=CFrame.new(rootpart.Position)*CFrame.fromEulerAnglesYXZ(0,math.rad(itpmod.angles.current.X),0) end
			CAMERA.CameraType=Enum.CameraType.Scriptable
			UserInputService.MouseBehavior=Enum.MouseBehavior.LockCenter
			
			if itpmod.focus==nil then
				CAMERA.CFrame=CFrame.new(rootpart.Position)*CFrame.fromEulerAnglesYXZ(math.rad(itpmod.angles.current.Y),math.rad(itpmod.angles.current.X),0)*CFrame.new(itpmod.offset)
			else
				local finalVector=local finalVector=CFrame.lookAt(CFrame.lookAt(rootpart.Position,itpmod.focus.Position)*CFrame.new(itpmod.offset).Position,itpmod.focus.CFrame:PointToWorldSpace(itpmod.focus_offset))
				local lookAtVector=Vector3.new(finalVector:ToEulerAnglesYXZ())
				itpmod.angles.current=Vector2.new(math.clamp(math.deg(lookAtVector.Y),itpmod.angles.min.X,itpmod.angles.max.X),math.clamp(math.deg(lookAtVector.X),itpmod.angles.min.Y,itpmod.angles.max.Y))
				--UserInputService.MouseBehavior=Enum.MouseBehavior.Default
				CAMERA.CFrame=finalVector
			end
			CAMERA.Focus=rootpart.CFrame--CFrame.new(rootpart.CFrame:PointToWorldSpace(Vector3.new(itpmod.offset.X,itpmod.offset.Y,0)))*rootpart.CFrame.Rotation
			if itpmod.popper.enabled then CAMERA.CFrame=calculate_popper(rootpart.CFrame:PointToWorldSpace(Vector3.new(itpmod.offset.X,itpmod.offset.Y,0)),itpmod.popper.ignore_list) end
		until true
		if itpmod.last_input~=nil then apply_camera_rot("CamController",itpmod.last_input.UserInputState,itpmod.last_input) end
	end
end)

I solved this issue, it was far easy then I thought it would be to fix this, and now it works great, EVEN allows for a “focus_offset” which was something else in this discussion I needed help with. Here is the “new” snippet.

local itpmod={
	enabled=false,
	focus=nil :: BasePart?,
	angles={
		current=Vector2.zero,
		min=Vector2.new(-math.huge,-75),
		max=Vector2.new(math.huge,75),
	},
	sensitivity=Vector2.new(1,.4),
	focus_offset=Vector3.new(2,2,8),
	offset=Vector3.new(2,2,8),
}
local imod={
	enabled=false,
}

RunService:BindToRenderStep("CamController",Enum.RenderPriority.Camera.Value+1,function(delta:number)
	if not imod.enabled then return end
	if itpmod.enabled then
		repeat
			local character=LOCALPLAYER.Character
			if not character then break end
			local rootpart=character:FindFirstChild("HumanoidRootPart") :: BasePart?
			if not rootpart or not rootpart:IsA("BasePart") then break end
			if itpmod.auto_rotate then rootpart.CFrame=CFrame.new(rootpart.Position)*CFrame.fromEulerAnglesYXZ(0,math.rad(itpmod.angles.current.X),0) end
			CAMERA.CameraType=Enum.CameraType.Scriptable
			UserInputService.MouseBehavior=Enum.MouseBehavior.LockCenter
			
			local cameraFocus=Vector3.zero
			if itpmod.focus==nil then
				local startCFrame=CFrame.new(rootpart.Position)*CFrame.fromEulerAnglesYXZ(math.rad(itpmod.angles.current.Y),math.rad(itpmod.angles.current.X),0)
				local cameraVector=startCFrame:PointToWorldSpace(itpmod.offset)
				cameraFocus=startCFrame:PointToWorldSpace(Vector3.new(itpmod.offset.X,itpmod.offset.Y,-100000))
				CAMERA.CFrame=CFrame.lookAt(cameraVector,cameraFocus)
			else
				local startCFrame=CFrame.lookAt(rootpart.CFrame:PointToWorldSpace(itpmod.offset),itpmod.focus.Position)
				local cameraVector=startCFrame:PointToWorldSpace(itpmod.offset)
				cameraFocus=itpmod.focus.CFrame:PointToWorldSpace(itpmod.focus_offset)
				local finalVector=CFrame.lookAt(startCFrame.Position,cameraFocus)
				local lookAtVector=Vector3.new(finalVector:ToEulerAnglesYXZ())
				itpmod.angles.current=Vector2.new(math.clamp(math.deg(lookAtVector.Y),itpmod.angles.min.X,itpmod.angles.max.X),math.clamp(math.deg(lookAtVector.X),itpmod.angles.min.Y,itpmod.angles.max.Y))
				--UserInputService.MouseBehavior=Enum.MouseBehavior.Default
				CAMERA.Focus=rootpart.CFrame
				CAMERA.CFrame=finalVector
			end
			CAMERA.Focus=rootpart.CFrame--CFrame.new(rootpart.CFrame:PointToWorldSpace(Vector3.new(itpmod.offset.X,itpmod.offset.Y,0)))*rootpart.CFrame.Rotation
			if itpmod.popper.enabled then CAMERA.CFrame=calculate_popper(rootpart.CFrame:PointToWorldSpace(Vector3.new(itpmod.offset.X,itpmod.offset.Y,0)),itpmod.popper.ignore_list) end
		until true
		if itpmod.last_input~=nil then apply_camera_rot("CamController",itpmod.last_input.UserInputState,itpmod.last_input) end
	end
end)

Basically I apply the offset in the startCFrame method when the player is “focusing” on a target. This seems to work, and allows the cameraFocus variable to apply the focus_offset aswell and that work.

BASICALLY everything in the else for the if statement checking if focus is set.

Scratch that, this works until the player starts getting up higher, where then it doesnt lock on anymore, and looks weird

1 Like

I am still hoping someone can better help me here, I have no clue how to do it, basically I want the lookAt vector to point to another part, but orbit around the character at the same point as the normal third person camera orbits. Basically i want it to act as if the player is “moving their mouse” to look at the enemy, but in actuality its a lock in system doing it, so that there is no “jump” from targeting the enemy and back to “normal” camera movement. I’ve simplified the code a little to make it easier to understand/read though in the original post.