R6 IKPF (Inverse Kinematics Procedural Footplanting)

I tried using this on a scaled down character and…

Does anyone know how to fix this?

2 Likes

the legs seem to jerk in a specific direction, it might be because I disabled the torso rotations
but its really annoying

edit:
nope its not the torso thing, I re-enabled it and its still there, i do notice it happens specifically on sloped surfaces and ESPECIALLY on terrain

3 Likes

I love this tutorial and it really helped me for my game, but I do have a important question. How would I change the sound of the walking based on the material? I cannot seem the figure this out.

3 Likes

The foot step sound is a connection within the ProceduralAnimator class which returns the raycast result, so material detection should be possible there.

function ProceduralAnimatorClass:ConnectFootStepSound(sound : Sound)
	self.FootStep:Connect(function(raycastResult)
		local soundPositionAttachment = Instance.new("Attachment")
		soundPositionAttachment.WorldPosition = raycastResult.Position
		soundPositionAttachment.Parent = workspace.Terrain

		local footStepSound = sound:Clone()
		local randomPlaybackSpeed = self.RandomNumGenerator:NextNumber(0.7,1)
		footStepSound.PlaybackSpeed = randomPlaybackSpeed

		local reverbEffect = Instance.new("ReverbSoundEffect")
		reverbEffect.Density = 0.8
		reverbEffect.DecayTime = 1
		reverbEffect.Parent = footStepSound
		footStepSound.PlayOnRemove = true

		footStepSound.Parent = soundPositionAttachment
		soundPositionAttachment:Destroy()
	end)
end
2 Likes

I love this and it makes a roblox character look less like a toy but i do have one question how would i prevent this from happening (the original animation looks like this) or just how to simply disable this script while prone.

2 Likes

hey! I was messing around and changed the torso from moving side to side but up and down. How do I make it so the legs don’t move around a lot?

2 Likes

How can we make custom animations

2 Likes

This works great, but the torso is jumping up and down WAY too intensely. How could I smooth or slow this out??

The whole point of inverse kinematics is procedurally generating the animation so you don’t have to do it yourself. :neutral_face:

3 Likes

Thanks mate. Btw, how do I make it, so it updates the position in the direction where I’m standing??

Red is the last position it took
and
green makes it go down where the floor is

6 Likes

I would want this to only be the walking animation but when doing an attack, and playing my animation for the attack it doesnt work, do i have to temporarly dissable the ikpf script for it to play my animations?

1 Like

how did you make it like this?? i wanna replicate this for my game

3 Likes

Inside ProceduralAnimator Module replace the function

function ProceduralAnimatorClass:MoveLegs(stepCycle,dt)
	--if moving
	for _, Leg in pairs(self.Legs) do
		local strideCF2 = Leg.StrideCF or self.DefaultStrideCF
		local strideOffset2 = Leg.StrideOffset or self.DefaultStrideOffset
		local raycastParams2 = self.RaycastParams
		Leg.CurrentCycle = (Leg.CurrentCycle+stepCycle)%360
		local cycle2 = Leg.CurrentCycle
		local IKTolerance2 = Leg.IKTolerance or 0

		local hip2			=Leg.HipAttachment.WorldPosition
		--Position of where the lower leg should be, spread out
		local ground2		=Leg.FootAttachment.WorldPosition
		local desiredPos2	=(CF(ground2, ground2+self.MovementDirectionXZ)*ANGLES(-cycle2, 0, 0)*strideCF2).p
		local futurePos1	=(CF(ground2, ground2+self.MovementDirectionXZ)*ANGLES(-(cycle2 + 0.7), 0, 0)*strideCF2).p
		local offset2		=(desiredPos2-hip2)--vector from hip to the circle
		local foffset		=(futurePos1-hip2)--vector from hip to the circle
		local highlightedfuture = (hip2 + foffset.unit*(foffset.magnitude+strideOffset2))
		local raycastResult2 = workspace:Raycast(hip2,offset2.unit*(offset2.magnitude+strideOffset2),raycastParams2)
		local footPos2 = raycastResult2 and raycastResult2.Position or (hip2 + offset2.unit*(offset2.magnitude+strideOffset2))
		
		if self.rootVelocityMagnitude > 0.1 then
			--Do IK towards foot pos
			--Leg.CCDIKController:CCDIKIterateOnce(footPos,IKTolerance)
			--Iterating once won't fully track the footPos, needs to iterate until
			-- DEBUG
			--[[local HighLight = Instance.new("Part", workspace)
			HighLight.Name = "Highlight"
			HighLight.Position = footPos2
			HighLight.CanCollide = false
			HighLight.Size = Vector3.new(0.5, 0.5, 0.5)
			HighLight.Shape = Enum.PartType.Ball
			HighLight.CanTouch = false
			HighLight.CanQuery = false
			HighLight.Anchored = true
			local HighLighter = Instance.new("Highlight", HighLight)
			HighLighter.FillColor = Color3.new(1, 0, 0.0156863)
			HighLighter.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
			game.Debris:AddItem(HighLight,0.05)]]
			
			Leg.CCDIKController:CCDIKIterateUntil(footPos2,IKTolerance2)
			
			if not Leg.TouchGround and raycastResult2 then
				self.FootStep:Fire(raycastResult2)
			end
			--
			if raycastResult2 then -- hit ground so raycast result
				Leg.TouchGround = true
			else
				Leg.TouchGround = false
			end
			
		else --stand still
			
			local strideCF = Leg.StrideCF or self.DefaultStrideCF
			local strideOffset = Leg.StrideOffset or self.DefaultStrideOffset
			local raycastParams = self.RaycastParams
			local IKTolerance = Leg.IKTolerance or 0

			local hip			=Leg.HipAttachment.WorldPosition
			--Position of where the lower leg should be, spread out
			local desiredPos		=Leg.FootAttachment.WorldPosition
			local offset		=(desiredPos-hip)--vector from hip to the circle
			local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude+strideOffset),raycastParams)
			local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude+strideOffset))
			
			-- LAST LEG POSITION DEBUG
			--[[local HighLight2 = Instance.new("Part", workspace)
			HighLight2.Name = "Highlight"
			HighLight2.Position = footPos2
			HighLight2.CanCollide = false
			HighLight2.Size = Vector3.new(0.5, 0.5, 0.5)
			HighLight2.Shape = Enum.PartType.Ball
			HighLight2.CanTouch = false
			HighLight2.CanQuery = false
			HighLight2.Anchored = true
			local HighLighter = Instance.new("Highlight", HighLight2)
			HighLighter.FillColor = Color3.new(1, 0, 0.0156863)
			HighLighter.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
			game.Debris:AddItem(HighLight2,0.05)
			
			-- CURRENT LEG POSITION DEBUG
			local HighLight = Instance.new("Part", workspace)
			HighLight.Name = "Highlight"
			HighLight.Position = Vector3.new(footPos2.X, footPos.Y, footPos2.Z)
			HighLight.CanCollide = false
			HighLight.Size = Vector3.new(0.5, 0.5, 0.5)
			HighLight.Shape = Enum.PartType.Ball
			HighLight.CanTouch = false
			HighLight.CanQuery = false
			HighLight.Anchored = true
			local HighLighter = Instance.new("Highlight", HighLight)
			HighLighter.FillColor = Color3.new(0.733333, 1, 0)
			HighLighter.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop
			game.Debris:AddItem(HighLight,0.05)]]
			
			--Leg.CCDIKController:CCDIKIterateOnce(Vector3.new(desiredPos2.X, desiredPos.Y + desiredPos2.Y,desiredPos2.Z),IKTolerance)
			Leg.CCDIKController:CCDIKIterateOnce(
				Vector3.new(footPos2.X, footPos.Y, footPos2.Z)
				,IKTolerance
			)
			
			if not Leg.TouchGround and raycastResult then
				self.FootStep:Fire(raycastResult) 
			end
			
			if raycastResult then -- hit ground so raycast result
				Leg.TouchGround = true
			else
				Leg.TouchGround = false
			end
			
		end
		
	end
	
end
8 Likes

its the sway also you need to red the notes in the script its quite usefull

1 Like

Help my tiny little brain can’t figure out how to use the script I can get to the part to use iterate once setup foot those things but the values it wants me to put in their just confuse me. I tried going to the tutorial but the link doesn’t work am I just dumb is this easy to setup

3 Likes

Could this also be done using the new IK Controls?

2 Likes

Yep, it only takes one line to replace the IK portion of the script in the quote above :CCDIKIterateUntil.

Just be aware of the changes made to the R6 leg to make it 2 limbs instead of 1 for that animation effect.

3 Likes

im very late but after a little bit of tweaking i figured it out so what you need to do

inside the procedural animator module copy this code its the whole script

– Procedural Animator Class
– Dthecoolest
– November 20, 2020
local RunService = game:GetService(“RunService”)

local ProceduralAnimatorClass = {}
ProceduralAnimatorClass.__index = ProceduralAnimatorClass

–Module to handle the procedural animation the hip and legs
–remnants from iGottics Code
local CF =CFrame.new
local ANGLES =CFrame.Angles
local x_and_y = Vector3.new(1, 0, 1)
local TAU = 2math.pi
local DOWN = 10
Vector3.new(0,-1,0)

–template for legs
– local mechLegs = {
– [“rightLeg”] = {
– [“CurrentCycle”] = 0,
– [“LimbChain”] = rightLegChain,
– [“HipAttachment”]= rightHipAttachment,
– [“FootAttachment”] = rightStepAttachment,
– },

– [“leftLeg”] = {
– [“CurrentCycle”] = math.pi,
– [“LimbChain”] =leftLegChain,
– [“CCDIKController”] =leftLegChain,
– [“HipAttachment”]= leftHipAttachment,
– [“FootAttachment”] = leftStepAttachment,
– }
– }
local Signal = require(script.Parent.Signal)

function ProceduralAnimatorClass.new(RootPart,Legs,RootMotor,raycastParams)

local self = setmetatable({}, ProceduralAnimatorClass)

--Constants
self.RootPart = RootPart
self.RaycastParams = raycastParams --manual input it

if RootMotor then
	self.RootMotor = RootMotor
	self.RootMotorC1Store = RootMotor.C1
	self.WaistCycle = 0 
end

self.Legs = Legs

--Default settings for legs
self.DefaultStride = 2 -- Changes how far the legs move
self.CycleSpeed = 15 -- How fast the leg-movement cycle is. Change this to suit your needs!
self.DefaultStrideOffset = 0
-- Radius of the circle at CFrame front of the player
self.DefaultStrideCF = CFrame.new(0, 0, -self.DefaultStride / 2) -- Turn that stride number into a CFrame we can use


--Variables that will change
self.MovementDirectionXZ = Vector3.new(1, 0, 1) -- This will be changed
self.rootvelm = 0

--Sound
self.FootStep = Signal.new();
self.MaxSpeed = 20
self.EngineSound = nil;
self.FootStepSound = nil;
self.RandomNumGenerator = Random.new()
--debug the signal, works
--self.FootStep:Connect(function()
--	print("Step")
--end)

self.WalkBounce = 0.4 -- factor by which it bounces
self.SwayX = -1*5 -- factor in Z direction front or behind, currently set to tilt forward
return self

end

function ProceduralAnimatorClass:MoveLegs(stepCycle,dt)
–if moving
if self.rootVelocityMagnitude > 0.1 then
for _, Leg in pairs(self.Legs) do
local strideCF = Leg.StrideCF or self.DefaultStrideCF
local strideOffset = Leg.StrideOffset or self.DefaultStrideOffset
local raycastParams = self.RaycastParams
Leg.CurrentCycle = (Leg.CurrentCycle+stepCycle)%360
local cycle = Leg.CurrentCycle
local IKTolerance = Leg.IKTolerance or 0

		local hip			=Leg.HipAttachment.WorldPosition
		--Position of where the lower leg should be, spread out
		local ground		=Leg.FootAttachment.WorldPosition
		local desiredPos	=(CF(ground, ground+self.MovementDirectionXZ)*ANGLES(-cycle, 0, 0)*strideCF).p
		local offset		=(desiredPos-hip)--vector from hip to the circle
		local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude+strideOffset),raycastParams)
		local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude+strideOffset))

		--debug foot pos position
		--local part = Instance.new("Part")
		--part.CanCollide = false
		--part.CanTouch = false
		--part.BrickColor = BrickColor.Red()
		--part.Anchored = true
		--part.CanQuery = false
		--part.Size = Vector3.new(0.1,0.1,0.1)
		--part.Position = footPos
		--part.Parent = workspace
		--game.Debris:AddItem(part,0.1)

		--Do IK towards foot pos
		--Leg.CCDIKController:CCDIKIterateOnce(footPos,IKTolerance)
		--Iterating once won't fully track the footPos, needs to iterate until
		Leg.CCDIKController:CCDIKIterateUntil(footPos,IKTolerance)

		if not Leg.TouchGround and raycastResult then
			--print("Stomp")
			self.FootStep:Fire(raycastResult)
		end
		--
		if raycastResult then -- hit ground so raycast result
			Leg.TouchGround = true
		else
			Leg.TouchGround = false
		end
	end
else--stand still
	for _, Leg in pairs(self.Legs) do
		local strideCF = Leg.StrideCF or self.DefaultStrideCF
		local strideOffset = Leg.StrideOffset or self.DefaultStrideOffset
		local raycastParams = self.RaycastParams
		local IKTolerance = Leg.IKTolerance or 0

		local hip			=Leg.HipAttachment.WorldPosition
		--Position of where the lower leg should be, spread out
		local desiredPos		=Leg.FootAttachment.WorldPosition+DOWN
		local offset		=(desiredPos-hip)--vector from hip to the circle
		local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude+strideOffset),raycastParams)
		local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude+strideOffset))

		--Do IK towards foot pos
		Leg.CCDIKController:CCDIKIterateOnce(footPos,IKTolerance)
		--Leg.LimbChain:IterateOnce(footPos,0.1)
		--Leg.LimbChain:UpdateMotors()
		if not Leg.TouchGround and raycastResult then
			--print("Stomp")
			self.FootStep:Fire(raycastResult)
		end

		if raycastResult then -- hit ground so raycast result
			Leg.TouchGround = true
		else
			Leg.TouchGround = false
		end

	end
end

end

function ProceduralAnimatorClass:MoveTorso(stepCycle,dt10,rootVelocity)

local lowercf = self.RootPart.CFrame
local waistjoint = self.RootMotor
local waist1 = self.RootMotorC1Store
local rootvel = rootVelocity


if self.rootVelocityMagnitude > 0.1 then

	self.WaistCycle = (self.WaistCycle+stepCycle)%360

	local relv0	= lowercf:vectorToObjectSpace(rootvel)
	local relv1	= relv0*0.2

	do -- Upper Torso
		local bounceCFrame = CFrame.new(0,self.WalkBounce*math.cos((self.WaistCycle+2+1)*0.02),0)

		local sway = math.rad(-relv1.X)+0.08*math.cos(self.WaistCycle+0.05)
		local swayY = 0.06*math.cos(self.WaistCycle)-0.1*math.rad(relv1.X)
		local swayX = math.rad(relv1.Z)*0.5*self.SwayX
		local goalCF = bounceCFrame*waist1*ANGLES(swayX,swayY,sway):inverse()
		-- goalCF *= CFrame.new(0,math.cos((self.WaistCycle+90+45)*2),0)-- Up and down
		--goalCF *= CFrame.new(0,self.WalkBounce*math.cos((self.WaistCycle+90+45)*2),0)-- Up and down
		--local rotationOnly = goalCF-goalCF.Position
		waistjoint.C1	=	waistjoint.C1:Lerp(goalCF,dt10)
	end

else
	--when not moving go back to original position
	local goalCF = waistjoint.C1:Lerp(waist1, dt10)
	--local rotationOnly = goalCF-goalCF.Position
	waistjoint.C1	= goalCF
end

end

function ProceduralAnimatorClass:Animate(dt)
if game.Players.LocalPlayer.Character:WaitForChild(“Humanoid”).FloorMaterial ~= Enum.Material.Air then
– Begin the step-------
local dt10 = math.min(dt*10, 1) – Normalize dt for our needs

local rootpart = self.RootPart
local rootvel0 = rootpart.Velocity -- Our movement velocity

local rootVelocity = rootvel0 * x_and_y --XY plane velocity only
local rootVelocityMagnitude = rootVelocity.Magnitude --root velocity magnitude
self.rootVelocityMagnitude = rootVelocityMagnitude

if self.EngineSound then
	self.EngineSound.PlaybackSpeed = (rootVelocityMagnitude / self.MaxSpeed) + 0.6
end

--if moving then lerp current direction
if rootVelocityMagnitude > 0.1 then 
	--lerp current direction towards curren velocity
	self.MovementDirectionXZ = self.MovementDirectionXZ:Lerp(rootVelocity.unit, dt10) 
end

local relativizeToHumanoidSpeed = rootVelocityMagnitude/16 --default walk speed is 16
local stepCycle = relativizeToHumanoidSpeed*dt*self.CycleSpeed

self:MoveLegs(stepCycle,dt)
if self.RootMotor then
	self:MoveTorso(stepCycle,dt10,rootVelocity)
	end	
end

end

function ProceduralAnimatorClass:ConnectFootStepSound(sound : Sound)
self.FootStep:Connect(function(raycastResult)
local soundPositionAttachment = Instance.new(“Attachment”)
soundPositionAttachment.WorldPosition = raycastResult.Position
soundPositionAttachment.Parent = workspace.Terrain

	local footStepSound = sound:Clone()
	local randomPlaybackSpeed = self.RandomNumGenerator:NextNumber(0.7,1)
	footStepSound.PlaybackSpeed = randomPlaybackSpeed

	local reverbEffect = Instance.new("ReverbSoundEffect")
	reverbEffect.Density = 0.8
	reverbEffect.DecayTime = 1
	reverbEffect.Parent = footStepSound
	footStepSound.PlayOnRemove = true

	footStepSound.Parent = soundPositionAttachment
	soundPositionAttachment:Destroy()
end)

end

function ProceduralAnimatorClass:StartEngineSound(sound : Sound)
local engineSound = sound:Clone()
engineSound.Parent = self.RootPart
engineSound.Looped = true
engineSound:Play()
end

function ProceduralAnimatorClass:InitDragDebug()
for _, Leg in pairs(self.Legs) do
Leg.CCDIKController:InitDragDebug()
end
end

function ProceduralAnimatorClass:Destroy()
if self.FootStep then
self.FootStep:Destroy()
end
self = nil
end

return ProceduralAnimatorClass

4 Likes

So if i try to animate them what will happen?
Trying to make kicking animations.
If this will do nothing(Pretty sure it will do nothing)
then how would I do this?

2 Likes

I downloaded this and did the command game:SetUniverseId(1818) so my offline experience would have R6 enabled. Placed it in StarterPlayerScripts because it was a LocalScript with a few modules.

This picture is the server-sided picture. Now as described, it creates 4 motor6d and 2 parts to set up a fake joint and then disables the old motor6ds. I do not know why they decided to do this in the first place.
Some basic behavior of motor6ds is that only the .Transform property is replicated from client to server (for animation purposes). So basically you have an unreplicated client implementation here. Also Roblox’s animation player uses the .Transform property as well.

Additionally, a more practical approach would be to use the .Transform property of the existing joint, and to create a boolean value inside of Humanoid to disable the IK animation temporarily so normal animations can be played. I believe this could be done if you simply record the properties (as in C0, C1, Transform) of the 4 motor6ds you created as variables. That way there are no new joints or parts. To get the resulting .Transform for the existing joint, perform a CFrame calculation.

I believe this would be more practical because instead of performing 20 character calculations on the server or 20 character calculations on each client, each client would perform 1 character’s calculation and replicate normally, and allow for animations.

2 Likes

Screenshot 2023-03-25 101741
I tried multiplayer and when a player resets the other player’s legs go crazy anyone knows how to fix this?

3 Likes