Procedural Foot-Planting with IKControls

Issue
Hello, with the recent release of IKControls, I wanted to attempt to create foot-planting for my r15 Characters. After about 1 1/2 hours of playing around, I ended up with a somewhat working result; however, it is very unreliable and unrealistic. Any tips or mistakes that I could fix that could improve it would be greatly appreciated because I’m not really the greatest at these things lol.

Screenshots of unrealistic footplanting:
https://gyazo.com/ef8e9383283cba00b9833bf64452c096
https://gyazo.com/7a53e995088b138164304b69819121ad
https://gyazo.com/d9f9f8f43e9ea9f16344df360b101986

Individual Leg Solver
Bound to RunService.Heartbeat

		local function Update(Name: string)
			--//LegLimbs
			local UpperLeg = Character:FindFirstChild(`{Name}UpperLeg`)
			local LowerLeg = Character:FindFirstChild(`{Name}LowerLeg`)
			local Foot = Character:FindFirstChild(`{Name}Foot`)
			if not UpperLeg or not LowerLeg or not Foot then return end
			--//IkControl
			local IkControl = Humanoid:FindFirstChild(`_{Name}_FootPlanting_Control`)
			if not IkControl then
				IkControl = Instance.new("IKControl")
				IkControl.Name = `_{Name}_FootPlanting_Control`
				IkControl.Weight = 1
				IkControl.Priority = 0
				IkControl.SmoothTime = .2
				IkControl.Enabled = false
				IkControl.Parent = Humanoid
			end
			local ChainRoot = IkControl.ChainRoot
			if not ChainRoot then
				ChainRoot = UpperLeg:FindFirstChild(`{Name}Hip`)
				IkControl.ChainRoot = ChainRoot
			end
			local Pole = IkControl.Pole
			if not Pole then
				Pole = LowerLeg:FindFirstChild(`{Name}Knee`)
				--IkControl.Pole = Pole
			end
			local EndEffector = IkControl.EndEffector
			if not EndEffector then
				EndEffector = Foot:FindFirstChild(`{Name}FootAttachment`)
				IkControl.EndEffector = EndEffector
			end
			local Target = IkControl.Target
			if not Target then
				Target = Instance.new("Attachment")
				IkControl.Target = Target
				Target.Name = `_{Name}_FootPlanting_Control_Target`
				Target.Parent = Humanoid.RootPart
			end
			local AnkleJoint = Foot:FindFirstChild(`{Name}Ankle`) --//try making this the endeffector?!?!?1 (nerp didnt work)
			if not ChainRoot or not Pole or not EndEffector or not Target or not AnkleJoint then IkControl.Enabled = false; return end
			local IsStateRunning = Humanoid:GetState() == Enum.HumanoidStateType.Running
			if not IsStateRunning then IkControl.Enabled = false; return end
			local IsMoving = false
			if (Humanoid.MoveDirection * Vector3.new(1, 0, 1)).Magnitude > .1 and Humanoid.RootPart.AssemblyLinearVelocity.Magnitude > 1 then
				IsMoving = true
			end
			if not IsMoving then return end
			local HipJointWorldCFrame: CFrame = ChainRoot.Part0.CFrame * ChainRoot.C0
			local KneePart1CF: CFrame = HipJointWorldCFrame * ChainRoot.Transform * ChainRoot.C1:Inverse()
			local FootPart1CF: CFrame = KneePart1CF * Pole.C0 * Pole.Transform * Pole.C1:Inverse()
			local ExpectedFootPlacement: CFrame = FootPart1CF * AnkleJoint.C0 * AnkleJoint.Transform * AnkleJoint.C1:Inverse()
			local ExpectedTargetPlacement: CFrame = ExpectedFootPlacement * EndEffector.CFrame
			--Frame --//remember endeffector is an att so .cframe is local 
			local RayDirection: Vector3 = ExpectedFootPlacement.Position - HipJointWorldCFrame.Position
			local RaycastResult: RaycastResult = workspace:Raycast(HipJointWorldCFrame.Position, RayDirection, RayParams)
			if RaycastResult and RaycastResult.Instance then	
				local rX, _, rZ = CFrame.lookAt(RaycastResult.Position, RaycastResult.Position + RaycastResult.Normal):ToOrientation()		
				rX = math.max(rX, 1.5)
				rZ = math.max(rZ, 1.5)
				IkControl.Target.WorldCFrame = (ExpectedTargetPlacement.Rotation + RaycastResult.Position) --* CFrame.Angles(rX, 0, rZ)
				IkControl.Enabled = true
			else
				IkControl.Enabled = false
			end
			if DEBUG then
				local DebugPart = workspace:FindFirstChild("_FootPlantingDebug")
				if not DebugPart then
					DebugPart = Instance.new("Part")
					DebugPart.Name = "_FootPlantingDebug"
					DebugPart.Anchored = true
					DebugPart.CanCollide = false
					DebugPart.Transparency = 1
					DebugPart.Parent = workspace
				end
				local DebugAtt = Instance.new("Attachment")
				DebugAtt.Name = `{Name}DebugAttachment`
				DebugAtt.Parent = DebugPart
				DebugAtt.WorldCFrame = IkControl.Target.WorldCFrame
				DebugAtt.Visible = true
				Debris:AddItem(DebugAtt, 60)
			end
		end

Explanation to Code
I created a function bound to heartbeat that calls a local function called Update(, passing the Name of the leg I want to update. The function would get the parts of the leg and then find or create an IKControl Instance and set it up. If it has everything it needs to update the leg, it will then see if the Humanoid’s state is running; otherwise, it would return and disable the IKControl so it would’nt ruin the action. Afterwards, it would check if the player is actually moving using a combination of Humanoid.MoveDirection and Humanoid.RootPart.AssemblyLinearVelocity. Then, if the character is “idle”, it would return and not disable the IKControl if it was enabled. Finally, we get to the complicated–at least to me–part based off this post (Prototype R15 Animation IKControl Footplant by @dthecoolest). If I’m truly honest, I have no idea what it really does other than subtract some CFrames. After raycasting, if there’s a result, the IKControl would stay enabled, and if there wasn’t it would be disabled. If the IKControl is enabled, then I get the angle of intersection using the RaycastResult.Normal and multiply it get only the X and Z. Afterwards, I set the IKControl.Target attachment’s WorldCFrame to the Intersection CFrame added to the ExpectedTargetPlacement CFrame and then added to RaycastResult.Position. Finally, I create an Attachment that shows where the Target was during the update.

Full Script
FootPlantingController.lua (4.8 KB)

I apologize for any mistakes with this post; it’s my first time lol.

7 Likes

It’ s not really a mistake but what I noticed is that you made the IK Pole the knee and that can actually make the leg rotate VERY weirdly and make it look like you cracked your bones.

Did you manage to figure this out?? im still trying to make this for my character