Attention: anybody who is very experienced with CFrame and willing to help me out!

I am making a game centered around flying planes. I want controls nearly identical to a game called War Thunder. but it is proving to be difficult. In War Thunder, there is a circle in the 3D space (I am using a placeholder part with a billboard gui of a circle) and it orbits around the plane using the mouse movement (mouse delta). The problem I am having is that when the plane rolls, the camera tilts with the plane. This means that when moving the mouse up on the screen, the circle should move upwards relative to the plane and sideways relative to the world. This isn’t happening though. I attempted to write code that resolved this issue by rotating the circle and using the camera as a reference but this leads to a weird behavior of the path of the circle spiraling and getting stuck in weird places. I have been stuck on this for HOURS so if anybody could help I would be eternally grateful :slight_smile:

game.ReplicatedStorage.Events.SpawnRequest:FireServer("Test", "Test")
--Services
local run_service = game:GetService("RunService")
local user_input_service = game:GetService("UserInputService")
local replicated_storage = game:GetService("ReplicatedStorage")

--Main variables
local player = game.Players.LocalPlayer
local plane = nil
local camera = workspace.CurrentCamera
local events = replicated_storage:WaitForChild("Events")
local spawn_event = events:WaitForChild("SpawnRequest")
local update_event = events:WaitForChild("PlaneUpdate")

--Rig parts
local mouse_rig = Instance.new("Part")
local camera_rig = Instance.new("Part")

--Temporary variables
local damp = 3
local speed = 15

--Input axes
local roll_input = 0
local pitch_input = 0
local yaw_input = 0

--Create visuals
local circle = Instance.new("Attachment")
local crosshair = Instance.new("Attachment")
local billboard = Instance.new("BillboardGui")
billboard.Size = UDim2.new(0,80,0,80)
billboard.AlwaysOnTop = true
local billboard2 = Instance.new("BillboardGui")
billboard2.Size = UDim2.new(0,40,0,40)
billboard2.AlwaysOnTop = true
local image = Instance.new("ImageLabel")
image.Image = "rbxassetid://11738792917"
image.Size = UDim2.new(1,0,1,0)
image.BackgroundTransparency = 1
image.Parent = billboard
local image = Instance.new("ImageLabel")
image.Image = "rbxassetid://316279304"
image.Size = UDim2.new(1,0,1,0)
image.BackgroundTransparency = 1
image.Parent = billboard2
billboard.Parent = circle
billboard2.Parent = crosshair
circle.Parent = workspace.Terrain
crosshair.Parent = workspace.Terrain


run_service.RenderStepped:Connect(function(delta_time)
	if plane == nil then
		camera.CameraType = Enum.CameraType.Follow
		billboard.Enabled = false
		billboard2.Enabled = false
	else
		camera.CameraType = Enum.CameraType.Scriptable
		--Variables
		local root = plane.PrimaryPart or plane:GetPropertyChangedSignal("PrimaryPart"):Wait()

		--Enable visuals
		billboard.Enabled = true
		billboard2.Enabled = true

		--Mouse input
		user_input_service.MouseBehavior = Enum.MouseBehavior.LockCenter
		local mouse_x = math.clamp(user_input_service:GetMouseDelta().X,-10,10) 
		local mouse_y = math.clamp(user_input_service:GetMouseDelta().Y,-8,8) 
		
		--Circle
		circle.WorldPosition = mouse_rig.Position + mouse_rig.CFrame.LookVector * 500
		crosshair.WorldPosition = root.Position + root.CFrame.LookVector * 500

		--Roll
		roll_input = 0
		if user_input_service:IsKeyDown(Enum.KeyCode.D) then
			roll_input-=1
		elseif user_input_service:IsKeyDown(Enum.KeyCode.A) then
			roll_input+=1
		end

		--Pitch
		pitch_input = 0
		if user_input_service:IsKeyDown(Enum.KeyCode.S) then
			pitch_input-=1
		elseif user_input_service:IsKeyDown(Enum.KeyCode.W) then
			pitch_input+=1
		end

		--Yaw
		yaw_input = 0
		if user_input_service:IsKeyDown(Enum.KeyCode.E) then
			yaw_input-=1
		elseif user_input_service:IsKeyDown(Enum.KeyCode.Q) then
			yaw_input+=1
		end
		
		--Mouse rig
		
		if user_input_service:IsKeyDown(Enum.KeyCode.C) then
			mouse_rig.CFrame *= CFrame.fromOrientation(pitch_input,yaw_input,0)
		else
			mouse_rig.Position = Vector3.new(0,15,0)
			print(mouse_y)
			
			mouse_rig.CFrame *= CFrame.fromAxisAngle(camera.CFrame:ToWorldSpace().UpVector, -mouse_x/100)
			
		end

		--Camera rig
		camera_rig.Position = camera_rig.Position:Lerp(root.Position, 5 * delta_time)
		if user_input_service:IsKeyDown(Enum.KeyCode.C) then
			camera_rig.CFrame *= CFrame.fromOrientation(math.rad(-mouse_y/100*15),math.rad(-mouse_x/100*15),0)
		else
			camera_rig.CFrame = camera_rig.CFrame:Lerp(CFrame.lookAt(root.Position, circle.Position, root.CFrame.UpVector), 1.5 * delta_time)
		end

		--Camera movement
		camera.CFrame = camera_rig.CFrame * CFrame.new(0,20,40)

		--Plane movement
		plane:PivotTo(root.CFrame:ToWorldSpace(CFrame.new(0,0,-speed * delta_time)))
		plane:PivotTo(plane.PrimaryPart.CFrame:Lerp(CFrame.lookAt(root.Position, mouse_rig.Position + mouse_rig.CFrame.LookVector * 500, root.CFrame.UpVector) * CFrame.fromOrientation(0,0,math.rad(roll_input*50)), delta_time * damp))

		--Send to server
		update_event:FireServer(root.CFrame)
	end		
end)

spawn_event.OnClientEvent:Connect(function(plane_input)
	local clone = nil
	if plane_input ~= nil then
		clone = plane_input:Clone()
		clone.Parent = workspace.Planes
		for i,v in pairs(workspace.Planes:GetChildren()) do
			if v:GetAttribute("user_id") == player.UserId then
				v:Destroy()
			end
		end
	end
	if plane ~= nil then
		plane:Destroy()
	end
	plane = clone
end)