パスファインディングは、キャラクターやオブジェクト(エージェント)が障害物を避けて目的地に到達するための論理的な経路に沿って移動するプロセスです。危険物や特定の領域を避けるオプションもあります。
ナビゲーションの可視化
パスファインディングのレイアウトやデバッグを支援するために、Studioはナビゲーションメッシュと修飾子ラベルをレンダリングできます。それらを有効にするには、3Dビューポートの右上隅にある可視化オプションウィジェットからナビゲーションメッシュとパスファインディング修飾子をトグルします。

ナビゲーションメッシュを有効にすると、色付けされたエリアがキャラクターが歩いたり泳いだりできる場所を示します。小さな矢印は、キャラクターがジャンプして到達しようとするエリアを示します。

実装
パスファインディングは、PathfindingServiceとその関連メソッド(たとえば、 CreatePath())を使用してさまざまな方法で実装できますが、このセクションではプレイヤーキャラクターのための次のパスファインディングスクリプトを使用します。
読む際にテストするには:
- 重要ExplorerでStarterPlayerコンテナを選択します。次に、プロパティウィンドウで、DevComputerMovementModeとDevTouchMovementModeの両方をScriptableに設定します。


次のコードをLocalScript内のStarterCharacterScriptsにコピーするか、このパッケージを取得して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-- 人間の足が地面からの距離を計算local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance-- 地面に対する人間の移動速度を計算local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)-- 人間の移動速度に基づいて地面からのしきい値を計算local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)-- もし距離がしきい値よりも小さいなら到達したとみなすreached = 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]-- 次のウェイポイントから現在のウェイポイントに向かう地面を生成currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position-- 上に登っていない場合はY平面に対して垂直に設定if 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-- エージェントがボートを使うためのカスタム関数を呼び出すelseif 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()-- パスを計算するpathfinderWorking = truewaitForGround()local success, errorMessage = pcall(function()path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)end)if not success or path.Status ~= Enum.PathStatus.Success thenwarn("パスが計算されませんでした!", errorMessage)returnend-- パスウェイポイントを取得waypoints = path:GetWaypoints()-- パスがブロックされているかどうかを検出するblockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)-- 障害物がパスの先にあるかどうかを確認するif blockedWaypointIndex >= nextWaypointIndex then-- パスが再計算されるまでの道をブロック検出を停止するblockedConnection:Disconnect()resetWaypointData()-- 新しいパスを再計算するための関数を呼び出すfollowPath()endend)findStartingPoint(waypoints)moveToNextWaypoint()endfollowPath()DESTINATION変数を3D世界内でプレイヤーキャラクターが到達できるVector3の目的地に編集します。
パスの計算とキャラクターの移動について学ぶために、以下のセクションを確認してください。
パスの作成
パスファインディングは、PathfindingServiceとそのCreatePath()メソッドを介して開始されます(
| キー | 説明 | タイプ | デフォルト |
|---|---|---|---|
| AgentRadius | エージェントの半径、スタッド単位。障害物からの最小の間隔を判断するのに役立ちます。 | 整数 | 2 |
| AgentHeight | エージェントの高さ、スタッド単位。この値より小さい空間、たとえば階段の下の空間は、移動不可能としてマークされます。 | 整数 | 5 |
| AgentCanJump | パスファインディング中にジャンプが許可されているかどうかを判断します。 | ブーリアン | true |
| AgentCanClimb | パスファインディング中にトラスパーツに登れるかどうかを判断します。登れないパスにはLabelがClimbという名前が付けられ、登れるパスのコストはデフォルトで1です。 | ブーリアン | false |
| WaypointSpacing | パス内の中間点の間隔。math.hugeに設定すると、中間点はありません。 | 数値 | 4 |
| Costs | 移動コストのための材料のテーブルまたは定義済みパスファインディング修飾子。エージェントが特定の材料や地域を他のものより好むようにするのに役立ちます。詳細については、修飾子を参照してください。 | テーブル | nil |
パスの計算
CreatePath()で有効なパスを作成したら、 Path:ComputeAsync()を呼び出して両方の出発点と目的地のVector3を持つ必要があります(

Pathが計算されると、始まりから終わりまでの経路をトレースする一連のウェイポイントが含まれます。これらのポイントはPath:GetWaypoints()メソッドで収集できます(

パスの移動
各PathWaypointは、Position(Vector3)およびAction(PathWaypointAction)の両方で構成されています。ロブロックスの典型的なキャラクターのようにHumanoidを含むキャラクターを移動させるには、ウェイポイントからウェイポイントに渡ってHumanoid:Move()を呼び出し、スクリプトのisCurrentWaypointReached()コールバックを使用してキャラクターが各ウェイポイントに到達したときに検出するのが最適です(
ブロックされたパス
多くのロブロックスの世界は動的です。パーツが移動したり落下したりし、床が崩れることがあります。これにより、計算されたパスがブロックされ、キャラクターが目的地にたどり着けなくなることがあります。これを処理するには、Path.Blockedイベントに接続し、ブロックされたものの周りのパスを再計算できます(
パスファインディング修飾子
デフォルトでは、Path:ComputeAsync()は開始点と目的地の間の最短パスを返しますが、ジャンプを回避しようとします。これは、いくつかの状況では不自然に見えます。たとえば、パスが水の中を通る場合、ジオメトリ的に短いために水の周りを回避する代わりに、スワンプウォーターを通ることがある等です。

パスファインディングをさらに最適化するには、さまざまな材料を越えた賢いパスを計算するために、パスファインディング修飾子を実装したり、定義された領域の周りを囲んだり、障害物を無視したりすることができます。
材料コスト
TerrainおよびBasePart資料を使用する場合、特定の材料を他の材料よりも移動しやすくするために、CostsテーブルをCreatePath()の中に含めることができます。すべての材料にはデフォルトでコストが1に設定されており、任意の材料はその値をmath.hugeに設定することで非可動と見なされます。
Costsテーブルのキーは、Enum.Materialの名前を表す文字列名であるべきで、例えばWaterはEnum.Material.Waterや、CrackedLavaは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}})
領域の設定
場合によっては、材料の好みだけでは不十分な場合があります。たとえば、キャラクターが特定の領域を避けることができるようにしたい場合、地面にある材料に関わらず、これを実現するのはPathfindingModifierオブジェクトをパーツに追加することで行えます。
領域の周りにAnchoredパーツを作成し、そのCanCollideプロパティをfalseに設定します。

パーツにPathfindingModifierインスタンスを挿入し、そのLabelプロパティを見つけ、DangerZoneのような意味のある名前を割り当てます。

CreatePath()のCostsテーブル内に一致するDangerZoneキーと関連する数値値を含めます。修飾子は、その値をmath.hugeに設定することで非可動として定義できます。
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}})
障害物を無視する
場合によっては、物理的な障害物を無視することができると便利です。これにより、計算が完全に失敗することなく、特定の物理的ブロッカーを通じてパスを計算することができます。
Anchoredパーツを障害物の周りに作成し、そのCanCollideプロパティをfalseに設定します。

パーツにPathfindingModifierインスタンスを挿入し、そのPassThroughプロパティを有効にします。

これにより、ゾンビNPCからプレイヤーキャラクターへのパスが計算されると、パスはドアを越えて拡張され、ゾンビにそれを横断するように促すことができます。たとえゾンビがドアを開けられなくても、キャラクターがドアの向こう側にいるかのように反応します。

パスファインディングリンク
時には、通常通れない空間を越えて経路を見つけ、次のウェイポイントに到達するためにカスタムアクションを実行する必要があります。これを実現できるのがPathfindingLinkオブジェクトです。
上記の例を使用して、エージェントがボートを使うようにすることができます。

この例を使用してPathfindingLinkを作成するには:
- オプション3Dビューポートの右上隅にある可視化オプションウィジェットからパスファインディング リンクをトグルします。これは、パスファインディングリンクを実装する際の可視化およびデバッグを支援します。
船の座席に1つ、船の着地ポイントの近くに1つのAttachmentsを作成します。

LabelプロパティにUseBoatのような意味のある名前を割り当てます。この名前は、エージェントが開始リンクポイントに到達したときにカスタムアクションをトリガーするためにパスファインディングスクリプトで使用されます。

CreatePath()内で、WaterキーとLabelプロパティ名に一致するカスタムキーを含むCostsテーブルを作成します。カスタムキーにはWaterの値よりも低い値を割り当てます。
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}})moveToNextWaypoint()関数内で(
LINES 93–114)、 Label修飾子名のためのカスタムチェックを使用して、Humanoid:Move()とは異なるアクションを取ることができます。この場合、エージェントをボートに置き、ボートを水上に移動させ、ボートの着地地点でエージェントを降ろし、その後エージェントのパスを最終目的地に向かって続行するための関数を呼び出すことができます。
ストリーミングの互換性
ゲーム内インスタンスストリーミングは、プレイヤーのキャラクターが世界を移動する際に3Dコンテンツを動的にロードおよびアンロードする強力な機能です。3D空間を探索する際、新しいサブセットがプレイヤーのデバイスにストリームされ、既存のサブセットの一部はストリームアウトする場合があります。
PathfindingServiceをストリーミングが有効なゲームで使用する際のベストプラクティスを以下に示します:
ストリーミング中にキャラクターが経路を進むと、特定のパスがブロックされたり解除されたりすることがあります。たとえば、キャラクターが森を走っているときに、どこか前方に木がストリームインしてパスを妨げることがあります。ストリーミングとパスファインディングをシームレスに機能させるために、ブロックされたパス(#blocked-paths)の手法を使用し、必要に応じてパスを再計算することを強くお勧めします。
パスファインディングにおける一般的なアプローチは、既存のオブジェクトの座標を計算に使用することです。たとえば、パスの目的地をワールド内の既存のTreasureChestモデルの位置に設定することが含まれます。このアプローチは、サーバーが常にワールド全体を完全に見ることができるため、サーバーサイドのScriptsと完全に互換性がありますが、クライアントで実行されるLocalScriptsやModuleScriptsは、ストリームされていないオブジェクトにパスを計算しようとすると失敗する可能性があります。
この問題に対処するためには、目的地を永続的なモデル内のBasePartの位置に設定することをお勧めします。永続モデルはプレイヤーが参加したほぼ直後にロードされ、決してストリームアウトしないため、クライアントサイドのスクリプトはPersistentLoadedイベントに接続し、イベントが発生した後にウェイポイントを作成するためのモデルに安全にアクセスできます。
制限と失敗要因
パスファインディングエンジンは、効率的な処理と最適なパフォーマンスを確保するための特定の制限を含んでいます。また、パスファインディングの計算は、以下に示すように、さまざまな理由で失敗する可能性があります。

