Leg Controller (IK Included) - A quality solution to leg movement

For some reason, it sometimes stops working after you die and respawn, I’m not sure why. edit: it also doesn’t replicate if you enable/disable IK at runtime

Is there any way to disable the torso rotation type thing?

Edit: Might just be my own animation but I can’t test it right now.

That’s very cool, but it doesn’t work in roblox for me (it only works in studio). Sometimes it works but not everytime.

Have you figured out how to disable it? I’m also trying to turn it off

Turn maxRootAngle to 0 in the loader.

1 Like

For everybody having this issue, the loader code provided has no checks to see if the character is already added/loaded, making it look like a bad resource, but it’s just simply the loader. Check if the character already exists (which is almost 100% the case for me) then call onCharacterAdded on them.

if player.Character then
	OnCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(OnCharacterAdded)

make sure to call it on CharacterAdded still or it won’t work upon death.

Don’t know why this took me so long to figure out, but whatever. This is also the reason why it always works for other players but not always yourself, the loader runs the code for existing characters, but doesn’t check if yours exists.

I feel like this is still a useful resource so I’m just gonna leave this here in case anyone is having problems with this.

4 Likes

This is honestly great, I was just wondering if there was a way to disable the tilting and only keep the IK. Reach out whenever you can, Thanks.

The torso tilting or the leg tilting? Yeah, set their respective angles to 0 in the loaded and it should disable

Does anyone have a file of this resource? It seems that I can’t find it through Roblox or with the link provided.

The link provided works for me, are you using this one?

Hi, thanks! The one I clicked on didn’t work and that got me confused. But thank you!

1 Like

I’ve been getting this whenever I reset or die. I doubt this is normal.

Considering I think I installed it correctly; I’m assuming this is not normal behavior. Am I missing something?

2 Likes

are you using a custom made sprinting system in this video? I find it very clean, and if it was open source could you possibly send it, Thanks.

I am using a R6 custom characters and leg don’t want to replicate for other clients. Does anyone know what can be the problem?

Go in the replicated storage script “LegController” and put this script:


-- Services --
local RunService = game:GetService("RunService")

-- Modules / Direcotires --
local Dependences = script.Dependencies

local InverseKinematics = require(Dependences.InverseKinematics)
local Trove = require(Dependences.Trove)

local LegController = {}
LegController.__index = LegController

local ikAttachments = {
	["leftHip"] = CFrame.new(-0.466, -0.944, 0),
	["leftFoot"] = CFrame.new(-0.5, -2.9, 0),
	["rightHip"] = CFrame.new(0.5, -0.944, 0),
	["rightFoot"] = CFrame.new(0.5, -2.9, 0)
}

local function nilContentsExist(Tbl : any)
	for Index, Val in pairs(Tbl) do
		if Val == nil then
			return true
		end
	end
	
	return false
end


function LegController.new(Character : Model, Configuration : any)
	local self = setmetatable({}, LegController)
	
	self.Trove = Trove.new() --Creating a Trove OOP object for cleanup and management
	
	-- Important variables --
	local Humanoid = Character:WaitForChild("Humanoid")
	local HRootPart = Character:WaitForChild("HumanoidRootPart")
	local rootJoint = HRootPart:WaitForChild("RootJoint")
	local leftHip = Character:FindFirstChild(Humanoid.RigType == Enum.HumanoidRigType.R15 and "LeftHip" or "Left Hip", true)
	local rightHip = Character:FindFirstChild(Humanoid.RigType == Enum.HumanoidRigType.R15 and "RightHip" or "Right Hip", true)
	
	self.States = {
		["tiltingEnabled"] = true,
		["ikEnabled"] = Configuration.ikEnabled
	}
	
	local motor6D = {
		rootJoint = rootJoint.C0,
		Hips = {
			["LeftHip"] = leftHip.C0,
			["RightHip"] = rightHip.C0,
		},
	}
	
	local characterIK = nil
	if Humanoid.RigType == Enum.HumanoidRigType.R6 then
		characterIK = InverseKinematics.New(Character)
	end
	
	-- Setting up raycast params for inverse kinematics --
	local ikParams = RaycastParams.new()
	ikParams.FilterType = Enum.RaycastFilterType.Exclude
	ikParams.FilterDescendantsInstances = {Character}
	for Index, exclusionObject in pairs(Configuration.ikExclude) do
		table.insert(ikParams.FilterDescendantsInstances, exclusionObject)
	end
	
	local ikParts = {}
	for Index, CF in pairs(ikAttachments) do
		local newAttachment = Instance.new("Attachment")
		newAttachment.Name = Index
		newAttachment.CFrame = CF
		newAttachment.Parent = HRootPart
		
		ikParts[newAttachment.Name] = newAttachment --Adding the newly created attachment to a table
		self.Trove:Add(ikParts[newAttachment.Name])
	end
	
	self.Trove:Connect(RunService.RenderStepped, function(deltaTime : number)
		local normalizedDeltaTime = deltaTime * 60
		
		local rootVelocity = Vector3.new(1, 0, 1) * HRootPart.Velocity
		local directionalRightVelocity = HRootPart.CFrame.RightVector:Dot(rootVelocity.unit)
		
		-- Inverse Kinematics --
		local ikLeftC0, ikRightC0 = nil, nil
		if characterIK and self.States.ikEnabled and not nilContentsExist(ikParts) and rootVelocity.Magnitude < Configuration.maxIkVelocity then
			local leftDir = ikParts.leftFoot.WorldCFrame.Position - ikParts.leftHip.WorldCFrame.Position
			local rightDir = ikParts.rightFoot.WorldCFrame.Position - ikParts.rightHip.WorldCFrame.Position
			
			local leftRay = workspace:Raycast(ikParts.leftHip.WorldCFrame.Position, leftDir, ikParams)
			local rightRay = workspace:Raycast(ikParts.rightHip.WorldCFrame.Position, rightDir, ikParams)
			
			if leftRay and leftRay.Material ~= Enum.Material.Water and leftRay.Instance.CanCollide then
				ikLeftC0 = characterIK:LegIK("Left", leftRay.Position)
			end
			if rightRay and rightRay.Material ~= Enum.Material.Water and rightRay.Instance.CanCollide then
				ikRightC0 = characterIK:LegIK("Right", rightRay.Position)
			end
		end
		
		-- For angle calculation --
		local canAngle = table.find(Configuration.onStates, Humanoid:GetState())
		local notInverse = HRootPart.CFrame.LookVector:Dot(Humanoid.MoveDirection) < -0.1
		
		local rootAngle = (self.States.tiltingEnabled and canAngle and rootVelocity.Magnitude > Configuration.activationVelocity and math.rad(directionalRightVelocity * Configuration.maxRootAngle) or 0)
			* (notInverse and 1 or -1)
		local legAngle = (self.States.tiltingEnabled and canAngle and rootVelocity.Magnitude > Configuration.activationVelocity and math.rad(directionalRightVelocity * Configuration.maxAngle) or 0)
			* (notInverse and 1 or -1)
		
		-- Setting motor6D C0s --
		local interpolationSpeed = Configuration.interploationSpeed.Speed * (rootVelocity.Magnitude < Configuration.interploationSpeed.highVelocityPoint and 2.8 or 1)
		rootJoint.C0 = rootJoint.C0:Lerp(motor6D.rootJoint * CFrame.Angles(0, 0, rootAngle), interpolationSpeed * normalizedDeltaTime)
		leftHip.C0 = leftHip.C0:Lerp(ikLeftC0 or motor6D.Hips.LeftHip * CFrame.Angles(0, legAngle, 0), interpolationSpeed * normalizedDeltaTime)
		rightHip.C0 = rightHip.C0:Lerp(ikRightC0 or motor6D.Hips.RightHip * CFrame.Angles(0, legAngle, 0), interpolationSpeed * normalizedDeltaTime)
	end)
	
	return self
end

function LegController:setState(stateString : string, Enabled : boolean)
	if not self.States[stateString] then error("Invalid string") return end
	
	self.States[stateString] = Enabled
end

function LegController:Destroy()
	self.Trove:Destroy()
	
	table.clear(self)
	setmetatable(self, nil)

	return
end

return LegController

I changed Humanoid.RootPart and now I dont have that problem.

1 Like

This is not working for me, I’ve tried looking through every reply in this post and even tried using the resource in a fresh studio file, but it still is not working. Does anyone have a version that works for them? I am not sure what the issue may be or why it pertains to just me.

1 Like

How do you set this up for R15? I’m a little new to scripting and don’t really understand too well.

It works for R6 but not R15 for me.

Same issue, seems like he forgot to add it?

It trys to call r6 parts, I’ll look more into it and try to see if I can make it work.

Okay thank you so much! I will also let you know if I find another solution. I was gonna use the default strafing provided in r15 but it looks a little weird when I’m holding a weapon or something else that’s why I wanted to try this script. Couldn’t get this to work so I’ve just been re-animating the default strafing animations but it doesn’t look very good.