R6 FootPlanting System | FREE

hi guys, i made a cool R6 FootPlanting system for yall, just paste this intro a script in ServerScriptService, if you notice any bugs or face problems, Js LMK!, this is the code.

showcase video:
(sorry for corrupted audio)

local CONFIG = {
	LerpSpeed       = 10, -- Speed of change of leg position
	RaycastLength   = 2, -- 2 is usually what you need for normal avatars
	FootRaiseOffset = 0.02, 
	FootRaiseScale  = 1.0, -- dont change or itll be weird or your leg will go thru the parts
	RotationScale   = 0.2, -- usually under 0.3 is good
	MaxRaisePercent = 0.6, -- basically change it so your leg will only raise on parts thats size is 60% or smaller from your leg.
	HipOffsetRight  = CFrame.new( 1, -1, 0),
	HipOffsetLeft   = CFrame.new(-1, -1, 0),
	RayDirection    = Vector3.new(0, -2, 0),
}

local Wait         = task.wait
local Spawn        = task.spawn
local CFrameNew    = CFrame.new
local CFrameAngles = CFrame.Angles
local V3           = Vector3.new

local Workspace  = game:GetService("Workspace")
local Players    = game:GetService("Players")
local RunService = game:GetService("RunService")

local function FindChildOfClassAndName(parent, className, name)
	if typeof(parent) ~= "Instance" then return end
	for _, child in ipairs(parent:GetChildren()) do
		if child:IsA(className) and child.Name == name then
			return child
		end
	end
end

local function WaitForChildOfClassAndName(parent, className, name)
	while Wait() do
		local found = FindChildOfClassAndName(parent, className, name)
		if found then return found end
	end
end

local function GetParent(instance)
	return typeof(instance) == "Instance" and instance.Parent
end

local function LerpJoint(joint, targetCFrame, speed)
	if not GetParent(joint) then return end
	if not joint:IsA("JointInstance") then return end
	if typeof(targetCFrame) ~= "CFrame" then return end
	if type(speed) ~= "number" then return end
	joint.C0 = joint.C0:Lerp(targetCFrame, speed)
end

local function SetupCharacter(character)
	local humanoidRootPart = WaitForChildOfClassAndName(character, "BasePart", "HumanoidRootPart")
	local rightLeg         = WaitForChildOfClassAndName(character, "BasePart", "Right Leg")
	local leftLeg          = WaitForChildOfClassAndName(character, "BasePart", "Left Leg")
	local torso            = WaitForChildOfClassAndName(character, "BasePart", "Torso")

	local rightHip  = WaitForChildOfClassAndName(torso, "JointInstance", "Right Hip")
	local leftHip   = WaitForChildOfClassAndName(torso, "JointInstance", "Left Hip")

	local rightHipC0 = rightHip.C0
	local leftHipC0  = leftHip.C0

	local maxRaiseRight = rightLeg.Size.Y * CONFIG.MaxRaisePercent
	local maxRaiseLeft  = leftLeg.Size.Y  * CONFIG.MaxRaisePercent

	local rayParams = RaycastParams.new()
	rayParams.FilterDescendantsInstances = { character }
	rayParams.FilterType = Enum.RaycastFilterType.Exclude

	RunService.PreSimulation:Connect(function(deltaTime)
		if not GetParent(character) or not GetParent(humanoidRootPart) then return end

		local speed      = deltaTime * CONFIG.LerpSpeed
		local rootCFrame = humanoidRootPart.CFrame

		local function worldRaiseToCFrame(raise)
			local localUp = torso.CFrame:VectorToObjectSpace(V3(0, raise, 0))
			return CFrameNew(localUp)
		end

		if rightLeg then
			local origin    = (rootCFrame * CONFIG.HipOffsetRight).Position
			local rayResult = Workspace:Raycast(origin, CONFIG.RayDirection, rayParams)

			if rayResult then
				local dist  = (rayResult.Distance - CONFIG.RaycastLength) - CONFIG.FootRaiseOffset
				local raise = math.max(-dist, 0) * CONFIG.FootRaiseScale
				local shift = math.max(dist, 0)
				local angle = raise * CONFIG.RotationScale

				if raise < maxRaiseRight then
					LerpJoint(rightHip,
						rightHipC0
							* CFrameNew(-shift, 0, 0)
							* worldRaiseToCFrame(raise)
							* CFrameAngles(0, 0, angle),
						speed)
				else
					LerpJoint(rightHip, rightHipC0, speed)
				end
			else
				LerpJoint(rightHip, rightHipC0, speed)
			end
		end

		if leftLeg then
			local origin    = (rootCFrame * CONFIG.HipOffsetLeft).Position
			local rayResult = Workspace:Raycast(origin, CONFIG.RayDirection, rayParams)

			if rayResult then
				local dist  = (rayResult.Distance - CONFIG.RaycastLength) - CONFIG.FootRaiseOffset
				local raise = math.max(-dist, 0) * CONFIG.FootRaiseScale
				local shift = math.max(dist, 0)
				local angle = raise * CONFIG.RotationScale

				if raise < maxRaiseLeft then
					LerpJoint(leftHip,
						leftHipC0
							* CFrameNew(shift, 0, 0)
							* worldRaiseToCFrame(raise)
							* CFrameAngles(0, 0, -angle),
						speed)
				else
					LerpJoint(leftHip, leftHipC0, speed)
				end
			else
				LerpJoint(leftHip, leftHipC0, speed)
			end
		end
	end)
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		Spawn(SetupCharacter, character)
	end)
end)
13 Likes

Any videos to showcase this? Would be greatly appreciated.

2 Likes

Why not use Inverse Kinematics?

From what I have heard ik doesn’t work well for r6

1 Like

Just about to ask the same lol :joy:

its really bad and unoptimized with R6

i added a showcase video at the top of the post

Can u make a R15 version?

Because that’s what I wanna see

this is false, many modules exist out there as well as simply making your own that make R6 IK an easy task. i’ve forked R6IK to support fully overwriting animation, or influencee it with an influence factor. 0.5 ik and 0.5 animation etc :stuck_out_tongue:

IK: Gifted power
R6 FootPlanting System: Pure Effort :money_mouth_face:

can u make a template on ur profile for others to copy aswell

sorry i dont work with R15 tho it shouldnt be hard to make R15 Variant, just more joins to connect and apply logic to

There are some issues like this, being a server script and a memory leak, since you never disconnect the PreSimulation Connection.

I’ve fixed the code and moved it to the client. This will work for other characters as well, without the need for replication, as each client does it for themselves and others locally. You could probably improve this by only doing it for players within a range, but I digress.

Place this in a local script inside StarterPlayerScripts

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local CONFIG = {
	LerpSpeed       = 10,
	RaycastLength   = 2,
	FootRaiseOffset = 0.02, 
	FootRaiseScale  = 1.0,
	RotationScale   = 0.2,
	MaxRaisePercent = 0.6,
	HipOffsetRight  = CFrame.new( 1, -1, 0),
	HipOffsetLeft   = CFrame.new(-1, -1, 0),
	RayDirection    = Vector3.new(0, -2, 0),
}

local activeCharacters = {}

local function worldRaiseToCFrame(torso, raise)
	local localUp = torso.CFrame:VectorToObjectSpace(Vector3.new(0, raise, 0))
	return CFrame.new(localUp)
end

local function addCharacter(character)
	local humanoid = character:WaitForChild("Humanoid", 5)
	if not humanoid or humanoid.RigType ~= Enum.HumanoidRigType.R6 then return end

	local root = character:WaitForChild("HumanoidRootPart", 5)
	local torso = character:WaitForChild("Torso", 5)
	local rightLeg = character:WaitForChild("Right Leg", 5)
	local leftLeg = character:WaitForChild("Left Leg", 5)
	local rightHip = torso:WaitForChild("Right Hip", 5)
	local leftHip = torso:WaitForChild("Left Hip", 5)

	if not (root and torso and rightLeg and leftLeg and rightHip and leftHip) then return end

	local rayParams = RaycastParams.new()
	rayParams.FilterDescendantsInstances = { character }
	rayParams.FilterType = Enum.RaycastFilterType.Exclude

	activeCharacters[character] = {
		Root = root,
		Torso = torso,
		RightHip = rightHip,
		LeftHip = leftHip,
		RayParams = rayParams,
		RightOffset = CFrame.new(),
		LeftOffset = CFrame.new(),
		MaxRaiseRight = rightLeg.Size.Y * CONFIG.MaxRaisePercent,
		MaxRaiseLeft = leftLeg.Size.Y * CONFIG.MaxRaisePercent
	}
end

local function removeCharacter(character)
	activeCharacters[character] = nil
end

local function onPlayerAdded(player)
	player.CharacterAdded:Connect(addCharacter)
	player.CharacterRemoving:Connect(removeCharacter)

	if player.Character then
		task.spawn(addCharacter, player.Character)
	end
end

Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in ipairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end

Players.PlayerRemoving:Connect(function(player)
	if player.Character then
		removeCharacter(player.Character)
	end
end)

---

RunService.Stepped:Connect(function(time, deltaTime)
	local speed = deltaTime * CONFIG.LerpSpeed

	for character, data in pairs(activeCharacters) do
		if not character.Parent or not data.Root.Parent then
			activeCharacters[character] = nil
			continue
		end

		local rootCFrame = data.Root.CFrame

		local originRight = (rootCFrame * CONFIG.HipOffsetRight).Position
		local rayResultRight = Workspace:Raycast(originRight, CONFIG.RayDirection, data.RayParams)
		local targetRightOffset = CFrame.new()

		if rayResultRight then
			local dist = (rayResultRight.Distance - CONFIG.RaycastLength) - CONFIG.FootRaiseOffset
			local raise = math.max(-dist, 0) * CONFIG.FootRaiseScale
			local shift = math.max(dist, 0)
			local angle = raise * CONFIG.RotationScale

			if raise < data.MaxRaiseRight then
				targetRightOffset = CFrame.new(-shift, 0, 0) 
					* worldRaiseToCFrame(data.Torso, raise) 
					* CFrame.Angles(0, 0, angle)
			end
		end

		local originLeft = (rootCFrame * CONFIG.HipOffsetLeft).Position
		local rayResultLeft = Workspace:Raycast(originLeft, CONFIG.RayDirection, data.RayParams)
		local targetLeftOffset = CFrame.new()

		if rayResultLeft then
			local dist = (rayResultLeft.Distance - CONFIG.RaycastLength) - CONFIG.FootRaiseOffset
			local raise = math.max(-dist, 0) * CONFIG.FootRaiseScale
			local shift = math.max(dist, 0)
			local angle = raise * CONFIG.RotationScale

			if raise < data.MaxRaiseLeft then
				targetLeftOffset = CFrame.new(shift, 0, 0) 
					* worldRaiseToCFrame(data.Torso, raise) 
					* CFrame.Angles(0, 0, -angle)
			end
		end

		data.RightOffset = data.RightOffset:Lerp(targetRightOffset, speed)
		data.LeftOffset = data.LeftOffset:Lerp(targetLeftOffset, speed)

		data.RightHip.Transform = data.RightHip.Transform * data.RightOffset
		data.LeftHip.Transform = data.LeftHip.Transform * data.LeftOffset
	end
end)

Simple R15 IK Foot-Planting Module

yo thanks for correcting me, i already made it client before you made this but still, yeah this is better.

1 Like