Hello everyone, today I want to update my npc tutorial. This one is going to be much more general and what you should do and not do.
Old post for attacking NPCs
Have you ever wondered how to make a npc that attacks you? Well here’s a tutorial for that!
Part 1 - Making the Dummy
First off, let’s create the npcs. For R15 games go to the plugins tab and press this icon in the screenshot to insert a R15 character make sure to un-anchor the root part!
Or if you want a R6 character you could insert this model of a R6 character:
https://www.roblox.com/library/8370047840/R6-Dummy-Rig?Category=Models&SortType=Relevance&SortAggregation=AllTime&CreatorId=0&Page=1&Position=7&SearchId=31af5b5c-1b58-4cbb-97ad-2dbca631e70e
Part 2 - Scripting Time
Alright scripting time also known as the best time!
So first, create a script inside the dummy/NPC you can name it whatever. So inside the script insert this code. (I won’t be going step by step so I don’t make this to post to long )
-- Follow Script
local PathFinding = game:GetService("PathfindingService")
local path = PathFinding:CreatePath()
local AttackEvent = script.Parent:WaitForChild("AttackEvent") -- Used to attack
local debounce = false -- Debounce so it doesn't spam attack
local cooldown = 0.5
local maxDistance = 100 -- Max Distance change to your liking
local closestPlayer = nil -- Locks to closest player
local closestDistance = maxDistance
local attackRange = 5 -- Range npc can attack you
local Players = game:GetService("Players")
local npc = script.Parent
local HRP = npc:WaitForChild("HumanoidRootPart")
local humanoid = npc:WaitForChild("Humanoid")
-- New Path Function
function newPath(character)
-- Compute the path
path:ComputeAsync(HRP.Position, character.HumanoidRootPart.Position)
local waypoints = path:GetWaypoints()
for i, waypoint in pairs(waypoints) do
npc.Humanoid:MoveTo(waypoint.Position + Vector3.new(1, 0, 1)) -- Move to the waypoint position
end
end
-- When damaged move the NPC
humanoid:GetPropertyChangedSignal("Health"):Connect(function()
while true do
for _, player in pairs(Players:GetPlayers()) do
local playerRoot = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
local playerHumanoid = player.Character and player.Character:FindFirstChild("Humanoid")
if playerRoot then
if playerHumanoid.Health <= 0 then
closestPlayer = nil
closestDistance = maxDistance
break
end
if (playerRoot.Position - HRP.Position).Magnitude <= attackRange then
AttackEvent:Fire() -- Fire Attacks Event
break
end
if (playerRoot.Position - HRP.Position).Magnitude <= maxDistance then
newPath(player.Character) -- Move characters
break
end
end
end
task.wait()
end
end)
Alright! We got a basic following script! Good job! Now let’s create the attack event! First create a bindable event and put it inside dummy/npc. Call it “AttackEvent.” Now for hit detection. First get the sword it can be a roblox sword or a custom sword it doesn’t matter. So to make it detect the event, instead of the activated just replace it with the event. For example:
local AttackEvent = script.Parent:WaitForChild("AttackEvent") -- Used to attack
function Attack()
end
AttackEvent.Event:Connect(Attack)
Also make sure to add an animate script to give the dummy animations. You could get the animate script by searching inside the toolbox.
Here’s a video of it in action:
Well this is it hopefully you learned something new. Well, have a nice day and thank you for reading .
Table of Content
- The Basics of NPCs
- Controlling NPCs with one Script
- Utilizing Modules
- Client NPCs
The Basics
N.P.C. stands for Non-Playable-Character.
NPCs are good for many types of games such as tower defense games, wave games, and much much more. It doesn’t have to be all about fighting NPC. But, in this tutorial I’m going to be talking more about fighting NPC.
So how should you organize your NPCs? You should always have it in a folder. The more organized the better.
Creating NPCs with one Script
BUT, less is more when scripting them. The less NPC scripts the better. You should always try having only one controller script instead of multiple scripts under each NPC. You can do something like this:
--// NPC Folder
local npcFolder = script.Parent
--// Loop NPC Function
local function loopNPC()
--// Loop All NPC
for _, npc in pairs(npcFolder:GetChildren()) do
-- We will wrap this so there's no specific order
coroutine.wrap(function()
local humanoid = npc:FindFirstChild("Humanoid")
local rootPart = npc:FindFirstChild("HumanoidRootPart")
-- Check if the NPC is a NPC
if npc:IsA("Model") and humanoid and rootPart then
-- Path find
end
end
end)()
end
end
--// Loop
loopNPC()
npcFolder.ChildAdded:Connect(loopNPC)
This is much more efficient since now you have less scripts meaning less lag.
Still lag is still a big problem.
- My npc is stuttering!
- I have too much NPCs it’s still lagging!
Utilizing Modules
What do you do??? First, I would first recommend Simple Path! It’s easy to use and makes NPCs path find much more smoother.
Here’s the updated code:
--// Service
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
--// Modules
local Modules = ServerStorage:WaitForChild("Server_Modules")
local SimplePath = require(Modules.SimplePath)
--// NPC Folder
local npcFolder = script.Parent
local Paths = {}
--// Create Path Function
local function createPath(npc)
if not npc:IsA("Model") then return end
if not npc:FindFirstChild("Humanoid") then return end
if Paths[npc] then return end
-- We create a path for the simple path module
Paths[npc] = SimplePath.new(npc)
end
--// New Path Fuction
local function newPath(npc, target, distance)
local Path = Paths[npc]
if Path and distance <= 200 then -- 200 is the maximum distance
local Goal = target.Position
-- Using :Run() moves the npc
Path:Run(Goal)
return Path:Run(Goal)
end
end
--// Get Closest Player Function
local function getClosestPlayer(npc, rootPart)
local current_target
local current_distance = 200
for _, target in pairs(Players:GetPlayers()) do
target = target.Character or target.CharacterAdded:Wait()
if not target:IsA("Model") or not target:FindFirstChild("HumanoidRootPart") then continue end
local targetRoot = target:WaitForChild("HumanoidRootPart")
local targetHumanoid = target:WaitForChild("Humanoid")
local distance = (rootPart.Position - targetRoot.Position).Magnitude
if targetHumanoid.Health ~= 0 and distance < current_distance then
current_target = targetRoot
current_distance = distance
end
end
return current_target, current_distance
end
--// Loop NPC Function
local function loopNPC()
--// Loop All NPC
for _, npc in pairs(npcFolder:GetChildren()) do
createPath(npc) -- Create Path for NPC
-- We will wrap this so there's no specific order
coroutine.wrap(function()
local humanoid = npc:FindFirstChild("Humanoid")
local rootPart = npc:FindFirstChild("HumanoidRootPart")
-- Check if the NPC is a NPC
if npc:IsA("Model") and humanoid and rootPart then
-- Path find
while npc do
local sucess = false
if humanoid.Health ~= 0 then
local current_target, current_Distance = getClosestPlayer(npc, rootPart)
if current_target and current_Distance then
sucess = newPath(npc, current_target, current_Distance)
end
end
if not sucess then -- We do this just in case the path fails so the everything doesn't crash
task.wait()
end
end
end
end)()
end
end
--// Loop
loopNPC()
npcFolder.ChildAdded:Connect(loopNPC)
If you want more information how it works I recommend checking the github for simple path!
So perfect! The npc runs smoothly and we have one script controlling all NPCs, perfect! Or is it? we can take it one step farther.
Client NPCs
Instead of having all the NPCs run on the server which can be extremely laggy especially if there’s lots of NPCs, why not run it on the client?
Instructions:
Basically copy the whole script and put it in the client. Make sure to move all your modules into Replicated Storage! Modules like Simple Path, Fastcast, and Raycasthitbox works on the client so no worries!
If you want more information I recommend this awesome video created by @5uphi.
Also here’s an example if you want. It doesn’t use all the practices, but it shows the differences between client and server npcs.
Place1.rbxl (83.5 KB)
Well that’s all for now! Have fun coding and good luck developers!
– Rapideed/mang