내일배움캠프 Unity 57일차 TIL - 팀 9와 4분의 3 - 개발일지

Wooooo·2024년 1월 16일
0

내일배움캠프Unity

목록 보기
59/94

[오늘의 키워드]

최종 프로젝트 개발일지

  1. 런타임에서 비동기로 NavMesh 생성/업데이트하기
  2. 활성화된 Chunk만큼만 NavMesh 업데이트하기

1. [런타임에서 비동기로 NavMesh 생성/업데이트하기]

NavMeshBuilder라는 정적 클래스를 이용해서 런타임 중에 NavMesh를 생성/업데이트할 수 있다. 비동기 방식도 지원한다.

UnityEngine.AI, UnityEditor.AI 두 네임스페이스에 같은 이름의 클래스가 들어있다!!!! 런타임에서 사용할 것이기 때문에 꼭 UnityEngine.AI 네임스페이스 것을 사용하자.

런타임에서 비동기로 NavMesh를 업데이트하려면 NavMeshBuilder.UpdateNavMeshDataAsync() 메서드를 사용하면 된다. 매개변수 정보는 다음과 같다.

  • NavMeshData : 말 그대로 NavMeshData. position, rotation, name, sourceBounds 등 정보가 들어있다.
  • NavMeshBuildSettings : NavMesh를 생성/갱신할 때 사용할 설정 정보 구조체
  • List<NavMeshBuildSource> : NavMesh를 생성하기 위한 Source들의 리스트. (ex. mesh, terrain 등등)
  • Bounds : NavMesh 생성 범위를 지정하는 박스 정보

어떤 매개변수가 필요한지 알았으니, 이제 매개변수를 하나하나 준비해보자.

    private NavMeshData _data;
    public NavMeshDataInstance NavMeshDataInstance => _instance;

    private void Start()
    {
        _data = new NavMeshData();
		_instance = NavMesh.AddNavMeshData(_data);
    }

NavMeshData는 그냥 기본 생성자로 생성해서 NavMesh 클래스에 넣어줬다.
NavMesh 클래스는 유니티에서 baked NavMesh에 접근하기 위한 싱글톤 클래스라고 한다.

	NavMeshBuildSettings buildSettings = NavMesh.GetSettingsByID(0);

기본값으로 생성해줬다.

List<NavMeshBuildSource>

    public void UpdateChunkSources(List<Chunk> activeChunks)
    {
        if (activeChunks.Count == 0)
            return;

        if (_sources == null)
        {
            _sources = new();
            return;
        }

        _sources.Clear();
        foreach (var chunk in activeChunks)
        {
            NavMeshBuildSource source = new()
            {
                shape = NavMeshBuildSourceShape.Mesh,
                sourceObject = chunk.Mesh,
                transform = chunk.TransformMatrix,
                area = 0,
            };
            _sources.Add(source);
        }
    }

World 클래스에서 이미 현재 활성화된 청크의 리스트를 갖고 있으므로 청크가 갱신될 때마다 이 메서드를 이용해서 넘겨주고 있다.

밑의 반복문 안에서 chunk의 Mesh 정보로 NavMeshBuildSource를 생성 중이다.
특이한 점은, tranform은 Matrix4x4 자료형이다. chunk 오브젝트의 transform.localToWorldMatrix 속성을 넘겨줬다.

Bounds

    private Bounds CalculateViewBounds()
    {
        float sizeX = _world.VoxelData.ChunkSizeX * _world.WorldData.ViewChunkRange * 2f;
        float sizeY = _world.VoxelData.ChunkSizeY + 30f;
        float sizeZ = _world.VoxelData.ChunkSizeZ * _world.WorldData.ViewChunkRange * 2f;

        Bounds bounds = new()
        {
            center = _player.position,
            size = Vector3.one,
            extents = new Vector3(sizeX, sizeY, sizeZ),
        };

        return bounds;
    }

현재 활성화된 Chunk를 이용해 NavMesh를 생성 중이고, Chunk의 활성화 조건은 Player의 시야 범위 내에 있을 때 이므로, Bounds도 플레이어의 시야범위 정도로 잡아줬다.

비동기로 갱신하기

    public AsyncOperation UpdateNavMesh(Action<AsyncOperation> callback = null)
    {
        if (_data == null || _sources == null)
            return null;

        var buildSettings = NavMesh.GetSettingsByID(0);
        var bounds = CalculateViewBounds();
        var op = NavMeshBuilder.UpdateNavMeshDataAsync(_data, buildSettings, _sources, bounds);
        op.completed += callback;
        return op;
    }

코루틴 내에서 사용하는 방법도 만들어두기 위해 AsyncOperation을 return하도록 했다.
또, 생성 완료 시 호출될 콜백도 매개변수로 지정해줄 수 있도록 해봤다.

AsyncOperation을 return하도록 하면 다음과 같은 방법으로도 사용 가능하다.

Start 라이프사이클 메서드를 코루틴으로 실행하기

    private IEnumerator Start()
    {
        _player = Managers.Game.Player.transform;
        _world = Managers.Game.World;
        _data = new NavMeshData();
        _instance = NavMesh.AddNavMeshData(_data);

        while (IsActive)
        {
            yield return UpdateNavMesh();
        }
    }

IsActive라는 bool 타입 프로퍼티를 true로 설정하면 끊임없이 비동기 생성을 반복하는 구조로 사용 가능하다.

우리 게임은 아직 실시간으로 NavMesh의 갱신이 있다기 보다는 Player의 Chunk 이동 시 갱신이 필요한 구조라서 아직 이 방법의 사용은 보류중이다.


2. [활성화된 Chunk만큼만 NavMesh 업데이트하기]

초기화

    private IEnumerator InitializeWorldNavMeshBuilderCoroutine()
    {
        _navMeshBuilder = new GameObject(nameof(WorldNavMeshBuilder)).AddComponent<WorldNavMeshBuilder>();
        _navMeshBuilder.IsActive = false; // 주기적으로 업데이트 X. 일단은 청크 정보가 바뀔 때마다 업데이트합니다.
        UpdateChunksInViewRange();
        yield return null;
        yield return _navMeshBuilder.UpdateNavMesh();
    }

위에서 작업한 WorldNavMeshBuilder라는 클래스를 World 클래스에서 초기화 할 수 있도록 했다.

의존성

        // 1. 리소스 로드
        ResourceLoad((key, count, total) =>
        {
            if (count == total)
            {
                Managers.Game.GenerateWorldAsync((progress, argument) =>
                {
                    // 맵 생성 진행 중 콜백
                    Debug.Log(progress + ": " + argument);
                },
                () =>
                {
                    // 맵 생성 완료 시 콜백
                    // 3. 객체 생성, 초기화
                    SpawnPlayer();
                    UIInitialize();
                    Managers.Game.World.InitializeWorldNavMeshBuilder(); // ★

                    var mon = Managers.Resource.GetCache<GameObject>("Skeleton.prefab");
                    Instantiate(mon);
                    SpawnObject();
                });
            }
        });

player의 위치정보, 활성화된 chunk의 정보가 필요해서 맵 생성, 플레이어 스폰이 완료된 후에 초기화해줘야하는 의존성이 있다.

갱신

    /// <summary> 시야범위 내의 청크 생성 </summary>
    public void UpdateChunksInViewRange()
    {
        _prevActiveChunks = _currentActiveChunks;
        _currentActiveChunks = new();

        for (int x = -WorldData.ViewChunkRange; x <= WorldData.ViewChunkRange; x++)
        {
            for (int z = -WorldData.ViewChunkRange; z <= WorldData.ViewChunkRange; z++)
            {
                if (_chunkMap.TryGetValue(_currentPlayerCoord + new ChunkCoord(x, z), out var currentChunk))
                {
                    _currentActiveChunks.Add(currentChunk);
                    currentChunk.SetActive(true);
                    _prevActiveChunks.Remove(currentChunk);

                    // NavMesh Source 전달
                    _navMeshBuilder.UpdateChunkSources(_currentActiveChunks);
                }
            }
        }

        foreach (var chunk in _prevActiveChunks)
            chunk.IsActive = false;
		
        // NavMesh 갱신
        _navMeshBuilder.UpdateNavMesh();
    }

청크가 갱신되면 NavMeshBuilder에게 갱신을 요청한다.


[구현 모습]

프레임도 120 ~ 180 프레임이 나와서 아직은 괜찮은 것 같다.


[참고 자료]

Unity NavMesh #3 실시간 네비메시 빌드

profile
game developer

0개의 댓글