パスファインディング

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

パスファインディングは、キャラクターやオブジェクト(エージェント)が障害物を避けながら目的地に到達するための論理的な経路に沿って移動するプロセスであり、危険な材料や定義された領域を避けることもできます。

ナビゲーションの可視化

パスファインディングのレイアウトとデバッグを支援するために、スタジオはナビゲーションメッシュと修飾子ラベルをレンダリングできます。これらを有効にするには、3Dビューポートの右上隅にある可視化オプションウィジェットからナビゲーションメッシュとパスファインディング修飾子をオンにします。

可視化オプションボタンが右上隅に示された3Dビューポートのクローズアップビュー。

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

スタジオで表示されるナビゲーションメッシュ

実装

パスファインディングは、PathfindingServiceとその関連メソッド(CreatePath()など)を通じてさまざまな方法で実装できますが、このセクションではプレイヤーのキャラクターのための次のパスファインディングスクリプトを使用します。

読みながらテストするには:

  1. 重要
    エクスプローラーでStarterPlayerコンテナを選択します。次に、プロパティウィンドウで、DevComputerMovementModeとDevTouchMovementModeの両方をScriptableに設定します。

  2. 次のコードを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.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    Water = 20
    }
    })
    local character = script.Parent
    local humanoid = character:WaitForChild("Humanoid")
    local waypoints
    local nextWaypointIndex
    local blockedConnection
    local currentWaypointReachedConnection
    local currentWaypointPlaneNormal = Vector3.zero
    local currentWaypointPlaneDistance = 0
    local pathfinderWorking = false
    local function disconnectCurrentWaypointReachedConnection()
    if not currentWaypointReachedConnection then return end
    currentWaypointReachedConnection:Disconnect()
    currentWaypointReachedConnection = nil
    end
    local function isCurrentWaypointReached()
    if humanoid.FloorMaterial == Enum.Material.Air then
    return false
    end
    local reached = false
    if currentWaypointPlaneNormal ~= Vector3.zero then
    -- Compute the distance from humanoid to destination plane
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- Compute the component of the humanoid velocity that is towards the plane
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- Compute the threshold from the destination plane based on humanoid velocity
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- Consider waypoint reached if less then threshold in front of the plane
    reached = dist < threshold
    else
    reached = true
    end
    if reached then
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    moveToNextWaypoint()
    end
    end
    local function calculateNextWaypointApproach()
    nextWaypointIndex += 1
    if nextWaypointIndex > #waypoints then
    return false
    end
    local currentWaypoint = waypoints[nextWaypointIndex - 1]
    local nextWaypoint = waypoints[nextWaypointIndex]
    -- Build destination plane from next waypoint towards current one
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- Set normal perpendicular to Y plane when not climbing up
    if nextWaypoint.Label ~= "Climb" then
    currentWaypointPlaneNormal = Vector3.new(currentWaypointPlaneNormal.X, 0, currentWaypointPlaneNormal.Z)
    end
    if currentWaypointPlaneNormal.Magnitude > 0.000001 then
    currentWaypointPlaneNormal = currentWaypointPlaneNormal.Unit
    currentWaypointPlaneDistance = currentWaypointPlaneNormal:Dot(nextWaypoint.Position)
    end
    return true
    end
    local function resetWaypointData()
    humanoid:Move(Vector3.zero)
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    disconnectCurrentWaypointReachedConnection()
    pathfinderWorking = false
    end
    local function waitForGround()
    while humanoid.FloorMaterial == Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    end
    function moveToNextWaypoint()
    if calculateNextWaypointApproach() then
    disconnectCurrentWaypointReachedConnection()
    currentWaypointReachedConnection = RunService.Heartbeat:Connect(isCurrentWaypointReached)
    local nextWaypointPosition = waypoints[nextWaypointIndex].Position
    local nextWaypointAction = waypoints[nextWaypointIndex].Action
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    if waypoints[nextWaypointIndex + 1] and waypoints[nextWaypointIndex + 1].Label == "UseBoat" then
    nextWaypointIndex += 1
    -- Call your own customized function to make agent use the boat
    elseif nextWaypointAction == Enum.PathWaypointAction.Jump then
    humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
    while humanoid.FloorMaterial ~= Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    end
    else
    resetWaypointData()
    end
    end
    local function findStartingPoint(waypoints)
    nextWaypointIndex = 1
    while nextWaypointIndex + 1 <= #waypoints do
    local dist = waypoints[nextWaypointIndex + 1].Position - humanoid.RootPart.Position
    dist = Vector3.new(dist.X, 0, dist.Z)
    if dist.magnitude >= 2 then
    return
    end
    nextWaypointIndex += 1
    end
    end
    local function followPath()
    -- Compute the path
    pathfinderWorking = true
    waitForGround()
    local success, errorMessage = pcall(function()
    path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)
    end)
    if not success or path.Status ~= Enum.PathStatus.Success then
    warn("パスが計算されていません!", errorMessage)
    return
    end
    -- Get the path waypoints
    waypoints = path:GetWaypoints()
    -- Detect if path becomes blocked
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- Check if the obstacle is further down the path
    if blockedWaypointIndex >= nextWaypointIndex then
    -- Stop detecting path blockage until path is re-computed
    blockedConnection:Disconnect()
    resetWaypointData()
    -- Call function to re-compute new path
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. DESTINATION変数を(

    LINE 5
    )プレイヤーキャラクターが到達できる3Dワールド内のVector3の目的地に編集します。

  4. パス計算とキャラクター移動について学ぶために、次のセクションに進みます。

パスの作成

パスファインディングは、PathfindingServiceとそのCreatePath()メソッドを通じて開始されます(

LINES 9–14
)。このメソッドは、キャラクター(エージェント)が経路に沿って移動する方法を微調整するためのオプションのパラメータテーブルを受け入れます。

キー説明タイプデフォルト
AgentRadiusエージェントの半径(スタッド単位)。障害物からの最小の分離を決定するのに役立ちます。整数2
AgentHeightエージェントの高さ(スタッド単位)。この値より小さい空間(階段の下の空間など)は通行不可としてマークされます。整数5
AgentCanJumpパスファインディング中にジャンプが許可されているかどうかを決定します。ブール値true
AgentCanClimbパスファインディング中にTrussPartsを登ることが許可されているかどうかを決定します。登れるパスにはLabelがClimbという名前で付けられ、登れるパスのコストはデフォルトで1です。ブール値false
WaypointSpacingパス内の中間ウェイポイント間の間隔。math.hugeに設定すると、中間ウェイポイントはありません。数値4
Costs通行のコストを持つ材料または定義されたPathfindingModifiersのテーブル。エージェントが特定の材料/領域を他のものより好むようにするのに役立ちます。詳細については修飾子を参照してください。テーブルnil

パス計算

CreatePath()で有効なパスを作成した後、Path:ComputeAsync()を呼び出して、開始点と目的地の両方にVector3を指定して計算する必要があります(

LINES 133–139
)。

2つの橋を横断するパスの開始/終了がマークされています

Pathが計算されると、開始から終了までのパスをトレースする一連のウェイポイントが含まれます。これらのポイントは、Path:GetWaypoints()メソッドを使用して取得できます(

LINE 142
)。返される配列は、パスの開始から終了までのウェイポイントの順序で配置されています。

計算されたパスに沿って示されたウェイポイント
計算されたパスに沿って示されたウェイポイント

パス移動

各PathWaypointは、Position(Vector3)とAction(PathWaypointAction)の両方で構成されています。通常のRobloxキャラクターのようにHumanoidを含むキャラクターを移動させる最良の方法は、ウェイポイントからウェイポイントへHumanoid:Move()を呼び出し、スクリプトのisCurrentWaypointReached()コールバック(

LINES 32–56
)を使用してキャラクターが各ウェイポイントに到達したときに検出することです。

ブロックされたパス

多くのRobloxの世界は動的です。部品が移動したり落下したり、床が崩れたりすることがあります。これにより、計算されたパスがブロックされ、キャラクターが目的地に到達できなくなる可能性があります。これに対処するには、Path.Blockedイベントに接続し、何がブロックしているかの周りでパスを再計算します(

LINES 145–154
)。

パスファインディング修飾子

デフォルトでは、Path:ComputeAsync()は開始点と目的地の間の最短パスを返しますが、ジャンプを避けようとします。これは、いくつかの状況では不自然に見えます。たとえば、パスが水を通過することが幾何学的に短いために水の周りを回避するのではなく、沼地の水を通過することがあります。

短いパスが必ずしも論理的でない2つのパスが示されています

パスファインディングをさらに最適化するには、さまざまな材料を横断するスマートなパスを計算するためにパスファインディング修飾子を実装するか、定義された領域の周りを回避するか、障害物を無視することができます。

材料コスト

TerrainおよびBasePart材料を使用する場合、CreatePath()内にCostsテーブルを含めて、特定の材料を他の材料よりも通行しやすくすることができます。すべての材料にはデフォルトコストが1であり、任意の材料はその値をmath.hugeに設定することで通行不可として定義できます。

Costsテーブルのキーは、Enum.Material名を表す文字列名である必要があります。たとえば、Enum.Material.WaterのWaterや、Enum.Material.CrackedLavaの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.01
local VELOCITY_MULTIPLIER = 0.0625
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Water = 20, CrackedLava = 100, Slate = 20
}
})

領域の設定

場合によっては、材料の好みだけでは不十分です。たとえば、キャラクターが足元の材料に関係なく定義された領域を避けるようにしたい場合があります。これは、パートにPathfindingModifierオブジェクトを追加することで実現できます。

  1. 領域の周りにAnchoredパートを作成し、そのCanCollideプロパティをfalseに設定します。

    パスファインディング修飾子を適用する領域を定義するためのアンカー付きパート。
  2. パートにPathfindingModifierインスタンスを挿入し、そのLabelプロパティを見つけて、DangerZoneのような意味のある名前を割り当てます。

    LabelプロパティがDangerZoneに設定されたPathfindingModifierインスタンス。
  3. 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.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20
    }
    })

障害物を無視する

場合によっては、固体の障害物を通過するようにパスファインディングを行うことが有用です。これにより、特定の物理的ブロッカーを通過するパスを計算でき、計算が完全に失敗することはありません。

  1. オブジェクトの周りにAnchoredパートを作成し、そのCanCollideプロパティをfalseに設定します。

    パスファインディング修飾子を適用する領域を定義するためのアンカー付きパート。
  2. パートにPathfindingModifierインスタンスを挿入し、そのPassThroughプロパティを有効にします。

    PassThroughプロパティが有効になったPathfindingModifierインスタンス。

    これで、ゾンビNPCからプレイヤーキャラクターへのパスが計算されると、パスはドアを越えて延び、ゾンビにそれを通過させることができます。たとえゾンビがドアを開けられなくても、キャラクターがドアの後ろにいるのを「聞く」かのように反応します。

    以前にブロックされていたドアを通過するゾンビNPCのパス。

パスファインディングリンク

時には、通常通行できない空間を横断するパスを見つける必要があり、次のウェイポイントに到達するためにカスタムアクションを実行する必要があります。これは、PathfindingLinkオブジェクトを通じて実現できます。

上記の例を使用して、エージェントがボートを使用するようにすることができます。

エージェントがボートを使用できる方法を示すPathfindingLink。

この例を使用してPathfindingLinkを作成するには:

  1. オプション
    3Dビューポートの右上隅にある可視化オプションウィジェットからパスファインディングリンクをオンにします。これは、パスファインディングリンクを実装する際の可視化とデバッグに役立ちます。

  2. ボートの座席とボートの着地点の近くに2つのAttachmentsを作成します。

    パスファインディングリンクの開始点と終了点のために作成されたアタッチメント。
  3. ワークスペースにPathfindingLinkオブジェクトを作成し、そのAttachment0とAttachment1プロパティをそれぞれ開始アタッチメントと終了アタッチメントに割り当てます。

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

    PathfindingLinkに指定されたLabelプロパティ。
  5. 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.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    UseBoat = 2, Water = 20
    }
    })
  6. moveToNextWaypoint()関数内(

    LINES 93–114
    )で、Label修飾子名のカスタムチェックを使用して、Humanoid:Move()とは異なるアクションを取ることができます。この場合、エージェントをボートに座らせ、ボートを水の上に移動させ、ボートの着地点でエージェントを降ろし、その後エージェントのパスを最終目的地に続ける関数を呼び出すことができます。

ストリーミング互換性

ゲーム内のインスタンスストリーミングは、プレイヤーのキャラクターが世界を移動する際に3Dコンテンツを動的に読み込み、アンロードする強力な機能です。彼らが3D空間を探索するにつれて、新しいサブセットが彼らのデバイスにストリーミングされ、既存のサブセットの一部がストリーミングアウトすることがあります。

ストリーミング対応ゲームでPathfindingServiceを使用するためのベストプラクティスを考慮してください:

  • ストリーミングは、キャラクターがパスに沿って移動する際に特定のパスをブロックまたはブロック解除することがあります。たとえば、キャラクターが森を走っている間に、木が彼らの前にストリーミングされてパスを妨げることがあります。ストリーミングとシームレスに連携させるために、ブロックされたパスの処理技術を使用し、必要に応じてパスを再計算することを強くお勧めします。

  • パスファインディングの一般的なアプローチは、既存のオブジェクトの座標を計算に使用することです。たとえば、パスの目的地をワールド内の既存のTreasureChestモデルの位置に設定します。このアプローチは、サーバーが常に世界を完全に把握しているため、サーバー側のScriptsと完全に互換性がありますが、クライアントで実行されるLocalScriptsやModuleScriptsは、ストリーミングされていないオブジェクトへのパスを計算しようとすると失敗する可能性があります。

    この問題に対処するには、永続的モデル内のBasePartの位置に目的地を設定することを検討してください。永続モデルはプレイヤーが参加した後すぐに読み込まれ、決してストリーミングアウトしないため、クライアント側のスクリプトはPersistentLoadedイベントに接続し、イベントが発火した後にウェイポイントを作成するためにモデルに安全にアクセスできます。

制限と失敗要因

パスファインディングエンジンには、効率的な処理と最適なパフォーマンスを確保するための特定の制限があります。さらに、パスファインディングの計算は、以下に示すさまざまな理由で失敗する可能性があります。

©2026 Roblox Corporation。Roblox(ロブロックス)、RobloxロゴおよびPowering Imaginationは、米国並びにその他の国における登録商標および非登録商標です。