How to create footlocking like in this video?
this code doesnt even work, why?
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local LocalPlayer = Players.LocalPlayer
-- Utility to clean up old IKControls and target parts
local function cleanupOld(character)
for _, obj in character:GetChildren() do
if obj:IsA("IKControl") or (obj:IsA("Part") and (obj.Name == "LeftFoot_IKTarget" or obj.Name == "RightFoot_IKTarget" or obj.Name == "Left Leg_IKTarget" or obj.Name == "Right Leg_IKTarget")) then
obj:Destroy()
end
end
end
local function getNeck(character)
-- Works for both R15 and R6
local head = character:FindFirstChild("Head")
if not head then return nil end
for _, obj in head:GetChildren() do
if obj:IsA("Motor6D") and obj.Name == "Neck" then
return obj
end
end
return nil
end
local function lookAtNeck(neck, head, torso, targetPos)
-- Calculate the look-at C0 for the neck joint
local headPos = head.Position
local torsoPos = torso.Position
local dir = (targetPos - headPos)
if dir.Magnitude < 0.1 then return end
dir = dir.Unit
-- Save original C0/C1 if not already
if not neck.OriginC0 then
neck.OriginC0 = neck.C0
neck.OriginC1 = neck.C1
end
-- Calculate relative position in torso space
local relative = torso.CFrame:PointToObjectSpace(targetPos)
local yaw = math.atan2(-relative.X, -relative.Z)
local pitch = math.atan2(relative.Y, math.sqrt(relative.X^2 + relative.Z^2))
-- Clamp pitch/yaw to reasonable values (to avoid unnatural twisting)
local maxYaw = math.rad(70)
local maxPitch = math.rad(40)
yaw = math.clamp(yaw, -maxYaw, maxYaw)
pitch = math.clamp(pitch, -maxPitch, maxPitch)
-- Apply rotation to original C0
neck.C0 = neck.OriginC0 * CFrame.Angles(pitch, yaw, 0)
end
local currentRenderStepped = nil
local function setupFootLock(character)
cleanupOld(character)
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then return end
humanoid.AutoRotate = false
local hrp = character:FindFirstChild("HumanoidRootPart")
if not hrp then return end
-- Detect rig type
local isR15 = character:FindFirstChild("LeftFoot") and character:FindFirstChild("RightFoot")
local leftFoot = isR15 and character:FindFirstChild("LeftFoot") or character:FindFirstChild("Left Leg")
local rightFoot = isR15 and character:FindFirstChild("RightFoot") or character:FindFirstChild("Right Leg")
local leftChainRoot = isR15 and character:FindFirstChild("LeftUpperLeg") or character:FindFirstChild("Left Leg")
local rightChainRoot = isR15 and character:FindFirstChild("RightUpperLeg") or character:FindFirstChild("Right Leg")
if not (leftFoot and rightFoot and leftChainRoot and rightChainRoot) then return end
-- Create invisible parts to act as foot lock targets
local function createTargetPart(foot)
local part = Instance.new("Part")
part.Size = Vector3.new(0.2, 0.2, 0.2)
part.Transparency = 1
part.Anchored = true
part.CanCollide = false
part.Name = foot.Name .. "_IKTarget"
part.CFrame = foot.CFrame
part.Parent = character
return part
end
local leftTarget = createTargetPart(leftFoot)
local rightTarget = createTargetPart(rightFoot)
-- Set up IKControls for both feet
local function setupIK(foot, target, chainRoot)
local ik = Instance.new("IKControl")
ik.Type = Enum.IKControlType.Position
ik.EndEffector = foot
ik.Target = target
ik.ChainRoot = chainRoot
ik.Parent = humanoid
ik.Enabled = true
return ik
end
local leftIK = setupIK(leftFoot, leftTarget, leftChainRoot)
local rightIK = setupIK(rightFoot, rightTarget, rightChainRoot)
-- Update target parts to stay under feet when standing still
local function updateTargets()
leftTarget.CFrame = leftFoot.CFrame
rightTarget.CFrame = rightFoot.CFrame
end
-- Get neck joint for head look-at
local neck = getNeck(character)
local head = character:FindFirstChild("Head")
local upperTorso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
-- Mouse reference
local mouse = LocalPlayer:GetMouse()
-- Disconnect previous RenderStepped if exists
if currentRenderStepped then
currentRenderStepped:Disconnect()
currentRenderStepped = nil
end
currentRenderStepped = RunService.RenderStepped:Connect(function()
if not LocalPlayer.Character or not LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then return end
local camera = workspace.CurrentCamera
if not camera then return end
-- Set HRP's orientation to match the camera's Y-axis rotation (ignore X/Z tilt)
local hrpPos = hrp.Position
local camCFrame = camera.CFrame
-- Extract yaw from camera's CFrame
local _, camYaw, _ = camCFrame:ToEulerAnglesYXZ()
-- Only update if yaw is different to avoid jitter
local currentYaw = select(2, hrp.CFrame:ToEulerAnglesYXZ())
if math.abs(currentYaw - camYaw) > 1e-4 then
hrp.CFrame = CFrame.new(hrpPos) * CFrame.Angles(0, camYaw, 0)
end
-- If not moving, update foot targets to lock feet in place
if humanoid.MoveDirection.Magnitude < 0.1 then
updateTargets()
leftIK.Enabled = true
rightIK.Enabled = true
else
leftIK.Enabled = false
rightIK.Enabled = false
end
-- Make the head/neck look at the mouse's 3D hit position
if neck and head and upperTorso and mouse then
local mouseHit = mouse.Hit
if mouseHit and typeof(mouseHit) == "CFrame" then
local targetPos = mouseHit.Position
lookAtNeck(neck, head, upperTorso, targetPos)
end
end
end)
end
-- Listen for character spawn
LocalPlayer.CharacterAdded:Connect(function(character)
if currentRenderStepped then
currentRenderStepped:Disconnect()
currentRenderStepped = nil
end
setupFootLock(character)
end)
if LocalPlayer.Character then
setupFootLock(LocalPlayer.Character)
end
I losttttttttttttttttttttttttttt hope…
I finally found the solution:
In order to create footlocking, Keep a copy of the original Motor6D CFrames at all times.
Add the calculated offset to the original CFrames every frame.
Modify the Motor6Ds (Neck, Waist, Root, etc.) every frame based on the logic similar to the provided example.
local function footlocking(dt)
local diffCFrame = currentHRPCFrame:ToObjectSpace(lastHRPCFrame)
local _,X,_ = diffCFrame:ToEulerAnglesYXZ()
finalDir = (finalDir + -X)
local dtVal = 1 + (snappyness * dt * 60)
local dtValWalk = 1 + ((snappyness * 0.8) * dt * 60)
if HumanoidRootPart.AssemblyLinearVelocity.Magnitude > 0.1 then
finalDir = math.clamp(finalDir/dtValWalk,-2.4,2.4) -- when player isnt standing still
else
if snap then
if math.abs(finalDir) > 0.1 then -- we work our way to the target
finalDir = math.clamp(finalDir/dtVal,-1,1)
else
snap = false
end
end
if math.abs(finalDir) > maxDiffBeforeSnap then
if maxDiffBeforeSnap < 2 then
snap = true
finalDir = math.clamp(finalDir/dtVal,-1,1)
end
end
end
if isScoped then
finalCFrames.Waist *= CFrame.fromEulerAnglesYXZ(0,finalDir,0)
else
finalCFrames.Neck *= CFrame.fromEulerAnglesYXZ(0,finalDir*0.25,0)
finalCFrames.Waist *= CFrame.fromEulerAnglesYXZ(0,finalDir*0.75,0)
end
finalCFrames.Root += CFrame.fromEulerAnglesYXZ(0,-finalDir,0)
lastHRPCFrame = currentHRPCFrame
end
references:
welds.Neck = waitForChild(waitForChild(Character, "Head"), "Neck")
welds.LS = waitForChild(waitForChild(Character, "LeftUpperArm"), "LeftShoulder")
welds.RS = waitForChild(waitForChild(Character, "RightUpperArm"), "RightShoulder")
welds.Waist = waitForChild(waitForChild(Character, "UpperTorso"), "Waist")
welds.Root = waitForChild(waitForChild(Character, "LowerTorso"), "Root")
On character spawn, cache the original C0/C1 of Neck, Waist, Root (and optionally Shoulders if needed).
Every frame, calculate the required offset (finalDir) using the player’s movement and camera.
Apply the offset to the original C0/C1 and set them on the Motor6Ds.
Clean up and restore original C0/C1 on character removal.
Use RunService.RenderStepped for per-frame updates.
my function now:
local function footlockLoop(character, welds)
local HumanoidRootPart = character:FindFirstChild("HumanoidRootPart")
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not HumanoidRootPart or not humanoid then return end
local lastHRPCFrame = HumanoidRootPart.CFrame
local finalDir = 0
local snap = false
local snappyness = 0.18
local maxDiffBeforeSnap = 1.2
local isScoped = character:FindFirstChild("IsScoped")
renderConn = RunService.RenderStepped:Connect(function(dt)
local currentHRPCFrame = HumanoidRootPart.CFrame
local diffCFrame = currentHRPCFrame:ToObjectSpace(lastHRPCFrame)
local _,X,_ = diffCFrame:ToEulerAnglesYXZ()
finalDir = (finalDir + -X)
local dtVal = 1 + (snappyness * dt * 60)
local dtValWalk = 1 + ((snappyness * 0.8) * dt * 60)
if HumanoidRootPart.AssemblyLinearVelocity.Magnitude > 0.1 then
finalDir = math.clamp(finalDir/dtValWalk,-2.4,2.4)
else
if snap then
if math.abs(finalDir) > 0.1 then
finalDir = math.clamp(finalDir/dtVal,-1,1)
else
snap = false
end
end
if math.abs(finalDir) > maxDiffBeforeSnap then
if maxDiffBeforeSnap < 2 then
snap = true
finalDir = math.clamp(finalDir/dtVal,-1,1)
end
end
end
-- Apply offsets to Motor6Ds, always relative to original C0
if isScoped.Value == true then
if welds.Waist and originalCFrames.Waist then
welds.Waist.C0 = originalCFrames.Waist.C0 * CFrame.fromEulerAnglesYXZ(0,finalDir,0)
end
else
if welds.Neck and originalCFrames.Neck then
welds.Neck.C0 = originalCFrames.Neck.C0 * CFrame.fromEulerAnglesYXZ(0,finalDir*0.25,0)
end
if welds.Waist and originalCFrames.Waist then
welds.Waist.C0 = originalCFrames.Waist.C0 * CFrame.fromEulerAnglesYXZ(0,finalDir*0.75,0)
end
end
if welds.Root and originalCFrames.Root then
welds.Root.C0 = originalCFrames.Root.C0 * CFrame.fromEulerAnglesYXZ(0,-finalDir,0)
end
lastHRPCFrame = currentHRPCFrame
end)
end
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.