[TUTORIAL] Civilian NPC's (Pathfinding Customer NPC's)

Hi there guys! I’ve searched the whole documentation for the past days and couldn’t find a working civilian npc system just like in Retail Tycoon / any other game that has that feature.

I’ve tried to recreate the same mechanics using AI and re-editing the script to make it work.
I am saying this now because i’m not the best scripter, some things you might wanna do differently but for the starting point of having an npc that goes to the Cashier Desk and after some time he goes and sits on a free chair at the table & leaves it’s good.

[P.S For this tutorial we assume you are using the customers for a Tycoon game]

First of all we need to do the following things :slight_smile: :

tutorial

Inside the [Tycoons] folder → [YourTycoonName] add the following things :

  • Tables model, CashierDesk & Exit [The cashier desk and Exit are just transparent parts]

CashierDesk must have 2 attributes added : Position & Occupants
Occupants - Number attribute
Position - Vector3 attribute with the position of the cashierDesk
cashierdeskattribute

The [Tables] model should look like this when opened :
TablesFolder

Inside [Tables] model we will find the Tables [parts or meshparts, whatever you want them to be], and inside each Table, we will have a model called Chairs with the corresponding chairs (having a Seat also) [numbered from 1-x so it helps us when debugging]

The Chairs inside Chairs Model must also have an attribute added to them :
chairattribute
IsOccupied - Boolean Attribute [True/False]

The [CashierDesk] & [Exit] will look like this in our game : [you can turn the transparency to 0 if needed]

CashierDesk [Green transparent part]

Exit Part [Red transparent part]
exitpart

After that add a Part inside Workspace, call it NPCSpawnZone just like so :
SpawnZone part

The NPCSpawnZone will look something like this and it will be placed wherever you want : [Make it fully transparent with the CanCollide turned off]
SpawnZone display

Good! now most of the stuff is already there, meaning we just need to import the NPC’s somewhere and make them move to our designated location

First of all, to add our NPC’s we need to :

  • Open ServerStorage → Create a NPC Folder → Add NPC’s there, from 1-x [depends on how many you want]

It should look something like this:
ServerStorage NPC's

After that we head over to → ServerScriptService Folder & create a script called “CustomerNPCS”
SSS CustomerNPCs

Now the scripting part starts, we open the script called “CustomerNPCS” and do this :

  • We define the services and the variables right at the top :
-- Services
local ServerStorage = game:GetService("ServerStorage")
local WorkspaceFolder = game:GetService("Workspace")
local PathfindingService = game:GetService("PathfindingService")

-- Variables
local tycoonsFolder = WorkspaceFolder:FindFirstChild("Tycoons")
local npcSpawnZone = WorkspaceFolder.NPCSpawnZone -- Reference to your NPC Spawn Zone
local tycoons = {} -- Create an empty table to store valid tycoon models
local npcFolder = ServerStorage:FindFirstChild("NPC")
local maxCashierCapacity = 5 -- How many NPC's will get in the line at the cashierDesk
local maxChairCapacity = 24 -- Max capacity of chairs where NPC's can sit
  • After that we filter only valid tycoon models :
for _, child in ipairs(tycoonsFolder:GetChildren()) do
	if child:IsA("Model") and (child.Name:match("Tycoon%d")) then
		table.insert(tycoons, child) -- Add only tycoon models to the list
	end
end
  • Then we create the function so the NPC’s can walk using PathFinding :
local function moveToPosition(npc, targetPosition)
	local humanoid = npc:FindFirstChild("Humanoid")
	if humanoid then
		local path = PathfindingService:CreatePath({
			AgentRadius = 2,
			AgentHeight = 5,
			AgentCanJump = true,
			AgentJumpHeight = 10,
			AgentMaxSlope = 45,
		})

		-- Compute the path to the target position
		path:ComputeAsync(npc.HumanoidRootPart.Position, targetPosition)

		-- Get waypoints and move the NPC
		local waypoints = path:GetWaypoints()
		for _, waypoint in ipairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	else
		warn("Humanoid not found for NPC")
	end
end
  • We then create a function for the NPC to find a free chair inside the Tycoon :
local function findFreeChair(tycoon)
	local freeChairs = {} -- Create a table to hold free chairs
	local tables = tycoon.Tables:GetChildren()

	for _, tableModel in pairs(tables) do
		local chairs = tableModel.Chairs:GetChildren()
		for _, chair in pairs(chairs) do
			if not chair:GetAttribute("IsOccupied") then
				table.insert(freeChairs, chair) -- Add free chair to the list
				print("Free chair found: ", chair.Name) -- Debugging
			end
		end
	end

	if #freeChairs > 0 then
		-- Randomly select a free chair
		local randomIndex = math.random(1, #freeChairs)
		return freeChairs[randomIndex] -- Return a random free chair
	else
		print("No free chair found in tycoon: ", tycoon.Name) -- Debugging
		return nil -- No free chair found
	end
end
  • If the NPC doesn’t find a free chair, in any of the Tycoons → Tycoon1/2/3/4… then we make it wander around :
local function npcWander(npc, currentTycoon)
	-- Move NPC randomly around the map (use moveToPosition here)
	local randomPosition = Vector3.new(
		math.random(-50, 50), 
		0, 
		math.random(-50, 50)
	)
	moveToPosition(npc, randomPosition)
end
  • Then we give him a behavior for when it enters the tycoon (Go to cashierDesk → wait a bit → go to the tables → find a free chair to sit → wait a bit → exit the tycoon) :
local function npcEnterTycoon(npc, tycoon)
	local cashierDesk = tycoon.CashierDesk

	-- Check cashier capacity
	local occupants = cashierDesk:GetAttribute("Occupants") or 0
	if occupants < maxCashierCapacity then
		-- Move NPC to cashier desk
		moveToPosition(npc, cashierDesk.Position)
		cashierDesk:SetAttribute("Occupants", occupants + 1)

		wait(3) -- Wait for 3 seconds at cashier

		-- Find a free chair
		local freeChair = findFreeChair(tycoon)
		local humanoid = npc:FindFirstChild("Humanoid")
		if freeChair then
			-- Move NPC to the free chair
			moveToPosition(npc, freeChair.Position)
			wait(2) -- Time to sit down

			-- Set the chair to occupied
			freeChair:SetAttribute("IsOccupied", true) 
			print(npc.Name .. " is now sitting in " .. freeChair.Name) -- Debugging

			wait(math.random(5, 30)) -- NPC sits for min 5 seconds to 30 seconds

			-- Free the chair and update attribute
			freeChair:SetAttribute("IsOccupied", false) 
			humanoid.Sit = false
			print(npc.Name .. " has left the chair " .. freeChair.Name) -- Debugging

			cashierDesk:SetAttribute("Occupants", occupants - 1) -- Free cashier spot

			-- NPC leaves the tycoon
			moveToPosition(npc, tycoon.Exit.Position) -- Move to exit
		else
			-- No free chair, try another tycoon
			npcWander(npc, tycoon)
		end
	else
		-- Cashier is full, try another tycoon
		npcWander(npc, tycoon)
	end
end
  • And we also need to make a reference to the NPC spawn point & to also spawn them :
-- Reference to the SpawnPoint
local spawnPoint = WorkspaceFolder:FindFirstChild("NPCSpawnzone")

-- Adjusted NPC spawning function
local function spawnNPC()
	local npcNames = {"NPC1", "NPC2", "NPC3", "NPC4", "NPC5"} -- Table of NPC names

	while true do
		wait(math.random(5, 10)) -- Spawn an NPC every 5-10 seconds

		-- Randomly select an NPC name from the table
		local randomNpcName = npcNames[math.random(1, #npcNames)]
		local npc = ServerStorage.NPC:FindFirstChild(randomNpcName):Clone() -- Clone the selected NPC

		-- Set NPC's position near the spawn zone
		local spawnPosition = npcSpawnZone.Position + Vector3.new(
			math.random(-5, 5), -- Randomize x position
			0,                 -- Keep the y position the same (assuming it's on the ground)
			math.random(-5, 5)  -- Randomize z position
		)

		npc:SetPrimaryPartCFrame(CFrame.new(spawnPosition)) -- Set the NPC position
		npc.Parent = WorkspaceFolder -- You can make them spawn anywhere, just change the variable from WorkspaceFolder to whatever you want

		-- Enter a random tycoon to start
		npcEnterTycoon(npc, tycoons[math.random(1, #tycoons)])
	end
end

-- Start spawning NPCs

spawn(function()
	spawnNPC()
end)

Hope this little tutorial helped you guys, I’m open to suggestions / edits to make it work better, as i’ve said it’s a STARTING POINT for thoes who want to start working on a game that includes NPC’s and can’t find anything related [If you’re new to scripting or you haven’t ever scripted this tutorial is pretty much good for you]

I’ve attached the .rblxm file if you don’t want to follow along and just copy-paste everything, it’s up to you guys but I would recommend you check out the tutorial so you know where everything goes.
CustomerNPC
CustomerNPC-javasquid.rbxm (74.9 KB)

7 Likes

Oh my gosh, you made an entire NPC civilian system. Impressive!

1 Like

Thanks, it’s not really that great as it might bug a little, depending on the project that you are working on but I’ve tried to make it as clear as possible so you know what the script does and adapt it for your game.

Hope it helps anyone that is looking forward to use it, I’m here if anyone needs help.

Thanks for your feedback :heart:

1 Like