So I’m attempting to script an NPC that will run to and attack the nearest player to it. The NPC will need pathfinding, as it has to go through obstacles, etc. As well, the nearest player to it could change, and it needs to respond to that change. So it can’t just pick the nearest player at one moment and then chase them down forever.
What I’ve got so far is essentially calculating the nearest player and a path to them, moving through a few points, and then recalculating the closest player and path. Although not the smoothest, it does work at getting the NPC close to the player. However, once the NPC is within about two studs of the player, the time it takes to calculate the path is long enough that by the time the NPC moves there, the player is no longer there.
I tried modifying the script so that when the NPC gets within about 5 studs, it changes to just a while true do loop with a humanoid:MoveTo(player.Torso.position) and whereas it’s a bit better, there’s the same problem. It’s moving the NPC to where the player was but by the time they get there, the player isn’t there, even if the NPC is ridiculously fast.
Side Note: When testing this, it’s important to use the “Network Simulation” feature in Studio Testing mode. Without it, it may appear as if it works, but it does not in-game.
So essentially, I need some way to have the NPC (once close) move a bit ahead in the direction the player is moving, and not to where they currently are. Humanoid movement is not really my specialty, so I guess it’s totally possible I’m doing this all wrong. Any help is much appreciated!
You could offset the target position by a few studs based on the direction a player is moving. To do this I would probably compare a players current position to their previous position x amount of time ago.
You would calculate it something like this (not tested):
local POLL_POSITION_WAIT = 0.5
local POSITION_OFFSET_STUDS = 3
local PlayerService = game:GetService("Players")
local PositionOffset = {}
local PreviousPosition = {}
spawn(function()
while wait(POLL_POSITION_WAIT) do
local players = PlayerService:GetPlayers()
for _, player in pairs(players) do
if player.Character then
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if hrp then
local pos = hrp.Position
if PreviousPosition[player] then
local offset = Vector3.new(0, 0, 0)
if (pos - PreviousPosition[player]).magnitude > 0.1 then
offset = (pos - PreviousPosition[player]).unit * POSITION_OFFSET_STUDS
end
PositionOffset[player] = offset
print(offset)
end
PreviousPosition[player] = pos
end
end
end
end
end)
And you could use PositionOffset[player] elsewhere in your code.
Yeah, I should have mentioned that. If you want to use a solution like this you should clear both the PreviousPosition and PositionOffset entries for a given player in the PlayerRemoving event.
I wrote a quick script to place a blue sphere where your function is offsetting to, and it resulted in a circle. Not quite sure why.
Note: I did make a couple edits to your script to fix minor typos, etc. I’ve posted it here just in case I messed something up
spawn(function()
while wait(POLL_POSITION_WAIT) do
local players = PlayerService:GetPlayers()
for _, player in pairs(players) do
if player.Character then
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if hrp then
local pos = hrp.Position
if PreviousPosition[player.UserId] then
local offset = Vector3.new(0, 0, 0)
if (pos - PreviousPosition[player.UserId]).magnitude > 0.1 then
offset = (pos - PreviousPosition[player.UserId]).unit * POSITION_OFFSET_STUDS
end
PositionOffset[player.UserId] = offset
local part = Instance.new('Part')
part.Size = Vector3.new(1,1,1)
part.CanCollide = false
part.Anchored = true
part.Material = 'Neon'
part.BrickColor = BrickColor.new('Really blue')
part.Shape = Enum.PartType.Ball
part.Position = PositionOffset[player.UserId]
part.Parent = workspace
else
table.insert(PositionOffset, player.UserId, pos)
table.insert(PreviousPosition, player.UserId, pos)
end
PreviousPosition[player.UserId] = pos
end
end
end
end
end)
game.Players.PlayerRemoving:Connect(function(p)
table.remove(PositionOffset, p.UserId)
table.remove(PreviousPosition, p.UserId)
end)
edit: accidentally posted slightly out-dated code
edit 2: just noticed the following in output:
(current is the position of the player, offset is the offset position)
You should add the offset to the players current position. The reason it results in a circle is because it offsets a set amount of studs in the direction that the player is moving.