Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
COMMUNITY QUESTION

Zombie NPC keeps trying to follow another Zombie NPC with the same name?

I'm trying to practice OOP with zombies. What it's supposed to do is check if a player is around them, NOT an NPC. It will check the magnitude of each player's characters, and chooses the character model with the least magnitude. Then it chases that player using pathfinding service. However, instead of doing that, it just moves to another NPC with the same name. I am using 2 module scripts and a regular script to make it work.

Script (Placed in ServerScriptService):

local replicatedStorage = game:GetService("ReplicatedStorage")
local zombieModel = workspace.Zombie
local Zombie = require(game:GetService("ServerScriptService").Modules.NPCModule.ZombieModule)

local newZombie = Zombie.new(zombieModel, "Zombie", workspace.Ground.Position + Vector3.new(0,10,0))
local model = newZombie.Model
local zombiePosition = model.HumanoidRootPart.Position
local targetPosition = nil

model.Humanoid.WalkSpeed = newZombie.Speed
model.Humanoid.Health = newZombie.Health
model.HumanoidRootPart.Position = newZombie.SpawnLocation
model.Parent = workspace

game.Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		print("reached")
		game:GetService("RunService").Heartbeat:Connect(function()
			targetPosition = newZombie:GetClosestTarget()
			print(targetPosition)
			if targetPosition and character.HumanoidRootPart.Position == targetPosition then
				newZombie:FindTarget(zombiePosition, targetPosition)
			end
		end)
	end)
end)

ZombieModule (Module script placed in ServerScriptService under a module called "NPCModule"):

local NPC = require(script.Parent)
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local PFS = game:GetService("PathfindingService")

local Zombie = {}
Zombie.__index = Zombie

function Zombie.new(model, zombieType, position)
	local newZombie = NPC.new(model, position)
	setmetatable(newZombie, Zombie)
	
	if zombieType == "Zombie" then
		print("is zombie")
		newZombie.Speed = 15
		newZombie.ZombieType = zombieType
		print(newZombie.Model.Name)
		print(newZombie.Model.HumanoidRootPart.Name)
	end
	
	return newZombie
end

function Zombie:GetClosestTarget()
	local model = self.Model
	if model then
		print("not nil!")
	end
	local target = nil
	local lowestMagnitudeTarget = nil
	local lowestMagnitude = 99999999999999999
	local targetPosition = nil
	local zombiePosition = model.HumanoidRootPart.Position
	
	for i, player in pairs(game.Players:GetPlayers()) do
		local characterModel = player.Character
		local hrpPosition = characterModel.HumanoidRootPart.Position
		print(hrpPosition)
		local magnitude = (hrpPosition - zombiePosition).Magnitude

		if magnitude < lowestMagnitude and game.Players:GetPlayerFromCharacter(characterModel)then
			lowestMagnitude = magnitude
			lowestMagnitudeTarget = hrpPosition
		end
	end
	
	target = lowestMagnitudeTarget
	return target
end

function Zombie:FindTarget(zombiePosition, targetPosition)
	local model = self.Model
	local zombieHumanoid = model.Humanoid
	
	local path = PFS:CreatePath({
		AgentRadius = 2,
		AgentHeight = 5,
		AgentCanJump = true,
		AgentCanClimb = true,
		Costs = {
			Water = 20
		}
	})
	
	while true do
		path:ComputeAsync(zombiePosition, targetPosition)
		if path.Status == Enum.PathStatus.Success then
			for i, waypoint in pairs(path:GetWaypoints()) do
				if waypoint.Action == Enum.PathWaypointAction.Jump then
					zombieHumanoid:ChangeState(Enum.HumanoidStateType.Jumping)
				end
				zombieHumanoid:MoveTo(waypoint.Position)
				zombieHumanoid.MoveToFinished:Wait()
			end
		end
	end
end

return Zombie

NPCModule (Module script placed in ServerScriptService):

local NPC = {}
NPC.__index = NPC

function NPC.new(model, position)
	local newNPC = {}
	setmetatable(newNPC, NPC)
	
	newNPC.Model = model:Clone()
	newNPC.Health = 100
	newNPC.Speed = 16
	newNPC.InteractionDistance = 2
	newNPC.SpawnLocation = position
	
	return newNPC
end

return NPC

As always, thank you for your time.

1 comment

0

I usually add a BoolValue named "NPC" in any NPCs. You can use them to check using "if not Zombie:FindFirstChild("NPC") then" or something. Another idea is to use "game.Players:GetPlayerFromCharacter(model)" when checking for players so that if it isn't a player, it returns nil

1 answer

hiya!

i'm not all too sure myself as to why you're code isn't working, although i thought cleaning it up would help; hopefully give a new aspect to the code

changes

  • changed how the zombie checks for the closest player
  • minified the amount of variables used in Zombie:GetClosestTarget()
  • added "AdvancedAI" function to zombie module

script in ServerScriptService

local replicatedStorage = game:GetService("ReplicatedStorage")
local zombieModel = workspace.Zombie
local Zombie = require(game:GetService("ServerScriptService").Modules.NPCModule.ZombieModule)

local newZombie = Zombie.new(zombieModel, "Zombie", workspace.Ground.Position + Vector3.new(0,10,0))
local model = newZombie.Model
local zombiePosition = model.HumanoidRootPart.Position
local targetPosition = nil

model.Humanoid.WalkSpeed = newZombie.Speed
model.Humanoid.Health = newZombie.Health
model.HumanoidRootPart.Position = newZombie.SpawnLocation
model.Parent = workspace

game:GetService("RunService").Heartbeat:Connect(newZombie.AdvancedAI)

zombie module

local NPC = require(script.Parent)
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local PFS = game:GetService("PathfindingService")

local Zombie = {}
Zombie.__index = Zombie

function Zombie.new(model, zombieType, position)
    local newZombie = NPC.new(model, position)
    setmetatable(newZombie, Zombie)

    if zombieType == "Zombie" then
        print("is zombie")
        newZombie.Speed = 15
        newZombie.ZombieType = zombieType
        print(newZombie.Model.Name)
        print(newZombie.Model.HumanoidRootPart.Name)
    end

    return newZombie
end

function Zombie:AdvancedAI()
    local ClosestTarget = self:GetClosestTarget()
    if not ClosestTarget then
        -- warn("unable to identify a close target - wth")
        return false -- fail :(
    end
    self:FindTarget(self.Model.HumanoidRootPart.Position, ClosestTarget.HumanoidRootPart.Position)
    return true -- success!
end

function Zombie:GetClosestTarget()
    assert(self.Model, string.format("no model exists! (got %s)", tostring(self.Model or nil)))
    --[[
    if you don't fancy a hard error, here's a less ASSERTIVE assert:

    if not self.Model then
        -- end the call
        return
    end
    ]]
    local target = nil
    local lowestMagnitudeTarget = nil
    local lowestMagnitude = 12^12 -- aka a big hecking number
    local zombiePosition = self.Model.HumanoidRootPart.Position

    for i, player in pairs(game.Players:GetPlayers()) do
        local characterModel = player.Character
        local magnitude = (characterModel.HumanoidRootPart.Position - zombiePosition).Magnitude

        if magnitude < lowestMagnitude then
            lowestMagnitude = magnitude
            lowestMagnitudeTarget = characterModel
        end
    end

    return lowestMagnitudeTarget
end

function Zombie:FindTarget(zombiePosition, targetPosition)
    local model = self.Model
    local zombieHumanoid = model.Humanoid

    local path = PFS:CreatePath({
        AgentRadius = 2,
        AgentHeight = 5,
        AgentCanJump = true,
        AgentCanClimb = true,
        Costs = {
            Water = 20
        }
    })

    while true do
        path:ComputeAsync(zombiePosition, targetPosition)
        if path.Status == Enum.PathStatus.Success then
            for i, waypoint in pairs(path:GetWaypoints()) do
                if waypoint.Action == Enum.PathWaypointAction.Jump then
                    zombieHumanoid:ChangeState(Enum.HumanoidStateType.Jumping)
                end
                zombieHumanoid:MoveTo(waypoint.Position)
                zombieHumanoid.MoveToFinished:Wait()
            end
        end
    end
end

return Zombie

This discussion is preserved as it was. New answers, comments, and votes are closed.