Problem with my Mech model

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

  1. What do you want to achieve? I want to achieve smooth movement for this Mech model and make sure the LeftFoot and RightFoot MeshParts inside the Mech always align properly with the ground (even/uneven). If anyone here can take a look and suggest improvements I’d really appreciate it. There’s something about the movement that just feels off right now.

  2. What is the issue?
    I noticed it works fine

when walking on the baseplate, but when we change the surface to another part and walk off it, the Mech model starts “floating,” as you can see

  1. What solutions have you tried so far? None, because I’m still learning scripting and don’t know much yet

Here is the script:

local RunService = game:GetService(“RunService”)
local ReplicatedStorage = game:GetService(“ReplicatedStorage”)

– RemoteEvents setup
local toggleCombat = ReplicatedStorage:FindFirstChild(“ToggleCombatMode”) or Instance.new(“RemoteEvent”, ReplicatedStorage)
toggleCombat.Name = “ToggleCombatMode”

local toggleEngine = ReplicatedStorage:FindFirstChild(“ToggleEngine”) or Instance.new(“RemoteEvent”, ReplicatedStorage)
toggleEngine.Name = “ToggleEngine”

– References
local mech = script.Parent
local seat = mech:FindFirstChildOfClass(“VehicleSeat”)
local humanoid = mech:FindFirstChildOfClass(“Humanoid”)
local walkAnim = mech:FindFirstChild(“WalkAnim”)
local animator = humanoid and humanoid:FindFirstChildOfClass(“Animator”)
local rootPart = humanoid and humanoid.RootPart or mech.PrimaryPart

local leftFoot = mech:FindFirstChild(“LeftFoot”)
local rightFoot = mech:FindFirstChild(“RightFoot”)

if not (seat and humanoid and walkAnim and animator and rootPart and leftFoot and rightFoot) then
warn(“Missing critical parts”)
return
end

– Enable humanoid states
humanoid:SetStateEnabled(Enum.HumanoidStateType.Running, true)
humanoid:SetStateEnabled(Enum.HumanoidStateType.FallingDown, true)
humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, true)
humanoid:SetStateEnabled(Enum.HumanoidStateType.Seated, true)

seat.Anchored = false
rootPart.Anchored = false

– Load animation
local walkTrack = animator:LoadAnimation(walkAnim)

– Force setup
local velocityForce = Instance.new(“LinearVelocity”)
velocityForce.Name = “MechMover”
velocityForce.Attachment0 = Instance.new(“Attachment”, rootPart)
velocityForce.RelativeTo = Enum.ActuatorRelativeTo.World
velocityForce.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector
velocityForce.MaxForce = 1e7
velocityForce.VectorVelocity = Vector3.zero
velocityForce.Parent = rootPart

local moveSpeed = 25
local turnSpeed = math.rad(50)

– Sounds
local startSound = Instance.new(“Sound”, rootPart)
startSound.SoundId = “rbxassetid://7948751841”

local runningSound = Instance.new(“Sound”, rootPart)
runningSound.SoundId = “rbxassetid://8626621990”
runningSound.Looped = true
runningSound.Volume = 0.7

local stopSound = Instance.new(“Sound”, rootPart)
stopSound.SoundId = “rbxassetid://317671259”

local footstepSoundIds = {
“rbxassetid://6620598807”,
“rbxassetid://6620595244”
}

local function playFootstepSound(foot)
local soundId = footstepSoundIds[math.random(1, #footstepSoundIds)]
local sound = Instance.new(“Sound”, foot)
sound.SoundId = soundId
sound.Volume = 2
sound.Pitch = math.random(70, 85) / 100
sound:Play()
game.Debris:AddItem(sound, 2)
end

– Footstep control
local lastFootstepTime = 0
local FOOTSTEP_INTERVAL = 0.7
local lastFootIndex = 1

– Ground detection
local RAY_LENGTH = 1.5
local GROUNDED_THRESHOLD = 0.1
local COYOTE_TIME = 0.03

local grounded = false
local groundedTimer = 0
local lastGroundedTime = 0

local function castGroundRay(origin, mechIgnoreList)
local directions = {
Vector3.new(0, -1, 0),
Vector3.new(0.2, -1, 0),
Vector3.new(-0.2, -1, 0),
Vector3.new(0, -1, 0.2),
Vector3.new(0, -1, -0.2)
}

local params = RaycastParams.new()
params.FilterDescendantsInstances = mechIgnoreList
params.FilterType = Enum.RaycastFilterType.Blacklist
params.IgnoreWater = true

for _, dir in ipairs(directions) do
	local result = workspace:Raycast(origin, dir * RAY_LENGTH, params)
	if result and result.Instance and (result.Instance.CanCollide or result.Instance:IsA("Terrain")) then
		return true
	end
end
return false

end

local function checkFootCollisions(part)
local params = OverlapParams.new()
params.FilterDescendantsInstances = {mech}
params.FilterType = Enum.RaycastFilterType.Blacklist

local touchingParts = workspace:GetPartsInPart(part, params)
for _, p in ipairs(touchingParts) do
	if (p:IsA("BasePart") and p.CanCollide) or p:IsA("Terrain") then
		return true
	end
end
return false

end

local function updateGrounded(dt)
local ignore = {mech}
local rayOrigin = rootPart.Position + Vector3.new(0, 4, 0)

local rayGrounded = castGroundRay(rayOrigin, ignore)
local left = checkFootCollisions(leftFoot) or castGroundRay(leftFoot.Position + Vector3.new(0, 0.5, 0), ignore)
local right = checkFootCollisions(rightFoot) or castGroundRay(rightFoot.Position + Vector3.new(0, 0.5, 0), ignore)
local floorMat = humanoid and humanoid.FloorMaterial ~= Enum.Material.Air

local groundedNow = rayGrounded or left or right or floorMat

if groundedNow then
	groundedTimer = 0
	grounded = true
	lastGroundedTime = tick()
else
	groundedTimer += dt
	if groundedTimer > GROUNDED_THRESHOLD and tick() - lastGroundedTime > COYOTE_TIME then
		grounded = false
	end
end

end

– State variables
local engineStarted = false
local lastEngineToggle = 0
local engineCooldown = 1

local waistPart = mech:FindFirstChild(“Waist”)
local waistMotor = waistPart and waistPart:FindFirstChildWhichIsA(“Motor6D”)
local originalC0 = waistMotor and waistMotor.C0
local inCombat = false
local lookVector = nil
local startSoundEndedConn

local horizontalVelocity = Vector3.zero
local velocityDampingSpeed = 6

local lastHumanoidState = nil
local lastIsMoving = false

– TURNING HOLD & COOLDOWN VARIABLES (NEW)
local turnHoldTime = 0 – How long turn key held
local turnHoldThreshold = 1 – Seconds to hold before turn starts
local lastTurnDirection = 0 – -1 for left, 1 for right, 0 for none
local turnCooldown = 0.5 – cooldown between turns (seconds)
local turnCooldownTimer = 0 – timer counting down cooldown
local canTurn = false – whether we can turn now

– Remote Events
toggleCombat.OnServerEvent:Connect(function(player, toggle, camVec)
if seat.Occupant and seat.Occupant.Parent == player.Character then
inCombat = toggle
lookVector = camVec
end
end)

toggleEngine.OnServerEvent:Connect(function(player)
if seat.Occupant and seat.Occupant.Parent == player.Character then
local now = tick()
if now - lastEngineToggle < engineCooldown then return end
lastEngineToggle = now

	engineStarted = not engineStarted
	seat:SetAttribute("EngineStarted", engineStarted)

	if engineStarted then
		startSound:Play()
		if startSoundEndedConn then startSoundEndedConn:Disconnect() end
		startSoundEndedConn = startSound.Ended:Connect(function()
			if engineStarted then runningSound:Play() end
		end)
	else
		if startSoundEndedConn then startSoundEndedConn:Disconnect() end
		runningSound:Stop()
		velocityForce.VectorVelocity = Vector3.zero
		velocityForce.MaxForce = 0
		stopSound:Play()
	end

	toggleEngine:FireAllClients(engineStarted)
end

end)

– Main loop
RunService.Heartbeat:Connect(function(dt)
local isMoving = false

if engineStarted and seat.Occupant then
	updateGrounded(dt)

	local steer = seat.Steer
	local steerDir = 0

	if steer > 0.05 then
		steerDir = 1
	elseif steer < -0.05 then
		steerDir = -1
	else
		steerDir = 0
	end

	-- TURNING HOLD & COOLDOWN LOGIC (UPDATED)
	if steerDir ~= 0 then
		if steerDir ~= lastTurnDirection then
			turnHoldTime = 0
			canTurn = false
		end

		if turnCooldownTimer <= 0 then
			turnHoldTime = turnHoldTime + dt
			if turnHoldTime >= turnHoldThreshold then
				canTurn = true
			end
		end
	else
		turnHoldTime = 0
		canTurn = false
	end

	lastTurnDirection = steerDir

	if turnCooldownTimer > 0 then
		turnCooldownTimer = math.max(turnCooldownTimer - dt, 0)
	end

	if canTurn then
		local turnAngle = steerDir * turnSpeed * dt
		rootPart.CFrame = rootPart.CFrame * CFrame.Angles(0, turnAngle, 0)
	end

	-- Start cooldown timer when turning stops
	if not canTurn and lastIsMoving and lastTurnDirection == 0 then
		if turnCooldownTimer == 0 then
			turnCooldownTimer = turnCooldown
		end
	end

	-- Movement logic
	local throttle = seat.Throttle
	if throttle ~= 0 and grounded then
		local forward = Vector3.new(rootPart.CFrame.LookVector.X, 0, rootPart.CFrame.LookVector.Z).Unit
		horizontalVelocity = horizontalVelocity:Lerp(forward * (moveSpeed * throttle), dt * 10)
		isMoving = true
	else
		horizontalVelocity = horizontalVelocity:Lerp(Vector3.zero, dt * velocityDampingSpeed)
	end

	-- Apply force
	if grounded then
		velocityForce.MaxForce = 1e7
		velocityForce.VectorVelocity = Vector3.new(horizontalVelocity.X, 0, horizontalVelocity.Z)
		if lastHumanoidState ~= Enum.HumanoidStateType.Running then
			humanoid:ChangeState(Enum.HumanoidStateType.Running)
			lastHumanoidState = Enum.HumanoidStateType.Running
		end
	else
		velocityForce.MaxForce = 1e7
		velocityForce.VectorVelocity = Vector3.new(horizontalVelocity.X, -200, horizontalVelocity.Z)
		if lastHumanoidState ~= Enum.HumanoidStateType.Freefall then
			humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
			lastHumanoidState = Enum.HumanoidStateType.Freefall
		end
	end

	-- Fix: Set isTurning properly
	local isTurning = canTurn or (steerDir ~= 0 and turnHoldTime > 0)

	-- Animation control
	if (isMoving or isTurning) and not lastIsMoving then
		if not walkTrack.IsPlaying then
			walkTrack:Play()
		end
	elseif not (isMoving or isTurning) and lastIsMoving then
		if walkTrack.IsPlaying then
			walkTrack:Stop()
		end
	end

	-- Footsteps
	if (isMoving or isTurning) and grounded and tick() - lastFootstepTime > FOOTSTEP_INTERVAL then
		local foot = (lastFootIndex == 1) and leftFoot or rightFoot
		lastFootIndex = 3 - lastFootIndex
		if foot and foot.Parent then
			playFootstepSound(foot)
		end
		lastFootstepTime = tick()
	end

	lastIsMoving = (isMoving or isTurning)

	-- Waist aiming
	if waistMotor and originalC0 then
		if inCombat and lookVector then
			local mechForward = Vector3.new(rootPart.CFrame.LookVector.X, 0, rootPart.CFrame.LookVector.Z).Unit
			local camDir = Vector3.new(lookVector.X, 0, lookVector.Z).Unit
			local dot = mechForward:Dot(camDir)
			local cross = mechForward:Cross(camDir)
			local angle = math.acos(math.clamp(dot, -1, 1))
			if cross.Y < 0 then angle = -angle end
			local pos = originalC0.Position
			local baseRot = originalC0 - pos
			local yawRotation = CFrame.Angles(0, angle, 0)
			local targetC0 = CFrame.new(pos) * yawRotation * baseRot
			waistMotor.C0 = waistMotor.C0:Lerp(targetC0, dt * 7)
		else
			waistMotor.C0 = waistMotor.C0:Lerp(originalC0, dt * 5)
		end
	end
else
	velocityForce.MaxForce = 0
	velocityForce.VectorVelocity = Vector3.zero
	if walkTrack.IsPlaying then walkTrack:Stop() end
	if lastHumanoidState ~= Enum.HumanoidStateType.Seated then
		humanoid:ChangeState(Enum.HumanoidStateType.Seated)
		lastHumanoidState = Enum.HumanoidStateType.Seated
	end
	lastIsMoving = false
end

end)

nah, aint nobody readin this :pray::pray:

first of all use a code block with ```
second:
have you tried adjusting the hipheight

1 Like

Use the </> option on the bar then post scripts within the two ``` marks.
I don’t think this is a script problem. Check HipHeight. If you’re using a Ragdoll death, try turning off that script and test (just to rule that out).