Pfadfindung ist der Prozess, einen Charakter oder ein Objekt (Agent) entlang eines logischen Pfades um Hindernisse herum zu bewegen, um ein Ziel zu erreichen, wobei optional gefährliche Materialien oder definierte Regionen vermieden werden.
Navigationsvisualisierung
Um bei der Anordnung und Fehlersuche der Pfadfindung zu helfen, kann Studio ein Navigationsnetz und Modifikator-Labels rendern. Um diese zu aktivieren, schalten Sie Navigationsnetz und Pfadfindungsmodifikatoren im Visualisierungsoptionen-Widget in der oberen rechten Ecke des 3D-Viewport ein.

Mit aktiviertem Navigationsnetz zeigen farbige Bereiche, wo ein Charakter gehen oder schwimmen kann. Kleine Pfeile zeigen Bereiche an, die ein Charakter durch Springen zu erreichen versucht.

Implementierung
Obwohl die Pfadfindung auf verschiedene Weise über PathfindingService und seine zugehörigen Methoden wie CreatePath() implementiert werden kann, verwendet dieser Abschnitt das folgende Pfadfindungsskript für den Charakter des Spielers.
Um während des Lesens zu testen:
- WICHTIGWählen Sie im Explorer den StarterPlayer-Container aus. Setzen Sie dann im Eigenschaften-Fenster sowohl DevComputerMovementMode als auch DevTouchMovementMode auf Scriptable.


Kopieren Sie den folgenden Code in ein LocalScript innerhalb von StarterCharacterScripts oder holen Sie sich dieses Paket und legen Sie es in StarterCharacterScripts ab.
PlayerPathFollow (LocalScript in StarterCharacterScripts)local PathfindingService = game:GetService("PathfindingService")local Players = game:GetService("Players")local RunService = game:GetService("RunService")local DESTINATION = Vector3.new(20, 0.5, 20)local GROUND_WAIT = 0.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {Water = 20}})local character = script.Parentlocal humanoid = character:WaitForChild("Humanoid")local waypointslocal nextWaypointIndexlocal blockedConnectionlocal currentWaypointReachedConnectionlocal currentWaypointPlaneNormal = Vector3.zerolocal currentWaypointPlaneDistance = 0local pathfinderWorking = falselocal function disconnectCurrentWaypointReachedConnection()if not currentWaypointReachedConnection then return endcurrentWaypointReachedConnection:Disconnect()currentWaypointReachedConnection = nilendlocal function isCurrentWaypointReached()if humanoid.FloorMaterial == Enum.Material.Air thenreturn falseendlocal reached = falseif currentWaypointPlaneNormal ~= Vector3.zero then-- Berechne die Entfernung vom Humanoid zur Ziel-Ebenelocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Berechne die Komponente der Humanoidgeschwindigkeit, die zur Ebene zeigtlocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Berechne die Schwelle von der Ziel-Ebene basierend auf der Humanoidgeschwindigkeitlocal threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- Betrachte den Wegpunkt als erreicht, wenn weniger als die Schwelle vor der Ebenereached = dist < thresholdelsereached = trueendif reached thencurrentWaypointPlaneNormal = Vector3.zerocurrentWaypointPlaneDistance = 0moveToNextWaypoint()endendlocal function calculateNextWaypointApproach()nextWaypointIndex += 1if nextWaypointIndex > #waypoints thenreturn falseendlocal currentWaypoint = waypoints[nextWaypointIndex - 1]local nextWaypoint = waypoints[nextWaypointIndex]-- Baue die Ziel-Ebene vom nächsten Wegpunkt zum aktuellencurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Setze die Normale senkrecht zur Y-Ebene, wenn nicht nach oben geklettert wirdif nextWaypoint.Label ~= "Climb" thencurrentWaypointPlaneNormal = Vector3.new(currentWaypointPlaneNormal.X, 0, currentWaypointPlaneNormal.Z)endif currentWaypointPlaneNormal.Magnitude > 0.000001 thencurrentWaypointPlaneNormal = currentWaypointPlaneNormal.UnitcurrentWaypointPlaneDistance = currentWaypointPlaneNormal:Dot(nextWaypoint.Position)endreturn trueendlocal function resetWaypointData()humanoid:Move(Vector3.zero)currentWaypointPlaneNormal = Vector3.zerocurrentWaypointPlaneDistance = 0disconnectCurrentWaypointReachedConnection()pathfinderWorking = falseendlocal function waitForGround()while humanoid.FloorMaterial == Enum.Material.Air dotask.wait(GROUND_WAIT)endendfunction moveToNextWaypoint()if calculateNextWaypointApproach() thendisconnectCurrentWaypointReachedConnection()currentWaypointReachedConnection = RunService.Heartbeat:Connect(isCurrentWaypointReached)local nextWaypointPosition = waypoints[nextWaypointIndex].Positionlocal nextWaypointAction = waypoints[nextWaypointIndex].Actionhumanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)if waypoints[nextWaypointIndex + 1] and waypoints[nextWaypointIndex + 1].Label == "UseBoat" thennextWaypointIndex += 1-- Rufen Sie Ihre eigene benutzerdefinierte Funktion auf, um den Agenten das Boot benutzen zu lassenelseif nextWaypointAction == Enum.PathWaypointAction.Jump thenhumanoid:ChangeState(Enum.HumanoidStateType.Jumping)while humanoid.FloorMaterial ~= Enum.Material.Air dotask.wait(GROUND_WAIT)endhumanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)endelseresetWaypointData()endendlocal function findStartingPoint(waypoints)nextWaypointIndex = 1while nextWaypointIndex + 1 <= #waypoints dolocal dist = waypoints[nextWaypointIndex + 1].Position - humanoid.RootPart.Positiondist = Vector3.new(dist.X, 0, dist.Z)if dist.magnitude >= 2 thenreturnendnextWaypointIndex += 1endendlocal function followPath()-- Berechne den PfadpathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("Pfad nicht berechnet!", errorMessage)returnend-- Hole die Wegpunkte des Pfadeswaypoints = path:GetWaypoints()-- Erkenne, ob der Pfad blockiert wirdblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Überprüfe, ob das Hindernis weiter unten auf dem Pfad istif blockedWaypointIndex >= nextWaypointIndex then-- Stoppe die Erkennung von Pfadblockierungen, bis der Pfad neu berechnet wirdblockedConnection:Disconnect()resetWaypointData()-- Rufe die Funktion auf, um den neuen Pfad neu zu berechnenfollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()Bearbeiten Sie die DESTINATION-Variable (
ZEILE 5) auf ein Vector3-Ziel innerhalb der 3D-Welt, das der Spielercharakter erreichen kann.Fahren Sie mit den folgenden Abschnitten fort, um mehr über die Pfadberechnung und die Bewegung des Charakters zu erfahren.
Pfaderstellung
Die Pfadfindung wird über PathfindingService und seine Methode CreatePath() initiiert (
| Schlüssel | Beschreibung | Typ | Standard |
|---|---|---|---|
| AgentRadius | Agentenradius in Studs. Nützlich, um den minimalen Abstand zu Hindernissen zu bestimmen. | integer | 2 |
| AgentHeight | Agentenhöhe in Studs. Leerraum, der kleiner als dieser Wert ist, wie der Raum unter Treppen, wird als nicht durchquerbar markiert. | integer | 5 |
| AgentCanJump | Bestimmt, ob das Springen während der Pfadfindung erlaubt ist. | boolean | true |
| AgentCanClimb | Bestimmt, ob das Klettern von TrussParts während der Pfadfindung erlaubt ist. Ein kletterbarer Pfad hat ein Label mit dem Namen Climb, und die Kosten für einen kletterbaren Pfad betragen standardmäßig 1. | boolean | false |
| WaypointSpacing | Abstand zwischen Zwischenwegpunkten im Pfad. Wenn auf math.huge gesetzt, gibt es keine Zwischenwegpunkte. | number | 4 |
| Costs | Tabelle von Materialien oder definierten PathfindingModifiers und deren Kosten für die Durchquerung. Nützlich, um den Agenten bestimmte Materialien/Regionen gegenüber anderen bevorzugen zu lassen. Siehe Modifikatoren für Details. | table | nil |
Pfadberechnung
Nachdem Sie einen gültigen Pfad mit CreatePath() erstellt haben, muss er berechnet werden, indem Sie Path:ComputeAsync() mit einem Vector3 für sowohl den Startpunkt als auch das Ziel aufrufen (

Sobald der Path berechnet ist, enthält er eine Reihe von Wegpunkten, die den Pfad vom Start bis zum Ende nachzeichnen. Diese Punkte können mit der Methode Path:GetWaypoints() gesammelt werden (

Pfadbewegung
Jeder PathWaypoint besteht sowohl aus einer Position (Vector3) als auch aus einer Action (PathWaypointAction). Um einen Charakter mit einem Humanoid, wie einem typischen Roblox-Charakter, zu bewegen, ist es am besten, Humanoid:Move() von Wegpunkt zu Wegpunkt aufzurufen und den Rückruf isCurrentWaypointReached() des Skripts (
Blockierte Pfade
Viele Roblox-Welten sind dynamisch; Teile können sich bewegen oder fallen und Böden können einstürzen. Dies kann einen berechneten Pfad blockieren und verhindern, dass der Charakter sein Ziel erreicht. Um dies zu handhaben, können Sie das Path.Blocked-Ereignis verbinden und den Pfad um das, was ihn blockiert, neu berechnen (
Pfadfindungsmodifikatoren
Standardmäßig gibt Path:ComputeAsync() den kürzesten Pfad zwischen dem Startpunkt und dem Ziel zurück, mit der Ausnahme, dass versucht wird, Sprünge zu vermeiden. Dies sieht in einigen Situationen unnatürlich aus; zum Beispiel kann ein Pfad durch Sumpfwasser führen, anstatt darum herum, einfach weil der Pfad durch das Wasser geometrisch kürzer ist.

Um die Pfadfindung noch weiter zu optimieren, können Sie Pfadfindungsmodifikatoren implementieren, um intelligentere Pfade über verschiedene Materialien, um definierte Regionen oder um Hindernisse zu ignorieren.
Materialkosten
Beim Arbeiten mit Terrain und BasePart-Materialien können Sie eine Costs-Tabelle innerhalb von CreatePath() einfügen, um bestimmte Materialien durchquerbarer zu machen als andere. Alle Materialien haben standardmäßig eine Kosten von 1, und jedes Material kann als nicht durchquerbar definiert werden, indem sein Wert auf math.huge gesetzt wird.
Schlüssel in der Costs-Tabelle sollten String-Namen darstellen, die Enum.Material-Namen repräsentieren, zum Beispiel Water für Enum.Material.Water oder CrackedLava für Enum.Material.CrackedLava.
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DESTINATION = Vector3.new(20, 0.5, 20)
local GROUND_WAIT = 0.01
local VELOCITY_MULTIPLIER = 0.0625
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Water = 20, CrackedLava = 100, Slate = 20
}
})Regionen konfigurieren
In einigen Fällen reicht die Materialpräferenz nicht aus. Zum Beispiel möchten Sie möglicherweise, dass Charaktere eine definierte Region vermeiden, unabhängig von den Materialien unter ihren Füßen. Dies kann erreicht werden, indem Sie ein PathfindingModifier-Objekt zu einem Teil hinzufügen.
Erstellen Sie ein Anchored-Teil um die Region und setzen Sie dessen CanCollide-Eigenschaft auf false.

Fügen Sie eine PathfindingModifier-Instanz auf das Teil ein, suchen Sie die Label-Eigenschaft und weisen Sie einen aussagekräftigen Namen wie DangerZone zu.

Fügen Sie eine übereinstimmende DangerZone-Schlüssel und den zugehörigen numerischen Wert innerhalb der Costs-Tabelle von CreatePath() hinzu. Ein Modifikator kann als nicht durchquerbar definiert werden, indem sein Wert auf math.huge gesetzt wird.
PlayerPathFollow (LocalScript)local PathfindingService = game:GetService("PathfindingService")local Players = game:GetService("Players")local RunService = game:GetService("RunService")local DESTINATION = Vector3.new(20, 0.5, 20)local GROUND_WAIT = 0.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20}})
Hindernisse ignorieren
In einigen Fällen ist es nützlich, durch feste Hindernisse zu pfadfinden, als ob sie nicht existieren würden. Dies ermöglicht es Ihnen, einen Pfad durch spezifische physische Blockaden zu berechnen, anstatt dass die Berechnung vollständig fehlschlägt.
Erstellen Sie ein Anchored-Teil um das Objekt und setzen Sie dessen CanCollide-Eigenschaft auf false.

Fügen Sie eine PathfindingModifier-Instanz auf das Teil ein und aktivieren Sie dessen PassThrough-Eigenschaft.

Jetzt, wenn ein Pfad vom Zombie-NPC zum Spielercharakter berechnet wird, erstreckt sich der Pfad über die Tür hinaus und Sie können den Zombie auffordern, sie zu durchqueren. Selbst wenn der Zombie die Tür nicht öffnen kann, reagiert er so, als ob er den Charakter hinter der Tür "hört".

Pfadfindungslinks
Manchmal ist es notwendig, einen Pfad über einen Raum zu finden, der normalerweise nicht durchquert werden kann, wie über eine Schlucht, und eine benutzerdefinierte Aktion auszuführen, um den nächsten Wegpunkt zu erreichen. Dies kann durch das PathfindingLink-Objekt erreicht werden.
Anhand des obigen Beispiels können Sie den Agenten dazu bringen, ein Boot zu benutzen.

Um ein PathfindingLink mit diesem Beispiel zu erstellen:
- OPTIONALSchalten Sie Pfadfindungslinks im Visualisierungsoptionen-Widget in der oberen rechten Ecke des 3D-Viewports ein. Dies hilft bei der Visualisierung und Fehlersuche bei der Implementierung von Pfadfindungslinks.
Erstellen Sie zwei Attachments, eine auf dem Sitz des Bootes und eine in der Nähe des Landepunkts des Bootes.

Erstellen Sie ein PathfindingLink-Objekt im Workspace und weisen Sie dessen Attachment0- und Attachment1-Eigenschaften den Start- und End-Anhängern zu.


Weisen Sie seiner Label-Eigenschaft einen aussagekräftigen Namen wie UseBoat zu. Dieser Name wird als Flag im Pfadfindungsskript verwendet, um eine benutzerdefinierte Aktion auszulösen, wenn der Agent den Startpunkt des Links erreicht.

Fügen Sie eine Costs-Tabelle innerhalb von CreatePath() hinzu, die sowohl einen Water-Schlüssel als auch einen benutzerdefinierten Schlüssel enthält, der dem Namen der Label-Eigenschaft entspricht. Weisen Sie dem benutzerdefinierten Schlüssel einen Wert niedriger als den von Water zu.
PlayerPathFollow (LocalScript)local PathfindingService = game:GetService("PathfindingService")local Players = game:GetService("Players")local RunService = game:GetService("RunService")local DESTINATION = Vector3.new(20, 0.5, 20)local GROUND_WAIT = 0.01local VELOCITY_MULTIPLIER = 0.0625local path = PathfindingService:CreatePath({AgentCanClimb = true,Costs = {UseBoat = 2, Water = 20}})In der Funktion moveToNextWaypoint() (
ZEILEN 93–114) kann eine benutzerdefinierte Überprüfung für den Label-Modifikatornamen verwendet werden, um eine andere Aktion als Humanoid:Move() auszuführen; in diesem Fall könnten Sie eine Funktion aufrufen, um den Agenten im Boot zu platzieren, das Boot über das Wasser zu bewegen, den Agenten am Landepunkt des Bootes abzusetzen und dann den Pfad des Agenten zu seinem endgültigen Ziel fortzusetzen.
Streaming-Kompatibilität
Das In-Game-Instanz-Streaming ist eine leistungsstarke Funktion, die 3D-Inhalte dynamisch lädt und entlädt, während sich der Charakter eines Spielers durch die Welt bewegt. Während sie den 3D-Raum erkunden, werden neue Teilmengen des Raums auf ihr Gerät gestreamt, und einige der vorhandenen Teilmengen könnten herausgestreamt werden.
Berücksichtigen Sie die folgenden Best Practices für die Verwendung von PathfindingService in Streaming-aktivierten Spielen:
Streaming kann einen bestimmten Pfad blockieren oder freigeben, während sich ein Charakter entlang bewegt. Zum Beispiel, während ein Charakter durch einen Wald läuft, könnte ein Baum irgendwo vor ihnen hereingestreamt werden und den Pfad blockieren. Um die Pfadfindung nahtlos mit Streaming zu verbinden, wird dringend empfohlen, die Technik blockierte Pfade behandeln zu verwenden und den Pfad bei Bedarf neu zu berechnen.
Ein gängiger Ansatz in der Pfadfindung besteht darin, die Koordinaten vorhandener Objekte für die Berechnung zu verwenden, wie z.B. das Setzen eines Pfad-Ziels auf die Position eines vorhandenen TreasureChest-Modells in der Welt. Dieser Ansatz ist vollständig kompatibel mit serverseitigen Scripts, da der Server zu jeder Zeit einen vollständigen Überblick über die Welt hat, aber LocalScripts und ModuleScripts, die auf dem Client ausgeführt werden, können fehlschlagen, wenn sie versuchen, einen Pfad zu einem Objekt zu berechnen, das nicht hereingestreamt wurde.
Um dieses Problem zu beheben, sollten Sie in Betracht ziehen, das Ziel auf die Position eines BasePart innerhalb eines persistenten Modells zu setzen. Persistente Modelle werden kurz nach dem Beitritt des Spielers geladen und sie werden niemals herausgestreamt, sodass ein clientseitiges Skript sich mit dem PersistentLoaded-Ereignis verbinden und das Modell sicher für die Erstellung von Wegpunkten nach dem Auslösen des Ereignisses zugreifen kann.
Einschränkungen und Fehlerfaktoren
Die Pfadfindungs-Engine hat spezifische Einschränkungen, um eine effiziente Verarbeitung und optimale Leistung sicherzustellen. Darüber hinaus können Pfadfindungs-Berechnungen aus verschiedenen Gründen fehlschlagen, wie unten aufgeführt.