Airplane Problem

Hello everyone
I’m having an issue with my aircraft camera system.
When performing a full 360-degree pitch loop, the camera unexpectedly flips or inverts once the aircraft passes through the vertical axis.

It seems like the camera’s up vector (or orientation) gets inverted when crossing over the top of the loop, so the view ends up upside down for a moment before correcting itself.

Here’s a short video showing the problem:


Streamable video link

I’m not sure if it’s a gimbal lock issue, but here’s the camera control part of the code:

function ClientAircraft:UpdateCamera(deltaTime)
	if not self:IsValid() then return end

	local camPart = self.Model:FindFirstChild("CamPart")
	if not camPart then return end

	local pos = camPart.Position
	local rot = camPart.CFrame.LookVector
	local desiredCFrame = CFrame.lookAt(pos, pos + rot)
	
	Camera.CameraType = Enum.CameraType.Scriptable
	Camera.CFrame = desiredCFrame
end

Any advice or explanation would be appreciated. Thanks.

1 Like

This probably won’t work/won’t help, but doesn’t CFrame.lookAt() have a third argument for up?
Maybe you could try adding camPart.CFrame.UpVector as a third argument for the lookAt.
Again, idk if this will help and I might just be wrong about this, but it’s worth a shot ig

1 Like

I tried that before, it works but the camera also follows the plane’s roll and it makes me very dizzy to control

3rd arg in lookAt as camPart.CFrame.UpVector

1 Like

then what the hell do you want? you are provided with a fix that you say works, your “dizziness” is not really our problem

You’ll need to decide what you want to happen at that case in the video, since it’s an edge case.

For example, the default Roblox character camera controller doesn’t let you cross the top of the character, since the camera roll would need to flip to maintain having the ground at the bottom of the camera. Your video is demonstrating the same edge case with camera angles.

I can see a few options:

  • Have the camera roll follow the plane roll and add a GUI to make it more clear which way is down
  • Prevent the plane from pitching past upwards and past downwards (no loop de loops)
  • Have the camera’s roll animate smoothly back to the correct way when the jet inverts (not ideal, but better than the sudden change)
  • Have the camera’s CFrame interpolate between your current solution and following the jet’s roll based on how upwards the trajectory is

Here is an example of that last one:

-- Your current solution
local levelGroundCFrame = CFrame.lookAt(pos, pos + rot, Vector3.yAxis)

-- Roll-relative solution
local rollRelativeCFrame = CFrame.lookAt(pos, pos + rot, camPart.CFrame.UpVector)

-- Outputs closer to 1 for how close rot is pointing upwards (or downwards), and closer to 0 for how horizontal the jet is
local pitchFactor = math.abs(rot:Dot(Vector3.yAxis))

-- Combined based on pitch
local desiredCFrame = (1 - pitchFactor) * levelGroundCFrame + pitchFactor * rollRelativeCFrame

You either need to provide a stable up direction (usually the world up vector) and handle the edge case where the camera’s look vector becomes nearly parallel to it. That is, when lookVector:Dot(upVector) is within 1 - eps.

Alternatively, you can clamp the camera’s pitch within safe bounds to prevent it from reaching that singular orientation, which is the more common approach, even in aircraft-style camera systems.

This is what I mean

You literally asked to fix the camera flip, we fixed it, it works as it should, what more do you need?

If it makes you dizzy, maybe you could try easing the roll to the aircrafts roll rather than having it locked to the aircrafts roll?
Idk if this will work but here’s an example of what I’m thinking might work:

local currentUp = Vector3.zero

function ClientAircraft:UpdateCamera(deltaTime)
	if not self:IsValid() then return end

	local camPart = self.Model:FindFirstChild("CamPart")
	if not camPart then return end

	local pos = camPart.Position
	local rot = camPart.CFrame.LookVector
    local up = camPart.CFrame.UpVector
    up = currentUp:Lerp(up, a_number * deltaTime) -- change a_number to a number you like
	local desiredCFrame = CFrame.lookAt(pos, pos + rot, up)
	
	Camera.CameraType = Enum.CameraType.Scriptable
	Camera.CFrame = desiredCFrame

    currentUp = up
end

I might not have used Lerp properly in this example but it gives an idea on what I mean