Im currently working on a zombie.
Its working perfectly fine when I test it in singleplayer, but when I test it in a local server with 2 players, the zombie is doing nothing.
Also, when Im not addind the CharacterAdded:Wait() in the char declaration, I become an error for the HRT. Using WaitForChild is also not working.
The code:
function findTarget()
for i, v in pairs(game.Players:GetPlayers()) do
local char = v.Character or v.CharacterAdded:Wait() --With only v.Character, the Script errors
if char then
local HRT = char.HumanoidRootPart
local distance = (script.Parent.Torso.Position - HRT.Position).Magnitude
if distance < maxDistance then
return v
else
return nil
end
end
end
end
I think it would be best to skip over the player if they have no character.
if v.Character then
-- Rest of code
end
I’m also a bit suspicious about the returning in the loop. When return is ran, the loop basically finishes.
If the first player joins out of the range of the zombie, it should move if they go into its range. But when a second player joins and the first player goes out of range, the zombie still loops through the players in order from Player1. So if Player1 is out of range and the loop returns nil, the loop is cancelled before it can check Player2’s distance from it, making the zombie only work for Player1. Hopefully you understand what I’m getting at.
Anyway, a better approach would be to use table.sort like this:
function FindTarget()
--// Adding everyone and their distances from the zombie to the table
local distances = {}
for _, player in game.Players:GetPlayers() do
local character = player.Character
if not character then continue end
local root = character:FindFirstChild("HumanoidRootPart")
if not root then continue end
local zombieRoot = --insert its hrp here. would personally use oop lol
local magnitude = (zombieRoot - root.Postion).Magnitude
table.insert(distances, {
Player = player,
Magnitude = magnitude
})
end
--// Sorting the table
table.sort(distances, function(a, b)
return a.Magnitude < b.Magnitude -- Or other arrow sign, forgor
end)
--// Returning the closest player (returns nil if there is nobody in range)
local closestPlayer = distances[1]
return closestPlayer
end
Yeah you are right, thank you!
But I preferred using another method than you, but could that cause problems?
EDIT: After a bit testing, its not working, but why?
function findTarget()
local distancesTab = {}
local nearestDis = maxDistance
local nearestPlr = nil
for i, v in pairs(game.Players:GetPlayers()) do
local char = v.Character
if char then
local HRT = char.HumanoidRootPart
local distance = (script.Parent.Torso.Position - HRT.Position).Magnitude
if distance < maxDistance then
table.insert(distancesTab, {distance, v})
end
end
end
for i, v in pairs(distancesTab) do
if v[1] < nearestDis then
nearestDis = v[1]
nearestPlr = v[2]
end
end
if nearestPlr ~= nil then
return nearestPlr
end
end
You should return out of the loop and the way you calculate distance is not right. Try this:
function findTarget()
local closestPlayer = nil
local minDistance = math.huge
for i, v in pairs(game.Players:GetPlayers()) do
local char = v.Character or v.CharacterAdded:Wait()
if char then
local HRT = char.HumanoidRootPart
local distance = (script.Parent.Torso.Position - HRT.Position).Magnitude
if distance < minDistance then
minDistance = distance
closestPlayer = v
end
end
end
return closestPlayer
end
function findTarget()
local distancesTab = {}
local nearestDis = maxDistance
local nearestPlr = nil
for i, v in pairs(game.Players:GetPlayers()) do
local char = v.Character
if char then
local HRT = char.HumanoidRootPart
local distance = (script.Parent.Torso.Position - HRT.Position).Magnitude
if distance < maxDistance then
table.insert(distancesTab, {distance, v})
end
end
end
table.sort(distancesTab, function(a, b)
return a[1] < b[1]
end)
if #distancesTab ~= 0 then
nearestPlr = distancesTab[1][2]
table.clear(distancesTab)
return nearestPlr
else
return nil
end
end
And this works now completely fine, so the Script detects which plr is the nearest. HOWEVER, if both player are getting hitted by the zombie, then if the zombie hits only one of them, both get damaged. That doesnt makes sense… Heres the Part of my Script which is dealing with that:
runService.Heartbeat:Connect(function(t)
local target = findTarget() --Getting the target through the function
if target ~= nil and target.Character.Humanoid.Health > 0 then --Checking if the player is valid to be followed
local char = target.Character
print(char) --Printing the Name of the Player (Prints what it should!!!)
if char then --Checking if char exists
local HRT = char.HumanoidRootPart
local distance = (script.Parent.Torso.Position - HRT.Position).Magnitude --Getting distance for attack
if distance < attackDistance and attacking == false then --Attack block
attacking = true
hitTrack:Play()
hitTrack.KeyframeReached:Connect(function(keyName) --If the KeyFrame in the Attack Animation is reached (still works fine)
if keyName == "Hit" then
char.Humanoid:TakeDamage(damage) --Dealing damage to the plr
end
end)
hitTrack.Ended:Connect(function()
attacking = false --Making attacking possible again when the animation ended
end)
end
hum:MoveTo(HRT.Position, HRT) --Moving the Zombie
if moving == false then --Here the Moving Animation Part starts
walkTrack:Play()
moving = true
end
end
else
walkTrack:Stop() --If target is not valid to follow, make the zombie stop
moving = false
hum:MoveTo(script.Parent.Torso.Position)
end
end)
I dont understand that. I mean I am only accessing the char of the plr who is the target. Cant see how that event is in connection with that bug. And if it would be so, how could I prevent it?
You must understand inside the heartbeat code is running multiple times. If both players are near the zombie you will open multiple function connections.
But the way you doing this is crazy. There are better ways to detect if the zombie arm touched the player. There is no need to find the nearest player, also I don’t know why you do this inside Hearbeat.
Just add this script inside the zombie model:
model = script.Parent
local canHit = true
local lastHit = os.time()
local damageCoolDown = 1.5 -- Set cooldown
local damageMuch = 1 -- Set how much damage
local function handleHit(other)
if canHit then
if other and other.Parent and other.Parent.Name ~= "Zombie" and other.Parent:FindFirstChild("Humanoid") then
local enemy = other.Parent
if enemy.Humanoid.WalkSpeed > 0 then
enemy.Humanoid.Health = enemy.Humanoid.Health - damageMuch
canHit = false
end
end
else
local now = os.time()
if now - lastHit > damageCoolDown then
lastHit = now
canHit = true
end
end
end
-- Connect the function to each arm, so if player touch the arm it does damage.
local leftHitConnect, rightHitConnect
leftHitConnect = model:FindFirstChild("Left Arm").Touched:connect(handleHit)
rightHitConnect = model:FindFirstChild("Right Arm").Touched:connect(handleHit)
Im using the Hearbeat event and the distance track to make the zombie chase the player. Distance track to get the nearest player and hearbeat event to update :MoveTo.
I also have some problems with using your method. .Touched events are not that safe everytime because they often fail to fire when used often. I would also need to have track of both arms and in addition, I cant set the hitbox size.
And I have to say that I still dont understand how both players are damaged. The char which is getting damaged cant be like duplicated and the event is only fired once when the dummy attacks.
I never had problem with Touched events, but I don’t know your case. You can connect the same function to both arms. It is possible to have an invisible part as hitbox.
Anyway, if you wanna stick with your method try to print and see if multiple connections are being created.
if keyName == "Hit" then
print("Who is damaged:",char)
char.Humanoid:TakeDamage(damage) --Dealing damage to the plr
end