How could I improve my zombie script?

Hello! A while ago, I made a zombie script using magnitude.

Full Code
local humanoid = script.Parent.Humanoid
local primary = script.Parent.PrimaryPart
local debounce = false

humanoid.Died:Connect(function()
	wait(3)
	script.Parent:Destroy()
end)

primary.Touched:Connect(function(hit)
	if debounce == false then
		if hit.Parent:FindFirstChildWhichIsA("Humanoid") and humanoid:GetState() ~= Enum.HumanoidStateType.Dead and hit.Parent.Name ~= script.Parent.Name then
			debounce = true
			hit.Parent.Humanoid:TakeDamage(10)
			wait()
			debounce = false
		end
	end
end)

while wait() do
	for _, v in pairs(workspace:GetDescendants()) do
		if v.Parent:FindFirstChild("Humanoid") and v:IsA("Part") and game.Players:GetPlayerFromCharacter(v.Parent) ~= nil then
			if (primary.Position - v.Position).Magnitude <= 20 then
	            humanoid:MoveTo(v.Position)
	        end
		end
	end
end

Basically, if you would insert a script with this code inside a dummy, the script would check every descendant of workspace, and if there is a part that is 20 studs away from the primary part and is a child of a character, it would go to the character to attack it. Here’s a video I recorded showing what it does if you wanna see it in video:

https://i.imgur.com/tCftC2m.mp4

while wait() do
	for _, v in pairs(workspace:GetDescendants()) do
		if v.Parent:FindFirstChild("Humanoid") and v:IsA("Part") and game.Players:GetPlayerFromCharacter(v.Parent) ~= nil then
			if (primary.Position - v.Position).Magnitude <= 20 then
	            humanoid:MoveTo(v.Position)
	        end
		end
	end
end

Now this is the only part of the code that needs to improve because it checks every single descendant in workspace, and I think it checks a lot of parts; like too much. Any ideas on how I could improve?

I don’t recommend doing that as so many parts will touch and it would kill instantly so use few seconds in wait(n).

Yeah, you’re looking at some massive server-side slowdowns if you run GetDescendants on the Workspace every time the task scheduler resumes this thread. Bonus points because it seems like this script is meant to be placed in an NPC instead of being a single script that handles all NPCs.

A better idea might be to just iterate through the game’s players provided your enemies do not need to target other NPCs as well, judging from the code. You can then check for distance between the players’ characters if they have them and move the zombie over.

Worth noting as well that your script doesn’t arbitrarily switch targets to the next closest target if any enter within its search range.

Here’s my recommendation for player searching. I usually run this check every 0.1-0.3 seconds or include it in a node in a behaviour tree (and oh please stop using while wait). Do not copy and paste it as the entirety of your code. This is a suggestion and rough code, it’s up to you to implement it properly if you do end up using the code or at least the structure of it.

local Players = game:GetService("Players")

local SEARCH_RANGE = 20

local function findNearestPlayer()
    local nearbyPlayers = {}

    for _, player in ipairs(Players:GetPlayers()) do
        -- Skip handling this player if they have no character
        if not player.Character then continue end

        -- Get a HumanoidRootPart, skip handling if it's not there
        local humanoidRootPart = player.Character:FindFirstChild("HumanoidRootPart")
        if not humanoidRootPart then continue end

        -- Get distance between the NPC and HRP, skip if it's over SEARCH_RANGE
        local distanceAway = (primary.Position - humanoidRootPart.Position).Magnitude
        if distanceAway <= SEARCH_RANGE then continue end

        -- Track this player as they are nearby
        nearbyPlayers[#nearbyPlayers + 1] = {RootPart = humanoidRootPart, Distance = distanceAway}
    end

    -- Prematurely return nothing if the table is empty
    if #nearbyPlayers == 0 then
        return nil
    end

    -- Sort the player table by their distances; ignore contested distances
    table.sort(nearbyPlayers, function(first, second)
        return first.Distance < second.Distance
    end)

    -- Return the first element in the table, which'd be the lowest distance root
    return nearbyPlayers[1].RootPart
end

This function just returns the root, provided that’s all you need. Then it’s just a matter of moving the NPC towards the returned part if any is given.

local nearestRoot = findNearestPlayer()
if nearestRoot then
    humanoid:MoveTo(nearestRoot.Position)
end
2 Likes

You may want to loop through the player list instead of the descendants in the workspace. That is pretty expensive on performance, especially when you add more than one of them. Just a tip.

I guess this would help, but this won’t stop the problem where it looks through every descendant of workspace, which is too much parts.

Woah! This is a long and detailed response. This was interesting to read, so thanks!

I guess this would help, but there’s a lot of players, it would probably be too much.

Even if there’s a lot of players it still narrows the field looping the workspace contains all the builds you have including all the players.