Pfadfinding 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 des Pfadfindings zu helfen, kann Studio ein Navigationsnetz und Modifikator-Labels rendern. Um diese zu aktivieren, schalten Sie Navigationsnetz und Pfadfindungsmodifikatoren im Widget Visualisierungsoptionen in der oberen rechten Ecke des 3D-Viewport ein.

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

Implementierung
Obwohl Pfadfinding auf verschiedene Weisen über PathfindingService und dessen zugehörige Methoden wie CreatePath() implementiert werden kann, verwendet dieser Abschnitt das folgende Pfadfindungs-Skript für den Charakter des Spielers.
Um beim Lesen zu testen:
- WICHTIGWählen Sie im Explorer den StarterPlayer-Container aus. Setzen Sie dann im Fenster Eigenschaften 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.
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-- Berechnen Sie die Entfernung vom Humanoid zur Zielflächelocal dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- Berechnen Sie die Komponente der Humanoidgeschwindigkeit, die zur Fläche zeigtlocal velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- Berechnen Sie den Schwellenwert von der Zielfläche basierend auf der Humanoidgeschwindigkeitlocal threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- Betrachten Sie den Wegpunkt als erreicht, wenn weniger als der Schwellenwert vor der Flächereached = 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]-- Erstellen Sie die Zielfläche vom nächsten Wegpunkt zur aktuellen FlächecurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- Setzen Sie die Normale senkrecht zur Y-Fläche, 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 angepasste 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()-- Pfad berechnenpathfinderWorking = 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-- Holen Sie sich die Wegpunktewaypoints = path:GetWaypoints()-- Erkennen, ob der Pfad blockiert wirdblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- Überprüfen Sie, ob das Hindernis weiter unten auf dem Pfad istif blockedWaypointIndex >= nextWaypointIndex then-- Stoppen Sie die Erkennung von Pfadblockaden, bis der Pfad neu berechnet wirdblockedConnection:Disconnect()resetWaypointData()-- Rufen Sie 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 Charakterbewegung zu erfahren.
Pfaderstellung
Pfadfinding wird über PathfindingService und dessen Methode CreatePath() initiiert (
| Schlüssel | Beschreibung | Typ | Standard |
|---|---|---|---|
| AgentRadius | Agent-Radius, in Studs. Nützlich zur Bestimmung des minimalen Abstands zu Hindernissen. | integer | 2 |
| AgentHeight | Agent-Höhe, in Studs. Leerraum, der kleiner als dieser Wert ist, wie der Platz unter Treppen, wird als nicht durchquerbar markiert. | integer | 5 |
| AgentCanJump | Bestimmt, ob Sprünge während des Pfadfindings erlaubt sind. | boolean | true |
| AgentCanClimb | Bestimmt, ob Klettern von TrussParts während des Pfadfindings erlaubt ist. Ein kletterbarer Pfad hat ein Label, das Climb heißt, und die Kosten für einen kletterbaren Pfad sind 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 zu bevorzugen. Siehe Modifikatoren für weitere Details. | table | nil |
Pfadberechnung
Nachdem Sie einen gültigen Pfad mit CreatePath() erstellt haben, muss er berechnet werden, indem Path:ComputeAsync() mit einem Vector3 sowohl für den Ausgangspunkt als auch für das Ziel aufgerufen wird (

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

Pfadbewegung
Jeder PathWaypoint besteht aus sowohl einer Position (Vector3) als auch 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 Callback isCurrentWaypointReached() des Skripts (
Blockierte Pfade
Viele Roblox-Welten sind dynamisch; Teile können sich bewegen oder fallen und Böden können zusammenbrechen. Dies kann einen berechneten Pfad blockieren und verhindern, dass der Charakter sein Ziel erreicht. Um damit umzugehen, können Sie das Path.Blocked-Ereignis verbinden und den Pfad um das Blockierte neu berechnen (
Pfadfindungsmodifikatoren
Standardmäßig gibt Path:ComputeAsync() den kürzesten Pfad zwischen dem Ausgangspunkt und dem Ziel zurück, mit der Ausnahme, dass er versucht, Sprünge zu vermeiden. Dies sieht in einigen Situationen unnatürlich aus; zum Beispiel kann ein Pfad durch Sumpfwasser führen, anstatt darum herum, nur weil der Pfad durch das Wasser geometrisch kürzer ist.

Um Pfadfinding noch weiter zu optimieren, können Sie Pfadfindungsmodifikatoren implementieren, um schlauere Pfade über verschiedene Materialien, um definierte Regionen herum 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 die Kosten 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 den Namen von Enum.Material repräsentieren, zum Beispiel Water für Enum.Material.Water oder CrackedLava für Enum.Material.CrackedLava.
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 = {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 darunter. Dies kann erreicht werden, indem Sie ein PathfindingModifier-Objekt zu einem Teil hinzufügen.
Erstellen Sie einen Anchored-Teil um die Region und setzen Sie seine CanCollide-Eigenschaft auf false.

Fügen Sie eine PathfindingModifier-Instanz auf den Teil ein, suchen Sie die Label-Eigenschaft und weisen Sie ihr einen sinnvollen Namen wie DangerZone zu.

Fügen Sie eine passende 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 bestimmte physische Blockierer zu berechnen, anstatt dass die Berechnung sofort fehlschlägt.
Erstellen Sie einen Anchored-Teil um das Objekt und setzen Sie seine CanCollide-Eigenschaft auf false.

Fügen Sie eine PathfindingModifier-Instanz zu dem Teil hinzu und aktivieren Sie die PassThrough-Eigenschaft.

Nun, 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 nicht in der Lage ist, die Tür zu öffnen, reagiert er, als ob er den Charakter hinter der Tür "hört".

Pfadfindungslinks
Gelegentlich ist es notwendig, einen Pfad über einen Raum zu finden, der nicht normalerweise durchquert werden kann, wie z.B. über einen Abgrund, und eine benutzerdefinierte Aktion durchzuführen, um den nächsten Wegpunkt zu erreichen. Dies kann durch das Objekt PathfindingLink erreicht werden.
Verwenden Sie das Beispiel von oben, Sie können den Agenten ein Boot benutzen lassen.

Um ein PathfindingLink mit diesem Beispiel zu erstellen:
- OPTIONALSchalten Sie Pfadfindungslinks im Widget Visualisierungsoptionen 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 Bootsitz und eine in der Nähe des Bootsanlegestelle.

Erstellen Sie ein PathfindingLink-Objekt im Arbeitsplatz, und weisen Sie dann die Eigenschaften Attachment0 und Attachment1 den entsprechenden Start- und Endbefestigungen zu.


Weisen Sie seiner Label-Eigenschaft einen sinnvollen Namen wie UseBoat zu. Dieser Name wird als Flag im Pfadfindungs-Skript verwendet, um eine benutzerdefinierte Aktion auszulösen, wenn der Agent den Start-Linkpunkt 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 moveToNextWaypoint()-Funktion (
ZEILEN 93–114) kann eine benutzerdefinierte Überprüfung auf den Namen des Label-Modifikators verwendet werden, um eine andere Aktion als Humanoid:Move() auszuführen; in diesem Fall können Sie eine Funktion aufrufen, um den Agenten im Boot zu platzieren, das Boot über das Wasser zu bewegen, den Agenten am Landungsort des Bootes abzuladen und dann den Pfad des Agenten zu seinem endgültigen Ziel fortzusetzen.
Streaming-Kompatibilität
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 Raumes auf ihr Gerät gestreamt und einige der vorhandenen Teilmengen könnten gestreamt werden.
Berücksichtigen Sie die folgenden Best Practices zur Verwendung von PathfindingService in Streaming-aktivierten Spielen:
Streaming kann einen bestimmten Pfad blockieren oder freigeben, während sich ein Charakter entlang dieser bewegt. Zum Beispiel, während ein Charakter durch einen Wald läuft, könnte ein Baum irgendwo vor ihnen gestreamt werden und den Pfad blockieren. Um sicherzustellen, dass das Pfadfinding nahtlos mit dem Streaming funktioniert, wird dringend empfohlen, die Technik blockierte Pfade behandeln zu verwenden und den Pfad bei Bedarf neu zu berechnen.
Ein gängiger Ansatz im Pfadfinding besteht darin, die Koordinaten vorhandener Objekte für die Berechnung zu verwenden, wie das Setzen eines Pfadziels auf die Position eines vorhandenen TreasureChest-Modells in der Welt. Dieser Ansatz ist vollständig kompatibel mit serverseitigen Scripts, da der Server die volle Sicht auf die Welt zu jeder Zeit hat, aber LocalScripts und ModuleScripts, die auf dem Client ausgeführt werden, könnten fehlschlagen, wenn sie versuchen, einen Pfad zu einem Objekt zu berechnen, das nicht gestreamt ist.
Um dieses Problem zu beheben, ziehen Sie in Betracht, das Ziel auf die Position eines BasePart innerhalb eines beständigen Modells zu setzen. Beständige Modelle werden kurz nach dem Beitritt des Spielers geladen und sie werden niemals gestreamt, sodass ein clientseitiges Skript an das Ereignis PersistentLoaded anschließen und sicher auf das Modell zugreifen kann, um Waypoints zu erstellen, nachdem das Ereignis ausgelöst wurde.
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, die nachfolgend aufgeführt sind.