You can write your topic however you want, but you need to answer these questions:
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
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 maxCharacterRadius = 15
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
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.
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
--Not sure about this???
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
task.wait(checkPeriod)
end
repeat task.wait() until moveToFinished
end
else
task.wait(1)
walkRandomly()
end
end
while true do
walkRandomly()
task.wait(3)
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
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?
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
task.spawn(function()
while true do
walkRandomly()
task.wait(3)
end
end)
task.spawn(function()
while true do
searchForTargets()
task.wait(0.5)
end
end)
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.