Feedback on Spider Mech with Procedural Animation

Hello everyone! I hope you all are well. I recently saw a mechanical spider model on the toolbox. It wasn’t rigged and it was a mess of parts everywhere. After some time rigging, setting up IKControl, and scripting it… I made this!

It’s movement is pretty horrible as of now, which is why I created this post.

How can I make this spider move nicer? What is it missing? All suggestions wanted!

Longer Video:

So here’s the module script:

-- Author: Roox4



--[[

Example Usage:
////////////////////////////////////////////////////////////////

local ProceduralModule = require(/moduleLocation/.ProceduralModule)

local runService = game:GetService("RunService")

local enemy = script.Parent
local root = enemy:FindFirstChild("HumanoidRootPart")
local hum = enemy:FindFirstChild("Humanoid")
local alignOrientation = root:FindFirstChild("AlignOrientation")

local target = workspace:FindFirstChild("Target")

--// IkTargets
local ikTargets = enemy:FindFirstChild("IkTargets")
local leftFront_IkTarget = ikTargets:FindFirstChild("LeftFront_IkTarget")
local leftBack_IkTarget = ikTargets:FindFirstChild("LeftBack_IkTarget")
local rightFront_IkTarget = ikTargets:FindFirstChild("RightFront_IkTarget")
local rightBack_IkTarget = ikTargets:FindFirstChild("RightBack_IkTarget")

--// Raycast parts
local raycastParts = enemy:FindFirstChild("RaycastParts")
local leftFront_RaycastPart = raycastParts:FindFirstChild("LeftFront_RaycastPart")
local leftBack_RaycastPart = raycastParts:FindFirstChild("LeftBack_RaycastPart")
local rightFront_RaycastPart = raycastParts:FindFirstChild("RightFront_RaycastPart")
local rightBack_RaycastPart = raycastParts:FindFirstChild("RightBack_RaycastPart")

--// IKControls
local leftFront_IKControl = hum:FindFirstChild("LeftFront_IKControl")
local leftBack_IKControl = hum:FindFirstChild("LeftBack_IKControl")
local rightFront_IKControl = hum:FindFirstChild("RightFront_IKControl")
local rightBack_IKControl = hum:FindFirstChild("RightBack_IKControl")

leftFront_IKControl.Pole = root.LeftFront_Pole
leftBack_IKControl.Pole = root.LeftBack_Pole
rightFront_IKControl.Pole = root.RightFront_Pole
rightBack_IKControl.Pole = root.RightBack_Pole

--// RaycastParams
local rayCastParams = RaycastParams.new()
rayCastParams.FilterDescendantsInstances = {enemy}
rayCastParams.FilterType = Enum.RaycastFilterType.Exclude

for _,v: BasePart in ikTargets:GetChildren() do
	v.Transparency = 1
end

for _,v: BasePart in raycastParts:GetChildren() do
	v.Transparency = 1
end

coroutine.wrap(function()
	while task.wait(0.25) do
		hum:MoveTo(target.Position)
	end
end)()

runService.Heartbeat:Connect(function()
	local rayCast = workspace:Raycast(root.Position, -1000 * root.CFrame.UpVector, rayCastParams)

	if rayCast then
		local rotateToFloorCFrame = ProceduralModule:getRotationBetween(root.CFrame.UpVector, rayCast.Normal)
		local floorOrientedCFrame = rotateToFloorCFrame * CFrame.new(root.Position)
		
		local dz = (root.Position.Z - target.Position.Z)
		local dx = (root.Position.X - target.Position.X)
		local horizontalAngle = math.atan2(dx, dz)
		
		alignOrientation.CFrame = floorOrientedCFrame.Rotation * CFrame.fromOrientation(0, horizontalAngle, 0)
	end
end)

while true do
	ProceduralModule:IkLegStep(leftFront_IkTarget, leftFront_RaycastPart, enemy.PrimaryPart, 2, 2, 1, 0.05, rayCastParams)
	ProceduralModule:IkLegStep(rightBack_IkTarget, rightBack_RaycastPart, enemy.PrimaryPart, 2, 2, 1, 0.05, rayCastParams)
	task.wait(0.1)
	ProceduralModule:IkLegStep(rightFront_IkTarget, rightFront_RaycastPart, enemy.PrimaryPart, 2, 2, 1, 0.05, rayCastParams)
	ProceduralModule:IkLegStep(leftBack_IkTarget, leftBack_RaycastPart, enemy.PrimaryPart, 2, 2, 1, 0.05, rayCastParams)
	task.wait(0.1)
end
////////////////////////////////////////////////////////////////

--]]



local ProceduralModule = {}


--// Returns a CFrame rotation between two vectors
--// Credit: @EgoMoose (Roblox)
function ProceduralModule:getRotationBetween(u, v)
	local randomAxis = Vector3.new(1, 0, 0)
	
	local dot = u:Dot(v)
	if (dot > 0.99999) then
		return CFrame.new()
		
	elseif (dot < -0.99999) then
		return CFrame.fromAxisAngle(randomAxis, math.pi)
	end
	
	return CFrame.fromAxisAngle(u:Cross(v), math.acos(dot))
end



--[[

Parameters:

ikTarget ->			A reference to the IK target of a leg
ikRayPart ->		A reference to the raycast part of a leg
root ->				The root part of a model
stepDistance ->		A number that specifies a distance a model has to travel before this function executes
stepForward -> 		A number that specifies a length of a step
stepHeight -> 		A number that specifies a maximum height the leg reaches when stepping
stepWait -> 		A number that specifies a how long the step takes (should be a small value like 0.05)
rayCastParams -> 	A reference to the RaycastParams

--]]


--// Calculates the position of a leg
function ProceduralModule:IkLegStep(ikTarget: BasePart, ikRayPart: BasePart, root: BasePart, stepDistance: number, stepForward: number, stepHeight: number, stepWait: number, rayCastParams: RaycastParams)
	stepWait = stepWait or 0.05
	stepForward = stepForward or 0
	stepHeight = stepHeight or 2
	
	local rayCast = workspace:Raycast(ikRayPart.Position, Vector3.new(0, -1000, 0), rayCastParams)
	ikTarget:SetAttribute("PreviousPos", Vector3.new(0, 0, 0))
	if rayCast then
		if not ikTarget:GetAttribute("PreviousPos") then
			ikTarget:SetAttribute("PreviousPos", Vector3.new(0, 0, 0))
		end
		
		local previousFootPos = ikTarget:GetAttribute("PreviousPos")
		
		local defaultFootPos = rayCast.Position
		local diff = (defaultFootPos - previousFootPos).Magnitude
		
		--// Do a step
		if diff > stepDistance then
			local finalFootPos = rayCast.Position + root.CFrame.LookVector * stepForward
			local middleFootPos = ((finalFootPos - previousFootPos) * 0.5 + previousFootPos) + Vector3.new(0, stepHeight, 0)
			
			coroutine.wrap(function()
				ikTarget.Position = middleFootPos
				task.wait(stepWait)
				ikTarget.Position = finalFootPos
				
				ikTarget:SetAttribute("PreviousPos", ikTarget.Position)
			end)()
		end
	end
end

return ProceduralModule

Spider movement script:

local ProceduralModule = require(game.ReplicatedStorage.ProceduralModule)

local runService = game:GetService("RunService")

local enemy = script.Parent
local root = enemy:FindFirstChild("HumanoidRootPart")
local hum = enemy:FindFirstChild("Humanoid")
local alignOrientation = root:FindFirstChild("AlignOrientation")

local target = workspace:WaitForChild("lyrnu"):WaitForChild("HumanoidRootPart")

--// IkTargets
local ikTargets = enemy:FindFirstChild("IKTargets")
local leftFront_IkTarget = ikTargets:FindFirstChild("FrontLeft_IKTarget")
local leftBack_IkTarget = ikTargets:FindFirstChild("BackLeft_IKTarget")
local rightFront_IkTarget = ikTargets:FindFirstChild("FrontRight_IKTarget")
local rightBack_IkTarget = ikTargets:FindFirstChild("BackRight_IKTarget")
local rightMiddle_IkTarget = ikTargets:FindFirstChild("MiddleRight_IKTarget")
local leftMiddle_IkTarget = ikTargets:FindFirstChild("MiddleLeft_IKTarget")


--// Raycast parts
local raycastParts = enemy:FindFirstChild("RaycastParts")
local leftFront_RaycastPart = raycastParts:FindFirstChild("FrontLeft_RaycastPart")
local leftBack_RaycastPart = raycastParts:FindFirstChild("BackLeft_RaycastPart")
local rightFront_RaycastPart = raycastParts:FindFirstChild("FrontRight_RaycastPart")
local rightBack_RaycastPart = raycastParts:FindFirstChild("BackRight_RaycastPart")
local rightMiddle_RaycastPart = raycastParts:FindFirstChild("MiddleRight_RaycastPart")
local leftMiddle_RaycastPart = raycastParts:FindFirstChild("MiddleLeft_RaycastPart")
--// IKControls
local leftFront_IKControl = hum:FindFirstChild("FrontLeft_IkControl")
local leftBack_IKControl = hum:FindFirstChild("BackLeft_IKControl")
local rightFront_IKControl = hum:FindFirstChild("FrontRight_IKControl")
local rightBack_IKControl = hum:FindFirstChild("BackRight_IKControl")
local rightMiddle_IKControl = hum:FindFirstChild("MiddleRight_IKControl")
local leftMiddle_IKControl = hum:FindFirstChild("MiddleLeft_IKControl")

leftFront_IKControl.Pole = root.FrontLeft_Pole
leftBack_IKControl.Pole = root.BackLeft_Pole
rightFront_IKControl.Pole = root.FrontRight_Pole
rightBack_IKControl.Pole = root.BackRight_Pole
rightMiddle_IKControl.Pole = root.MiddleRight_Pole
leftMiddle_IKControl.Pole = root.MiddleLeft_Pole


--// RaycastParams
local rayCastParams = RaycastParams.new()
rayCastParams.FilterDescendantsInstances = {enemy}
rayCastParams.FilterType = Enum.RaycastFilterType.Exclude

for _,v: BasePart in ikTargets:GetChildren() do
	v.Transparency = 1
end

for _,v: BasePart in raycastParts:GetChildren() do
	v.Transparency = 1
end

coroutine.wrap(function()
	while task.wait(0.25) do
		hum:MoveTo(target.Position)
	end
end)()

runService.Heartbeat:Connect(function()
	local rayCast = workspace:Raycast(root.Position, -1000 * root.CFrame.UpVector, rayCastParams)

	if rayCast then
		local rotateToFloorCFrame = ProceduralModule:getRotationBetween(root.CFrame.UpVector, rayCast.Normal)
		local floorOrientedCFrame = rotateToFloorCFrame * CFrame.new(root.Position)

		local dz = (root.Position.Z - target.Position.Z)
		local dx = (root.Position.X - target.Position.X)
		local horizontalAngle = math.atan2(dx, dz)

		alignOrientation.CFrame = floorOrientedCFrame.Rotation * CFrame.fromOrientation(0, horizontalAngle, 0)
	end
end)

local function playSound()
	local sound = script.Parent.HumanoidRootPart["Metal Baseball Bat Hit"]
	sound.PlaybackSpeed = math.random(100,130)/100
	sound:Play()
end

while true do
	ProceduralModule:IkLegStep(leftFront_IkTarget, leftFront_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)
	playSound()
	task.wait(0.2)
	ProceduralModule:IkLegStep(rightBack_IkTarget, rightBack_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)
	playSound()
	task.wait(0.2)
	ProceduralModule:IkLegStep(leftMiddle_IkTarget, leftMiddle_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)
	playSound()
	task.wait(0.2)
	ProceduralModule:IkLegStep(rightFront_IkTarget, rightFront_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)
	playSound()
	task.wait(0.2)
	ProceduralModule:IkLegStep(leftBack_IkTarget, leftBack_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)
	playSound()
	task.wait(0.2)
	ProceduralModule:IkLegStep(rightMiddle_IkTarget, rightMiddle_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)
	playSound()
	task.wait(0.2)
end

Yeah… don’t ask me about the while true do right now LOL.

6 Likes

image
right now, your legs seem to be using the red path to get from point A to B, making them slide.
try making them follow the green path from point A to B.

a good example is the scarabs from halo 3.

3 Likes

Looks great, spider movement is truly hard.

3 Likes

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!

  2. What is the issue? Include screenshots / videos if possible!

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

-- This is an example Lua code block

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

3 Likes

what did you mean by this default message

1 Like

I’m new so I’m sorry I shared the wrong post.

3 Likes

Why do you have the Low Graphics? @lyrnu
And what is that red point at the upper right corner
Is it a MacOS?

2 Likes

Thanks so much! I’ll keep that in mind.

2 Likes

I can provide source code for you, I’m not really sure how I can implement this…

2 Likes

Thank you very much for your feedback!

3 Likes

Yes It’s a MacOS. The orange thingy is telling me that my screen is being recorded! And I accidentally turned down quality. Good questions!

2 Likes

Don’t worry about it man! :slight_smile: lol

1 Like

dude, for some reason I loved you

reading through, I saw that you are using:

ProceduralModule:IkLegStep(leftFront_IkTarget, leftFront_RaycastPart, enemy.PrimaryPart, math.random(2,5), math.random(2,5), 1, 0.01, rayCastParams)

to move the legs, and the variables you pass are:

ikTarget: leftFront_IkTarget,
ikRayPart: leftFront_RaycastPart,
root: enemy.PrimaryPart,
stepDistance: math.random(2,5),
stepForward: math.random(2,5),
stepHeight: 1,
stepWait: 0.01,
rayCastParams: rayCastParams
.
the stepHeight and stepWait on the module change how much the leg is lifted and how fast the leg moves, I think you could maybe change stepHeight to something like 3 and the stepWait to something like 0.5, making it slower and with more of an arch.

1 Like

I agree! I’ll try that now. Thank you for your reply!

I love “you” too LOL! (CHARACTER LIMITTTTTTTTTTTTT)