Okay, here’s my step by step guide on how to do this:
Step 1
Begin by making the RootJoint inside the HumanoidRootPart rotate toward the direction of the camera. You can do this by creating a simple update function that gets called every frame. I went ahead and made some code for this:
-- services
local runService = game:GetService("RunService")
-- player stuff
local player = game.Players.LocalPlayer
local camera = workspace.CurrentCamera
local character = player.Character
-- character stuff
local humanoid = character:WaitForChild("Humanoid")
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
local torso = character:WaitForChild("Torso")
-- put all motors inside the character into a dictionary for easy access
-- remove spaces from motor names
local motors = {}
for _, motor in pairs(character:GetDescendants()) do
if motor:IsA("Motor6D") then
-- gives a table with the motor as the first index, and the motor initial c0 as the second
motors[string.gsub(motor.Name, "%s+", "")] = {motor, motor.C0}
end
end
-- update function
function renderStepped(dt)
-- get useful motor6ds
local root = motors["RootJoint"]
local leftHip = motors["LeftHip"]
local rightHip = motors["RightHip"]
-- get angles of camera
local y, x, z = camera.CFrame:ToEulerAnglesYXZ()
-- rotate motor6ds
root[1].C0 = root[2] * CFrame.fromEulerAnglesYXZ(-y, 0, 0)
end
-- connections
runService:BindToRenderStep("updateMotors", Enum.RenderPriority.Character.Value, renderStepped)
Assuming the player spawns in with an R6 rig, and this code is placed in a local script inside of StarterCharacter, you should have something that looks like this:
Step 2
Now that we have something that rotates the torso, we need to rotate the legs to go down toward the ground. We can achieve this by rotating the Hip Motor6Ds with the same rotation that we rotated the Torso. Let’s attempt to do this with some code. Adding the bottom two lines to the renderStepped function, we should be able to rotate the Hip joints along with the Root joint.
-- update function
function renderStepped(dt)
-- get useful motor6ds
local root = motors["RootJoint"]
local leftHip = motors["LeftHip"]
local rightHip = motors["RightHip"]
-- get angles of camera
local y, x, z = camera.CFrame:ToEulerAnglesYXZ()
-- rotate motor6ds
root[1].C0 = root[2] * CFrame.fromEulerAnglesYXZ(-y, 0, 0)
leftHip[1].C0 = leftHip[2] * CFrame.fromEulerAnglesYXZ(0, 0, y)
rightHip[1].C0 = rightHip[2] * CFrame.fromEulerAnglesYXZ(0, 0, -y)
end
The result should look like this:
Step 3
Now you may notice there is a small problem. As the Torso begins to rotate away from its original rotation, the legs begin to go up off the ground. We can solve this by using some trigonometry. Let me explain.
As the Torso begins to rotate, the space between the legs and the ground becomes the same as half the size of the Y component of the Torso’s size. This is because the Torso’s position remains the same, but the rotation of the Torso is changing. As the rotation of the Torso reaches an offset of 90 degrees, it’s basically sideways. So the offset of the Torso from the ground becomes
math.abs(0.5 * Torso.Size.Y - 0.5 * Torso.Size.Z)
Now how do we calculate this depending on the rotation of the Torso at the given moment? Well, since we’re dealing with the rotation of the camera about the X axis (rotation up and down), we can take the sine of the angle and multiply it by the offset we calculated above. Of course, the sine function can give a positive or a negative number, so we’ll take the absolute value of the function as well. In code, this looks like this:
-- update function
function renderStepped(dt)
-- get useful motor6ds
local root = motors["RootJoint"]
local leftHip = motors["LeftHip"]
local rightHip = motors["RightHip"]
-- get angles of camera
local y, x, z = camera.CFrame:ToEulerAnglesYXZ()
local groundOffset = math.abs(0.5 * torso.Size.Y - 0.5 * torso.Size.Z) * math.abs(math.sin(y))
-- rotate motor6ds
root[1].C0 = root[2] * CFrame.fromEulerAnglesYXZ(-y, 0, 0) * CFrame.new(0, 0, -groundOffset)
leftHip[1].C0 = leftHip[2] * CFrame.fromEulerAnglesYXZ(0, 0, y)
rightHip[1].C0 = rightHip[2] * CFrame.fromEulerAnglesYXZ(0, 0, -y)
end
After adding this to the code, we should fix the problem we had earlier. We end up with something like this:
Hopefully this helps you out!