the above code also results in
Try increasing the COMPARISON_CHECKS
setting or try to increase the waypoint spacing and see if that makes any difference.
increased Comparison checks to
- 5 (no change)
- 10 (no change)
- 20 (no change)
Documentation for ErrorType of ComputeError are wrong. Should be ComputationError. It gave me a nil when it happened and had to look on the simple path module.
Thanks for letting me know. The mistake is now fixed in the documentation.
Hi, 1st of all, thank you for the module! is awesome
I am running two functions, 1 to generate a random walking pattern and the 2nd is to go after the target. I am unable to end the 2nd function with Path:Stop() after the target is too far so now the NPC is walking random and trying to go at the target direction causing it to move all weird.
if targetPresent == false then
print("walkrandom no target")
local PathRandom = SimplePath.new(worm)
local goal = myRoot.Position + Vector3.new(math.random(-0,10),0,math.random(-0,50))
PathRandom.Visualize = false
PathRandom:Run(goal)
if targetPresent == true then
print("walkrandom OFF")
PathRandom:Stop()
end
end
end
function findPath(target)
local Path = SimplePath.new(worm)
Path.Visualize = false
Path.Blocked:Connect(function()
Path:Run(target)
end)
Path.WaypointReached:Connect(function()
Path:Run(target)
end)
Path.Error:Connect(function(errorType)
Path:Run(target)
end)
Path:Run(target)
if (myRoot.Position - target.Position).Magnitude <= 20 then
myHuman.WalkSpeed = 50
local Sound = Instance.new("Sound", target.Parent.HumanoidRootPart)
Sound.SoundId = "rbxassetid://6028987187"
Sound:Play()
task.wait(1)
target.Parent.Humanoid:takeDamage(200)
elseif(myRoot.Position - target.Position).Magnitude > 200 then
Path:Stop(target)
-- Path:Destroy(target)
targetPresent = false
myHuman.WalkSpeed = 8
print("target too far")
end
end```
I tried Parth:Stop() and also tried Path:Destroy() and both and none and all everything in between... with no luck.
Make sure you only call Path:Stop()
on the same Path object. I can’t say for sure without going over your entire script but looking at your functions, I can see that you create a new Path inside of both and you also call Path.Stop in both as well. I would suggest you instantiate Path only once per rig for the entire system to keep everything in order and prevent unintentional behaviour.
I’m also having a slightly similar problem… when I put the goal on top of the wall then move it somewhere else, it thinks that the top of the wall is a passable part for some reason (I’m using a skinned mesh + non-edited code from the API) https://gyazo.com/aab3690297f757c2a5cc0b9abaf680b4
Something weird is happening.
When using a R6 rig (from the default rig maker) the NPC will hop constantly to the target.
Can anyone re-create this using the example map?
Help me fix this please.
When I go into the actual module script and comment the 2 single lines that say “Humanoid.Jump = true” the R6 rig stops hopping towards the target. What could cause this?
Edit: this line causes it (line 66)
if (p1.Y - p0.Y >= self._humanoid.HipHeight) or (raycast and p1.Y - raycast.Position.Y >= self._humanoid.HipHeight) then
-- self._humanoid.Jump = true
end
I understand it’s something with the HipHeight of 0, as you mentioned you fixed it once but still seems to be occuring xD
You are using an older version of the module. Try it again by adding in the newer version using the Roblox link.
xD, that’s true… seems like I used the one from the example map that you still have uploaded, you may wanna change it later. thanks
I have another question since you answered so fast…
I get my characters position and I want the NPC to stop near the player for an attack animation.
But since it’s a while loop it loops through Path:Stop()
and fires a warning
SimplePath: Attempt to run Path:Stop() in idle state
local function GetNearestPlayer(minimumDistance)
local closestMagnitude = minimumDistance or math.huge
local closestPlayer
for i,v in next, game.Players:GetPlayers() do
local chr = v.Character
if (chr) then
if (model.Humanoid.Health > 0) then
local distance = v:DistanceFromCharacter(model.HumanoidRootPart.Position)
local mag = (model.HumanoidRootPart.Position - model.HumanoidRootPart.Position).Magnitude
if distance <= closestMagnitude and distance > stat[2] then
closestPlayer = v
closestMagnitude = mag
print('running')
Path:Run(chr:WaitForChild("HumanoidRootPart"))
--runAnimation:Play()
end
if distance < stat[2] then
print('attacking')
Path:Stop()
--runAnimation:Stop()
end
end
end
end
return closestPlayer
end
coroutine.wrap(function()
while true do
local target = GetNearestPlayer(stat[1])
wait()
end
end)()
Any idea on how to properly use the Path:Stop()?
If Path.Visualize = true
and you call Path:Destroy()
it creates a C stack overflow in the output.
You receive that warning if Path:Stop()
is called when Path.Status is in idle state. If you want to properly handle this, just add an if statement like this:
if Path.Status == SimplePath.StatusType.Active then Path:Stop() end
Sorry about that, the bug is now fixed.
firstly thanks for fixing the minor glitch before
I have a problem I’m really hoping you can help with.
This problem is present in SimplePath but not subject to it.
I have a setup that looks something like this:
agent.Humanoid.Died:Connect(function()
path:Destroy()
end)
local function pathfind()
path:Run()
end
path.Error:Connect(pathfind)
path.WaypointReached:Connect(pathfind)
path.Blocked:Connect(pathfind)
path.Reached:Connect(pathfind)
pathfind()
As you can see when the agent dies the path gets destroyed - disconnecting the signals as not to cause memory leaks.
The issue is is that SimplePath has yielding code that could run after Path:Destroy(), it’s a race condition bug that looks something like this:
- Path:Run() gets called
- Path:Run() calls ComputeAsync (a yielding function)
- Path:Destroy() gets called (a non yielding function)
- ComputeAsync finshes yielding
- Any code after ComputeAsync is running when the Path object is “dead”
- The output fills up with a slew of “this is not a valid member” errors
In this instance it’s not a big deal, I can mask the errors by wrapping Path:Run() in a pcall. But this is an underlying problem I have been trying to solve.
My question basically is “How to end a program with yielding code”
I made a thread with this question but I didn’t get meaningful replies (or I didn’t understand them, but I think it’s the latter)
Here’s a little test file to further demonstrate
pathfinding_example.rbxl (53.6 KB)
If you know an answer please let me know this has been bothering me for a while now, and I don’t want to have to put flag checks in my code every time it yields.
Well, let’s take a look. I made a simple script to demonstrate how you can go about doing this.
local car = {}
car.__index = car
function car.new()
return setmetatable({
_speed = 0;
}, car)
end
function car:Drive()
self._speed += 100
task.wait(3)
--If car was destroyed during the yield, this part will error
self._speed -= 100
end
function car:Destroy()
self._speed = nil
setmetatable(self, nil)
end
local car = car.new()
task.spawn(car.Drive, car)
car:Destroy()
Here, the car.Drive
method yields and executes some code after the yield as well. But if car was destroyed during the yield, self._speed
would not exist so this will error. So the most obvious fix here would be to make sure car still exists after the yield. For example, you can add something like this:
function car:Drive()
self._speed += 100
task.wait(3)
--Checks if self._speed still exists
--If car is destroyed, self is just an empty table
if not self._speed then return end
self._speed -= 100
end
In most cases, you will have lots of code after a yield and it won’t be really efficient to add an if statement for every value you have inside of your object. If you take a look at the car.Destroy
method, it overrides the object’s metatable to nil. We can use this to our advantage to easily check if the object was destroyed or not. For example, in the car.Drive
, we can change it to:
function car:Drive()
self._speed += 100
task.wait(5)
--Checks if metatable still exists
if not getmetatable(self) then return end
self._speed -= 100
end
As you can see above, to make sure your code runs cleanly, all you have to do is perform checks to make sure a value exists after a yield. In this case, if we want to make sure the car object still exists after a yield, just check if the metatable still exists.
So essentially you’re saying check a flag (or in your case metatable) after every time the code yields. I was hoping there was some kind of “set once and forget” way of doing it because I can have a lot of things that yield and it feels janky having to put a check by every yield and inside every loop.
For your case even if it does error it’s not that big of a deal since the object is destroyed and it’s the end of the thread.
Where I am worried about is in cases like a RoundService where when I call Start it can be changing upvalues so when I call End and then Start again I can have 2 instance of Start changing the same values which can break completely break the game. Even in a case like yours I might want to change some values on Destroy, like maybe move the position or play animation and if something in the main code runs over that that will also break.
I thought of some things like any code that runs after a yield gets put in an event and then after every yield it fires the next event, then on destroy it disconnects all the events. But that seems very hacky and if I’m going to all that effort I may as do your method and do a simple check.
I feel like there has to be a definitive answer because millions of programs have a stop button so it has to somehow let yielding code know not to run.
I guess I’ll use checks after every time the code yields and not worry about it, I think I’m overthinking it because I feel like I’m an intermediate scripter and multiple threads and race conditions get me insecure about my code even though it’s probably fine.
Thank you again for your help!
I was working on something like this recently. What I wanted to achieve was to override a previous loop when the function is called, something like this:
local function makeLoop(i)
for i = 1, i do
print(i)
task.wait(1)
end
end
--Make this line override the previous running loop
task.defer(makeLoop, 20)
makeLoop(50)
With the current code above, it will end up making both loops run in parallel. A good technique might be to use BindableEvents. Whenever you call the function again, fire this bindable event. Since the connection for this bindable is in the previous loop, you can disconnect it as soon as it fires. Then in the loop, after each yield, just check if the connection exists before continuing. For example, in code, it would look something like this:
local bindable = Instance.new("BindableEvent")
local function makeLoop(i)
--Fire the bindable at the beginning of each call to cancel previous loop
bindable:Fire()
local connection
--Create a new connection whenever function is called
connection = bindable.Event:Connect(function()
connection = connection:Disconnect()
end)
for i = 1, i do
print(i)
task.wait(1)
--After each yield, check if connection still exists
if not connection then return end
end
--Disconnect connection after loop ends
if connection then connection:Disconnect() end
end
--This line will now override the previous loop
task.defer(makeLoop, 20)
makeLoop(50)
Other than this, to change certain values after a yield as you mentioned, things like position and animation, store the necessary values in a local variable within the scope of the function, even if, for example, the metatable doesn’t exist anymore, the reference to those object still exists, so the objects are not destroyed yet.