I’ve been struggling with this problem for a couple of months now and because of my relative inexperience in this field, I do not know what to try. Because the issue is of fundamental importance to the game I’m making, I haven’t been able to get any real work done on the project since the issue has popped up.
The game I’m making is essentially a zombie survival game with different types of zombie AI to fight against in large numbers. In order for this game to work, the AIs need to pose a serious threat to the survival of the players. As such, I’ve made a type of fast AI that is meant to be able to quickly and accurately catch up to fleeing players and deal chip damage to them. I’ve seen this type of fast-traveling AI countless times in other games without the problem I’m experiencing, so I know it’s possible to do.
Whenever the fastest-traveling AI gets within a few studs of the player while the player is running away, their movement speed essentially slows all the way down to the default speed in that distance and the AI becomes unable to catch up to the player. This trivializes the gameplay because so long as the player is moving away from the AI (which would be really easy to do in the final game because of the size of the maps), the player will never get hurt. It also makes it extremely difficult, almost impossible, to effectively balance these AI because if I balance them according to this current system, they will become punishingly difficult to deal with should the problem be figured out later down the line. This issue seems to be reflected across all enemy types, regardless of movement speed.
Over several threads, I’ve seemed to nail down the issue to being one of the AI not accurately reading the position of the player, with the position that they think the player is located lagging a few studs behind where the player actually is. This is pretty well-showcased by this picture I took here: the green dot is what the pathfinding module that I’m using (SimplePath) recognizes as the final waypoint, or the destination that the AI is meant to go. Notice how it’s behind me as I run away.
This essentially means that the AI’s target is not where I am, but where I was, which means that so long as I’m moving away from them, they will never catch up to me and so they will never hurt me. I don’t want to extend their hitboxes so they can hit me from where they are because as someone who enjoys playing games like the one I’m making, I’ve come to hate unreasonably large hitboxes, and I think the players of my game would too if I were to try to compensate in that way for a distance this large.
Below is the code for the small, fast-traveling enemy. The code is the same for all moving enemy types except for minor differences in recognizing size and such, and the script resides in the AI’s character model. I’ve been told that I should make it more centralized and use CollectionService, but I want to fix this problem first. If that’s what’s needed to fix the problem, then I’ll do it, but fixing this problem supersedes anything else right now.
-- services and modules
local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local SP = require(SS:WaitForChild("SimplePath"))
-- variables
local sprinter = script.Parent
local hum = sprinter:WaitForChild("Humanoid")
local HRP = sprinter:WaitForChild("HumanoidRootPart")
-- SimplePath path creation
local path = SP.new(sprinter)
path.Visualize = true
-- array of enemy names
local names = {}
for _, name in pairs(RS:WaitForChild("Enemies"):GetChildren()) do
table.insert(names, name.Name)
end
-- function to check if a humanoid is an enemy humanoid
function checkNames(query)
for _, name in pairs(names) do
if query == name then
return true
end
end
return false
end
-- function to find a target
function findTarget()
local aggro = 200
local target
local blocked = true
for _, character in pairs(workspace:GetChildren()) do
local human = character:FindFirstChild("Humanoid")
local RP = character:FindFirstChild("HumanoidRootPart")
if human and RP and human.Health > 0 and not checkNames(RP.Parent.Name) then
if (RP.Position - HRP.Position).Magnitude < aggro then
aggro = (RP.Position - HRP.Position).Magnitude
target = RP
local rayInfo = RaycastParams.new()
rayInfo.FilterType = Enum.RaycastFilterType.Blacklist
rayInfo.FilterDescendantsInstances = sprinter:GetChildren()
local hit = workspace:Raycast(HRP.Position, (RP.Position - HRP.Position).Unit * 200, rayInfo)
if hit.Instance == RP then
blocked = false
end
end
end
end
return target, blocked
end
-- main function, uses MoveTo() if there's nothing in the way and uses SimplePath if otherwise
while true do
wait()
local target, blocked = findTarget()
if target then
if not blocked then
print("not blocked")
hum:MoveTo(target.Position, workspace.Terrain)
elseif blocked then
path:Run(target)
end
end
end
Here is the documentation on the pathfinding module I’m using, SimplePath.
I’ve looked at the Developer Hub, especially the pathfinding section, changed from Roblox pathfinding to the pathfinding module above, used several tutorial AI from YouTube videos like those published by TheDevKing and Y3llow Mustang, and I’ve been told to do client-side pathfinding once but could only find one resource on it that I couldn’t understand how to manipulate for my situation. Any other potential solutions I would have to be told of before I could try them because of my relative inexperience with development. Please let me know if there’s anything else I can try.
tl;dr: my AI is supposed to catch me but it can’t because the position of the AI’s target is not where I am but where I was, and it needs to be able to catch me for the game I’m making. I’ve tried everything I can think of trying and nothing’s changed.