I have a question regarding enemy NPC “AI”. How can I go about creating a system that updates the player’s position every 2 seconds (or so). I know this seems like an amateur-ish question, but I’m still kinda starting out. Any help or guidance would be greatly appreciated.
If you only want a system that updates the player’s pos every 2 seconds you can use a task.spawn() to make the script not pause while it’s looping and a while true loop to detect if players are nearby every 2 seconds.
local PlayerService = game:GetService("Players")
task.spawn(function()
while true do
for _,aPlayer in PlayerService:GetPlayers() do
if aPlayer.Character then -- checks if player has a character
local HumRoot = aPlayer.Character.HumanoidRootPart
-- update position
end
end
task.wait(2) -- waits 2 seconds
end
end)
-- your other code
Looking at your profile you seem pretty new. I would suggest you not make an Ai system as your first project as it will get very complicated. You’re free to do whatever though.
Not sure if you mean like ChatGpt for “Ai”, but Here’s a simple, Roblox-friendly way to “update the player’s position every ~2 seconds” on the server, and something tiny for melee enemies to use it.
1) Server tracker (drop in ServerScriptService)
Stores each character’s last position in a Character Attribute (LastPos) every ~2s.
Auto-handles respawns.
-- ServerScriptService/PlayerPositionTracker.server.lua
local Players = game:GetService("Players")
local function trackCharacter(char: Model)
local hrp = char:WaitForChild("HumanoidRootPart")
char:SetAttribute("LastPos", hrp.Position)
task.spawn(function()
while char.Parent and hrp.Parent do
char:SetAttribute("LastPos", hrp.Position)
task.wait(2 + math.random() * 0.5) -- small jitter to avoid spikes
end
end)
end
local function onPlayer(p: Player)
if p.Character then trackCharacter(p.Character) end
p.CharacterAdded:Connect(trackCharacter)
end
Players.PlayerAdded:Connect(onPlayer)
for _, p in ipairs(Players:GetPlayers()) do onPlayer(p) end
2) Using it in melee AI (tiny example)
Every ~2s, pick the nearest player by their LastPos (falls back to live HRP position if attribute isn’t set yet).
Recompute a path only on that cadence (keeps it cheap).
-- Script inside your Enemy model (server)
local PathfindingService = game:GetService("PathfindingService")
local enemy = script.Parent
local hum = enemy:WaitForChild("Humanoid")
local root = enemy:WaitForChild("HumanoidRootPart")
local SEARCH_RANGE = 120
local MELEE_RANGE = 4
while task.wait(2 + math.random() * 0.5) do
local closestChar, closestPos, closestDist = nil, nil, math.huge
for _, plr in ipairs(game:GetService("Players"):GetPlayers()) do
local char = plr.Character
local hrp = char and char:FindFirstChild("HumanoidRootPart")
if hrp then
local pos = char:GetAttribute("LastPos") or hrp.Position
local d = (root.Position - pos).Magnitude
if d < closestDist and d <= SEARCH_RANGE then
closestChar, closestPos, closestDist = char, pos, d
end
end
end
if closestPos then
if closestDist <= MELEE_RANGE then
hum:MoveTo(root.Position) -- stop & swing
-- play attack here
else
local path = PathfindingService:CreatePath()
path:ComputeAsync(root.Position, closestPos)
for _, wp in ipairs(path:GetWaypoints()) do
hum:MoveTo(wp.Position)
hum.MoveToFinished:Wait(0.5)
end
end
end
end
That’s it: one server tracker updating every ~2 seconds, and enemies that read it on the same cadence. This avoids heavy per-frame work and is perfectly fine for melee AI.
This was the point that I left off on. It’s not perfect, but it’s closer to what I want.
local npc = workspace:WaitForChild("Rig")
local npcHumanoid = npc:WaitForChild("Humanoid")
local npcRoot = npc:WaitForChild("HumanoidRootPart")
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
-- Settings
local DETECTION_RADIUS = 50 -- How close the player must be for the monster to chase
local UPDATE_INTERVAL = 2 -- How often to update the path (seconds)
local CHECK_PLAYERS_INTERVAL = 0.5 -- How often to check for nearby players
local chasingAnimationId = "http://www.roblox.com/asset/?id=10921336997"
-- Load chasing animation once
local chasingAnimation = Instance.new("Animation")
chasingAnimation.AnimationId = chasingAnimationId
local animationTrack = npcHumanoid:LoadAnimation(chasingAnimation)
local chasingPlayer = nil
local lastUpdateTime = 0
-- Function to get closest player in range
local function getNearestPlayerInRange()
local closestPlayer = nil
local shortestDistance = DETECTION_RADIUS
for _, player in pairs(Players:GetPlayers()) do
local char = player.Character
if char and char:FindFirstChild("HumanoidRootPart") then
local distance = (npcRoot.Position - char.HumanoidRootPart.Position).Magnitude
if distance <= shortestDistance then
shortestDistance = distance
closestPlayer = player
end
end
end
return closestPlayer
end
-- Function to move towards player using pathfinding
local function updatePathToPlayer(player)
if not player.Character or not player.Character:FindFirstChild("HumanoidRootPart") then return end
local playerRoot = player.Character.HumanoidRootPart
local path = PathfindingService:CreatePath()
path:ComputeAsync(npcRoot.Position, playerRoot.Position)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
if #waypoints > 1 then
npcHumanoid:MoveTo(waypoints[2].Position)
elseif #waypoints == 1 then
npcHumanoid:MoveTo(waypoints[1].Position)
end
end
end
-- Main loop
RunService.Heartbeat:Connect(function(dt)
-- Look for player if not currently chasing
if not chasingPlayer then
chasingPlayer = getNearestPlayerInRange()
if chasingPlayer then
animationTrack:Play()
lastUpdateTime = 0 -- Reset timer
end
else
-- Check if player is still valid and in range
local char = chasingPlayer.Character
local playerRoot = char and char:FindFirstChild("HumanoidRootPart")
if not char or not playerRoot or (npcRoot.Position - playerRoot.Position).Magnitude > DETECTION_RADIUS then
chasingPlayer = nil
animationTrack:Stop()
npcHumanoid:MoveTo(npcRoot.Position)
return
end
-- Update path every 2 seconds
lastUpdateTime += dt
if lastUpdateTime >= UPDATE_INTERVAL then
lastUpdateTime = 0
updatePathToPlayer(chasingPlayer)
end
end
end)
For anyone who’s interested, I got the script to work better
-- Inside of the enemy npc
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local npc = script.Parent
local humanoid = npc:WaitForChild("Humanoid")
local hrp = npc:WaitForChild("HumanoidRootPart")
-- Function to get the closest player
local function getClosestPlayer()
local closestPlayer = nil
local shortestDistance = 100 -- # of studs the npc detects players
for _, player in ipairs(Players:GetPlayers()) do
local character = player.Character
if character and character:FindFirstChild("HumanoidRootPart") then
local distance = (character.HumanoidRootPart.Position - hrp.Position).Magnitude
if distance < shortestDistance then
shortestDistance = distance
closestPlayer = character
end
end
end
return closestPlayer
end
-- Main loop
while true do
task.wait(0.1)
local target = getClosestPlayer()
if not target then continue end
local targetHRP = target:FindFirstChild("HumanoidRootPart")
if not targetHRP then continue end
-- Compute path
local path = PathfindingService:CreatePath()
path:ComputeAsync(hrp.Position, targetHRP.Position)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints(task.wait(1.5))
for _, waypoint in ipairs(waypoints) do
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Connect(function()
task.wait(0.1)
end)
if not reached then break end
end
else
warn("Path failed to compute")
end
end