If you didnt know theres a service called PathfindingService which obviously meant for pathfinding for custom AI for NPC's or actual players, but im going to focus on NPCs. PathfindingService has one main method, :CreatePath(). The arguments of it is a dictionary with the keys AgentHeight and AgentRadius, though I do not know what AgentHeight does (you can clearly see it makes a difference when you lower or increase it) since the wiki didn't provide a sufficient amount of information
about the new pathfinding (barely anything of the API actually, but I
know what AgentRadius is from observations which i'll explain later. If
you're a beginner scripter or do not know what I am saying, don't take
this tutorial THE DIFFICULTY IS HARD, since I expect you to know what
methods and algorithms im saying without explaining that much.
To create this system, we can use a while loop and use the wait() method
to make it yield from a random (not really) amount of time between 2
numbers. Firstly we're going to make a variable defined as the
:CreatePath() method which returns the Path object with specific methods
in them to create our system. So in our dictionary keys AgentHeight and
AgentRadius is all going to be defined as 1.
--server, note you should put this in the character
local humanoid = script.Parent.Humanoid
local torso = script.Parent.HumanoidRootPart
local ps = game:GetService("PathfindingService")
while true do
local path = ps:CreatePath({AgentHeight = 1, AgentRadius = 1})
wait(math.random(2,3.5))
end
After that, we're going to use the :ComputeAsync() which will make us a
path with specific arguments, the starting point which is obviously our
character's root (HumanoidRootPart) and the ending position (destination)
which we will create right now. To create it we're going to make a offset
from the characters root's position and set the x,z axes as a random cord.
local humanoid = script.Parent.Humanoid
local torso = script.Parent.HumanoidRootPart
local ps = game:GetService("PathfindingService")
while true do
local pos = torso.CFrame.Position + Vector3.new(math.random(-50,50),0,math.random(-50,50)) -- 100x100 square radius
local path = ps:CreatePath({AgentHeight = 1, AgentRadius = 1})
local comp = path:ComputeAsync(torso.CFrame.Position, pos)
wait(math.random(2,3.5))
end
Right now we got the basics done right now, but we dont have anything
to do with it, But before we get starting with the main block we need
to check if we can create a path to our destination by checking
path.Status returns Enum.PathStatus.Success. If theres no path to getting
to the point then it'll return Enum.PathStatus.NoPath. After if there is a
path, then we can call :GetWaypoints() which returns the positions the
character needs to move to to get to our point of the Path object
:CreatePath() returns. It returns a array with dictionaries inside of it
with the keys the position and the action which i'll explain later. Also I
might say now what the AgentRadius does is how far the point will be from
the character, so if it was 1 then it'll be 1 stud ex 100; 100 studs away,
though i'd suggest using 1 because it'll make the character a lot easier
to detect when to jump than 100 or so. Then to move to those points we
can call the :MoveTo() method and wait for when the :MoveTo() finishes by listening
for MoveToFinished:Wait().
local humanoid = script.Parent.Humanoid
local torso = script.Parent.HumanoidRootPart
local ps = game:GetService("PathfindingService")
while true do
local pos = torso.CFrame.Position + Vector3.new(math.random(-50,50),0,math.random(-50,50))
local path = ps:CreatePath({AgentHeight = 1, AgentRadius = 1})
local comp = path:ComputeAsync(torso.CFrame.Position, pos)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
for _,point in pairs(waypoints) do
humanoid:MoveTo(point.Position)
-- this is just a demonstration of where the point is, you can remove it
local part = Instance.new("Part")
part.CanCollide = false
part.Locked = true
part.Anchored = true
part.Material = Enum.Material.Neon
part.BrickColor = BrickColor.Red()
part.Size = Vector3.new(1,0.5,1)
part.CFrame = CFrame.new(point.Position)
part.Transparency = 0.25
part.Parent = workspace
humanoid.MoveToFinished:Wait()
part:Destroy()
end
end
wait(math.random(2,3.5))
end
Now the NPC moves around, but what if they need to jump over something
or on something which is why point.Action exists. It'll have a value of
Enum.PathWaypointAction.Jump or Enum.PathWaypointAction.Walk which we
can check then call Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
to make the character jump. Though what if the character sits, it'll just
stay there forver, so in which we can listen for the Seated event of the
humanoid and check if the 1st parameter is true then call the
Humanoid:ChangeState() method. But we have another problem, what if an
object blocks the character's path while traveling there. We can use the
Blocked event of the path object and listen for it, then just call
:MoveTo() to the character's own positon to cancel the NPC from walking
towards the blockade.
local humanoid = script.Parent.Humanoid
local torso = script.Parent.HumanoidRootPart
local ps = game:GetService("PathfindingService")
math.randomseed(tick()) -- create a seed
while true do
local pos = torso.CFrame.Position + Vector3.new(math.random(-50,50),0,math.random(-50,50))
local path = ps:CreatePath({AgentHeight = 1, AgentRadius = 1})
local comp = path:ComputeAsync(torso.CFrame.Position, pos)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
humanoid.Seated:Connect(function(seated)
if seated == true then
print("seated")
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
end)
path.Blocked:Connect(function()
print("blocked!")
humanoid:MoveTo(torso.CFrame.Position)
end)
for _,point in pairs(waypoints) do
humanoid:MoveTo(point.Position)
local part = Instance.new("Part")
part.CanCollide = false
part.Locked = true
part.Anchored = true
part.Material = Enum.Material.Neon
part.BrickColor = BrickColor.Red()
part.Size = Vector3.new(1,0.5,1)
part.CFrame = CFrame.new(point.Position)
part.Transparency = 0.25
part.Parent = workspace
if point.Action == Enum.PathWaypointAction.Jump then
print("jumping")
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid.MoveToFinished:Wait()
part:Destroy()
end
end
wait(math.random(2,3.5))
end
Since we've doing creating the entire script, you could try making an
obstacle course and see if the NPC can get out of it. Note that if your
using multiple NPCs with this type of system instead of placing a script
in each of them, you could place a script inside ServerScriptService as
a command centre. Then you could place all the AI inside a folder in
workspace and modify this script so its compatible with a generic for
loop ex:
local ps = game:GetService("PathfindingService")
math.randomseed(tick())
for _,npc in pairs(workspace.NPCS:GetChildren()) do
spawn(function() -- spawn a new thread
while true do
local humanoid = npc.Humanoid
local torso = npc.HumanoidRootPart
local pos = torso.CFrame.Position + Vector3.new(math.random(-50,50),0,math.random(-50,50))
local path = ps:CreatePath({AgentHeight = 1, AgentRadius = 1})
local comp = path:ComputeAsync(torso.CFrame.Position, pos)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
humanoid.Seated:Connect(function(seated)
if seated == true then
print("seated")
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
end)
path.Blocked:Connect(function()
print("blocked!")
humanoid:MoveTo(torso.CFrame.Position)
end)
for _,point in pairs(waypoints) do
humanoid:MoveTo(point.Position)
local part = Instance.new("Part")
part.CanCollide = false
part.Locked = true
part.Anchored = true
part.Material = Enum.Material.Neon
part.BrickColor = BrickColor.Red()
part.Size = Vector3.new(1,0.5,1)
part.CFrame = CFrame.new(point.Position)
part.Transparency = 0.25
part.Parent = workspace
if point.Action == Enum.PathWaypointAction.Jump then
print("jumping")
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid.MoveToFinished:Wait()
part:Destroy()
end
end
wait(math.random(2,3.5))
end
end)
end
If you want to see areas that the NPC can go, just go to files>settings>
studio>visual which is way at the bottom and it should show some things.
the purple areas is where it could go, the non coloured areas is where
it cant go and the arrows is where the NPC needs to jump.
If you could see, the NPC doesn't have any idle, jumping nor walking
animation. So what you could do if you didn't know, is play solo in studio
and copy the Animate LocalScript inside your character to the clipboard
and quit play solo. Secondly paste it in the explorer and add a server script
in the NPC (LocalScripts can not run in workspace with the exception of the player's character)
and copy the contents and also the instances inside of the
Animate script in the server script. Finally you can remove lines 510 to
524 since the server doesn't know which player "LocalPlayer" is and NPCs
cant chat anyways.