How to slow player speed when close to an npc or enemy

You can write your topic however you want, but you need to answer these questions:

1. What do you want to achieve? Keep it simple and clear!
I want to make an NPC that can slow down player movement when close
2. What is the issue? Include screenshots / videos if possible!
I don’t know how to implement it since my NPC walk randomly and has myHuman.MoveToFinished:Wait() this will break the loop or check if close to the player

here is my code

``````local function findtarget()
local smallestDistance, target = math.huge, nil
local getplayers = game:GetService("Players")
local Players = {

}
for k,player in ipairs(getplayers:GetChildren()) do

local character = player.Character
table.insert(Players, character)
end
for i, v in ipairs(Players) do
local humanoidRootPart = v:FindFirstChild('HumanoidRootPart')
if not humanoidRootPart then continue end
local currentMagnitude = (myRoot.Position - humanoidRootPart.Position).Magnitude
if (currentMagnitude < smallestDistance) and (currentMagnitude < maxCharacterRadius) then
smallestDistance, target = currentMagnitude, v
end
end
return target

end

function walkRandomly()
local xoff = math.random(5, 10)
if math.random() > .5 then
xoff = xoff * -1
end
local zoff = math.random(5, 10)
if math.random() > .5 then
zoff = zoff * -1
end
local goal = Vector3.new(bot.Torso.Position.X + xoff,bot.Torso.Position.Y,bot.Torso.Position.Z + zoff)
findPath:ComputeAsync(myRoot.Position, goal)
local waypoints = findPath:GetWaypoints()
if findPath.Status == Enum.PathStatus.Success then
for _, waypoint in ipairs(waypoints) do

-- this is not the best method so that why I need help
--because we need to wait for the bot to reach the next waypoint (myHuman.MoveToFinished:Wait()) before scan the nearest target
--its will take more than 2-4 seconds because my bot walkspeed is 3
local target = findtarget()
if target  then
target.Humanoid.WalkSpeed = 3
else
--will be error because target is nil
--Idk how to reset player speed after player is outside the range
target.Humanoid.WalkSpeed = 13
end

myHuman:MoveTo(waypoint.Position)
myHuman.MoveToFinished:Wait()

end
else
wait(1)
walkRandomly()
end
end

while true do
walkRandomly()
wait(3)
end

``````
2 Likes

Try changing the Speed value in the function up there so if the magnitude is greater it returns to normal speed value, or use return at the end of the function if the player is close or far from the npc and if the value is positive it gives slow , negative return to normal speed.

3 Likes

Here’s a solution that uses your current approach:

``````local Players = game:GetService("Players")

local FIND_TARGET_RANGE = 15
local TARGET_SLOWED_WALKSPEED = 3

local currentTargetDefaultWalkspeed
local currentTarget

function findNearestCandidate()
local candidates = {}
for _, player in ipairs(Players:GetPlayers()) do
table.insert(candidates, player.Character)
end

local smallestDistance, target = math.huge, nil
for _, candidate in ipairs(candidates) do
local humanoidRootPart = candidate:FindFirstChild('HumanoidRootPart')
if not humanoidRootPart then continue end

local distance = (myRoot.Position - humanoidRootPart.Position).Magnitude
if (distance < smallestDistance) and (distance < FIND_TARGET_RANGE) then
smallestDistance, target = distance, candidate
end
end

return target
end

function randomSign()
return math.random() < 0.5 and -1 or 1
end

function unsetTarget()
assert(currentTarget ~= nil)

local humanoid = currentTarget:FindFirstChild("Humanoid")
if humanoid then
humanoid.WalkSpeed = currentTargetDefaultWalkspeed
currentTargetDefaultWalkspeed = nil
else
warn("Figure out how to deal with this!")
end

currentTarget = nil
end

function setTarget(target)
if target == currentTarget then
--Otherwise currentTargetDefaultWalkspeed would be overwritten D:
return
end

if currentTarget then
unsetTarget()
end

currentTarget = target

local humanoid = currentTarget:FindFirstChild("Humanoid")
if humanoid then
currentTargetDefaultWalkspeed = humanoid.WalkSpeed
humanoid.WalkSpeed = TARGET_SLOWED_WALKSPEED
else
warn("Figure out how to deal with this!")
end
end

function walkRandomly()
local offset =
Vector3.xAxis * math.random(5, 10) * randomSign() +
Vector3.zAxis * math.random(5, 10) * randomSign()
local goal = bot.Torso.Position + offset

findPath:ComputeAsync(myRoot.Position, goal)
local waypoints = findPath:GetWaypoints()

if findPath.Status == Enum.PathStatus.Success then
for _, waypoint in ipairs(waypoints) do
--This works even if the target-finding loop below isn't done when MoveToFinished fires
local moveToFinished, c = false
c = myHuman.MoveToFinished:Connect(function()
moveToFinished = true
c:Disconnect()
end)

myHuman:MoveTo(waypoint.Position)

--Do target-finding lots of times between waypoints
local checkPeriod = 1/2
local timeToWaypoint = (waypoint.Position - myRoot.Position).Magnitude / myHuman.WalkSpeed
timeToWaypoint *= 0.9 --Make it a biiit smaller because I often find that MoveToFinished fires a bit before actually reaching the waypoint
for t = 0, timeToWaypoint, checkPeriod do
local newTarget = findNearestCandidate()
setTarget(newTarget) --Calls unsetTarget if currentTarget is not nil, and does nothing if newTarget is same as currentTarget

end

end
else
walkRandomly()
end
end

while true do
walkRandomly()
end
``````

I modified a few other things just while trying to understand your code, but you can ignore everything except the stuff in the waypoint-following loop and the setTarget and unsetTarget functions.

This approach works okay if you really want it so that the enemy only slows down players while the enemy itself is walking to a waypoint. If you want it to always slow down players, it’s better to use a less “loop based” approach than this.

There’s also the issue of applying/removing effects by just setting a variable. The setTarget/unsetTarget functions make sure that the “default” walkspeed is remembered so the target’s walkspeed can be reset when the enemy no longer applies it’s effect. But this doesn’t work if there’s anything else in the entire game that can modify the target’s walkspeed, including other identical enemies. This problem can be solved in a few ways, like with FSMs or PDAs. That’s a whole topic though, let me know if you’d like an example of how that can be applied here

3 Likes

thank you its works very well, and that’s very smart
yea I think so, I should not only scan the player when the NPC walking,
may I know what is that means by less “loop based”? should I use an event instead of a loop? for example a touch event maybe?

2 Likes

Hmm not necessarily, I’m not 100% sure what I meant actually xD

But right now there’s just a single loop running in the entire script. Since you want several things to happen at once, IMO it makes more sense to have several loops running, one for each thing. So I’d make one for walking and one for scanning for nearby players. E.g.

``````--Both loops run at the same time
while true do
walkRandomly()
end
end)

while true do
searchForTargets()
end
end)
``````
1 Like

ouh true, I also thought that way, but doesn’t that gonna hurt the server? because I want to spawn multiple NPC, not just 1, bigger round = more NPC

1 Like

Hmm, I don’t know how having more threads doing less work each affects performance vs having one thread do all the work (assuming the total work is the same, ofc).

So far there’s very little going so I don’t think it would be an issue.

1 Like

all right, thank you so much for your help @ThanksRoBama , I learn something new

2 Likes