パスファインディング

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

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

ナビゲーションの可視化

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

ビジュアライゼーションオプションボタンが右上隅に表示されている3Dビューポートのクローズアップ画像。

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

Studioに表示されたナビゲーションメッシュ

実装

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

読む際にテストするには:

  1. 重要
    ExplorerStarterPlayerコンテナを選択します。次に、プロパティウィンドウで、DevComputerMovementModeDevTouchMovementModeの両方を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
    -- 人間の足が地面からの距離を計算
    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 < 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]
    -- 次のウェイポイントから現在のウェイポイントに向かう地面を生成
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- 上に登っていない場合はY平面に対して垂直に設定
    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
    -- エージェントがボートを使うためのカスタム関数を呼び出す
    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()
    -- パスを計算する
    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
    -- パスウェイポイントを取得
    waypoints = path:GetWaypoints()
    -- パスがブロックされているかどうかを検出する
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- 障害物がパスの先にあるかどうかを確認する
    if blockedWaypointIndex >= nextWaypointIndex then
    -- パスが再計算されるまでの道をブロック検出を停止する
    blockedConnection:Disconnect()
    resetWaypointData()
    -- 新しいパスを再計算するための関数を呼び出す
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. DESTINATION変数を3D世界内でプレイヤーキャラクターが到達できるVector3の目的地に編集します。

  4. パスの計算とキャラクターの移動について学ぶために、以下のセクションを確認してください。

パスの作成

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

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

キー説明タイプデフォルト
AgentRadiusエージェントの半径、スタッド単位。障害物からの最小の間隔を判断するのに役立ちます。整数2
AgentHeightエージェントの高さ、スタッド単位。この値より小さい空間、たとえば階段の下の空間は、移動不可能としてマークされます。整数5
AgentCanJumpパスファインディング中にジャンプが許可されているかどうかを判断します。ブーリアンtrue
AgentCanClimbパスファインディング中にトラスパーツに登れるかどうかを判断します。登れないパスにはLabelClimbという名前が付けられ、登れるパスのコストはデフォルトで1です。ブーリアンfalse
WaypointSpacingパス内の中間点の間隔。math.hugeに設定すると、中間点はありません。数値4
Costs移動コストのための材料のテーブルまたは定義済みパスファインディング修飾子。エージェントが特定の材料や地域を他のものより好むようにするのに役立ちます。詳細については、修飾子を参照してください。テーブルnil

パスの計算

CreatePath()で有効なパスを作成したら、 Path:ComputeAsync()を呼び出して両方の出発点と目的地のVector3を持つ必要があります(

LINES 133–139
)。

二つの橋を越えたパスの始まりと終わりを示しています

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

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

計算されたパスを示すウェイポイント
計算されたパスを示すウェイポイント

パスの移動

PathWaypointは、PositionVector3)およびActionPathWaypointAction)の両方で構成されています。ロブロックスの典型的なキャラクターのようにHumanoidを含むキャラクターを移動させるには、ウェイポイントからウェイポイントに渡ってHumanoid:Move()を呼び出し、スクリプトのisCurrentWaypointReached()コールバックを使用してキャラクターが各ウェイポイントに到達したときに検出するのが最適です(

LINES 32–56
)。

ブロックされたパス

多くのロブロックスの世界は動的です。パーツが移動したり落下したりし、床が崩れることがあります。これにより、計算されたパスがブロックされ、キャラクターが目的地にたどり着けなくなることがあります。これを処理するには、Path.Blockedイベントに接続し、ブロックされたものの周りのパスを再計算できます(

LINES 145–154
)。

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

デフォルトでは、Path:ComputeAsync()は開始点と目的地の間の最短パスを返しますが、ジャンプを回避しようとします。これは、いくつかの状況では不自然に見えます。たとえば、パスが水の中を通る場合、ジオメトリ的に短いために水の周りを回避する代わりに、スワンプウォーターを通ることがある等です。

二つのパスは短い方が必ずしも論理的ではありません

パスファインディングをさらに最適化するには、さまざまな材料を越えた賢いパスを計算するために、パスファインディング修飾子を実装したり、定義された領域の周りを囲んだり、障害物を無視したりすることができます。

材料コスト

TerrainおよびBasePart資料を使用する場合、特定の材料を他の材料よりも移動しやすくするために、CostsテーブルをCreatePath()の中に含めることができます。すべての材料にはデフォルトでコストが1に設定されており、任意の材料はその値をmath.hugeに設定することで非可動と見なされます。

Costsテーブルのキーは、Enum.Materialの名前を表す文字列名であるべきで、例えばWaterEnum.Material.Waterや、CrackedLavaEnum.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.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. 船の座席に1つ、船の着地ポイントの近くに1つの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をストリーミングが有効なゲームで使用する際のベストプラクティスを以下に示します:

  • ストリーミング中にキャラクターが経路を進むと、特定のパスがブロックされたり解除されたりすることがあります。たとえば、キャラクターが森を走っているときに、どこか前方に木がストリームインしてパスを妨げることがあります。ストリーミングとパスファインディングをシームレスに機能させるために、ブロックされたパス(#blocked-paths)の手法を使用し、必要に応じてパスを再計算することを強くお勧めします。

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

    この問題に対処するためには、目的地を永続的なモデル内のBasePartの位置に設定することをお勧めします。永続モデルはプレイヤーが参加したほぼ直後にロードされ、決してストリームアウトしないため、クライアントサイドのスクリプトはPersistentLoadedイベントに接続し、イベントが発生した後にウェイポイントを作成するためのモデルに安全にアクセスできます。

制限と失敗要因

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

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