For context before people start getting mad at me for not using pathfinding, I used moveto instead for these 2 reasons:
I’m still rather new to scripting, I want to start simple
I’d love to learn pathfinding but this is a quick project and it doesn’t really require npcs to pathfind (I’ll implement to learn it in the future since I’m a bit busy learning guis, backend scripting, datastores and everything at the same time)
As you can see, while the NPC moves towards me it starts teleporting / stuttering whatever you want to call it. This is my first ever NPC so I have no idea why it’s doing this
Modulescript for managing movement
local EnemyNPCModule = {}
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local npcConnections = {}
local npcStunStates = {} -- Table to keep track of stun states for each NPC
-- Function to get the nearest player within aggro range
function EnemyNPCModule.getNearestPlayerWithinAggroRange(npc, aggroRange)
local myRoot = npc:FindFirstChild("HumanoidRootPart")
if not myRoot then return nil end
local nearestPlayer = nil
local shortestDistance = aggroRange
for _, player in pairs(Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
local playerRoot = player.Character.HumanoidRootPart
local distance = (playerRoot.Position - myRoot.Position).magnitude
if distance < shortestDistance then
shortestDistance = distance
nearestPlayer = player
end
end
end
return nearestPlayer
end
-- Function to start chasing the player
function EnemyNPCModule.StartChase(npc, aggroRange, minDistance)
local myHuman = npc:FindFirstChild("Humanoid")
local myRoot = npc:FindFirstChild("HumanoidRootPart")
if not myHuman or not myRoot then return end
local isChasing = false
local connection
connection = RunService.Heartbeat:Connect(function()
-- Check if NPC is stunned
if npcStunStates[npc] and npcStunStates[npc].isStunned then
-- Stop movement if stunned
if isChasing then
myHuman:MoveTo(myRoot.Position) -- Stop moving
isChasing = false
end
return
end
local targetPlayer = EnemyNPCModule.getNearestPlayerWithinAggroRange(npc, aggroRange)
if targetPlayer and targetPlayer.Character and targetPlayer.Character:FindFirstChild("HumanoidRootPart") then
local targetRoot = targetPlayer.Character.HumanoidRootPart
local distance = (targetRoot.Position - myRoot.Position).magnitude
if distance > minDistance then
myHuman:MoveTo(targetRoot.Position)
isChasing = true
else
if isChasing then
myHuman:MoveTo(myRoot.Position) -- Stop moving if within the minimum distance
isChasing = false
end
end
else
if isChasing then
myHuman:MoveTo(myRoot.Position) -- Stop moving if no player is in aggro range
isChasing = false
end
end
end)
npcConnections[npc] = connection
end
-- Function to stop chasing
function EnemyNPCModule.StopChase(npc)
local connection = npcConnections[npc]
if connection then
connection:Disconnect()
npcConnections[npc] = nil
end
end
-- Function to set stun state for an NPC
function EnemyNPCModule.setStunState(npc, isStunned)
npcStunStates[npc] = { isStunned = isStunned }
end
return EnemyNPCModule
Serverscript inside the NPC (Only the parts I think might be causing the error)
local function performMove()
local now = tick()
local targetPlayer = EnemyNPCModule.getNearestPlayerWithinAggroRange(npc, aggroRange)
if targetPlayer and targetPlayer.Character and targetPlayer.Character:FindFirstChild("HumanoidRootPart") then
local myRoot = npc:FindFirstChild("HumanoidRootPart")
local targetRoot = targetPlayer.Character.HumanoidRootPart
if not myRoot or not targetRoot then return end
local distanceToPlayer = (targetRoot.Position - myRoot.Position).magnitude
-- Face the target player
myRoot.CFrame = CFrame.lookAt(myRoot.Position, targetRoot.Position)
local moveToPerform = nil
-- Choose a move based on cooldowns and distance
for moveName, moveData in pairs(moves) do
if moveData and now - moveData.lastUsed >= moveData.cooldown and distanceToPlayer <= moveData.distance then
moveToPerform = moveName
break
end
end
if moveToPerform then
if not moves[moveToPerform].canMoveWhileUsing then
isStunned = true
stunEndTime = now + moves[moveToPerform].stunTime
EnemyNPCModule.setStunState(npc, true) -- Set stun state in module
print(npc.Name .. " is stopping movement to use " .. moveToPerform)
end
-- Call the corresponding move function
if moveToPerform == "Move1" then
Move1()
elseif moveToPerform == "Move2" then
Move2()
end
-- Update the lastUsed time for the move that was performed
moves[moveToPerform].lastUsed = now
end
end
end
-- Function to handle NPC movement
local function handleMovement()
if isStunned then
if tick() > stunEndTime then
isStunned = false
EnemyNPCModule.setStunState(npc, false) -- Reset stun state in module
print(npc.Name .. " is resuming movement")
else
return -- Exit early if stunned
end
end
-- Movement logic here
if not isStunned then
EnemyNPCModule.StartChase(npc, aggroRange, minDistance)
end
end
-- Main loop to perform moves and handle movement
while true do
handleMovement()
performMove()
wait(1) -- Wait for 1 second between each check, adjust as needed
end
Yes I know I used some horrible methods to implement this but it’s just a learning process for me
Any advice is appreciated, if I need to provide the full script I can.
while true do
handleMovement()
performMove()
wait(1) -- Wait for 1 second between each check, adjust as needed
end
I know for a fact it’s something to do with this line because when I take the handlemovement() out of the loop and put it in a seperate while true it works fine. I have no idea how to fix the main issue tho just found the root cause
Maybe because :MoveTo() can’t be runned in a “HeartBeat”. Try this:
local connection
connection = function()
while task.wait(.1) do
-- Check if NPC is stunned
if npcStunStates[npc] and npcStunStates[npc].isStunned then
-- Stop movement if stunned
if isChasing then
myHuman:MoveTo(myRoot.Position) -- Stop moving
isChasing = false
end
return
end
local targetPlayer = EnemyNPCModule.getNearestPlayerWithinAggroRange(npc, aggroRange)
if targetPlayer and targetPlayer.Character and targetPlayer.Character:FindFirstChild("HumanoidRootPart") then
local targetRoot = targetPlayer.Character.HumanoidRootPart
local distance = (targetRoot.Position - myRoot.Position).magnitude
if distance > minDistance then
myHuman:MoveTo(targetRoot.Position)
isChasing = true
else
if isChasing then
myHuman:MoveTo(myRoot.Position) -- Stop moving if within the minimum distance
isChasing = false
end
end
else
if isChasing then
myHuman:MoveTo(myRoot.Position) -- Stop moving if no player is in aggro range
isChasing = false
end
end
end)
end
end
while task.wait(1) do
handleMovement()
performMove()
end
If that doesn’t work, try this:
local debounceConnectionWait1 = false
RunService.RenderStepped:Connect(function()
if not debounceConnectionWait1 then
debounceConnectionWait1 = true
handleMovement()
task.wait(.1)
performMove()
task.wait(1)
debounceConnectionWait1 = false
end
end)
while task.wait(1) do
handleMovement()
performMove()
end
made the delay slower. I think I know why it is happening. The performMove() function is a function I have made to check if the npc is in aggro distance of something and can attack it. Maybe running it in the same loop is the same issue?
-- Function to choose and perform a move
local function performMove()
local now = tick()
local targetPlayer = EnemyNPCModule.getNearestPlayerWithinAggroRange(npc, aggroRange)
if targetPlayer and targetPlayer.Character and targetPlayer.Character:FindFirstChild("HumanoidRootPart") then
local myRoot = npc:FindFirstChild("HumanoidRootPart")
local targetRoot = targetPlayer.Character.HumanoidRootPart
if not myRoot or not targetRoot then return end
local distanceToPlayer = (targetRoot.Position - myRoot.Position).magnitude
-- Face the target player
myRoot.CFrame = CFrame.lookAt(myRoot.Position, targetRoot.Position)
local moveToPerform = nil
-- Choose a move based on cooldowns and distance
for moveName, moveData in pairs(moves) do
if moveData and now - moveData.lastUsed >= moveData.cooldown and distanceToPlayer <= moveData.distance then
moveToPerform = moveName
break
end
end
if moveToPerform then
if not moves[moveToPerform].canMoveWhileUsing then
isStunned = true
stunEndTime = now + moves[moveToPerform].stunTime
EnemyNPCModule.setStunState(npc, true) -- Set stun state in module
print(npc.Name .. " is stopping movement to use " .. moveToPerform)
end
-- Call the corresponding move function
if moveToPerform == "Move1" then
Move1()
elseif moveToPerform == "Move2" then
Move2()
end
-- Update the lastUsed time for the move that was performed
moves[moveToPerform].lastUsed = now
end
end
end
issue is with the current way i structured this I have no idea how I’m going to get it out of this loop and still work
That looks like network ownership lag. Basically when an unanchored basepart goes near a player, its ownership will be assigned to the player. This applies to NPCs as well and it’s what you’re experiencing. Try setting the ownership of the primarypart (the HumanoidRootPart) of the npc to the server (nil).
npc.PrimaryPart:SetNetworkOwner(nil);
OR
npc.HumanoidRootPart:SetNetworkOwner(nil);
You can also set the ownership of every basepart of the NPC, in case only the HRP doesn’t work:
for _, descendant in ipairs(npc:GetDescendants()) do
if descendant:IsA("BasePart") == false then continue end;
descendant:SetNetworkOwner(nil);
end;
Doesn’t seem to fix it. I should clarify again that the
while task.wait(1) do
handleMovement() --ThisPart handles the movement of the npc
performMove() - This part handles the npc using attacks, and aggroing to me
end
When I remove performMove() from that loop it works fine (but breaks the rest of the script because the npc no longer uses attacks of course)
It think the issue might be related to it running the entire performMove() function then redoing the loop.
yapping? you literally said your self you though the problem was in a certain part. Alright then. We tried helping and now is helping the issue? Sorry, like I said, you told us the part you though was wrong was the waiting or the loop
I’m not pressed but it’s a hate people saying these things like we are useless or like we didn’t try to help at all