Over the past few days, I have been working on an AI that utilizes pathfinding and rays for a project. My current and final step of the AI (i hope) is making the AI prioritize something it can directly see with a ray over something it has to pathfind to (my plan is to make it pathfind if it can see it or not, just incase it needs to jump or is walking on a path with lots of holes).
I have already worked on a script but I am confused on how I would implement this feature. My current idea is adding everything that requires pathfinding to a table, and checking if it is in that table, and if so, to ignore. I am not sure how I will implement this though, and I need help.
Here is the script I have made so far for finding nearest torso:
function findNearestTorso(pos)
local list = game.Workspace:children()
local torso = nil
local dist = 200
local temp = nil
local human = nil
local temp2 = nil
for x = 1, #list do
temp2 = list[x]
if(temp2.className == "Model") and (temp2 ~= script.Parent) and temp2.Name ~= "SCP049" and temp2:FindFirstChild("SCP049-2") == nil then
if (temp ~= nil) and (human ~= nil) and (human.Health > 0) and (temp.Position - pos).magnitude < dist then
temp = temp2:findFirstChild("HumanoidRootPart")
local ray = Ray.new(AI.HumanoidRootPart.Position, (temp.Position - AI.HumanoidRootPart.Position).Unit * dist)
local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent})
if hit then --always prioritize something it can directly see
if hit:IsDescendantOf(temp.Parent) then
return temp
else --something behind a wall
temp.Parent.Name = ignore[#ignore + 1]
return temp
end
end
end
end
end
end
I know something is wrong with this script because it is constantly printing ânilâ instead of the target when in range of the AI.
Oh man, that looks like an old script mixed with some new stuff. Before I help out a bit - can you confirm if you want it to only find playersâ torsos or do you have other NPCs it can target?
Iâll assume you mean the torsos of other players then. Iâd write it like this. This will give you two tables. One with characters it can see, and one with characters you need to pathfind to.
local AI: (Model) = script.Parent -- You can redefine this
local AIRootPart: (BasePart) = AI.PrimaryPart
local maxDistance: (number) = 200
local function findAllCharactersInDistance(distance: (number)): (table)
local characters: (table) = {}
local players: (table) = game.Players:GetPlayers()
for _, player: (Player) in pairs(players) do
local char: (Model?) = player.Character
if (char) then
local humanoid: (Humanoid?) = char:FindFirstChild("Humanoid")
if (humanoid and humanoid.Health > 0) then
local distance: (number) = player:DistanceFromCharacter(AI.PrimaryPart)
if (distance <= distance) then
table.insert(characters, char)
end
end
end
end
return characters
end
--[[
* Will return two tables. The first will be characters the AI can see
]]
local function getPriorityCharacters(allCharacters: (table)): (table, table)
local priorityChars: (table) = {}
local secondaryChars: (table) = {}
local params: (RaycastParams) = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Blacklist
for _, char: (Model) in pairs(allCharacters) do
local vectorToChar: (Vector3) = (char.PrimaryPart.Position - AIRootPart.Position)
params.FilterDescendantsInstances = {AI, char}
local result: (RaycastResult) = workspace:Raycast(char.PrimaryPart.Position, vectorToChar, params)
if (not result) then
table.insert(priorityChars, char)
else
table.insert(secondaryChars, char)
end
end
return priorityChars, secondaryChars
end
local allChars: (table) = findAllCharactersInDistance(maxDistance)
local priorityChars: (table), secondaryChars: (table) = getPriorityCharacters(allChars)
This is going to kill your performance if you have an unorganised workspace. It looks as though you only want the NPC to kill players?
If so, just loop through the player list and find those with characters that are alive, and then raycast towards them.
e.g.
local players = game:GetService 'Players'
-- checks whether player is alive
local function isAlive(player)
local char = player.Character
if not char or not char:IsDescendantOf(game.Workspace) or not char:FindFirstChild 'Humanoid' or char.Humanoid:GetState() == Enum.HumanoidStateType.Dead then
return
end
return char
end
-- pass it:
-- (1) the Vec3 pos of wherever you want to check from
-- (2) the distance of how far it can see
-- (3) what you want the ray to ignore
local function findClosestVisibleTorso(rootPos, dist, ignore)
local curDis
local curChar
-- find all the players in the server
for _, player in ipairs(players:GetPlayers()) do
-- check if they're alive
local character = isAlive(player)
-- this is called a 'ternary operator' (kind of) - it will return the primary part of the character if it's there, or return nil
local primary = character and character.PrimaryPart or nil
if primary then
-- get the distance from our starting point and the position of the character we're looking at
local dis = (primary.Position - rootPos).magnitude
-- if we currently don't have a known distance reference, or if the new distance is smaller than our last, then continue
if not curDis or dis <= curDis then
-- raycast from our start position to the character, ignoring anything that was passed to us through 'ignore'
local ray = Ray.new(rootPos, (primary.Position - rootPos).unit * dist)
local hit, pos = game.Workspace:FindPartOnRay(ray, ignore)
-- have we hit something, and is that thing part of the character we're looking at?
if hit and hit:IsDescendantOf(character) then
-- yes it is, so let's set it as our new distance reference + character as it's the closest one so far in our iterative steps
curDis = dis
curChar = character
end
end
end
end
return curChar, curDis
end
-- now for usage...
local closestCharacter, theDistance = findClosestVisibleTorso(AI.HumanoidRootPart.Position, 200, script.Parent)
if closestCharacter then
-- we found the closest character that's visible to us!
-- do something
else
-- we haven't found anyone visible
-- maybe we should pathfind?
end
``
I have a question:
What is the purpose of curDis and curChar if it has no value and it is being assigned no value? And if it has no value, how is it being used in
Youâre right, initially the variable is undefined, but it is assigned a value when the following conditions are met:
If curDis is still undefined or if it has been defined in a previous iteration and the dis < curDis
If it hits the character weâre currently examining on that particular iteration
You can see it getting assigned here:
-- yes it is, so let's set it as our new distance reference + character as it's the closest one so far in our iterative steps
curDis = dis
curChar = character
Essentially, in the if statement:
if not curDis or dis <= curDis then
I am saying: If âcurDisâ is undefined, or if the new âdisâ is less or equal to the curDis then continue. The latter part of that question, âif the new 'dis is less or equal to the curDisâ wonât be checked unless ânot curDisâ returns false e.g. when it has been defined.
The reason I initialised both values outside of the scope of the loop is so that I can track which of the visible players are closest.
p.s. it should actually say if not curDis or dis < curDis then - I accidentally threw an equal op in there. Similarly, you could also check whether dis <= dist as a condition for that if statement to avoid an unnecessary raycast