-
What do you want to achieve? Keep it simple and clear!
I would like to fix my pathfinding script to stop my monster for going back and forward
-
What is the issue? Include screenshots / videos if possible!
I want to fix my pathfinding monster, so basically the monster patrols and has waypoints that it can go to and it can also transform randomly at each waypoint into a random player in the server which all works well. But one problem, after it transforms to a player SOMETIMES (not always) the model will get stuck going back and forward and won’t go to a waypoint. Eventually though it will go to a waypoint but sometimes it just stops moving and its like the script is stuck on the walking to the waypoint because I put a print(“Hi”) in the while task.Wait(0.8) do and it doesnt print it which means the script is stuck in a function.
-
What solutions have you tried so far? Did you look for solutions on the Developer Hub?
I have tried YT, DevForum and ChatGPT and Roblox’s Assistant.
Here is my script, it is a script in workspace and I have my monster model in workspace too named “Monster”, the script has a folder named “Animations” in it and it has animations inside of it like, walking and running. I reset the variables needed after transformation and there are no errors in the output when the glitch happens. Also ignore the “timesWalked” thing it was a variable that I made because the script would try and transform right when the game started because it created a new path, so i just used that to check if it has walked to a part before so it doesn’t transform right at the start.
-- Services --
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local JumpscareRE = ReplicatedStorage:WaitForChild("JumpscareRE")
local animations = script:WaitForChild("Animations")
local anim = animations:WaitForChild("Walk")
-- Changing Variables --
local rig = workspace:WaitForChild("Monster")
local hitBox = rig:WaitForChild("Hitbox")
local walkAnimation = rig:WaitForChild("Humanoid").Animator:LoadAnimation(anim)
-- Variables --
local deb = false
local isAttacking = false
local isChasing = false
local cloned = false
local timesWalked = 0
-- Functions --
local patrol
local calculatePath
local walkToDestination
local CloneToPlayer
local CloneMe
local CloneToMonster
local Jumpscare
local checkForCharacter
local findNearestPlayer
local attack
Jumpscare = function(character)
if cloned == true and isAttacking == false then
CloneToMonster()
end
task.wait(0.1)
local attackAnimation = rig.Humanoid.Animator:LoadAnimation(animations.Attack)
attackAnimation:Play()
isChasing = false
isAttacking = true
local player = game.Players:GetPlayerFromCharacter(character)
local cameraPart = rig:WaitForChild("CameraPart")
character:WaitForChild("Humanoid").WalkSpeed = 0
character:WaitForChild("Humanoid").JumpHeight = 0
character:FindFirstChild("Knocked").Value = true
JumpscareRE:FireClient(player, cameraPart, true)
rig:WaitForChild("HumanoidRootPart"):WaitForChild("Jumpscare"):Play()
task.wait(4)
JumpscareRE:FireClient(player, cameraPart, false)
character:WaitForChild("Humanoid").WalkSpeed = 8
attackAnimation:Stop(0)
task.wait(5)
isAttacking = false
patrol()
end
checkForCharacter = function(character)
local rayOrigin = rig:FindFirstChild("HumanoidRootPart").Position
local rayDirection = (character.HumanoidRootPart.Position - rayOrigin).Unit * 40
local raycastResult = workspace:Raycast(rayOrigin, rayDirection, RaycastParams.new())
if raycastResult then
local raycastInstance = raycastResult.Instance
if raycastInstance:IsDescendantOf(character) then
return true
end
else
return false
end
end
findNearestPlayer = function()
local players = Players:GetPlayers()
local nearestPlayer = nil
local maxDistance = 100
for _, player in pairs(players) do
if player.Character ~= nil then
local targetCharacter = player.Character
local distance = (rig.HumanoidRootPart.Position - targetCharacter.HumanoidRootPart.Position).Magnitude
if distance < maxDistance and checkForCharacter(targetCharacter) then
if targetCharacter:FindFirstChild("Knocked").Value == false and targetCharacter:FindFirstChild("Humanoid").Health > 0 and targetCharacter:FindFirstChild("Hiding").Value == false then
nearestPlayer = targetCharacter
maxDistance = distance
end
end
end
end
return nearestPlayer
end
attack = function(character)
local distance = (rig.HumanoidRootPart.Position - character.HumanoidRootPart.Position).Magnitude
if distance > 5 and character:FindFirstChild("Knocked").Value == false and character:FindFirstChild("Humanoid").Health > 0 and character:FindFirstChild("Hiding").Value == false then
if walkAnimation.IsPlaying then
walkAnimation:Stop()
end
rig.Humanoid:MoveTo(character.HumanoidRootPart.Position)
isChasing = true
else
Jumpscare(character)
end
end
calculatePath = function(destination)
local agentParams = {
["AgentHeight"] = hitBox.Size.Y,
["AgentRadius"] = hitBox.Size.X,
["AgentCanJump"] = false
}
local path = PathfindingService:CreatePath(agentParams)
path:ComputeAsync(rig.HumanoidRootPart.Position, destination)
if timesWalked ~= 2 then
timesWalked += 1
end
if timesWalked >= 2 then
local ranNumber = math.random(1,10)
if ranNumber >= 5 then
local Number = math.random(1,2)
if Number == 1 then
if cloned == true and isAttacking == false and isChasing == false then
CloneToMonster()
end
else
if cloned == false and isAttacking == false and isChasing == false then
CloneToPlayer()
end
end
end
end
return path
end
walkToDestination = function(destination)
local path = calculatePath(destination)
if path.Status == Enum.PathStatus.Success then
for _, waypoint in pairs(path:GetWaypoints()) do
local nearestPlayer = findNearestPlayer()
if nearestPlayer and not isAttacking then
attack(nearestPlayer)
break
else
rig.Humanoid:MoveTo(waypoint.Position)
if isChasing == true then
isChasing = false
end
rig.Humanoid.MoveToFinished:Wait()
end
end
else
rig.Humanoid:MoveTo(destination - (rig.HumanoidRootPart.CFrame.LookVector * 10))
end
end
patrol = function()
local waypoints = workspace.Waypoints:GetChildren()
local randomNumber = math.random(1, #waypoints)
if not walkAnimation.IsPlaying then
walkAnimation:Play()
walkAnimation.Looped = true
end
walkToDestination(waypoints[randomNumber].Position)
end
CloneMe = function(char)
char.Archivable = true
local clone = char:Clone()
char.Archivable = false
return clone
end
CloneToPlayer = function()
local PlayersService = Players:GetPlayers()
if #PlayersService >= 1 then
local ChosenPlayer = PlayersService[math.random(1, #PlayersService)]
if ChosenPlayer.Character then
local charClone = CloneMe(ChosenPlayer.Character)
charClone.Parent = workspace
charClone.Name = "Monster"
local startPosition = rig.PrimaryPart.Position
charClone:SetPrimaryPartCFrame(CFrame.new(startPosition))
charClone.Humanoid.WalkSpeed = 16
rig.Parent = game.ServerStorage
rig.WalkingSoundScript:Clone().Parent = charClone
rig = charClone
walkAnimation = rig:WaitForChild("Humanoid").Animator:LoadAnimation(anim)
if charClone:FindFirstChild("Knocked") then
charClone:FindFirstChild("Knocked"):Destroy()
end
if charClone:FindFirstChild("Hiding") then
charClone:FindFirstChild("Hiding"):Destroy()
end
local cameraPartNew = Instance.new("Part")
cameraPartNew.Transparency = 1
cameraPartNew.CanCollide = false
cameraPartNew.Size = Vector3.new(1, 1, 1)
local newPosition = charClone:WaitForChild("Head").Position + (charClone:WaitForChild("Head").CFrame.LookVector * 3)
cameraPartNew.Position = newPosition
cameraPartNew.Name = "CameraPart"
cameraPartNew.Parent = charClone
cameraPartNew.Anchored = false
cameraPartNew.Orientation = Vector3.new(0, 180, 0)
local weld = Instance.new("WeldConstraint")
weld.Parent = cameraPartNew
weld.Part0 = cameraPartNew
weld.Part1 = charClone:WaitForChild("Head")
local hitboxNew = Instance.new("Part")
hitboxNew.Size = charClone:GetExtentsSize()
hitboxNew.Position = charClone.PrimaryPart.Position
hitboxNew.Parent = charClone
hitboxNew.Name = "HitBox"
hitboxNew.Anchored = false
hitboxNew.Transparency = 1
hitboxNew.CanCollide = false
hitBox = hitboxNew
local weld2 = Instance.new("WeldConstraint")
weld2.Parent = hitboxNew
weld2.Part0 = hitboxNew
weld2.Part1 = charClone.PrimaryPart
script.Jumpscare:Clone().Parent = charClone.HumanoidRootPart
script.WalkingSound:Clone().Parent = charClone.HumanoidRootPart
cloned = true
task.delay(1, function()
patrol()
end)
end
end
end
CloneToMonster = function()
local oldRig = game.ServerStorage:FindFirstChild("Monster")
oldRig.Parent = workspace
oldRig:MoveTo(rig.PrimaryPart.Position)
rig:Destroy()
rig = oldRig
hitBox = oldRig:WaitForChild("Hitbox")
walkAnimation = rig:WaitForChild("Humanoid").Animator:LoadAnimation(anim)
cloned = false
task.delay(1, function()
patrol()
end)
end
while task.wait(0.8) do
if isAttacking == false then
patrol()
end
hitBox.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild("Humanoid") then
local player = Players:GetPlayerFromCharacter(hit.Parent)
local character = hit.Parent
if player and deb == false and character:FindFirstChild("Knocked").Value == false and character:FindFirstChild("Humanoid").Health > 0 and character:FindFirstChild("Hiding").Value == false and isAttacking == false then
deb = true
Jumpscare(character)
task.wait(1)
deb = false
end
end
end)
end
I am NOT asking for free scripts, just a little pointer into which direction I should go because I know I need to do something when it calculates the path or walks to it if it has transformed but I can’t figure out what it is I need to change, Thanks!
3 Likes
If the size of the monster changes significantly when it transforms, then remember to recalculate the path in-order to adjust the AgentHeight and AgentRadius according to the monster’s new dimensions. You’ll also need to wait until the monster has finished transforming before doing so (unless the transformation is instantaneous) in-order for the dimensions to be correct
2 Likes
it sets the hitbox variable to the new hitbox when it transforms and then patrols which re calculates everything
1 Like
It could be that the issue is being caused by not checking whether the monster is already in partol before calling the patrol function, so doing something like this should fix the issue:
-- Services --
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local JumpscareRE = ReplicatedStorage:WaitForChild("JumpscareRE")
local animations = script:WaitForChild("Animations")
local anim = animations:WaitForChild("Walk")
-- Changing Variables --
local rig = workspace:WaitForChild("Monster")
local hitBox = rig:WaitForChild("Hitbox")
local walkAnimation = rig:WaitForChild("Humanoid").Animator:LoadAnimation(anim)
-- Variables --
local deb = false
local isAttacking = false
local isChasing = false
local isPatrolling = false -- new variable for the patrol function debounce
local cloned = false
local timesWalked = 0
-- Functions --
local patrol
local calculatePath
local walkToDestination
local CloneToPlayer
local CloneMe
local CloneToMonster
local Jumpscare
local checkForCharacter
local findNearestPlayer
local attack
Jumpscare = function(character)
if cloned == true and isAttacking == false then
CloneToMonster()
end
task.wait(0.1)
local attackAnimation = rig.Humanoid.Animator:LoadAnimation(animations.Attack)
attackAnimation:Play()
isChasing = false
isAttacking = true
local player = game.Players:GetPlayerFromCharacter(character)
local cameraPart = rig:WaitForChild("CameraPart")
character:WaitForChild("Humanoid").WalkSpeed = 0
character:WaitForChild("Humanoid").JumpHeight = 0
character:FindFirstChild("Knocked").Value = true
JumpscareRE:FireClient(player, cameraPart, true)
rig:WaitForChild("HumanoidRootPart"):WaitForChild("Jumpscare"):Play()
task.wait(4)
JumpscareRE:FireClient(player, cameraPart, false)
character:WaitForChild("Humanoid").WalkSpeed = 8
attackAnimation:Stop(0)
task.wait(5)
isAttacking = false
patrol()
end
checkForCharacter = function(character)
local rayOrigin = rig:FindFirstChild("HumanoidRootPart").Position
local rayDirection = (character.HumanoidRootPart.Position - rayOrigin).Unit * 40
local raycastResult = workspace:Raycast(rayOrigin, rayDirection, RaycastParams.new())
if raycastResult then
local raycastInstance = raycastResult.Instance
if raycastInstance:IsDescendantOf(character) then
return true
end
else
return false
end
end
findNearestPlayer = function()
local players = Players:GetPlayers()
local nearestPlayer = nil
local maxDistance = 100
for _, player in pairs(players) do
if player.Character ~= nil then
local targetCharacter = player.Character
local distance = (rig.HumanoidRootPart.Position - targetCharacter.HumanoidRootPart.Position).Magnitude
if distance < maxDistance and checkForCharacter(targetCharacter) then
if targetCharacter:FindFirstChild("Knocked").Value == false and targetCharacter:FindFirstChild("Humanoid").Health > 0 and targetCharacter:FindFirstChild("Hiding").Value == false then
nearestPlayer = targetCharacter
maxDistance = distance
end
end
end
end
return nearestPlayer
end
attack = function(character)
local distance = (rig.HumanoidRootPart.Position - character.HumanoidRootPart.Position).Magnitude
if distance > 5 and character:FindFirstChild("Knocked").Value == false and character:FindFirstChild("Humanoid").Health > 0 and character:FindFirstChild("Hiding").Value == false then
if walkAnimation.IsPlaying then
walkAnimation:Stop()
end
rig.Humanoid:MoveTo(character.HumanoidRootPart.Position)
isChasing = true
isPatrolling = false -- The monster has found a player, so it's no longer patrolling
else
Jumpscare(character)
end
end
calculatePath = function(destination)
local agentParams = {
["AgentHeight"] = hitBox.Size.Y,
["AgentRadius"] = hitBox.Size.X,
["AgentCanJump"] = false
}
local path = PathfindingService:CreatePath(agentParams)
path:ComputeAsync(rig.HumanoidRootPart.Position, destination)
if timesWalked ~= 2 then
timesWalked += 1
end
if timesWalked >= 2 then
local ranNumber = math.random(1,10)
if ranNumber >= 5 then
local Number = math.random(1,2)
if Number == 1 then
if cloned == true and isAttacking == false and isChasing == false then
CloneToMonster()
end
else
if cloned == false and isAttacking == false and isChasing == false then
CloneToPlayer()
end
end
end
end
return path
end
walkToDestination = function(destination)
local path = calculatePath(destination)
if path.Status == Enum.PathStatus.Success then
for _, waypoint in pairs(path:GetWaypoints()) do
local nearestPlayer = findNearestPlayer()
if nearestPlayer and not isAttacking then
attack(nearestPlayer)
break
else
rig.Humanoid:MoveTo(waypoint.Position)
if isChasing == true then
isChasing = false
end
rig.Humanoid.MoveToFinished:Wait()
end
end
else
rig.Humanoid:MoveTo(destination - (rig.HumanoidRootPart.CFrame.LookVector * 10))
end
end
patrol = function()
local waypoints = workspace.Waypoints:GetChildren()
local randomNumber = math.random(1, #waypoints)
if not walkAnimation.IsPlaying then
walkAnimation:Play()
walkAnimation.Looped = true
end
walkToDestination(waypoints[randomNumber].Position)
end
CloneMe = function(char)
char.Archivable = true
local clone = char:Clone()
char.Archivable = false
return clone
end
CloneToPlayer = function()
local PlayersService = Players:GetPlayers()
if #PlayersService >= 1 then
local ChosenPlayer = PlayersService[math.random(1, #PlayersService)]
if ChosenPlayer.Character then
local charClone = CloneMe(ChosenPlayer.Character)
charClone.Parent = workspace
charClone.Name = "Monster"
local startPosition = rig.PrimaryPart.Position
charClone:SetPrimaryPartCFrame(CFrame.new(startPosition))
charClone.Humanoid.WalkSpeed = 16
rig.Parent = game.ServerStorage
rig.WalkingSoundScript:Clone().Parent = charClone
rig = charClone
walkAnimation = rig:WaitForChild("Humanoid").Animator:LoadAnimation(anim)
if charClone:FindFirstChild("Knocked") then
charClone:FindFirstChild("Knocked"):Destroy()
end
if charClone:FindFirstChild("Hiding") then
charClone:FindFirstChild("Hiding"):Destroy()
end
local cameraPartNew = Instance.new("Part")
cameraPartNew.Transparency = 1
cameraPartNew.CanCollide = false
cameraPartNew.Size = Vector3.new(1, 1, 1)
local newPosition = charClone:WaitForChild("Head").Position + (charClone:WaitForChild("Head").CFrame.LookVector * 3)
cameraPartNew.Position = newPosition
cameraPartNew.Name = "CameraPart"
cameraPartNew.Parent = charClone
cameraPartNew.Anchored = false
cameraPartNew.Orientation = Vector3.new(0, 180, 0)
local weld = Instance.new("WeldConstraint")
weld.Parent = cameraPartNew
weld.Part0 = cameraPartNew
weld.Part1 = charClone:WaitForChild("Head")
local hitboxNew = Instance.new("Part")
hitboxNew.Size = charClone:GetExtentsSize()
hitboxNew.Position = charClone.PrimaryPart.Position
hitboxNew.Parent = charClone
hitboxNew.Name = "HitBox"
hitboxNew.Anchored = false
hitboxNew.Transparency = 1
hitboxNew.CanCollide = false
hitBox = hitboxNew
local weld2 = Instance.new("WeldConstraint")
weld2.Parent = hitboxNew
weld2.Part0 = hitboxNew
weld2.Part1 = charClone.PrimaryPart
script.Jumpscare:Clone().Parent = charClone.HumanoidRootPart
script.WalkingSound:Clone().Parent = charClone.HumanoidRootPart
cloned = true
task.delay(1, function()
patrol()
end)
end
end
end
CloneToMonster = function()
local oldRig = game.ServerStorage:FindFirstChild("Monster")
oldRig.Parent = workspace
oldRig:MoveTo(rig.PrimaryPart.Position)
rig:Destroy()
rig = oldRig
hitBox = oldRig:WaitForChild("Hitbox")
walkAnimation = rig:WaitForChild("Humanoid").Animator:LoadAnimation(anim)
cloned = false
task.delay(1, function()
patrol()
end)
end
while task.wait(0.8) do
if isAttacking == false and isPatrolling == false then -- Check whether the monster is already patrolling
isPatrolling = true
patrol()
end
hitBox.Touched:Connect(function(hit)
if hit.Parent:FindFirstChild("Humanoid") then
local player = Players:GetPlayerFromCharacter(hit.Parent)
local character = hit.Parent
if player and deb == false and character:FindFirstChild("Knocked").Value == false and character:FindFirstChild("Humanoid").Health > 0 and character:FindFirstChild("Hiding").Value == false and isAttacking == false then
deb = true
Jumpscare(character)
task.wait(1)
deb = false
end
end
end)
end
2 Likes
the monster now just walks back and forward, it never reaches a waypoint
1 Like
The only test left that I can think of is to try checking to see if one of the if statements is being triggered incorrectly, and to make sure that the functions are being called in the correct order
I suspect that something is calling the patrol function more frequently than it should, which is why the monster is walking back and forth
2 Likes
i checked using print, patrol stops getting called. The code gets stuck somewhere in the walkToDestination and calculatePath
2 Likes
Try replacing this (in the calculatePath function):
with:
path:ComputeAsync(hitBox.Position, destination)
My reasoning is that if the hit-box is following the monster and the hit-box’s size is changing, then it makes sense to use its position as the path’s starting point
2 Likes
didnt work, but thanks for helping. I decided to not let the monster have the ability to transform
2 Likes
I can’t believe it took me this long to notice, but you have a memory leak here:
You’re creating the hit-box’s Touched connection within the while loop, and it’s almost never a good idea to create connections within an infinite loop unless you’re extremely careful about disconnecting them
I don’t think it’s what caused the original issue though, I just wanted to mention this since creating connections inside of infinite loops is a bad habit to have
@RetroAmythest
I had the time to attempt to rewrite the script (I also did it because I like the concept of a monster that’s able to stay hidden by disguising itself as a player), although admittedly I haven’t added all of the functionality of the original. It’s more of a reference you can use if you wish to return to the original concept in the future
The new script (I've minimized it so that this comment isn't too long)
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local patrolWaypoints = Workspace:WaitForChild("Waypoints")
local currentPatrolWaypoint = nil
local thread1 = nil
local thread2 = nil
local function getNearestPlayer(rigPosition: Vector3): Player?
local nearestPlayer = nil
local previousDistance = 128 -- Change this to match the desired detection radius (in studs)
for _, player in Players:GetPlayers() do
if player.Character then
local distance = player:DistanceFromCharacter(rigPosition)
if distance < previousDistance then
nearestPlayer = player
previousDistance = distance
end
end
end
return nearestPlayer
end
-- This function will guarantee that a new random waypoint is returned for patrolling
local function getRandomPatrolWaypoint(): BasePart
local waypoints = patrolWaypoints:GetChildren()
local waypoint = waypoints[math.random(#waypoints)]
if waypoint == currentPatrolWaypoint then
getRandomPatrolWaypoint()
else
currentPatrolWaypoint = waypoint
return waypoint
end
end
local function walkToPosition(humanoid: Humanoid, path: Path, rigPosition: Vector3, target: Vector3)
path:ComputeAsync(rigPosition, target)
if path.Status == Enum.PathStatus.Success then
for _, waypoint in path:GetWaypoints() do
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
else
warn("Failed to compute path")
end
end
local function rigStart(humanoid: Humanoid, path: Path, rigPosition: Vector3)
while true do
task.wait(0.8)
if thread2 then -- Using threads will prevent any issues with overlapping movements
task.cancel(thread2)
end
local player = getNearestPlayer(rigPosition)
thread2 = task.spawn(
walkToPosition,
humanoid,
path,
rigPosition,
if player then player.Character:GetPivot().Position else getRandomPatrolWaypoint())
end
end
local function transform(into: Model)
if thread1 then
task.cancel(thread1)
end
local hitBox = into:WaitForChild("HitBox")
local humanoid = into:WaitForChild("Humanoid")
local path = PathfindingService:CreatePath({
AgentHeight = hitBox.Size.Y,
AgentRadius = hitBox.Size.X,
AgentCanJump = false})
thread1 = task.spawn(rigStart, humanoid, path, into:GetPivot().Position)
end
transform(Workspace:WaitForChild("Monster"))
2 Likes
its not the original issue, also i changed it after i posted this because i forgot i put it in there