I have an NPC movement system in my personal project that almost runs entirely on the client. The NPCs are spawned on each client, that client handles their own pathfinding calculations. The only thing is I don’t know how to replicate the NPC’s movement from the client who owns the NPC to the clients who don’t.
I’ve looked into suphi kaner’s method, but it has the client calculating paths for every NPC in the game, even the ones they don’t own.
I wanted to ask if this is not as much a performance hit as I think, or give me an insight on how to implement this system.
--module
local module = {}
module.__index = module
local paths: {Path} = {}
local PathS = game:GetService("PathfindingService")
local RunS = game:GetService("RunService")
local TS = game:GetService("TweenService")
local function FindPath(pos1: Vector3, pos2: Vector3)
if paths[tostring(pos1).."-"..tostring(pos2)] then print("Path already computed") return paths[tostring(pos1).."-"..tostring(pos2)] end
local path = PathS:CreatePath({
AgentCanClimb = false,
AgentCanJump = false
})
local tries = 0
while not paths[tostring(pos1).."-"..tostring(pos2)] do
tries += 1
local success, msg = pcall(function() path:ComputeAsync(pos1, pos2) end)
if success and path.Status == Enum.PathStatus.Success then
print("Calculated path after "..tries.. " tries")
paths[tostring(pos1).."-"..tostring(pos2)] = path
return paths[tostring(pos1).."-"..tostring(pos2)]
elseif not success then
warn("Path not computed: "..msg)
end
if tries >= 3 then
warn("Exceeded 3 tries. Returning")
return
end
end
end
function module.ClientSpawn(npc)
setmetatable(npc, module)
npc:Spawn()
end
function module.New(player)
local Plot = require(game.ServerScriptService.PlotFunctions)
local npc = require(game.ReplicatedStorage.Configs.NPCs)[RandomizeNPC()]
local rarityTable = {"Common", "Uncommon", "Rare", "Very Rare", "Legendary", "Mythical"}
local purchasePrefTable = {"Cheap", "Expensive"}
local bindableEvent = Instance.new("BindableEvent")
local self = setmetatable({},module)
self.CustomerOf = player
self.Money = math.random(npc.MinMoney, npc.MaxMoney)
self.RarityPreferences = npc.RarityPreference
self.PurchasePreferences = purchasePrefTable[math.random(1,#purchasePrefTable)]
self.Model = npc.Model[math.random(1, #npc.Model)]
self.Plot = Plot.GetPlot(player)
for i = 1, #rarityTable, 1 do
if table.find(self.RarityPreferences, rarityTable[i]) then table.remove(rarityTable,i) end
end
table.insert(self.RarityPreferences, rarityTable[math.random(1, #rarityTable)])
game.ReplicatedStorage.Events.SpawnNPC:FireClient(player, self)
return self
end
function module:Spawn()
if RunS:IsClient() then
local cloned = self.Model:Clone()
cloned.Parent = self.Plot.NPCs
self.Model = cloned
cloned:PivotTo(CFrame.new(0,5,0))
self:WalkIn()
end
end
function module:WalkIn()
if RunS:IsClient() then
local path: Path = FindPath(CFrame.new(0,5,0).Position, self.Plot.NPCDestination.Position)
local humanoid: Humanoid = self.Model.Humanoid
Walk(humanoid, path, self.Plot.NPCDestination.CFrame.RightVector * -1)
self:Wander()
end
end
function module:Wander()
local possibleDestinations = {}
local maxDestinations = math.random(3,7)
for _, item in self.Plot.StoreItems:GetChildren() do
if item:FindFirstChild("NPCDestination") then table.insert(possibleDestinations, item.NPCDestination) end
end
while(#possibleDestinations > maxDestinations) do
table.remove(possibleDestinations, math.random(1,#possibleDestinations))
end
local humanoid = self.Model.Humanoid
possibleDestinations = RandomizeTable(possibleDestinations)
for i,v in possibleDestinations do
local origin
if i == 1 then origin = possibleDestinations[1] else origin = possibleDestinations[i - 1] end
local path = FindPath(origin.Position, v.Position)
Walk(humanoid, path, possibleDestinations[i].CFrame.RightVector * -1)
task.wait(2)
end
end
return module
-- server
local NPC = require(game.ReplicatedStorage.Modules.NPCSHandler)
local PS = game:GetService("Players")
while task.wait(4) do
for _,v in PS:GetPlayers() do
npc = NPC.New(v)
end
end
--client
local NPC = require(game.ReplicatedStorage.Modules.NPCSHandler)
RS:WaitForChild("Events"):WaitForChild("SpawnNPC").OnClientEvent:Connect(function(npc)
NPC.ClientSpawn(npc)
end)