This is a continuation of a previous post since the helper wasn’t responding anymore.
I have gotten NPCs to be able to go to random waypoints. However, they stutter and the script is constantly trying to move each of them incrementally but also changes their waypoints, too. But I want the NPCs to individually move simultaneously, and only change waypoint once they’ve reached it.
I also want to implement pathfinding, but I’m also a bit confused on that topic.
Adding MoveToFinished:Wait()
to the wander function in the NPCManager module that tells the NPCs to go to the designated location makes the NPCs move individually to the locations which is not what I want.
There is also a problem where the ChangeState()
function in that same module doesn’t actually work. Changing it from "Wandering"
to "Idle"
does nothing.
I’m not sure whether or not it’s that I just don’t understand the module script or something is wrong with the script. But I am lost on how I can fix this.
Serverscript:
local function GetNPC(identifier: number)
return activeNPCs[identifier] or error(`{identifier} is not in activeNPCs`)
end
NPCManager.NPCs = {
["Base Pedestrian"] = function(player, npc, actingNPC)
local sound
local outcome
local AnimOutcome
local Animating
local humanoid = npc:WaitForChild("Humanoid")
print("...")
local waitingTime = math.random(3, 5)
TurnToPlayer(player, npc, waitingTime)
actingNPC:StopMoving()
local decisionMade = math.random(1,2)
if decisionMade == 1 then
print("Sure thing!")
outcome = 1
sound = Var1SureSounds
AnimOutcome = YesAnims
else
print("No way")
outcome = 2
sound = Var1refuseSounds
AnimOutcome = NoAnims
end
local playing = sound[math.random(1,#sound)]
local animatingT = AnimOutcome
Animating = humanoid.Animator:LoadAnimation(animatingT)
Animating.Priority= Enum.AnimationPriority.Action
playing:Play()
playing.Ended:Wait()
task.wait(0.5)
ReceivedEvent:FireClient(player, outcome)
CurrentlySigning(actingNPC)
end,
["Cop"] = function(player, npc, gunActive, actingNPC)
print("...")
TurnToPlayer(player, npc, 4)
print("Go away")
local playing = Var1refuseSounds[math.random(1, #Var1refuseSounds)]
playing:Play()
playing.Ended:Wait()
ReceivedEvent:FireClient(player, 3)
CurrentlySigning(actingNPC)
end,
["Friendly Pedestrian"] = function(player, npc, actingNPC)
print("...")
TurnToPlayer(player, npc, 4)
print("For sure!")
local playing = Var1SureSounds[math.random(1, #Var1SureSounds)]
playing:Play()
playing.Ended:Wait()
ReceivedEvent:FireClient(player, 1)
CurrentlySigning(actingNPC)
end,
}
local function onRemoteTriggered(player, npcID)
print(player.Name .. "'s signal was received, now triggering script for " .. npcID.Name)
local npc = game.Workspace:FindFirstChild(npcID.Name)
if npc and NPCManager.NPCs[npcID.Name] then
print("Script running!")
print(player)
print(npc)
local actingNPC = GetNPC(npcID)
actingNPC:StopMoving()
actingNPC:ChangeState("Idle")
NPCManager.NPCs[npcID.Name](player, npcID, actingNPC)
else
print(npcID.Name.. " was not a valid NPC")
ReceivedEvent:FireClient(player, 4)
end
end
AskedEvent.OnServerEvent:Connect(onRemoteTriggered)
for _, AddedNPC in CollectionService:GetTagged(InteractableNPC) do
NPCAdded(AddedNPC)
end
CollectionService:GetInstanceRemovedSignal(InteractableNPC):Connect(NPCRemoved)
CollectionService:GetInstanceAddedSignal(InteractableNPC):Connect(NPCAdded)
while task.wait(0.1) do
for _, npc in activeNPCs do
npc:StopMoving()
if npc:GetState() == "Wandering" then
-- This NPC is meant to wander! Have it walk to a random point nearby.
local random_point = Waypoints[math.random(1, #Waypoints)]
npc:WalkTo(random_point.Position)
end
end
end
ModuleScript:
export type NPC_State = "Wandering" | "Idle" -- Fill this in with your desired states. It wont affect functionality, only autocompletion. Will make your life easier.
local randomSeed = Random.new()
local NPCManager = {}
-- Create a new class. This creates the NPC and all you will need to work with it.
local NPC = {}
NPC.__index = NPC
-- Now we can create our functions.
-- This is just for our use case scenario. Would likely be ineffecient in any other case.
-- Return the current Vector3 of the HumanoidRootPart
function NPC:GetPosition(): Vector3
return self._Humanoid.RootPart.Position
end
-- Change the internal state of the NPC between "Wandering" and "Idle". Alternatively, Attributes can be used.
function NPC:ChangeState(new_state: NPC_State)
self._state = new_state
end
-- Return the current self._state
function NPC:GetState(): NPC_State
return self._state
end
-- Iterate through and disconnect all connections.
function NPC:CleanupAsync() -- You can use a Janitor module to help with cleanup.
for index: number = 1, #self._connections do
local connection = self._connections[index]
connection:Disconnect()
end
end
-- Reset the WalkToPoint so the Humanoid stops moving.
function NPC:StopMoving()
self._Humanoid.WalkToPoint = Vector3.zero
end
-- Call :MoveTo on the Humanoid, printing off the result when `point` is reached.
function NPC:WalkTo(point: Vector3)
self._Humanoid:MoveTo(point)
self._Humanoid.MoveToFinished:Wait()
end
function NPCManager.new<NPC>(NPCModel: Model)
local newNPC = {}
setmetatable(newNPC, NPC) -- In layman's terms, we are adding the `NPC` table into this `newNPC` table, making the methods we constructed available to it.
local function walkToFinished()
print(`{newNPC._Model.Name} finished walking!`)
end
local Humanoid = NPCModel:FindFirstChildOfClass("Humanoid")
newNPC._Humanoid = Humanoid -- I like to use an underscore for "do not touch" properties
newNPC._Model = NPCModel
newNPC._state = "Wandering"
-- Connections
local moveToConnection = Humanoid.MoveToFinished:Connect(walkToFinished)
local destroyedConnection = NPCModel.Destroying:Once(function()
newNPC:CleanupAsync()
end)
newNPC._connections = {
moveToConnection,
destroyedConnection
}
return newNPC
end
return NPCManager