[CLOSED] Ai Pathfinding has Breaks/Pauses in Movement

Settings:

return 
    {
	range = math.huge,
	chaseRange = 500,
	minSpeed = 5,
	maxSpeed = 29,
	speedIncreaseRate = 1,
	damage = 100,
	attackRange = 2,
	attackCooldown = 1,
	targetScanCooldown = 1,
	knockback = 10,
	wanderDistance = 15,
	wanderCooldown = {3, 6},
	tagged = {"AiTag"},
	attackAnimation = "rbxassetid://3489169607",
	attackSounds = {"18942724219"},
	respawning = true,
	ragdoll = true,
    }

Ai

local Ai = script.Parent
local humanoid = Ai:WaitForChild("Ai")  -- Fixed variable name to "Humanoid"
local root = Ai:WaitForChild("HumanoidRootPart")
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")

local module = require(Ai:WaitForChild("Settings"))

-- Load Attack Animation
local attackAnim = Instance.new("Animation")
attackAnim.AnimationId = module.attackAnimation
local attackAnimation = humanoid:LoadAnimation(attackAnim)

-- Setup Attack Sounds
local attackSounds = {}
for i, soundId in ipairs(module.attackSounds) do
	local sound = Instance.new("Sound")
	sound.SoundId = "rbxassetid://" .. soundId
	sound.Name = "Attack" .. i
	sound.Volume = 0.75
	sound.Parent = Ai:FindFirstChild("Head") or Ai
	table.insert(attackSounds, sound)
end

-- Variables
local target = nil
local lastTargetPosition = nil
local currentSpeed = module.minSpeed > 0 and module.minSpeed or 8
local lastDamageTime = -math.huge
local lastScanTime = -math.huge
local lastPathTime = 0
local speedMultiplier = 20
local currentPath = nil
local pathIndex = 1
local reachedConnection = nil

humanoid.WalkSpeed = currentSpeed

-- Respawn Logic
if module.respawning then
	local clone = Ai:Clone()
	humanoid.Died:Connect(function()
		task.delay(4, function()
			clone:SetPrimaryPartCFrame(Ai:GetPrimaryPartCFrame())
			clone.Parent = workspace
			clone.Humanoid.Health = clone.Humanoid.MaxHealth
			clone:MakeJoints()
			Ai:Destroy()
		end)
	end)
end

-- Ragdoll Logic
if module.ragdoll then
	humanoid.BreakJointsOnDeath = false
	humanoid.Died:Connect(function()
		root.Velocity = root.CFrame.LookVector * -15
		root.CanCollide = false
		for _, joint in pairs(Ai:GetDescendants()) do
			if joint:IsA("Motor6D") then
				local socket = Instance.new("BallSocketConstraint")
				local a1 = Instance.new("Attachment", joint.Part0)
				local a2 = Instance.new("Attachment", joint.Part1)
				socket.Attachment0 = a1
				socket.Attachment1 = a2
				socket.LimitsEnabled = true
				socket.TwistLimitsEnabled = true
				socket.Parent = joint.Parent
				a1.CFrame = joint.C0
				a2.CFrame = joint.C1
				joint:Destroy()
			end
		end
	end)
end

-- Attack Function
local function attack(targetRoot, range)
	if humanoid.Health <= 0 then return end
	lastDamageTime = time()

	if not attackAnimation.IsPlaying then
		attackAnimation:Play()
	end

	-- Apply knockback
	targetRoot.Velocity = (targetRoot.Position - root.Position).Unit * module.knockback + Vector3.new(0, module.knockback / 2, 0)

	-- Damage and sound after half animation length
	task.delay(attackAnimation.Length / 2, function()
		if target and (targetRoot.Position - root.Position).Magnitude <= range then
			target:TakeDamage(module.damage)
		end
		attackSounds[math.random(#attackSounds)]:Play()
	end)

	-- Stop animation after full length if cooldown passed
	task.delay(attackAnimation.Length, function()
		if time() - lastDamageTime >= 0.2 then
			attackAnimation:Stop()
		end
	end)
end

-- Follow Path Function
local function followPath(path, targetRoot)
	if reachedConnection then
		reachedConnection:Disconnect()
		reachedConnection = nil
	end

	local waypoints = path:GetWaypoints()
	pathIndex = 1

	local function moveToNext()
		if humanoid.Health <= 0 or pathIndex > #waypoints then return end

		local waypoint = waypoints[pathIndex]
		local distToWaypoint = (root.Position - waypoint.Position).Magnitude

		if distToWaypoint < 1 then
			pathIndex += 1
			moveToNext()
			return
		end

		humanoid:MoveTo(waypoint.Position)
		pathIndex += 1
	end

	moveToNext()

	reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
		if not reached then return end

		if targetRoot and (targetRoot.Position - root.Position).Magnitude <= module.attackRange then
			if time() - lastDamageTime >= module.attackCooldown then
				attack(targetRoot, module.attackRange)
			end
		else
			-- Small wait to prevent jittering before moving to next waypoint
			task.wait(0.05)
			moveToNext()
		end
	end)
end

-- Compute Path to Target
local function computePathToTarget(targetRoot)
	if not targetRoot then return end

	local path = PathfindingService:CreatePath({
		AgentRadius = 2,
		AgentHeight = 5,
		AgentCanJump = true,
		AgentJumpHeight = humanoid.JumpHeight,
		AgentWalkableFloorAngle = humanoid.MaxSlopeAngle,
		WaypointSpacing = 2
	})

	path:ComputeAsync(root.Position, targetRoot.Position)

	if path.Status == Enum.PathStatus.Success then
		currentPath = path
		followPath(path, targetRoot)
	else
		-- Pathfinding failed - optionally handle here
	end
end

-- Find Closest Valid Target
local function findClosestTarget()
	local closestTarget = nil
	local closestDistance = math.huge

	for _, humanoidCandidate in pairs(workspace:GetDescendants()) do
		if humanoidCandidate:IsA("Humanoid") and humanoidCandidate ~= humanoid and humanoidCandidate.Health > 0 then
			local isTagged = false
			for _, tag in ipairs(module.tagged) do
				if humanoidCandidate.Parent:FindFirstChild(tag) then
					isTagged = true
					break
				end
			end

			if not isTagged then
				local candidateRoot = humanoidCandidate.Parent:FindFirstChild("HumanoidRootPart")
				if candidateRoot then
					local dist = (candidateRoot.Position - root.Position).Magnitude
					if dist <= module.range and dist < closestDistance then
						closestDistance = dist
						closestTarget = humanoidCandidate
					end
				end
			end
		end
	end

	return closestTarget
end

-- AI Main Loop
RunService.Heartbeat:Connect(function(dt)
	if humanoid.Health <= 0 then return end

	humanoid.Sit = false

	-- Target scanning
	if time() - lastScanTime >= module.targetScanCooldown then
		lastScanTime = time()
		local newTarget = findClosestTarget()

		if newTarget ~= target then
			target = newTarget
			if target then
				local targetRoot = target.Parent and target.Parent:FindFirstChild("HumanoidRootPart")
				if targetRoot then
					computePathToTarget(targetRoot)
				end
			end
		end
	end

	if target and target.Health > 0 then
		local targetRoot = target.Parent and target.Parent:FindFirstChild("HumanoidRootPart")
		if targetRoot then
			local dist = (targetRoot.Position - root.Position).Magnitude

			-- Increase speed when chasing
			if dist <= module.chaseRange then
				currentSpeed = math.min(currentSpeed + module.speedIncreaseRate * dt * speedMultiplier, module.maxSpeed)
				humanoid.WalkSpeed = currentSpeed
			end

			-- Conditions to recompute path
			local shouldRepath = false

			if not currentPath or pathIndex > #currentPath:GetWaypoints() then
				shouldRepath = true
			elseif lastTargetPosition and (lastTargetPosition - targetRoot.Position).Magnitude > 10 then
				shouldRepath = true
			elseif humanoid.MoveDirection.Magnitude == 0 and (os.clock() - lastPathTime > 1) then
				shouldRepath = true
			end

			if shouldRepath and (os.clock() - lastPathTime > 1.5) then
				lastTargetPosition = targetRoot.Position
				computePathToTarget(targetRoot)
				lastPathTime = os.clock()
			end
		else
			target = nil -- target root missing, reset target
		end
	else
		-- Reset speed when no target
		if currentSpeed ~= module.minSpeed then
			currentSpeed = module.minSpeed
			humanoid.WalkSpeed = currentSpeed
		end
	end
end)

I know the code is messy (I haven’t finished the pathfinding method)

I just realised the video doesn’t show the issue…

Not sure if you have it in your code since its quite large but try setting the HumRoot part of the humanoid to nil, HumanoidRootPart:SetNetworkOwner(nil), may have spelt owner function thing wrong, not in studio

I’ve added this into the script:

root:SetNetworkOwner(nil)

Also, I don’t know if this is relevant I made the HRP the PrimaryPart for the Rig Model.

I fixed the issue now, you were right:

for _, part in ipairs(Ai:GetDescendants()) do
	if part:IsA("BasePart") then
		part:SetNetworkOwner(nil)
	end
end

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.