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.
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.
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
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.
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