How to make a zombie

Welcome to my first tutorial!

Note: I’m not too good at English

Today, we will make a smart zombie, that detects the nearest player, attacks them, animation.
Without any further ado, let’s jump right into it

1. Create a dummy

Open the plugins tab, then find “Build Rig”
Click it, and use R6 or R15.
For me, I want R6

you can customize it, but for me, I’ll leave it as it is.

2. Create a hitbox

First, we need to create a hitbox, this hitbox will damage the player if the player is inside of it.

Cover all the body of the dummy

Make the Transparency = 1, CanCollide = false and Anchored = true
Then, the hitbox will be invisible

Next, weld the Hitbox to the character’s HumanoidRootPart
Create a weld in the HumanoidRootPart, set Part0 to HumanoidRootPart and Part1 to Hitbox.

3. Scripting time!!

Firstly, let’s create a server script in the dummy.

And then, let’s make some variable

local PathfindingService = game:GetService("PathfindingService")
local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")

we will use PathfindingService to find a path to the player, we are using some WaitForChild, just to be safe.

Next, we need to create a function that gives us the nearest player.

function getClosestPlayer()
	local Players = game.Players:GetPlayers()
	local nearestPlayer = Players[1]
	
	if #Players > 1 then
		for i = 2, #Players do
			if nearestPlayer.Character == nil then
				nearestPlayer.CharacterAdded:Wait()
			end
			
			if Players[i].Character == nil then
				Players[i].CharacterAdded:Wait()
			end
			
			local distance1 = (nearestPlayer.Character.HumanoidRootPart.Positioon - rootPart.Position).Magnitude
			local distance2 = (Players[i].Character.HumanoidRootPart.Position - rootPart.Position).Magnitude
			
			if distance2 < distance1 then
				nearestPlayer = Players[i]
			end
		end
	end
	
	return nearestPlayer
end

What did this function do?
well, It will create the nearest player variable. for the first time, it will set it to the first player. After that, it will do a for loop if the player count is greater than 1. Then, It will compare the current nearest player position to a new one, if the current one is greater than the new one then overwrites it, and then that function will return the closest player.

next, create a while loop to repeatedly find a new path and make it walk towards the closest player.

coroutine.wrap(function()
	while wait() do
		local closestPlayer = getClosestPlayer()
		
		if closestPlayer.Character == nil then
			closestPlayer.CharacterAdded:Wait()
		end
		
		path:ComputeAsync(rootPart.Position, closestPlayer.Character.HumanoidRootPart.Position)
		
		local waypoints = path:GetWaypoints()
			
		for _, waypoint in pairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	end
end)()

What this will do?
It will compute the path that we just created, and then move the humanoid into each waypoint.

Next, we need to create a region3 that will detect if the player is inside the hitbox.

First, create a while loop

while wait() do

end

now, let’s create the hitbox variable at the top of the script!

local PathfindingService = game:GetService("PathfindingService")
local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")
local hitbox = character:WaitForChild("Hitbox") -- This is that we just created

Let’s begin detecting players!

local debounce = false

while wait() do
	local found = false
	
	local region = Region3.new(hitbox.Position - (hitbox.Size / 2), hitbox.Position + (hitbox.Size / 2))
	
	local parts = game.Workspace:FindPartsInRegion3WithIgnoreList(region, script.Parent:GetDescendants())
	
	local model
	
	for _, part in pairs(parts) do
		model = part:FindFirstAncestorOfClass("Model")
		
		if model then
			if model:FindFirstChild("Humanoid") then
				local player = game.Players:GetPlayerFromCharacter(model)
				if player then
					found = true
					break
				end
			end
		end
	end
	
	if found then
		if not debounce then
			model.Humanoid.Health -= 10
			debounce = true
			wait(1)
			debounce = false
		end
	end
end

Note: I’m adding debounce variable there

What this will do? It will create a region3 with our hitbox, I won’t explain it now maybe read this would help.
then, we will get every part inside of that region. We will check if the ancestor of that part is a model. If it’s a model, then it will check if that model has a humanoid. If that model has a humanoid, then check if that character is a player. If it’s a player, then set the found variable to true.
Then after that for loop, it will check if the found variable is true. If it’s true, then damage the player.

Test it yourself, my computer can’t upload videos :sad:

4. Animating

Next, we need to animate the zombie. The animation you need is Walking and Attack

I’m already making my animation.

To make it yourself, Go to plugins → animation editor
Create new animation.
Animate it.
Before you export it, here is something important

  • The Walking animation priority is set to Movement
  • The Attack animation priority is set to Action

5. Scripting the animation!

This is the last step.

Go to our script. then write the following

local runId = 0000 -- Replace this with your anim id
local attackId = 0000 -- Replace this also

local run = Instance.new("Animation")
run.Name = "Run"
run.AnimationId = "rbxassetid://"..runId
run.Parent = workspace
run = humanoid:LoadAnimation(run)

local attack = Instance.new("Animation")
attack.Name = "Attack"
attack.AnimationId = "rbxassetid://"..attackId
attack.Parent = workspace
attack = humanoid:LoadAnimation(attack)

run:Play()
attack:Play()

Now! that’s it! I hope you guys enjoyed it!

Here’s our full script

local PathfindingService = game:GetService("PathfindingService")
local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")
local hitbox = character:WaitForChild("Hitbox")


local runId = 0000 -- Replace this with your anim id
local attackId = 0000 -- Replace this also

local run = Instance.new("Animation")
run.Name = "Run"
run.AnimationId = "rbxassetid://"..runId
run.Parent = workspace
run = humanoid:LoadAnimation(run)

local attack = Instance.new("Animation")
attack.Name = "Attack"
attack.AnimationId = "rbxassetid://"..attackId
attack.Parent = workspace
attack = humanoid:LoadAnimation(attack)

run:Play()
attack:Play()

function getClosestPlayer()
	local Players = game.Players:GetPlayers()
	local nearestPlayer = Players[1]

	if nearestPlayer == nil then return end

	if #Players > 1 then
		for i = 2, #Players do
			if nearestPlayer.Character == nil then
				nearestPlayer.CharacterAdded:Wait()
			end
			if Players[1].Character == nil then
				Players[1].CharacterAdded:Wait()
			end

			local distance1 = (nearestPlayer.Character.HumanoidRootPart.Position - rootPart.Position).Magnitude
			local distance2 = (Players[i].Character.HumanoidRootPart.Position - rootPart.Position).Magnitude

			if distance1 > distance2 then
				nearestPlayer = Players[i]
			end
		end
	end

	return nearestPlayer
end

local path = PathfindingService:CreatePath({
	["AgentCanJump"] = false,
})

coroutine.wrap(function()
	while wait() do
		local closestPlayer = getClosestPlayer()
		
		if closestPlayer.Character == nil then
			closestPlayer.CharacterAdded:Wait()
		end
		
		path:ComputeAsync(rootPart.Position, closestPlayer.Character.HumanoidRootPart.Position)
		
		local waypoints = path:GetWaypoints()
		
		for _, waypoint in pairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	end
end)()

local debounce = false

while wait() do
	local found = false
	
	local region = Region3.new(hitbox.Position - (hitbox.Size / 2), hitbox.Position + (hitbox.Size / 2))
	
	local parts = game.Workspace:FindPartsInRegion3WithIgnoreList(region, script.Parent:GetDescendants())
	
	local model
	
	for _, part in pairs(parts) do
		model = part:FindFirstAncestorOfClass("Model")
		
		if model then
			if model:FindFirstChild("Humanoid") then
				local player = game.Players:GetPlayerFromCharacter(model)
				if player then
					found = true
					break
				end
			end
		end
	end
	
	if found then
		if not debounce then
			model.Humanoid.Health -= 10
			debounce = true
			wait(1)
			debounce = false
		end
	end
end

Use this if you have a problem.

If there is still a problem here’s the .rblx file!
Tutorial.rbxl (45.8 KB)

22 Likes

I recommend just having pre-made animation instances inside the Zombie model instead of creating it at runtime, and also switching to Humanoid.Animator instead of just Humanoid, since it’s :LoadAnimation() function is deprecated
image

Also, last time I checked you don’t run when you attack.
image

5 Likes

Sorry, I’ll fix it. The humanoid one, I just know about it. Thank you so much!

2 Likes