EZ Pathfinding V3

I am excited to release EZ Pathfinding V3 today!! After a month or so of programming V2, I decided to start on a brand new version with better features and better compatibility. There are added features such as making a part jump which is still being modified. If you are having trouble using it, read the instructions below. V2 is no longer being modified or updated, but V3 will. V3 will probably be the last version, but will be constantly updated.

About this Module:

The EZ Pathfinding modules are designed to make using pathfinding a whole lot easier. These modules also include special functions that make controlling parts and NPCs a whole lot easier. V3 has more functions and is more compatible and easier to modify than V2. V2 will still be open to the public, but I strongly suggest if you want one of these modules, that you get V3.

Instructions

  1. To create the path that your part or npc is going to use, you will have to use the module.new(params) function : Example :
local module = require(5697665462) -- or require(game:WaitForChild("ReplicatedStorage"):WaitForChild("MainModule"))

local path = module.new(NPC, endPart) -- replace startPart and endPart with Instances
  1. To get your part or npc to move, you will need to use the module:Move(params) function : Example :
module:Move(path, NPC)
  1. If you want to change the settings for the pathfinding, you can do so using the module.SetSettings(params) function : Example :
module.SetSettings("Tween_Info", TweenInfo.new(tweenParams))
List of Setting Types
"Tween_Info",
"Walk_Speed",
"Jump_Power",
"PFparams"
  1. After using these instructions your code look something like this:
local module = require(5697665462) -- or require(game:WaitForChild("ReplicatedStorage"):WaitForChild("MainModule"))

module.SetSettings("Tween_Info", TweenInfo.new(tweenParams))

local path = module.new(NPC, endPart) -- replace startPart and endPart with Instances

module:Move(path, NPC)

Extra Module Functions

  • module.Jump(NPC, power) -- makes a part or npc jump. Power only is for part jumping. JumpPower can be modified in the function, module.SetSettings()

  • module.PauseTweening(partName) -- pauses a tween for parts with the name of the partName param

  • module.ResumeTweening(partName) -- resumes a tween for parts with the name of the partName param

  • module.StopNPC(npc) -- stops that npc's current path completely

  • module.PauseNPC(npc) -- pauses that npc's current path. Can be resumed.

  • module.ResumeNPC(npc) -- resumes that npc's current path. Works only if it is paused.

  • module.GetNearestPlayer(npc, maxDistance) -- fetches the nearest player and returns the player object.

  • module.ChaseNearestPlayer(path, npc) -- Chases the player computed in the path. Same as doing module:Move(path, npc)

  • module.ClearPartWaypoints() -- clears every parts waypoints and sends them to the debris where they will have a lifetime of three seconds to be tweened too if not already.

  • module.ArrayMovement(npc, {[1] = part1, [2] = part2, [3] = part3} -- Moves an npc to the first part then the next and next with ease. No part count limit but distance between parts cannot be too far.

Script Functions

  • module.RigWaypointReached = function() end -- triggers every time a npc reaches a new waypoint

  • module.PartWaypointReached = function() end -- triggers every time a part reaches a new waypoint

Link to the V3 and V2:

EZ Pathfinding V3:

EZ Pathfinding V2 (older version):

EZ Pathfinding V3 Test Place (Uncopylocked):

Source Code
local module = {}

local meta = {}

module.__index = module

local self = setmetatable(meta, module)

local AR, AH, ACJ = 5, 10, true

--// Services
local Pathfinding = game:GetService("PathfindingService")

local tservice = game:GetService("TweenService")

local debris = game:GetService("Debris")

--// Variables
local currentWaypointIndex = 2

local currentTweenPart

local ptpause = false

local StoppedPath = false

--//Tables
local paused = {[1] = {Number = 1, Name = ""}}

local waypoints = nil

--local stopped = {[1] = {Number = 1, Name = ""}}

module.PartWaypointReached = {}

module.RigWaypointReached = {}

--//Tweening
local tweeninfo = TweenInfo.new(
	3,
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In,
	0,
	false,
	0
)

--// Functions

local function onWaypointReached(reached, npc)
	local humanoid = npc:FindFirstChildOfClass("Humanoid")
	if reached and currentWaypointIndex < #waypoints then
		currentWaypointIndex = currentWaypointIndex + 1
		humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
		
		if typeof(module.RigWaypointReached) == "function" then
			module.RigWaypointReached()
		end
	end
end

local function onPathBlocked(blockedWaypointIndex, npc, endpart, vector)
	if blockedWaypointIndex > currentWaypointIndex then
		if vector == true then
			local path = module.new(npc, vector)
			module:Move(path, npc)
			else
		local path = module.new(npc, endpart)
			module:Move(path, npc)
			end
	end
end

function module.new(npc, endpart, vector)
	local params = {AgentRadius = AR, AgentHeight = AH, AgentCanJump = ACJ}
	local path = Pathfinding:CreatePath(params)
	
	if npc:FindFirstChildOfClass("Humanoid")then
		if vector ~= nil then
			path:ComputeAsync(npc:WaitForChild("HumanoidRootPart").Position, vector)
			path.Blocked:Connect(onPathBlocked, npc, endpart, vector)
		else
		path:ComputeAsync(npc:WaitForChild("HumanoidRootPart").Position, endpart.Position)
		
		path.Blocked:Connect(onPathBlocked, npc, endpart, vector)
		npc.Humanoid.MoveToFinished:Connect(function(status)	
				onWaypointReached(status, npc)
			end)
			end
	elseif npc:IsA("BasePart")then
		if vector ~= nil then
			path:ComputeAsync(npc.Position, vector)
			else
		path:ComputeAsync(npc.Position, endpart.Position)
		end
		end
	
	return path
end

function module:Move(path, npc)
	if path then
		waypoints = path:GetWaypoints()
		
		local hum
		waypoints = path:GetWaypoints()
		if npc:FindFirstChildOfClass("Humanoid")then
			hum = npc:FindFirstChildOfClass("Humanoid")
		end
		
		if path.Status == Enum.PathStatus.Success then
			if hum then
				if waypoints[currentWaypointIndex] then
				hum:MoveTo(waypoints[currentWaypointIndex].Position)
					hum.MoveToFinished:Wait()
					end
				
			else
		StoppedPath = false
		ptpause = false
				
				local currentpartt
				
				currentTweenPart = npc
				
				currentTweenPart.Anchored = true

	for i, waypoint in pairs(waypoints) do
			wait(1)
				if ptpause == false then
					local newpart = Instance.new("Part")
						newpart.Position = waypoint.Position
	newpart.Size = Vector3.new(0.6,0.6,0.6)
	newpart.Shape = "Ball"
	newpart.Anchored = true
		newpart.CanCollide = false
		newpart.Name = "Waypoint:EzPathfindingV3"
		newpart.Parent = workspace
		newpart.Transparency = 1
		
						newpart.Touched:Connect(function()
							if typeof(module.PartWaypointReached) == "function" then
								module.PartWaypointReached()
							end
						end)
						wait(0.1)
	pcall(function()
			local part = currentTweenPart
	if newpart.Name == "Waypoint:EzPathfindingV3" then
			local goal = {Position = newpart.Position}
			local tween = tservice:Create(part, tweeninfo, goal)
			local pause = false
			local stopped2 = false
			for _,v in pairs(paused)do
				if v then
					if v.Name then
						if currentTweenPart.Name == v.Name then
							--ptpause = true
							tween:Pause()
							pause = true
						else
							if pause == false then
								if stopped2 == false then
									--ptpause = false
									tween:Play()
									end
							end
							end
					end
				else
					if pause == false then
						if stopped2 == false then
							tween:Play()
							end
					end
					end
			end
			--[[for _, value in pairs(stopped)do
				if value then
					if value.Name then
						if currentTweenPart.Name == value.Name then
							tween:Cancel()
							stopped2 = true
						end
					end
				end
			end--]]
			end
		end)
				end
		end
		
			end
		else
			if hum then
				hum:MoveTo(npc.HumanoidRootPart.Position)
				end
		end
		end
end

function module.SetSettings(setType, info, char)
	if setType == "Tween_Info" then
		local Time = info.Time
		local style = info.EasingStyle
		local direction = info.EasingDirection
		local rCount = info.RepeatCount
		local reverses = info.Reverses
		local delaytime = info.DelayTime
		
		local info = TweenInfo.new(
			Time,
			style,
			direction,
			rCount,
			reverses,
			delaytime
		)
	
	tweeninfo = info
	end
	
	if setType == "Jump_Power" then
		if char then
			if char:FindFirstChildOfClass("Humanoid") then
				local hum = char:FindFirstChildOfClass("Humanoid")
				hum.JumpPower = info
				self.JumpPower = info
			end
		end
	end
	
	if setType == "Walk_Speed" then
		if char then
			if char:FindFirstChildOfClass("Humanoid") then
				local hum = char:FindFirstChildOfClass("Humanoid")
				hum.WalkSpeed = info
				self.WalkSpeed = info
			end
		end
	end
	
	if setType == "PF_Params" then
		local agentHeight = info.AgentHeight
		local agentRadius = info.AgentRadius
		local agentCanJump = info.AgentCanJump
		
		AR, ACJ, AH = agentRadius, agentCanJump, agentHeight
	end
	
	if setType == "All" then
		if char then
			if char:FindFirstChildOfClass("Humanoid") then
				local hum = char:FindFirstChildOfClass("Humanoid")
				hum.JumpPower = info.JumpPower or 50
				self.JumpPower = info.JumpPower or 50
				
				hum.WalkSpeed = info.WalkSpeed or 16
				self.WalkSpeed = info.WalkSpeed or 16
			end
		end
		
		local agentHeight = info.AgentHeight or 10
		local agentRadius = info.AgentRadius or 5
		local agentCanJump = info.AgentCanJump or true
		
		AR, ACJ, AH = agentRadius, agentCanJump, agentHeight
	end
end

function module.PauseTweening(partName)
	local number = #paused +1
	paused[number] = {Number = number, Name = partName}
end

--[[function module.StopTweening(partName)
	local number = #stopped + 1
	stopped[number] = {Number = number, Name = partName}
end--]]

function module.ResumeTweening(partName)
	for _,v in pairs(paused)do
		if v.Name == partName then
			local num = v.Number
			table.remove(paused, num)
		end
		end
end

function module.StopNPC(npc)
	if npc:FindFirstChildOfClass("Humanoid")then
		local hum = npc:FindFirstChildOfClass("Humanoid")
		hum:MoveTo(npc.HumanoidRootPart.Position)
	end
end

function module.PauseNPC(npc)
	for _,v in pairs(npc:GetChildren())do
		if v:IsA("BasePart")then
			v.Anchored = true
		end
	end
end

function module.ResumeNPC(npc)
	for _,v in pairs(npc:GetChildren())do
		if v:IsA("BasePart")then
			v.Anchored = false
		end
end
end

function module.Jump(npc, power)
	if npc then
		if npc:FindFirstChildOfClass("Humanoid")then
			local hum = npc:FindFirstChildOfClass("Humanoid")
			if power ~= nil then
				hum.JumpPower = power
			end
			hum:Jump()
			
		else
			npc.Velocity = Vector3.new(0, 0, 0) -- reset velocity
			module.PauseTweening(npc.Name)
			local touchingTable = {[1] = "true"}
			
			local touching = npc:GetTouchingParts()
			
			repeat
				wait(0)
				touchingTable = npc:GetTouchingParts()
				
					npc.Position = npc.Position + Vector3.new(0, 5, 0)
				until
				
				touchingTable[1] == nil
			
			
			local Storage = Instance.new("Folder", script)
			for _, v in pairs(npc:GetChildren())do
				if v:IsA("Weld") or v:IsA("WeldConstraint")then
					v.Parent = Storage
				end
			end
			
			wait(0.3)
			
				npc.Velocity = Vector3.new(0, power, 0)
				
			npc.Anchored = false
			repeat 
				wait(0.1)	
			until
			npc:GetTouchingParts()[1] ~= nil
			
			npc.Anchored = true
			module.ResumeTweening(npc.Name)	
		end
	end
end

function findNearestTorso(pos, npc, dist)
	local list = game.Workspace:GetChildren()
	local torso = nil

	local temp = nil
	local human = nil
	local temp2 = nil
	
	for i = 1, #list do
		temp2 = list[i]
		if temp2.ClassName == "Model" then
			if temp2 ~= npc then
			temp = temp2:FindFirstChild("HumanoidRootPart")
			human = temp2:FindFirstChild("Humanoid")
			if (temp ~= nil) and (human ~= nil) and (human.Health > 0) then
				if (temp.Position - pos).magnitude < dist then
					torso = temp
					dist = (temp.Position - pos).magnitude
					end
					end
			end
		end
	end
	
	return torso
end

function module.GetNearestPlayer(npc, maxDis)
	
	if npc then
		
		if npc:FindFirstChild("HumanoidRootPart") then
			
			local humRoot = npc:FindFirstChild("HumanoidRootPart")
			
			local target = findNearestTorso(humRoot.Position, npc, maxDis)
			
			local path = module.new(npc, target)
			
			self.CurrentPlrPath = path
			
			self.CurrentTarget = target

			return path
		end
	end
end

function module.ChaseNearestPlayer(path, npc)
	module:Move(path, npc)
end

function module.ClearPartWaypoints()
	for _, v in pairs(game.Workspace:GetChildren())do
		if v then
			if v.Name == "Waypoint:EzPathfindingV3" then
				debris:AddItem(v, 3)
			end
		end
	end
end

return module

If you have any suggestions or questions, please reply to this thread and I’ll reply back as quickly as possible. If you have any concerns, message me so we can privately bug fix or solve your issue.

Poll (please only vote if you have tried out this module):

  • I love this module, no feedback.
  • I have some feedback (Reply)
  • I have a lot of feedback (Reply)

0 voters

An update log will be kept here for updates:

Update Log

10/11/20 - Smooth tween to jump transitions / Tweening doesn't overide jumping.

10/12/20 - Added new functions. (GetNearestPlayer, ChaseNearestPlayer, ClearPartWaypoints). Documentation on them located on the Extra Module Functions tab.

10/17/20 - ArrayMovement Function. Allows movement from part to part in one function. Requires an array of parts with indexes counting up (e.g. 1, 2, 3). Documentation located in the Extra Module Functions tab.

Thank you for reading this and I hope you have a fabulous day!

45 Likes

Added a uncopylocked place if you want to try out the module. I also added a source code for those wondering what it looks like inside the module. Once again, I hope this module proves useful to you.

4 Likes

Do you have a git repo link for tracking issues & merging community fixes?

2 Likes

No I do not, but I feel like messaging me would get private issues solved while community issues and solutions should be put in this thread so everyone can view it.

1 Like

Everyone who is voting in the poll saying they have feedback are not replying. Remember, if you do have feedback, please reply to this instead of voting.

2 Likes

10/11/20 Smooth tween to jump transitions / No overiding each other.

Extra Details

Before, when jumping in the middle of a tweening session, it would resume tweening before the part fell back down. That has now been fixed. The part also does not go into the ground when jumping, so the power of the velocity wont be different.

There are many more updates to come and I hope this little update today makes the tween-to-jump experience a whole lot easier to handle.

1 Like

Updated the source code, update log and pushed out three more handy functions.

Details

The source code, update log have now been updated to match the module correctly. Three more function were introduced to EZ Pathfinding V3. Examples of how to use these functions /

module.ClearPartWaypoints() -- clears every parts waypoints and puts them in the debris where they have a lifetime of 3 seconds before they cannot be tweened to. 

while wait(0.1) do
local playerPath = module.GetNearestPlayer

module.ChaseNearestPlayer(path, npc)
end

More documentation can be found in the Extra Module Functions tab.

I hope these three new functions prove a use to everyone. Many more updates to come. Thanks for reading!!

1 Like

can this be used with vehicles/vehicleseats?

1 Like

Not sure if I didn’t explain the uses of this module correctly. EZ Pathfinding is only to help with pathfinding. I am unsure of why pathfinding would be of use when making a vehicle unless you wanted to make a self driving vehicle. If that is the case, you can tween parts using the .new and :Move functions.

Good module, but i have feedback.
Why in function :GetNearestPlayer() it returns path instead of actual player or character.

1 Like

Actual Searching something in workspace not so good ideas since. AI can be placed in folders and other things.

1 Like

It is searching for players though and all player character’s parent is workspace.

GetNearestPlayer returns a path instead so that way you can move the npc with the path provided since the :Move function requires a path parameter.

Really great job on the module, for someone who isn’t a professional in scripting, this helps to better my understanding on NPC pathfinding. Also, I understand that you can plug in a single end part instance within this parameter: “local path = module.new(NPC, endPart)”, yet I am curious as to how one would go about creating a script that allows an NPC to continuously travel from one end part to the next? It would be great if you could please share some insight on this, thank you.

1 Like

If I am correct, there should be a function called PartMovement which basically moves an NPC along a path to an array of parts after it reaches the next part.

May you please elaborate with an example? I attempted to find the PartMovement function within your module and then under the Humanoid instance, but unfortunately, it did not seem to appear. I was just surprised that your pathfinding module didn’t already contain a function to already enter multiple end parts, but if the only option is to use this ‘PartMovement’ function, I would be grateful if you supplied me with some resources on it, thank you.

1 Like

Added that in right now. Totally forgot to add that in to the module as it was in V2. Thank you for reminding me. EX:

local module = require(game.ReplicatedStorage:WaitForChild("EzPathfindingV3"))

local tab = {
[1] = Part1,
[2] = Part2,
[3] = Part3,
-- and so on
}

module.ArrayMovement(npc, tab)

Still needs to be worked on a little more.

10/17/20 - ArrayMovement (Compatible with NPCs only).

Details

Allows movement from one part to another in one function. Can be stopped or paused. Example of how to use it:

local tab = {
[1] = Part1,
[2] = Part2,
[3] = Part3,
-- and so on
}

module.ArrayMovement(npc, tab)

This function was originally in V2 and has now been implemented into V3 with a much more flexible system. Thanks to @MaskedBrick for the reminder.

May you please share the source code for the ArrayMovement() function within the module? I have noticed that when I define three ‘endparts’ within workspace and then space them apart, the NPC simply walks towards the first part normally, yet stops dead in its tracks on the way to the second part, here is the code I am using (placed within a server script within the NPC):

local module = require(5697665462)

local path = {

[1] = game.workspace.EndPart,

[2] = game.workspace.EndPart2,

[3] = game.workspace.EndPart3,

– and so on
}

module.ArrayMovement( script.Parent, path)

print(‘Done!’)

The first image indicates the three parts that I defined in the above script where ‘P1’ is part one or ‘game.workspace.EndPart’, where you would expect the NPC to go from P1 to P2 then to P3 as defined:

In the second image, the green path is the path that I expected the NPC to follow, yet instead it walked from the start position in image one to ‘P1’, and on the way to the second part, the NPC stopped dead in its tracks (indicated by the red path):

As a side note, the output just continuously printed out ‘2’ while on the way to the second part while the NPC remained stationary as seen in the second image.
Strangely enough as well, if I move the parts closer together, the NPC seems to go to each part respectively in the defined order as in the script (‘P1’ to ‘P2’ to ‘P3’).

I understand that you just created this module, but it would be rather useful if you could ensure that every function works in theory as well as in practice, as this will ensure that issues like this are avoided.

You have to set the AgentRadius so it is higher because that determines how far it will go. I will add more documentary later on but this module isn’t really meant to hand hold as the functions pretty much explain most of it.

1 Like

Deprecated PartWaypointReached, ClearPartWaypoints, and the use of parts when tweening. They still can be used, but I recommend leaving them alone.