unity/유니티 기초

IEnumerator을 활용한 맵 생성 시뮬

rimugiri 2024. 8. 19. 20:41
728x90

BSP 알고리즘을 활용한 맵생성의 과정을 한눈에 보기 위해서 IEnumerator를 한번 활용해 보았다

 

결과

 

방 분리과정

 

다리 연결과정

 

 

기초 코드는 아래를 참조하였습니다

 

Unity2D 절차 지향적 맵 생성

2D 타일맵을 검색해 보면 BSP 알고리즘을 이용한 생성과 간단한 절차 지향적 알고리즘을 이용한 생성을 많이 소개합니다그중 절차 지향적으로 랜덤맵을 생성하는 방법을 시도해 보았습니다 결

rimugiri.tistory.com

 

1. 맵이 나눠지는 과정을 확인할 수 있는 코드입니다

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace PKW_Tilemap
{
    public class TestVisual : DungenGeneratorBase
    {
        public RandomSquareSO _randomSquareSO;
        [Range(1, 10)] public int offset = 1;

        public static IEnumerator<List<BoundsInt>> _roomList;

        public virtual void Clear()
        {
            tilemapVisualizer.Clear();
            _roomList = null;
        }
        protected override void RunGenerator()
        {
            if (_roomList == null)
            {
                _roomList = BinarySpacePartitioning(new BoundsInt((Vector3Int)startPosition,
    new Vector3Int(_randomSquareSO.maxWidth, _randomSquareSO.maxHeight, 0)), _randomSquareSO.minWidth, _randomSquareSO.minHeight);
            }
            _roomList.MoveNext();
            HashSet<Vector2Int> _squareSet = new HashSet<Vector2Int>();
            foreach (BoundsInt _room in _roomList.Current)
            {
                for (int col = offset; col < _room.size.x - offset; col++)
                {
                    for (int row = offset; row < _room.size.y - offset; row++)
                    {
                        Vector2Int _pos = new Vector2Int(_room.x + col, _room.y + row);
                        _squareSet.Add(_pos);
                    }
                }
            }

            tilemapVisualizer.PaintFloorTile(_squareSet);
            WallGenerator.GenerateWall(_squareSet, tilemapVisualizer);
        }


        IEnumerator<List<BoundsInt>> BinarySpacePartitioning(BoundsInt _spaceSplited, int _minWidth, int _minHeight)
        {
            Queue<BoundsInt> _roomList = new Queue<BoundsInt>();
            List<BoundsInt> _roomListTemp = new List<BoundsInt>();
            _roomList.Enqueue(_spaceSplited);

            while (_roomList.Count > 0)
            {
                BoundsInt _room = _roomList.Dequeue();
                // 현재 수직 또는 수평으로 나눠준다.
                if (_room.size.x >= _minWidth && _room.size.y >= _minHeight)
                {
                    if (Random.value < 0.5f)
                    {
                        if (_room.size.x >= _minWidth * 2)
                        {
                            SplitVertically(_room, _roomList, _minWidth);
                        }
                        else if (_room.size.y >= _minHeight * 2)
                        {
                            SplitHorizontally(_room, _roomList, _minHeight);
                        }
                        else
                        {
                            _roomListTemp.Add(_room);

                        }
                    }
                    else
                    {
                        if (_room.size.y >= _minHeight * 2)
                        {
                            SplitHorizontally(_room, _roomList, _minHeight);
                        }
                        else if (_room.size.x >= _minWidth * 2)
                        {
                            SplitVertically(_room, _roomList, _minWidth);
                        }
                        else
                        {
                            _roomListTemp.Add(_room);

                        }
                    }
                    yield return _roomList.ToList();
                }
            }

            yield return _roomList.ToList();
        }

        private void SplitHorizontally(BoundsInt _room, Queue<BoundsInt> _roomList, int _minHeight)
        {
            int _splitPointY = Random.Range(_minHeight, _room.size.y - _minHeight);
            BoundsInt _room1 = new BoundsInt(_room.min, new Vector3Int(_room.size.x, _splitPointY, _room.size.z));
            BoundsInt _room2 = new BoundsInt(new Vector3Int(_room.min.x, _room.min.y + _splitPointY, _room.size.z),
                new Vector3Int(_room.size.x, _room.size.y - _splitPointY, _room.size.z));

            _roomList.Enqueue(_room1);
            _roomList.Enqueue(_room2);
        }

        private void SplitVertically(BoundsInt _room, Queue<BoundsInt> _roomList, int _minWidth)
        {
            int _splitPointX = Random.Range(_minWidth, _room.size.x - _minWidth);
            BoundsInt _room1 = new BoundsInt(_room.min, new Vector3Int(_splitPointX, _room.size.y, _room.size.z));
            BoundsInt _room2 = new BoundsInt(new Vector3Int(_room.min.x + _splitPointX, _room.min.y, _room.size.z),
                               new Vector3Int(_room.size.x - _splitPointX, _room.size.y, _room.size.z));

            _roomList.Enqueue(_room1);
            _roomList.Enqueue(_room2);
        }

    }
}

 

 

 

2. 길이 생성되는 과정을 나타낸 코드입니다 

현재 가장 가까운 점끼리 연결하지만 맵을 관통하는 다리가 발생하는경우가 생깁니다

추후 UnionFind를 이용하여 이점을 수정해보겠습니다

using System.Collections.Generic;
using UnityEngine;

namespace PKW_Tilemap
{
    public class TestVisualCorridor : TestVisual
    {
        public static IEnumerator<HashSet<Vector2Int>> _enumerator;
        private HashSet<Vector2Int> _floorPosition;
        public override void Clear()
        {
            tilemapVisualizer.Clear();
            _enumerator = null;

            List<BoundsInt> _roomList = ProcedualTilemap.BinarySpacePartitioning(new BoundsInt((Vector3Int)startPosition,
                               new Vector3Int(_randomSquareSO.maxWidth, _randomSquareSO.maxHeight, 0)), _randomSquareSO.minWidth, _randomSquareSO.minHeight);
            List<Vector2Int> _roomCenterList = new List<Vector2Int>();
            foreach (BoundsInt _room in _roomList)
            {
                Vector2Int _center = (Vector2Int)Vector3Int.RoundToInt(_room.center);
                _roomCenterList.Add(_center);
            }

            _floorPosition = CreateRooms(_roomList);
            _enumerator = ConnectRooms(_roomCenterList);

        }
        protected override void RunGenerator()
        {
            _enumerator.MoveNext();
            _floorPosition.UnionWith(_enumerator.Current);
            tilemapVisualizer.PaintFloorTile(_floorPosition);
            WallGenerator.GenerateWall(_floorPosition, tilemapVisualizer);
        }


        private HashSet<Vector2Int> CreateRooms(List<BoundsInt> _roomList)
        {
            HashSet<Vector2Int> _squareSet = new HashSet<Vector2Int>();
            foreach (BoundsInt _room in _roomList)
            {
                for (int col = offset; col < _room.size.x - offset; col++)
                {
                    for (int row = offset; row < _room.size.y - offset; row++)
                    {
                        Vector2Int _pos = new Vector2Int(_room.x + col, _room.y + row);
                        _squareSet.Add(_pos);
                    }
                }
            }

            return _squareSet;
        }

        IEnumerator<HashSet<Vector2Int>> ConnectRooms(List<Vector2Int> _roomCenterList)
        {
            HashSet<Vector2Int> _corridor = new HashSet<Vector2Int>();
            Vector2Int _curCenter = _roomCenterList[Random.Range(0, _roomCenterList.Count)];
            _roomCenterList.Remove(_curCenter);

            while (_roomCenterList.Count > 0)
            {
                Vector2Int _closestCenter = GetClosestCenter(_curCenter, _roomCenterList);
                _roomCenterList.Remove(_closestCenter);

                HashSet<Vector2Int> _newCorridor = CreateCorridor(_curCenter, _closestCenter);
                _curCenter = _closestCenter;
                _corridor.UnionWith(_newCorridor);
                yield return _corridor;
            }

            yield return _corridor;
        }

        private HashSet<Vector2Int> CreateCorridor(Vector2Int _curCenter, Vector2Int _closestCenter)
        {
            HashSet<Vector2Int> _corridor = new HashSet<Vector2Int>();
            Vector2Int _curPos = _curCenter;
            _corridor.Add(_curPos);

            while (_curPos.y != _closestCenter.y)
            {
                if (_curPos.y < _closestCenter.y)
                {
                    _curPos.y += Vector2Int.up.y;
                }
                else
                {
                    _curPos.y += Vector2Int.down.y;
                }
                _corridor.Add(_curPos);
            }
            while (_curPos.x != _closestCenter.x)
            {
                if (_curPos.x < _closestCenter.x)
                {
                    _curPos.x += Vector2Int.right.x;
                }
                else
                {
                    _curPos.x += Vector2Int.left.x;
                }
                _corridor.Add(_curPos);
            }

            return _corridor;
        }

        private Vector2Int GetClosestCenter(Vector2Int curCenter, List<Vector2Int> roomCenterList)
        {
            float _distance = float.MaxValue;
            Vector2Int _closestCenter = Vector2Int.zero;

            foreach (Vector2Int _center in roomCenterList)
            {
                float _curDistance = Vector2Int.Distance(curCenter, _center);
                if (_curDistance < _distance)
                {
                    _distance = _curDistance;
                    _closestCenter = _center;
                }
            }

            return _closestCenter;
        }
    }
}

 

 

3. Editor입니다

using UnityEditor;
using UnityEngine;

namespace PKW_Tilemap
{
    [CustomEditor(typeof(TestVisual), true)]
    public class TestVisualEditor : Editor
    {
        private TestVisual _generator;

        private void Awake()
        {
            _generator = target as TestVisual;
        }
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            if (GUILayout.Button("Generate"))
            {
                _generator.GenerateDungen();
            }
            if (GUILayout.Button("Reset"))
            {
                _generator.Clear();
            }
        }
    }
}
728x90