Keeping camera relative to moving part's CFrame

Right, so I’m trying to create a camera system of which the camera is fixated to a determined part (a moving part, that is). Easy enough, right? Just a simple workspace.CurrentCamera.CFrame = workspace.Part.CFrame, maybe with the CameraType set to Scriptable. Well, what I’m trying to go for is free camera movement (which I am looking to have restricted to a maximum of 40 to 50 degrees in any direction) that is still relative to said moving part. Setting the CameraSubject will leave the camera to possess free movement from said point, though the camera angle does not bode with the part’s new angle, should it move. I’m rather stumped on this, so any answer that can point me in the right direction would be of great help. If you need more information on what I’m trying to describe, take the ‘Death from Above’ mission from Call of Duty 4: Modern Warfare. It’s directly what I’m trying to achieve and is practically the only part of the whole system I’m lost on.

tl;dr: Need pointers on how I should go about panning a camera freely while staying relative to a moving part’s angle.

3 Likes

Okay let’s name moving object plane and camera stays camera.

Okay plane moves 5 ft. Wouldn’t that mean that the camera would move 5ft too? However since it is free let’s say it goes 2 ft around the plane to keep it moving freely.

Camera = plane + X. (Of course we are adding CFrames here and X is variable maybe moving back in forth or so)

Not on my computer so just trying to make sense of this.

So let’s say there are ten frames with the plane. To make it easy we will say that the camera is in front of the plane and will either zoom in or zoom out on the plane. We want to zoom out 10 feet on frame 10 and the plane moves 20 feet.

Current frame:
Plane: 0 feet
Zoom: 0 feet
Total feet the camera moves: 0 feet

Frame 1:
Plane: 2 feet
Zoom: 1 feet
Camera has moved: 3 feet

Frame 2:
Plane: 4 feet
Zoom: 2 feet
Camera has moved: 6 feet

Frame 5:
Plane: 10 feet
Zoom: 5 feet
Camera has moved: 15 feet

Frame 10:
Plane: 20 feet
Zoom: 10 feet
Camera has moved: 30 feet

That will give the effect that the camera is zooming out 10 feet even though the plane moved 20 feet while that was happening.

Conclusion:
So when you tween the camera, just add the zoom and the distance the plane is going to move. It of course doesn’t have to be a plane. Not entirely sure if that answers your question, but I felt I would give it a try.

I probably didn’t word it properly, my apologies if that’s the case. I’ve handled on how the camera’s position itself moves along with the moving part, but not how the camera’s angle will always stay relative to the moving part’s angle plus the player’s freedom to move the camera too. It’s sort of hard to explain, but what I’m trying to achieve is shown in the video posted with how the player is moving the camera around, with the camera still staying relative to the plane’s rotating angle.

Perhaps this could work for you. The smoothing is a little buggy, though and the automatic return to center has no smoothing at all. I don’t know how to make a better smoothing system.

local UserInputService = game:GetService("UserInputService")

-- the camera can go this amount of degrees to both directions (negative and positive) from your default CFrame
-- the smoothing reduces this a little bit, and ROUND_CORNERS affects this too.
local FREEDOM_DEGREES = 30 

local BIND_NAME = "CameraFollow"

local CAMERA_MOVE_SENSITIVITY = .5
local CAMERA_MOVE_SMOOTHNESS = 0 -- I recommed keeping this between 0 and 1, if it's bigger, the camera won't reach the max angle
local CAMERA_SMOOTH_START = 0 -- the smoothing affects the camera movement when math.abs(angle) >= CAMERA_SMOOTH_START*maxRad
local ROUND_CORNERS = true -- limits the camera movement to a circular area

local AUTOMATIC_CENTER_RETURN = false -- The variables below have no effect when this is false.
local RETURN_TO_CENTER_DELAY = 2
local RETURN_TO_CENTER_TIME = 2

local priority = Enum.RenderPriority.Camera.Value+1
local maxRad = math.rad(FREEDOM_DEGREES)
local minRad = -maxRad
local screenSize = camera.ViewportSize

local xAngle, yAngle = 0, 0

local startToReturnTick
local originalXAngle, originalYAngle -- x and y angles when the returning begins

camera.CameraType = Enum.CameraType.Custom


local function mouseCameraRotate(cf) -- cf is your default CFrame without rotation from player input
	-- GetMouseDelta only works when mouse is locked, otherWise coordinates are 0.
	-- The mouse is locked when the player holds down the right mouse button.
	local mouseDelta = UserInputService:GetMouseDelta()
	
	if mouseDelta ~= Vector2.new() then
		local angleCalculationHelperValue = 1/screenSize.Y*math.pi*CAMERA_MOVE_SENSITIVITY*.1
		
		local xAngleToAdd, yAngleToAdd = mouseDelta.Y*angleCalculationHelperValue, mouseDelta.X*angleCalculationHelperValue
		local rawXAngle, rawYAngle = xAngle+xAngleToAdd, yAngle+yAngleToAdd
		
		-- additional limiting values to round corners
		local roundXMaxRad, roundYMaxRad
		if ROUND_CORNERS and rawXAngle ~= 0 and rawYAngle ~= 0 then
			local atanXAYA = math.abs(math.atan2(rawXAngle, rawYAngle))
			roundXMaxRad, roundYMaxRad = math.abs(math.sin(atanXAYA)*maxRad), math.abs(math.cos(atanXAYA)*maxRad)
		end
		local xMaxRad, yMaxRad = roundXMaxRad or maxRad, roundYMaxRad or maxRad
		local xMinRad, yMinRad = -xMaxRad, -yMaxRad
		local smoothCalcXMinus, smoothCalcYMinus = CAMERA_SMOOTH_START*xMaxRad, CAMERA_SMOOTH_START*yMaxRad
		
		-- smoothing the camera movement when angle is big enough
		local smoothXAngleToAdd, smoothYAngleToAdd
		if math.abs(rawXAngle/xMaxRad) >= CAMERA_SMOOTH_START then
			local smoothAddMinus = (math.abs(rawXAngle)-smoothCalcXMinus)/(xMaxRad-smoothCalcXMinus)^CAMERA_MOVE_SMOOTHNESS*xAngleToAdd
			smoothXAngleToAdd = xAngleToAdd-smoothAddMinus
		end
		if math.abs(rawYAngle/yMaxRad) >= CAMERA_SMOOTH_START then
			local smoothAddMinus = (math.abs(rawYAngle)-smoothCalcYMinus)/(yMaxRad-smoothCalcYMinus)^CAMERA_MOVE_SMOOTHNESS*yAngleToAdd
			smoothYAngleToAdd = yAngleToAdd-smoothAddMinus
		end
		
		-- these will be clamped to make sure the angles aren't bigger than max angle values.
		local xAngleToClamp, yAngleToClamp = xAngle+(smoothXAngleToAdd or xAngleToAdd), yAngle+(smoothYAngleToAdd or yAngleToAdd)
		
		-- these are the finished angles
		xAngle, yAngle = math.clamp(xAngleToClamp, xMinRad, xMaxRad), math.clamp(yAngleToClamp, yMinRad, yMaxRad)
		
		if AUTOMATIC_CENTER_RETURN then
			startToReturnTick = tick()+RETURN_TO_CENTER_DELAY
			originalXAngle, originalYAngle = xAngle, yAngle
		end
	else local currentTick = tick()
		if startToReturnTick and currentTick >= startToReturnTick then
			if currentTick-startToReturnTick >= RETURN_TO_CENTER_TIME then
				print("hi")
				xAngle, yAngle = 0, 0
			else local m = 1-(currentTick-startToReturnTick)/RETURN_TO_CENTER_TIME
				xAngle, yAngle = originalXAngle*m, originalYAngle*m
			end
		end
	end
	return cf*CFrame.Angles(-xAngle, -yAngle, 0)
end
1 Like

I’ll have to give this a try later, thank you.