I am attempting to create a horror game AI that is able to chase players around corners by going to their last known position and seeing if the player has their flashlight on. I wish to use pathfinding even when the player is in sight of the AI, as I don’t want my level design to be limited (such as any form of verticality being a no-go). The problem with this is that it just doesn’t work. Paths being created every frame often means that the AI starts jittering as it starts new paths during previous ones, and the script only becomes more complicated and difficult to work with as I try different solutions, move things around, reorganize and change methods in the hope that something will work. As of now, the game horribly lags as it jankily pathfinds its way to the given position.
I’m not exactly sure what to try, I’m not very experienced with pathfinding or AI in general. I believe that a possible solution could be to have it only recalculate the path once it reaches a waypoint and then once it’s within a few studs of the player move directly to them like some other threads have suggested, though I’m not sure how I’d implement that in the mess of code that I currently have. It’d help as well if someone have any videos or tutorials that give me a base to add mechanics on top of.
I’ll leave the script down below if it makes things clearer, thank you.
-- get services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PFS = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
-- npc variables
local npc = script.Parent
local hum = npc:WaitForChild("Humanoid")
local hrp = hum.RootPart
npc.PrimaryPart = hrp
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {npc}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
-- pathfinding and movement
local waypoints
local nextWaypointIndex
local reachedConnection
local lastPos
local patrolSections = game.Workspace.NPCWaypoints:GetChildren()
local sectionToPatrol = math.random(1, #patrolSections)
-- flags
local state -- 0 = wandering; 1 = searching; 2 = chasing
local canSeePlayer = false
local prevCanSeePlayer = false
local targetPos
-- find nearest player. if there is one, return the player. if there isn't, return nil
local function canSeeTargetsLight(plr)
if plr.Character then
local flashlight = plr.Character.Head:FindFirstChild("Flashlight")
if flashlight then
if flashlight.SpotLight.Enabled == true then
local lightPart = game.Workspace.FilterParts.ClientSpawnables:WaitForChild(plr.Name.."LightHit")
local lightSightRay = workspace:Raycast(hrp.Position, lightPart.Position - hrp.Position, raycastParams)
if lightSightRay then
local distance = lightSightRay.Distance
local part = lightSightRay.Instance
if part == lightPart then
print("I see a light!")
return true
else
return false
end
end
end
end
end
end
local function canSeeTarget(plr)
local char = plr.Character
local plrHRP = char:WaitForChild("Humanoid").RootPart
local sightRay = workspace:Raycast(hrp.Position, plrHRP.Position - hrp.Position, raycastParams)
if sightRay then
local distance = sightRay.Distance
local part = sightRay.Instance
if part:IsDescendantOf(char) then
print("I see a player")
return true
end
else
print("Can't see player")
return false
end
end
local function findTarget()
local plyrs = Players:GetPlayers()
local nearestTarget
for i,plr in pairs(plyrs) do
if plr.Character then
if canSeeTarget(plr) then
nearestTarget = plr.Character.HumanoidRootPart
canSeePlayer = true
elseif canSeeTargetsLight(plr) then
nearestTarget = game.Workspace.FilterParts.ClientSpawnables:FindFirstChild(plr.Name.."LightHit")
canSeePlayer = true
else
canSeePlayer = false
end
end
end
return nearestTarget
end
local function getPath(pos)
-- set up pathfinding
local path = PFS:CreatePath({
AgentRadius = 2,
AgentHeight = 6,
AgentCanJump = true,
AgentCanClimb = true,
WaypointSpacing = 2,
})
-- compute path
path:ComputeAsync(npc.PrimaryPart.Position, pos)
print("Path created")
return path
end
local function walkTo(pos)
local path = getPath(pos)
if path.Status == Enum.PathStatus.Success then
-- get path waypoints
waypoints = path:GetWaypoints()
local lastPos = waypoints[#waypoints]
for i, nextWaypoint in pairs(waypoints) do
local waypointVis = Instance.new("Part")
waypointVis.Parent = workspace
waypointVis.Position = nextWaypoint.Position
waypointVis.Anchored = true
waypointVis.CanCollide = false
waypointVis.Size = Vector3.new(1, 1, 1)
waypointVis.Shape = Enum.PartType.Ball
game.Debris:AddItem(waypointVis, 5)
end
for i, nextWaypoint in pairs(waypoints) do
if i == 1 then
continue
end
path.Blocked:Connect(function()
path:Destroy()
end)
-- move to next waypoint
hum:MoveTo(nextWaypoint.Position)
print("Moving to ", nextWaypoint.Position)
hum.MoveToFinished:Wait()
end
end
end
local function patrolSection(section)
if state == 0 then
local pointsInSection = patrolSections[section]:GetChildren()
for i,v in pairs(pointsInSection) do
walkTo(v.Position)
end
end
end
RunService.Heartbeat:Connect(function(dT)
local target = findTarget()
if canSeePlayer == true then
targetPos = target.Position
walkTo(targetPos)
state = 2
elseif prevCanSeePlayer == true then
print("Lost sight of player!")
walkTo(targetPos)
else
state = 0
patrolSection(sectionToPatrol)
end
--print(targetPos)
prevCanSeePlayer = canSeePlayer
task.wait()
end)```