유니티 에셋/A* Pathfinding project pro

Graph Updates during Runtime

스크립팅하는애님 2024. 5. 26. 01:37

Contents


Overview

때때로 런타임 중에 그래프를 업데이트해야 할 필요가 있을 수 있습니다. 예를 들어, 플레이어가 새로운 건물을 건설했거나 문이 열렸을 때가 그렇습니다. 그래프는 완전히 다시 계산하거나 부분적으로 업데이트할 수 있습니다. 전체 재계산은 전체 맵을 변경한 경우에 좋지만, 그래프의 일부분만 변경해야 할 경우에는 작은 업데이트가 훨씬 빠르게 수행될 수 있습니다.

그래프를 업데이트하는 방법은 많고, 이를 원하는 다양한 시나리오가 있습니다. 여기 가장 일반적인 경우를 요약한 표가 있습니다. 게임마다 매우 다르므로, 이 목록에 없는 해결책이 여러분의 게임에 적합할 수도 있다는 점을 염두에 두세요.

 

원하는 항목 수행 방법
그래프 전체를 재계산하기 위해 참조 Recalculating the whole graph
어떤 노드가 통행 가능하고 어떤 노드가 불가능한지 정확하게 설정하기 위해 (예: 타일맵을 기반으로) 참조 Writing Custom Grid Graph Rules, Using direct access to graph data, Agent-Specific Pathfinding, Grid Graph Rules
일부 객체가 이동하거나 생성/파괴됨에 따라 그래프를 업데이트하기 위해 참조 Recalculating parts of graphs 계산하기위해 DynamicGridObstacle, Navmesh Cutting
단일 노드의 속성을 업데이트하기 위해 참조 Using direct access to graph data, GraphUpdateScene  Grid Graph Rules
그래프를 동적으로 로드하고 언로드하기 위해 (예: 파일로) 참조 Saving and Loading Graphs
플레이어를 따라다니는 작은 그리드 그래프 (예: 절차적으로 생성되는 월드에서) 참조 ProceduralGraphMover
새 노드를 생성하고 코드를 사용하여 연결하기 위해 참조 PointGraph.AddNode 와 Writing Graph Generators
그래프의 일부를 통과하는 비용을 높이기 위해 참조 Writing Custom Grid Graph Rules, Recalculating parts of graphs, Using direct access to graph data, GraphUpdateScene, Grid Graph Rules
일부 유닛이 특정 노드를 통과하지 못하게 하고 다른 유닛은 통과할 수 있도록 하기 위해 참조 Agent-Specific Pathfinding, Recalculating parts of graphs, GraphUpdateScene, Using direct access to graph data, Grid Graph Rules
움직이는 객체에서 경로 탐색을 하기 위해 (예: 배) 'Moving'이라는 예제 장면을 참조하세요 (프로 버전에서만 가능). 현재 이 예제는 문서화가 잘 되어 있지 않습니다.
장애물을 추가하기 위해, 하지만 먼저 그것이 어떤 유닛도 차단하지 않는지 확인하기 위해 (타워 디펜스 게임에서 유용) 참조 Check for blocking placements

 

 

What to update

그래프를 업데이트할 때, 일반적으로 두 가지 중 하나를 수행하려고 합니다.

초기 생성 시 사용된 설정과 동일한 설정으로 그래프를 재계산하고 싶을 수 있습니다. 그러나 그래프의 작은 영역만 업데이트한 경우, 전체 그래프를 재계산하는 것은 비효율적일 수 있습니다 ( AstarPath.Scan 사용). 예를 들어, 타워 디펜스 게임에서 플레이어가 새로운 타워를 배치한 경우입니다.

기존 그래프의 일부 설정을 변경하고 싶을 수 있습니다. 예를 들어, 일부 노드의 태그나 패널티를 변경하고 싶을 수 있습니다.

그래프의 작은 부분을 초기 생성 시와 동일한 방식으로 재계산하는 것은 NavMeshGraph를 제외한 모든 그래프에서 가능합니다. NavMeshGraph는 보통 완전히 재계산하는 것이 더 의미가 있습니다. 스크립트와 GraphUpdateScene 컴포넌트를 사용하여 "updatePhysics" 필드를 true로 설정하면 이 작업을 수행할 수 있습니다. 이 이름이 가장 설명적이지는 않아서 죄송합니다.

그리드 그래프는 예상대로 작동합니다. 경계를 지정하기만 하면 모든 작업을 자동으로 수행합니다. 그러나 침식 등 요소를 제대로 반영하기 위해 지정한 영역보다 약간 더 큰 영역을 재계산할 수 있습니다.


리캐스트 그래프는 한 번에 하나의 타일만 재계산할 수 있습니다. 따라서 업데이트 요청이 있을 때 경계에 닿는 모든 타일이 완전히 재계산됩니다. 따라서 재계산 시간이 너무 길어지는 것을 피하기 위해 타일 크기를 작게 사용하는 것이 좋습니다. 그러나 너무 작게 설정하면 본질적으로 그리드 그래프가 됩니다. 멀티스레딩을 사용하는 경우 타일 재계산의 상당 부분이 별도의 스레드로 오프로드되어 FPS에 미치는 영향을 줄일 수 있습니다.


포인트 그래프는 경계를 통과하는 모든 연결을 재계산합니다. 그러나 새로 추가된 노드를 GameObject로 인식하지는 않습니다. 이를 위해서는  AstarPath.Scan 을 사용해야 합니다.

기존 그래프의 속성을 변경하려면 여러 가지를 변경할 수 있습니다.

노드의 태그를 변경할 수 있습니다. 이를 통해 일부 유닛은 특정 지역을 통과할 수 있게 하고, 다른 유닛은 통과하지 못하게 할 수 있습니다. 태그에 대한 자세한 내용은 여기에서 확인할 수 있습니다: Working with tags.


노드의 패널티를 변경할 수 있습니다. 이를 통해 일부 노드를 다른 노드보다 더 어렵거나 느리게 통과하도록 하여, 에이전트가 특정 경로를 더 선호하게 만들 수 있습니다. 하지만 몇 가지 제한 사항이 있습니다. 사용되는 알고리즘이 음수 패널티를 처리할 수 없으므로 음수 패널티를 지정할 수 없습니다(처리할 수 있다고 해도 시스템은 훨씬 느려질 것입니다). 일반적인 트릭은 매우 큰 초기 패널티를 설정한 후(그래프 설정에서 가능) 그 높은 값에서 패널티를 줄이는 것입니다. 그러나 이렇게 하면 더 많은 노드를 검색해야 하므로 경로 탐색이 전반적으로 느려집니다. 필요한 패널티 값은 상당히 높습니다. 실제 "패널티 단위"는 없지만, 1000의 패널티는 대략 한 월드 유닛의 이동에 해당합니다.


노드의 통행 가능성도 직접 수정할 수 있습니다. 따라서 특정 경계 내의 모든 노드를 모두 통행 가능하게 하거나 모두 통행 불가능하게 만들 수 있습니다.

GraphUpdateScene component를 사용하는 방법에 대한 정보는 해당 클래스의 문서를 참조하십시오. GraphUpdateScene 컴포넌트 설정은 스크립트를 사용하여 그래프를 업데이트할 때 사용하는 GraphUpdateObject와 거의 1:1로 매핑됩니다. 따라서 스크립트만 사용할 예정이라도 해당 페이지를 읽는 것을 권장합니다.

Recalculating the whole graph

모든 그래프 또는 일부 그래프를 완전히 재계산하려면 다음과 같이 합니다:

// 모든 그래프를 재계산합니다.
AstarPath.active.Scan();

// 첫 번째 그리드 그래프만 재계산합니다.
var graphToScan = AstarPath.active.data.gridGraph;
AstarPath.active.Scan(graphToScan);

// 첫 번째 및 세 번째 그래프만 재계산합니다.
var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
AstarPath.active.Scan(graphsToScan);

그래프를 비동기적으로 재계산할 수도 있습니다(프로 버전에서만 가능). 이는 좋은 프레임 레이트를 보장하지는 않지만, 최소한 로딩 화면을 표시할 수 있습니다.

IEnumerator Start () {
    foreach (Progress progress in AstarPath.active.ScanAsync()) {
        Debug.Log("Scanning... " + progress.ToString());
        yield return null;
    }
}
참조
AstarPath.Scan
AstarPath.ScanAsync

 

Recalculating parts of graphs

작은 그래프 업데이트는 Unity 인스펙터에서 편집할 수 있는 GraphUpdateScene 컴포넌트를 사용하거나,  AstarPath 클래스의 메서드를 호출하여 Bounds 객체나 Pathfinding.GraphUpdateObject 를 사용하는 스크립팅을 통해 수행할 수 있습니다 (GraphUpdateScene 컴포넌트가 실제로 백그라운드에서 수행하는 작업입니다).

// 예를 들어, 부착된 콜라이더의 경계 상자를 사용합니다.
Bounds bounds = GetComponent<Collider>().bounds;

AstarPath.active.UpdateGraphs(bounds);
// Pathfinding사용; //스크립트 상단에 추가

//예를 들어, 부착된 콜라이더의 경계 상자를 사용합니다.
Bounds bounds = GetComponent<Collider>().bounds;
var guo = new GraphUpdateObject(bounds);

// 일부 설정을 지정합니다.
guo.updatePhysics = true;
AstarPath.active.UpdateGraphs(guo);

이 메서드는 작업을 큐에 넣어 다음 경로 계산 전에 수행되도록 합니다. 바로 수행할 경우 경로 탐색과 충돌할 수 있으며, 특히 멀티스레딩이 활성화된 경우 다양한 오류를 초래할 수 있기 때문입니다. 따라서 그래프의 업데이트가 즉시 반영되지 않을 수 있지만, 다음 경로 탐색 계산이 시작되기 전에 항상 업데이트됩니다(거의 항상, AstarPath.limitGraphUpdates를 참조).

리캐스트 그래프는  navmesh cutting 을 사용하여 의사 업데이트(pseudo-update)할 수도 있습니다. 네비메쉬 컷팅은 네비메쉬에 장애물을 위한 구멍을 뚫을 수 있지만, 더 많은 네비메쉬 표면을 추가할 수는 없습니다.  Pathfinding.NavmeshCut 을 참조하십시오. 이는 전체 리캐스트 타일을 재계산하는 것보다 훨씬 빠르지만, 제한적입니다.

GraphUpdateScene 컴포넌트를 사용하는 것이 Unity 에디터에서 알려진 그래프를 작업할 때 가장 쉽습니다. 예를 들어, 특정 영역의 태그를 코드 없이 쉽게 변경할 수 있습니다. 그러나 런타임 중에 동적으로 그래프를 업데이트하는 경우 코드로 그래프를 업데이트하는 것이 더 쉽습니다. 예를 들어, 타워 디펜스 게임에서 새로 배치된 건물을 반영하기 위해 그래프를 업데이트하는 경우가 그렇습니다.

Using Scripting

그래프를 업데이트하려면 GraphUpdateObject를 생성하고 원하는 매개변수를 설정한 다음, AstarPath.UpdateGraphs 메서드를 호출하여 업데이트를 큐에 넣습니다.

// using Pathfinding; //스크립트 상단에 추가

// 예를 들어, 부착된 콜라이더의 경계 상자를 사용합니다.
Bounds bounds = GetComponent<Collider>().bounds;
var guo = new GraphUpdateObject(bounds);

// S일부 설정을 지정합니다.
guo.updatePhysics = true;
AstarPath.active.UpdateGraphs(guo);

bounds 변수는 UnityEngine.Bounds 객체를 참조합니다. 이는 그래프를 업데이트할 축 정렬 상자를 정의합니다. 종종 새로 생성된 객체 주변의 그래프를 업데이트하고 싶어합니다. 예제에서는 부착된 콜라이더에서 경계 상자를 가져옵니다.

그러나 이 객체가 그래프에 의해 인식될 수 있는지 확인해야 합니다. 그리드 그래프의 경우, 객체의 레이어가 충돌 테스트 마스크 또는 높이 테스트 마스크에 포함되어 있는지 확인해야 합니다.

그리드 그래프를 사용할 때 노드의 모든 매개변수 또는 포인트 그래프를 사용할 때 연결을 다시 계산할 필요가 없는 경우, 불필요한 계산을 피하기 위해 updatePhysics(  GridGraph specific details 참조)를 false로 설정할 수 있습니다.

guo.updatePhysics = false;

어떤 필드를 설정해야 하는지에 대한 자세한 내용은  GraphUpdateObject  클래스의 문서를 참조하십시오.

 

 

Using direct access to graph data

일부 경우에는 GraphUpdateObject를 사용하여 그래프를 업데이트하는 것이 불편할 수 있습니다. 이러한 경우 그래프 데이터를 직접 업데이트할 수 있습니다. 하지만 주의가 필요합니다. GraphUpdateObject를 사용할 때 시스템이 많은 작업을 자동으로 처리해줍니다.

참조:
그래프 데이터 접근: 그래프 데이터에 접근하는 방법에 대한 정보는 Accessing graph data 를 참조하십시오.
노드 속성과 Pathfinding.GraphNode: 노드가 가지는 속성에 대한 정보는 Node properties and Pathfinding.GraphNode 를 참조하십시오.
그리드 그래프 규칙: 커스텀 그리드 그래프 규칙을 만드는 방법에 대한 정보는 Grid Graph Rules 를 참조하십시오. 그리드 그래프를 사용하는 경우, 커스텀 그리드 그래프 규칙을 작성하는 것이 종종 더 안정적입니다.

다음은 그리드 그래프의 모든 노드를 변경하고, 페를린 노이즈를 사용하여 노드가 통행 가능해야 하는지 여부를 결정하는 예제입니다:

AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => {
    var gg = AstarPath.active.data.gridGraph;
    for (int z = 0; z < gg.depth; z++) {
        for (int x = 0; x < gg.width; x++) {
            var node = gg.GetNode(x, z);
            // 이 예제는 페를린 노이즈를 사용하여 지도를 생성합니다.
            node.Walkable = Mathf.PerlinNoise(x * 0.087f, z * 0.087f) > 0.4f;
        }
    }

    // 모든 그리드 연결을 다시 계산합니다
    // 일부 노드의 통행 가능성을 업데이트했기 때문에 이 작업이 필요합니다.
    gg.RecalculateAllConnections();

    // 하나 또는 몇 개의 노드만 업데이트하는 경우 성능을 위해
    // gg.CalculateConnectionsForCellAndNeighbours를 해당 노드에만 사용하고 싶을 수 있습니다.
}));

 

위의 코드는 다음과 같은 그래프를 생성합니다:

그래프 데이터는 안전할 때만 수정해야 합니다. 경로 탐색이 언제든 실행될 수 있으므로 먼저 경로 탐색 스레드를 일시 중지한 후 데이터를 업데이트해야 합니다. 이를 가장 쉽게 수행하는 방법은  AstarPath.AddWorkItem 을 사용하는 것입니다.

AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
    // 여기서 그래프를 안전하게 업데이트하십시오
    var node = AstarPath.active.GetNearest(transform.position).node;
    node.Walkable = false;
}));
AstarPath.active.AddWorkItem(() => {
    // 여기서 그래프를 안전하게 업데이트하십시오
    var node = AstarPath.active.GetNearest(transform.position).node;
    node.position = (Int3)transform.position;
});

노드의 연결성이 변경되면 시스템은 그래프의 연결된 구성 요소를 자동으로 다시 계산합니다. 이에 대한 자세한 내용은 Pathfinding.HierarchicalGraph 클래스의 문서를 참조하십시오. 그러나 작업 항목 시퀀스 중간에 있을 때는 최신 상태가 아닐 수 있습니다. Pathfinding.PathUtilities.IsPathPossible 또는  Pathfinding.GraphNode.Area 속성과 같은 메서드를 사용하는 경우, 먼저 ctx.EnsureValidFloodFill 메서드를 호출해야 합니다. 일반적으로 작업 항목이 이 작업을 필요로 하지 않습니다.

버전
4.2 이전에는 이 데이터를 최신 상태로 유지하기 위해 QueueFloodFill과 같은 메서드를 수동으로 호출해야 했습니다. 그러나 4.2 이상에서는 그럴 필요가 없습니다. 시스템이 더 성능 좋은 방식으로 자동으로 처리합니다.

 

AstarPath.active.AddWorkItem(new AstarWorkItem((IWorkItemContext ctx) => {
    ctx.EnsureValidFloodFill();

    // 위 호출은 이 메서드가 그래프에 대한 최신 정보를 가지고 있음을 보장합니다.
    if (PathUtilities.IsPathPossible(someNode, someOtherNode)) {
        // 작업을 수행합니다
    }
}));

통행 가능성을 수정하는 경우 연결이 올바르게 다시 계산되도록 해야 합니다. 이는 주로 그리드 그래프에 중요합니다. GridGraph.RecalculateConnectionsInRegion 또는 GridGraph.RecalculateAllConnections 메서드를 사용할 수 있습니다. 이 메서드는 통행 가능성을 변경한 노드뿐만 아니라 인접한 노드에도 호출되어야 합니다. 인접한 노드도 변경된 노드와의 연결을 변경해야 할 수 있기 때문입니다. 단일 노드만 업데이트하는 경우 GridGraph.CalculateConnectionsForCellAndNeighbours 메서드를 사용하여 간단하게 처리할 수 있습니다.

AddWorkItem 메서드는 더 고급 방식으로도 사용할 수 있습니다. 예를 들어, 필요한 경우 계산을 여러 프레임에 걸쳐 분산시킬 수 있습니다:

AstarPath.active.AddWorkItem(new AstarWorkItem(() => {
    // 첫 번째 메서드 호출 바로 전에 한 번 호출됩니다.
},
    force => {
    // 완료될 때까지 매 프레임마다 호출됩니다.
    // 작업 항목이 완료되었음을 신호하려면 true를 반환하십시오.
    // "force" 매개변수가 true인 경우,
    // 작업 항목이 즉시 완료되어야 함을 의미합니다.
    // 이 경우 이 메서드는 완료될 때까지 블록하고 true를 반환해야 합니다.
    return true;
}));

다양한 작업 항목 생성 방법 목록은  Pathfinding.AstarWorkItem 의 생성자를 참조하십시오.

Debugging

일부 항목, 예를 들어 통행 가능성과 위치는 즉시 보이는 변경 사항입니다. 그러나 태그와 패널티는 그래프에 직접 표시되

지 않습니다. 그러나 태그와 패널티를 시각화할 수 있는 다른 보기 모드를 활성화할 수 있습니다.

보기 모드는 A* Inspector -> Settings -> Debug -> Graph Coloring에서 편집할 수 있습니다. 이를 "Tags"로 설정하면 모든 태그가 그래프에서 다른 색상으로 표시됩니다.

 


패널티를 표시하도록 설정할 수도 있습니다. 기본적으로 패널티가 가장 높은 노드는 순수한 빨간색으로 표시되고, 패널티가 가장 낮은 노드는 녹색으로 표시되도록 자동으로 조정됩니다.

 


Technical Details

GraphUpdateObject를 사용하여 그래프를 업데이트할 때, 모든 그래프가 반복되고 업데이트할 수 있는 그래프(모든 내장 그래프)가 ScheduleGraphUpdates 함수를 호출합니다.

각 노드는 그래프가 GraphUpdateObject.Apply 를 호출하여 업데이트됩니다. Apply 함수는 패널티, 통행 가능성 또는 지정된 다른 매개변수를 변경합니다. 그래프는 일반적으로 그래프를 업데이트하는 사용자 지정 로직도 포함하고 있으며, 그리드 그래프(GridGraph)가 그 예입니다 ( GridGraph specific details 참조).

이를 사용하기 위해 모든 함수 호출을 이해할 필요는 없으며, 이는 주로 소스 코드를 다루고자 하는 사람들을 위한 것입니다.

GridGraph specific details

그리드 그래프를 업데이트할 때  updatePhysics 변수는 매우 중요합니다. 이 값이 true로 설정되면 영향을 받는 모든 노드의 높이가 다시 계산되고 여전히 통행 가능한지 확인됩니다. 그리드 그래프를 업데이트할 때 이 값을 true로 설정하는 것이 일반적입니다.

그리드 그래프를 업데이트할 때, GraphUpdateObject의 Apply 함수(통행 가능성, 태그 및 패널티 변경)가 경계 내의 각 노드에 대해 호출됩니다. updatePhysics 변수를 확인하고, 값이 true이면(기본값) 지정된 충돌 테스트 설정의 직경에 따라 영역이 확장되고, 해당 영역 내의 모든 노드가 충돌을 확인합니다. 그러나 값이 false이면 해당 영역(확장되지 않음) 내의 노드에 대해서만 Apply 함수가 호출되고 그 외의 작업은 수행되지 않습니다.

Navmesh based graphs

네비메쉬 기반 그래프(NavMeshGraph 및 RecastGraph)는 기존 노드에서 패널티, 통행 가능성 등을 업데이트하는 것만 지원하며, 리캐스트 그래프의 경우 전체 타일을 완전히 재계산할 수 있습니다. GraphUpdateObjects를 사용하여 새 노드를 생성할 수는 없습니다(전체 타일을 재계산하는 경우 제외). GraphUpdateObject는 GUO의 경계와 교차하거나 포함된 모든 노드/삼각형에 영향을 미칩니다.
리캐스트 그래프의 경우 navmesh cutting 을 사용하여 그래프를 빠르게 업데이트할 수도 있습니다.

PointGraphs

포인트 그래프는 경계 내의 모든 노드에서 Apply를 호출합니다. GraphUpdateObject.updatePhysics가 true로 설정된 경우(기본값 true), 경계 객체를 통과하는 모든 연결도 다시 계산됩니다.

참고 포인트 그래프 업데이트는 A* Pathfinding Project의 프로 버전에서만 가능합니다.
A* 프로 기능
이 기능은 A* Pathfinding Project 프로 버전에서만 제공되는 기능입니다. 이 함수/클래스/변수는 A* Pathfinding Project의 무료 버전에서는 존재하지 않거나 기능이 제한될 수 있습니다. 프로 버전은 여기에서 구매할 수 있습니다.

The Graph Update Object

GraphUpdateObject에는 각 노드를 업데이트하는 방법에 대한 몇 가지 기본 변수가 포함되어 있습니다. 자세한 내용은 Pathfinding.GraphUpdateObject 문서를 참조하십시오.

Inheriting from the GraphUpdateObject

클래스는 GraphUpdateObject를 상속하여 일부 기능을 재정의할 수 있습니다.
다음은 노드를 일부 오프셋으로 이동하면서 기본 기능을 유지하는 GraphUpdateObject의 예입니다.

using UnityEngine;
using Pathfinding;

public class MyGUO : GraphUpdateObject {
    public Vector3 offset = Vector3.up;
    public override void Apply (GraphNode node) {
        // Keep the base functionality
        base.Apply(node);
        // 노드의 위치는 Int3이므로 offset을 캐스팅해야 합니다.
        node.position += (Int3)offset;
    }
}

그런 다음 해당 GUO를 다음과 같이 사용할 수 있습니다:

public void Start () {
    MyGUO guo = new MyGUO();

    guo.offset = Vector3.up*2;
    guo.bounds = new Bounds(Vector3.zero, Vector3.one*10);
    AstarPath.active.UpdateGraphs(guo);
}

 

Check for blocking placements

타워 디펜스 게임에서 일반적으로 플레이어가 배치한 타워가 스폰 지점과 목표 지점 사이의 경로를 차단하지 않도록 해야 합니다. 이것은 어려워 보일 수 있지만, 이를 위한 API가 있습니다.

 Pathfinding.GraphUpdateUtilities.UpdateGraphsNoBlock  메서드를 사용하면 주어진 그래프 업데이트 객체가 두 개 이상의 지점 사이의 경로를 차단하게 될지를 먼저 확인할 수 있습니다. 그러나 이 방법은 일반적인 그래프 업데이트보다 느리기 때문에 너무 자주 사용하지 않는 것이 좋습니다.

예를 들어, 타워 디펜스 게임에서 플레이어가 타워를 배치할 때, 이를 인스턴스화한 후 UpdateGraphsNoBlock 메서드를 호출하여 새로 배치된 타워가 경로를 차단하는지 확인할 수 있습니다. 만약 경로를 차단한다면 즉시 타워를 제거하고 플레이어에게 선택한 위치가 유효하지 않음을 알립니다. UpdateGraphsNoBlock 메서드에 노드 목록을 전달할 수 있으므로, 예를 들어 시작 지점에서 목표 지점까지의 경로가 차단되지 않는지 뿐만 아니라 모든 유닛이 여전히 목표 지점에 도달할 수 있는지도 확인할 수 있습니다(적들이 주변을 돌아다닐 때 타워를 배치할 수 있는 경우).

var guo = new GraphUpdateObject(tower.GetComponent<Collider>().bounds);
var spawnPointNode = AstarPath.active.GetNearest(spawnPoint.position).node;
var goalNode = AstarPath.active.GetNearest(goalPoint.position).node;

if (GraphUpdateUtilities.UpdateGraphsNoBlock(guo, spawnPointNode, goalNode, false)) {
    // 유효한 타워 위치
    // 메서드 호출의 마지막 매개변수(alwaysRevert)가 false이기 때문에
    // 그래프가 이제 업데이트되었고 게임이 계속 진행될 수 있습니다.
} else {
    // 유효하지 않은 타워 위치입니다. 이것은 스폰 지점과 목표 지점 사이의 경로를 차단합니다.
    // 그래프에 대한 영향이 원래대로 복원되었습니다.
    Destroy(tower);
}