I have found the solution, and it’s actually to not use the humanoid’s CFrame. The reason why updating the CFrame causes the bug is because its constantly realigning itself and switching back to the standing position(Which is why you constantly see it jittering when the character is pointed downwards and becomes normal happens when you make it straight). It will also mess with the physics calculation when you are too close to the ground/part (Not sure why) so we definitely have to scrap the idea of updating the character’s CFrame directly and find a smoother and safer approach.
The solution is to combine it with Align Orientation.
What Align Orientation does is it will calculate the rotation of the first part and slowly(or quickly depending on how fast you want it to be) align it to the second part. This is way better because it will actually rotate the character instead of simply CFraming it every frame as we did before.
Here is the full guide:
-- Player related
local player = game:GetService("Players").LocalPlayer
local playerCharacter = player.Character or player.CharacterAdded:Wait();
local playerHRP = playerCharacter:WaitForChild("HumanoidRootPart");
local playerAnimations:boolean = playerCharacter.Animate.Disabled;
-- Instantiating the Align Orientation
local camera = workspace.CurrentCamera;
local trackCamera = Instance.new("Part", game.Workspace); -- create a part that uses the camera CFrame (more explanation outside this code in the comment below)
local cameraAttachment = Instance.new("Attachment", trackCamera);
local alignOrientation = Instance.new("AlignOrientation", playerHRP);
-- Default settings for the Align Orientation
trackCamera.CanCollide = false; -- make sure the camera is not colliding with this
trackCamera.Transparency = 1; -- make sure the camera can't see the part
alignOrientation.Attachment1 = cameraAttachment; -- Set the orientation of the part you want it to track
alignOrientation.RigidityEnabled = true; -- This its for instant rotation
RunService.PreRender:Connect(function()
trackCamera.CFrame = camera.CFrame; -- This is important! We need the rotation of the camera and setting it to the part
if flying == true then
alignOrientation.Attachment0 = playerHRP.RootAttachment; -- Connects the Align Orientation so it can start rotating the character
fly(); -- your fly code
end
if flying == false then
alignOrientation.Attachment0 = nil; - Disconnects the Align Orientation so you can move freely
end
end)
First we create a Part that tracks the current camera. The reason we are doing this instead of directly using the camera is because adding attachments to the camera is illegal in Roblox Studio (Not sure why lol). So to counter this, we just create a part with the attachment and copy the camera’s CFrame to the part so that it can be used for the player’s rotation.
Then we add the default settings. These do not need to be updated at any given point but is necessary for this whole thing to work. Doing this will make sure the character actually rotates at the desired orientation
Lastly in the RunService, in order to connect the rotation of the camera to the character we just simply connect the Attachment0 from the align orientation when fly is turned on and disconnecting it when fly is turned off
Here is a video demo
Here is the whole code:
--!native
--!strict
-- Includes
local UIS = game:GetService("UserInputService");
local RunService=game:GetService("RunService");
-- Player related
local player = game:GetService("Players").LocalPlayer
local playerCharacter = player.Character or player.CharacterAdded:Wait();
local playerHRP = playerCharacter:WaitForChild("HumanoidRootPart");
local playerAnimations:boolean = playerCharacter.Animate.Disabled;
local camera = workspace.CurrentCamera;
-- Fly related
local trackCamera = Instance.new("Part", game.Workspace);
trackCamera.CanCollide = false;
trackCamera.Transparency = 1;
local cameraAttachment = Instance.new("Attachment", trackCamera);
local linearVelocity = Instance.new("LinearVelocity", playerHRP);
linearVelocity.ForceLimitMode = Enum.ForceLimitMode.Magnitude;
linearVelocity.VectorVelocity = Vector3.new(0,0,0);
linearVelocity.MaxForce = 100000;
local alignOrientation = Instance.new("AlignOrientation", playerHRP);
alignOrientation.Attachment1 = cameraAttachment;
alignOrientation.RigidityEnabled = true;
local flying = false;
local keyPressed = {
W = false,
S = false,
A = false,
D = false
};
UIS.InputBegan:Connect(function(key, GPE)
if GPE then return; end
if key.KeyCode == Enum.KeyCode.W then keyPressed.W = true; end
if key.KeyCode == Enum.KeyCode.A then keyPressed.A = true; end
if key.KeyCode == Enum.KeyCode.S then keyPressed.S = true; end
if key.KeyCode == Enum.KeyCode.D then keyPressed.D = true; end
if key.KeyCode == Enum.KeyCode.Space then
if flying then
flying = not flying;
playerAnimations = not playerAnimations;
else
linearVelocity.Attachment0 = playerHRP.RootAttachment;
alignOrientation.Attachment0 = playerHRP.RootAttachment;
flying = true;
end
end
end)
UIS.InputEnded:Connect(function(key, GPE)
if key.KeyCode == Enum.KeyCode.W then keyPressed.W = false; end
if key.KeyCode == Enum.KeyCode.A then keyPressed.A = false; end
if key.KeyCode == Enum.KeyCode.S then keyPressed.S = false; end
if key.KeyCode == Enum.KeyCode.D then keyPressed.D = false; end
end)
local function flyDirection()
--playerHRP.CFrame = CFrame.new(playerHRP.Position) * camera.CFrame.Rotation;
playerHRP.LinearVelocity.VectorVelocity = Vector3.new(0,0,0);
if keyPressed.W then playerHRP.LinearVelocity.VectorVelocity = camera.CFrame.LookVector * 50; end
if keyPressed.A then playerHRP.LinearVelocity.VectorVelocity = camera.CFrame.RightVector * -50; end
if keyPressed.S then playerHRP.LinearVelocity.VectorVelocity = camera.CFrame.LookVector * -50; end
if keyPressed.D then playerHRP.LinearVelocity.VectorVelocity = camera.CFrame.RightVector * 50; end
end
local function playerFly()
if flying == true then
flyDirection();
end
if flying == false then
linearVelocity.Attachment0 = nil;
alignOrientation.Attachment0 = nil;
end
end
RunService.PreRender:Connect(function()
trackCamera.CFrame = camera.CFrame;
playerFly();
end)
Let me know if this worked for you or not. If it did, be sure to mark this as solution so that others can find this post!
