캐릭터 경로 찾기

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

경로 찾기 는 장애물을 피하고 (옵션으로) 위험한 재료나 정의된 지역에 도달하기 위해 캐릭터를 논리적 경로를 따라 이동하는 과정입니다.

탐색 시각화

경로 찾기 레이아웃과 디버깅을 지원하기 위해 Studio는 탐색 메쉬와 수정자 레이블을 렌더링할 수 있습니다.활성화하려면 3D 뷰포트의 오른쪽 상단에 있는 시각화 옵션 위젯에서 탐색 메시경로 찾기 수정자를 토글하십시오.

A close up view of the 3D viewport with the Visualization Options button indicated in the upper-right corner.

탐색 메시 를 활성화하면 색상이 있는 영역에서 캐릭터가 걸을 수 있거나 수영할 수 있는 곳을 보여주고, 색상이 없는 영역은 차단됩니다.작은 화살표는 캐릭터가 점프하여 도달하려는 영역을 나타내며, 생성 경로를 설정할 때 으로 설정하면 됩니다.

Navigation mesh showing in Studio

경로 찾기 수정자 를 활성화하면 텍스트 레이블은 경로 찾기 수정자를 사용할 때 고려되는 특정 재료와 지역을 나타냅니다.

Navigation labels showing on navigation mesh

알려진 제한

경로 찾기 기능은 효율적인 처리와 최적의 이행보장하기 위해 특정 제한 사항이 있습니다.

수직 배치 제한

경로 찾기 계산은 특정 垂直 경계 내의 부품만 고려합니다:

  • 하한 경계 — 하단 Y 좌표가 -65,536스터드 미만인 부품은 무시됩니다.
  • 상한 경계 — 상위 Y 좌표를 가진 부품이 65,536스터드를 초과하면 무시됩니다.
  • 수직 스팬 — 가장 낮은 부분의 하단 Y 좌표에서 가장 높은 부분의 상단 Y 좌표까지의 수직 거리는 65,536스터드를 초과해서는 안되며, 그렇지 않으면 경로 찾기 시스템이 그 부분을 경로 찾기 계산 중에 무시합니다.

검색 거리 제한

시작 지점에서 종료 지점까지의 직선 시야 거리는 3,000스터드를 초과해서는 안됩니다.이 거리를 초과하면 NoPath 상태가 발생합니다.

경로 생성

경로 찾기는 PathfindingService 및 해당 CreatePath() 함수를 통해 시작됩니다.

로컬 스크립트

local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath()

CreatePath()는 문자(에이전트)가 경로를 따라 이동하는 방식을 미세 조정하는 선택적 매개변수 테이블을 수락합니다.

설명유형기본
AgentRadiusAgent 반경, 스터드 단위. 장애물로부터의 최소 분리를 결정하는 데 유용합니다.정수 integer2
AgentHeight에이전트 높이, 스터드 단위. 계단 아래의 공간과 같이 이 값보다 작은 빈 공간은 통과할 수 없는 것으로 표시됩니다.정수 integer5
AgentCanJump경로 찾기 중에 점프가 허용되는지 여부를 결정합니다.부울true
AgentCanClimb경로 찾기 중에 등반 TrussParts 이 허용되는지 여부를 결정합니다.부울false
WaypointSpacing경로에서 중간 웨이포인트 사이의 간격. math.huge로 설정하면 중간 웨이포인트가 없습니다.번호4
Costs재료 테이블 또는 정의된 PathfindingModifiers 및 검색에 대한 비용.에이전트가 다른 것보다 특정 재료/지역을 선호하도록 만드는 데 유용합니다.자세한 내용은 모디파이어를 참조하십시오.테이블nil
로컬 스크립트

local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath({
AgentRadius = 3,
AgentHeight = 6,
AgentCanJump = false,
Costs = {
Water = 20
}
})

에이전트는 경로 탐색 중에 동안 올라갈 수 있으며, 경로를 생성할 때 를 으로 설정하고 트러스 등반 경로에서 에이전트를 차단하는 것이 없다고 가정합니다.등반할 수 있는 경로에는 등반 레이블과 비용이 있으며, 등반할 수 있는 경로의 비용은 기본적으로 1 이다.

Path going up a climbable TrussPart ladder
로컬스크립트 - 트러스 등반 경로

local PathfindingService = game:GetService("PathfindingService")
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Climb = 2 -- 등반 경로 비용; 기본값은 1입니다
}
})

경로를 따라 이동

이 섹션에서는 플레이어의 캐릭터에 대해 다음 경로 찾기 스크립트를 사용합니다. 읽으면서 테스트하려면:

  1. 코드를 LocalScript 내에 StarterCharacterScripts 에 복사하십시오.
  2. 플레이어 캐릭터가 도달할 수 있는 3D 세계의 TEST_DESTINATION``Datatype.Vector3 설정하십시오.
  3. 경로 계산과 문자 이동에 대해 알아보려면 다음 섹션을 계속 진행하십시오.
로컬스크립트 - 문자 경로 찾기

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 경로 계산
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 경로 지점 가져오기
waypoints = path:GetWaypoints()
-- 경로가 차단되는지 여부 감지
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- 장애물이 경로 아래에 있는지 확인하십시오
if blockedWaypointIndex >= nextWaypointIndex then
-- 경로가 다시 계산될 때까지 경로 차단 감지 중지
blockedConnection:Disconnect()
-- 새로운 경로를 다시 계산하기 위해 함수 호출
followPath(destination)
end
end)
-- 다음 웨이포인트로의 이동이 완료되었는지 감지
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and nextWaypointIndex < #waypoints then
-- 경로 지점 인덱스 증가 및 다음 경로 지점으로 이동
nextWaypointIndex += 1
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
reachedConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
end
-- 처음에 두 번째 웨이포인트로 이동(첫 번째 웨이포인트는 경로 시작; 건너뛰기)
nextWaypointIndex = 2
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
warn("Path not computed!", errorMessage)
end
end
followPath(TEST_DESTINATION)

경로 계산

를 사용하여 시작 지점과 목적지 모두에 대해 를 호출하여 유효한 경로를 만든 후에는 두 지점 모두에 대해 를 계산해야 합니다.

로컬스크립트 - 문자 경로 찾기

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 경로 계산
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
end
Path start/end marked on series of islands and bridges

웨이포인트 받기

Path가 계산되면 시작부터 종료경로를 추적하는 일련의 웨이포인트 가 포함됩니다.이 포인트는 Path:GetWaypoints() 함수로 수집할 수 있습니다.

로컬스크립트 - 문자 경로 찾기

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 경로 계산
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 경로 지점 가져오기
waypoints = path:GetWaypoints()
end
end
Waypoints indicated across computed path
계산된 경로에 표시된 웨이포인트

경로 이동

각 웨이포인트는 위치 ()와 작업 ()이 모두 포함됩니다.일반적인 Roblox 캐릭터와 같이 캐릭터에 Humanoid 를 이동하려면 방법점에서 방법점으로 Humanoid:MoveTo() 를 호출하여 캐릭터가 각 방법점에 도달했을 때 검색할 MoveToFinished 이벤트를 사용하여 가장 쉬운 방법입니다.

로컬스크립트 - 문자 경로 찾기

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 경로 계산
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 경로 지점 가져오기
waypoints = path:GetWaypoints()
-- 경로가 차단되는지 여부 감지
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- 장애물이 경로 아래에 있는지 확인하십시오
if blockedWaypointIndex >= nextWaypointIndex then
-- 경로가 다시 계산될 때까지 경로 차단 감지 중지
blockedConnection:Disconnect()
-- 새로운 경로를 다시 계산하기 위해 함수 호출
followPath(destination)
end
end)
-- 다음 웨이포인트로의 이동이 완료되었는지 감지
if not reachedConnection then
reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
if reached and nextWaypointIndex < #waypoints then
-- 경로 지점 인덱스 증가 및 다음 경로 지점으로 이동
nextWaypointIndex += 1
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
reachedConnection:Disconnect()
blockedConnection:Disconnect()
end
end)
end
-- 처음에 두 번째 웨이포인트로 이동(첫 번째 웨이포인트는 경로 시작; 건너뛰기)
nextWaypointIndex = 2
humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
warn("Path not computed!", errorMessage)
end
end

차단된 경로 처리

많은 Roblox 세계가 동적이며, 부품이 이동하거나 떨어지고 바닥이 무너질 수 있습니다.이렇게 하면 계산된 경로가 차단되고 문자가 목적지에 도달하지 못할 수 있습니다.이를 처리하려면 Path.Blocked 이벤트를 연결하고 차단한 것의 주위의 경로를 다시 계산할 수 있습니다.

로컬스크립트 - 문자 경로 찾기

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath()
local player = Players.LocalPlayer
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local TEST_DESTINATION = Vector3.new(100, 0, 100)
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local function followPath(destination)
-- 경로 계산
local success, errorMessage = pcall(function()
path:ComputeAsync(character.PrimaryPart.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
-- 경로 지점 가져오기
waypoints = path:GetWaypoints()
-- 경로가 차단되는지 여부 감지
blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
-- 장애물이 경로 아래에 있는지 확인하십시오
if blockedWaypointIndex >= nextWaypointIndex then
-- 경로가 다시 계산될 때까지 경로 차단 감지 중지
blockedConnection:Disconnect()
-- 새로운 경로를 다시 계산하기 위해 함수 호출
followPath(destination)
end
end)
end
end

경로 찾기 수정자

기본적으로, 는 시작 지점과 목적지 사이의 가장 짧은 경로를 반환하지만, 점프를 피하려고 시도합니다.이는 일부 상황에서 자연스럽지 않아 보입니다. 예를 인스턴스, 물을 통해 경로가 지나갈 수 있으며, 물을 통한 경로가 기하학적으로 더 짧기 때문입니다.

Two paths indicated with the shorter path not necessarily more logical

경로 찾기를 최적화하려면 경로 찾기 수정자 를 구현하여 다양한 재료, 주어진 지역, 또는 장애물을 통해 더 똑똑한 경로를 계산할 수 있습니다.

재료 비용 설정

TerrainBasePart 재료로 작업할 때, 특정 재료를 다른 재료보다 더 가로지르게 만들기 위해 Costs 테이블을 CreatePath() 내에 포함할 수 있습니다.모든 재료에는 기본 비용이 1이고 모든 재료는 값을 으로 설정하여 트래버스할 수 없는 것으로 정의될 수 있습니다.

Costs 테이블의 키는 예를 들어 Enum.Material 이름을 나타내는 문자열 이름이어야 합니다, 예를 들어 Water 에 대한 Enum.Material.Water.

로컬스크립트 - 문자 경로 찾기

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local path = PathfindingService:CreatePath({
Costs = {
Water = 20,
Mud = 5,
Neon = math.huge
}
})

지역 작업

일부 경우, 재료 선호도가 충분하지 않습니다.예를 들어, 캐릭터가 발 아래의 재료에 관계없이 정의된 영역을 피하기를 원할 수 있습니다. 부품에 PathfindingModifier 개체를 추가하여 이를 달성할 수 있습니다.

  1. 위험한 지역 주위에 Anchored 부품을 만들고 해당 CanCollide 속성을 false 로 설정합니다.

    Anchored part defining a region to apply a pathfinding modifier to
  2. 부품에 PathfindingModifier 인스턴스를 삽입하고, 해당 Label 속성을 찾아 의미 있는 이름, 예를 들어 DangerZone 을 할당합니다.

    PathfindingModifier instance with Label property set to DangerZone
  3. 일치하는 키와 관련된 숫자 값이 포함된 Costs 테이블을 CreatePath() 내에 포함하십시오.변형자는 값을 math.huge로 설정하여 트래버스할 수 없는 것으로 정의할 수 있습니다.

    로컬스크립트 - 문자 경로 찾기

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local Workspace = game:GetService("Workspace")
    local path = PathfindingService:CreatePath({
    Costs = {
    DangerZone = math.huge
    }
    })

장애물 무시

경우에 따라 존재하지 않는 것처럼 단단한 장애물을 통해 길을 찾는 것이 유용합니다.이렇게 하면 특정 물리적 차단기를 통해 경로를 계산할 수 있으므로 계산이 실패하는 것보다 낫습니다.

  1. 개체 주위에 Anchored 부품을 만들고 해당 CanCollide 속성을 false 로 설정합니다.

    Anchored part defining a region to apply a pathfinding modifier to
  2. 부품에 PathfindingModifier 인스턴스를 삽입하고 PassThrough 속성을 활성화합니다.

    PathfindingModifier instance with PassThrough property enabled

    이제 좀비 NPC에서 플레이어 캐릭터로 경로가 계산될 때 경로는 문 너머로 확장되며 좀비에게 경로를 통과하도록 요청할 수 있습니다.좀비가 문을 열 수 없더라도 문 뒤에 있는 캐릭터를 "들은 것"처럼 반응합니다.

    Zombie NPC path passing through the previously blocking door

경로 찾기 링크

때때로 협곡과 같이 일반적으로 통과할 수 없는 공간에서 경로를 찾아 다음 웨이포인트에 도달하기 위한 사용자 지정 작업을 수행해야 합니다.이는 PathfindingLink 개체를 통해 달성할 수 있습니다.

위의 섬 예제를 사용하여 에이전트가 모든 다리를 걸어 건너는 대신 보트를 사용하도록 만들 수 있습니다.

PathfindingLink showing how an agent can use a boat instead of walking across all of the bridges

이 예제를 사용하여 PathfindingLink를 생성하려면:

  1. 시각화 및 디버깅을 지원하기 위해, 3D 뷰포트의 오른쪽 상단에 있는 시각화 옵션 위젯에서 경로 찾기 링크를 토글합니다.

  2. 보트의 좌석과 보트 착륙 지점 근처에 두 개의 Attachments를 만듭니다.

    Attachments created for pathfinding link's start and end
  3. 작업 공간에 PathfindingLink 개체를 생성하고 해당 첨부 파일0첨부 파일1 속성을 시작 및 종료 첨부 파일에 할당합니다.

    Attachment0/Attachment1 properties of a PathfindingLink PathfindingLink visualized in the 3D world
  4. 의미 있는 이름, 예를 들어 UseBoat 을 해당 Label 속성에 할당합니다.이 이름은 경로 찾기 스크립트에서 플래그로 사용되어 에이전트가 시작 링크 지점에 도달할 때 사용자 지정 작업을 트리거합니다.

    Label property specified for PathfindingLink
  5. 두 개의 키와 속성 이름에 일치하는 사용자 지정 키가 포함된 테이블을 포함하여 속성 이름에 맞는 테이블을 포함하십시오.사용자 지정 키에 값 Water 보다 낮은 값을 할당합니다.

    로컬스크립트 - 문자 경로 찾기

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local Workspace = game:GetService("Workspace")
    local path = PathfindingService:CreatePath({
    Costs = {
    Water = 20,
    UseBoat = 1
    }
    })
  6. 경로 지점에 도달할 때 발생하는 이벤트에서 모디파이어 이름에 대한 사용자 지정 검사를 추가하고 다른 행동을 수행합니다. 이 경우, 함수를 호출하여 에이전트를 보트에 앉히고, 물을 가로지르고 목적지에 도착하면 에이전트의 경로를 계속하는 것입니다.

    로컬스크립트 - 문자 경로 찾기

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local Workspace = game:GetService("Workspace")
    local path = PathfindingService:CreatePath({
    Costs = {
    Water = 20,
    UseBoat = 1
    }
    })
    local player = Players.LocalPlayer
    local character = player.Character
    local humanoid = character:WaitForChild("Humanoid")
    local TEST_DESTINATION = Vector3.new(228.9, 17.8, 292.5)
    local waypoints
    local nextWaypointIndex
    local reachedConnection
    local blockedConnection
    local function followPath(destination)
    -- 경로 계산
    local success, errorMessage = pcall(function()
    path:ComputeAsync(character.PrimaryPart.Position, destination)
    end)
    if success and path.Status == Enum.PathStatus.Success then
    -- 경로 지점 가져오기
    waypoints = path:GetWaypoints()
    -- 경로가 차단되는지 여부 감지
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- 장애물이 경로 아래에 있는지 확인하십시오
    if blockedWaypointIndex >= nextWaypointIndex then
    -- 경로가 다시 계산될 때까지 경로 차단 감지 중지
    blockedConnection:Disconnect()
    -- 새로운 경로를 다시 계산하기 위해 함수 호출
    followPath(destination)
    end
    end)
    -- 다음 웨이포인트로의 이동이 완료되었는지 감지
    if not reachedConnection then
    reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
    if reached and nextWaypointIndex < #waypoints then
    -- 경로 지점 인덱스 증가 및 다음 경로 지점으로 이동
    nextWaypointIndex += 1
    -- 웨이포인트 레이블이 "UseBoat"인 경우 보트를 사용하고, 그렇지 않으면 다음 웨이포인트로 이동합니다
    if waypoints[nextWaypointIndex].Label == "UseBoat" then
    useBoat()
    else
    humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
    end
    else
    reachedConnection:Disconnect()
    blockedConnection:Disconnect()
    end
    end)
    end
    -- 처음에 두 번째 웨이포인트로 이동(첫 번째 웨이포인트는 경로 시작; 건너뛰기)
    nextWaypointIndex = 2
    humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
    else
    warn("Path not computed!", errorMessage)
    end
    end
    function useBoat()
    local boat = Workspace.BoatModel
    humanoid.Seated:Connect(function()
    -- 에이전트가 앉아 있으면 보트 이동 시작
    if humanoid.Sit then
    task.wait(1)
    boat.CylindricalConstraint.Velocity = 5
    end
    -- 섬과 관련된 제약 조건 위치 감지
    local boatPositionConnection
    boatPositionConnection = RunService.PostSimulation:Connect(function()
    -- 섬 옆에서 보트 멈추기
    if boat.CylindricalConstraint.CurrentPosition >= 94 then
    boatPositionConnection:Disconnect()
    boat.CylindricalConstraint.Velocity = 0
    task.wait(1)
    -- 에이전트 제거 및 목적지 계속
    humanoid.Sit = false
    humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
    end
    end)
    end)
    end
    followPath(TEST_DESTINATION)

스트리밍 호환성

경험 내 인스턴스 스트리밍은 플레이어의 캐릭터가 세계 주변을 이동하는 동안 3D 콘텐츠를 동적으로 로드하고 언로드하는 강력한 기능입니다.3D 공간을 탐색하면서 공간 스트림의 새로운 하위 집합이 장치에 스트림되고 기존 하위 집합 중 일부가 스트림될 수 있습니다.

스트리밍 활성화 경험에서 PathfindingService를 사용하기 위한 다음 모범 사례를 고려하십시오:

  • 스트리밍은 특정 경로를 이동하는 동안 차단하거나 차단을 해제할 수 있습니다.예를 들어, 캐릭터가 숲을 통과하는 동안 나무가 앞서서 스트림을 방해하고 경로를 차단할 수 있습니다.스트리밍과 함께 경로 찾기 작업을 원활하게 수행하려면 처리 차단된 경로 기술을 사용하고 필요할 때 경로를 다시 계산하는 것이 좋습니다.

  • 경로 찾기에서 공통적으로 사용되는 방법은 세계에서 기존 보물 상자 모델의 위치로 경로 목적지를 설정하는 것과 같이 기존 개체의 좌표를 사용하여 계산 하는 것입니다.이 접근법은 서버가 항상 세계의 전체 뷰를 가지고 있기 때문에 서버 측과 완전히 호환되며, Scripts 클라이언트에서 실행되는 LocalScriptsModuleScripts 는 입력되지 않는 개체로 가는 경로를 계산하려고 시도하면 실패할 수 있습니다.

    이 문제를 해결하려면 영구 모델 내에서 의 위치에 대상을 설정하는 것을 고려하십시오.지속적 모델은 플레이어가 참여한 후 곧 로드되며 결코 스트리밍되지 않으므로 클라이언트 스크립트가 이벤트가 발생한 후 방향점을 생성하기 위해 모델에 안전하게 액세스할 수 있습니다.