unity/유니티 초보

Unity2D 절차 지향적 맵 생성

rimugiri 2024. 8. 18. 02:49
728x90

2D 타일맵을 검색해 보면 BSP 알고리즘을 이용한 생성과 간단한 절차 지향적 알고리즘을 이용한 생성을 많이 소개합니다

그중 절차 지향적으로 랜덤맵을 생성하는 방법을 시도해 보았습니다

 

결과

 

 

타일맵 코드

1. 기본 랜덤 타일

기본적으로 던전을 실행하면 화면상의 타일맵을 초기화 하고 다시 타일맵을 생성합니다

using UnityEngine;

namespace PKW_Tilemap
{
    public abstract class DungenGeneratorBase : MonoBehaviour
    {
        [SerializeField] protected TilemapVisualizer tilemapVisualizer; // 시각화 도구
        [SerializeField] protected Vector2Int startPosition = Vector2Int.zero;
        public void GenerateDungen()
        {
            tilemapVisualizer.Clear();
            RunGenerator();
        }
        protected abstract void RunGenerator();
    }
}

 

2. 여러 크기의 타입맵 생성

직사각형의 크기로 타일맵을 생성해 줍니다

using System.Collections.Generic;
using UnityEngine;

namespace PKW_Tilemap
{
    public class RandomSquareTilemapGenerator : DungenGeneratorBase
    {
        [SerializeField] protected RandomSquareSO _randomSquareSO;
        protected override void RunGenerator()
        {
            HashSet<Vector2Int> _squareSet = RunRandomSquare(_randomSquareSO, startPosition);
            tilemapVisualizer.PaintFloorTile(_squareSet);
            WallGenerator.GenerateWall(_squareSet, tilemapVisualizer);
        }

        protected HashSet<Vector2Int> RunRandomSquare(RandomSquareSO _randomSquareData, Vector2Int _startPosition)
        {
            Vector2Int _curPosition = _startPosition;

            int _randomWidth = UnityEngine.Random.Range(_randomSquareData.minWidth, _randomSquareData.maxWidth);
            int _randomHeight = UnityEngine.Random.Range(_randomSquareData.minHeight, _randomSquareData.maxHeight);

            HashSet<Vector2Int> _squareSet = ProcedualTilemap.GetRandomSquareCollider(_curPosition, _randomWidth, _randomHeight);

            return _squareSet;
        }
    }
}

 

3. 이동가능한 길과 각각의 방 표현

랜덤한 크기의 타일맵과 서로 연결한 길을 표시해 줍니다

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

namespace PKW_Tilemap
{
    public class RandomSquareColliderTilemapGenerator : RandomSquareTilemapGenerator
    {
        [SerializeField] private int _colliderCount; // 몇개의 다리를 생성할건지
        [SerializeField] private int _colliderLength; // 얼마의 길이로 생성할건지
        [SerializeField]
        [Range(0.1f, 1)] private float _roomPercent; // 방을 몇퍼센트 확률로 생성할건지.
        protected override void RunGenerator()
        {
            RunRandomSquareCollider();
        }

        private void RunRandomSquareCollider()
        {
            HashSet<Vector2Int> _floorPosition = new HashSet<Vector2Int>();
            HashSet<Vector2Int> _roomTempPosition = new HashSet<Vector2Int>();
            CreateCollider(_floorPosition, _roomTempPosition);

            HashSet<Vector2Int> _roomPosition = CreateRoom(_roomTempPosition);

            List<Vector2Int> _deadEnds = FindDeadEnds(_floorPosition);

            CreateRoomAtDeadEnds(_deadEnds, _roomPosition);
            _floorPosition.UnionWith(_roomPosition);
            tilemapVisualizer.PaintFloorTile(_floorPosition);

            WallGenerator.GenerateWall(_floorPosition, tilemapVisualizer);
        }

        private void CreateRoomAtDeadEnds(List<Vector2Int> deadEnds, HashSet<Vector2Int> roomPosition)
        {
            foreach (Vector2Int _deadEnd in deadEnds)
            {
                if (roomPosition.Contains(_deadEnd))
                    continue;

                HashSet<Vector2Int> _room = RunRandomSquare(_randomSquareSO, _deadEnd);
                roomPosition.UnionWith(_room);
            }
        }

        private List<Vector2Int> FindDeadEnds(HashSet<Vector2Int> floorPosition)
        {
            List<Vector2Int> _deadEnds = new List<Vector2Int>();
            foreach (Vector2Int _floorPos in floorPosition)
            {
                int _adjacentCount = 0;
                foreach (Vector2Int _dir in Direction2D.directions)
                {
                    if (floorPosition.Contains(_floorPos + _dir))
                        _adjacentCount++;
                }
                if (_adjacentCount == 1)
                    _deadEnds.Add(_floorPos);
            }
            return _deadEnds;
        }

        private HashSet<Vector2Int> CreateRoom(HashSet<Vector2Int> _roomTempPosition)
        {
            HashSet<Vector2Int> _roomPosition = new HashSet<Vector2Int>();
            int _roomCount = (int)(_roomPercent * _roomTempPosition.Count);

            List<Vector2Int> _roomList = _roomTempPosition.OrderBy(x => Guid.NewGuid()).Take(_roomCount).ToList();
            foreach (Vector2Int _roomPos in _roomList)
            {
                HashSet<Vector2Int> _room = RunRandomSquare(_randomSquareSO, _roomPos);
                _roomPosition.UnionWith(_room);
            }

            return _roomPosition;
        }

        private void CreateCollider(HashSet<Vector2Int> _floorPosition, HashSet<Vector2Int> _roomTempPosition)
        {
            Vector2Int _curPosition = startPosition;
            _roomTempPosition.Add(_curPosition);

            for (int i = 0; i < _colliderCount; i++)
            {
                List<Vector2Int> _colliderList = ProcedualTilemap.GetRandomWalkCollider(_curPosition, _colliderLength);
                _floorPosition.UnionWith(_colliderList);
                _curPosition = _colliderList[_colliderList.Count - 1];
                _roomTempPosition.Add(_curPosition);
            }
        }
    }
}

 

데이터 표현

최대 최소 좌우상하 너비의 크기를 지정해 줍니다

using UnityEngine;


namespace PKW_Tilemap
{
    [CreateAssetMenu(fileName = "RandomSquareSO", menuName = "PKW_Tilemap/RandomSquareSO")]
    public class RandomSquareSO : ScriptableObject
    {
        [Range(0, 100)] public int maxWidth, minWidth;
        [Range(0, 100)] public int maxHeight, minHeight;

    }
}

 

Helper Class

각종 타일맵을 시각화 해줍니다

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

namespace PKW_Tilemap
{
    public class TilemapVisualizer : MonoBehaviour
    {
        [SerializeField] private Tilemap _floorTilemap;
        [SerializeField] private TileBase _floorTile;
        [SerializeField] private Tilemap _wallTilemap;
        [SerializeField] private TileBase _wallTile;
        public void PaintFloorTile(IEnumerable<Vector2Int> _positions)
        {
            PaintTiles(_positions, _floorTilemap, _floorTile);
        }

        private void PaintTiles(IEnumerable<Vector2Int> _positions, Tilemap _tilemap, TileBase _tile)
        {
            foreach (Vector2Int _pos in _positions)
            {
                PaintSingleTile(_pos, _tilemap, _tile);
            }
        }

        public void PaintSingleWallTile(Vector2Int pos)
        {
            PaintSingleTile(pos, _wallTilemap, _wallTile);
        }

        private void PaintSingleTile(Vector2Int _pos, Tilemap _tilemap, TileBase _tile)
        {
            Vector3Int _tilePos = _tilemap.WorldToCell((Vector3Int)_pos);
            _tilemap.SetTile(_tilePos, _tile);
        }

        public void Clear()
        {
            _floorTilemap.ClearAllTiles();
            _wallTilemap.ClearAllTiles();
        }


    }
}

 

벽을 생성해 줍니다

아주 간단하게 상하좌우만 확인하여 모서리 끝부분은 벽이 생성되지 않아 이건 보완해야 할거같다

using System.Collections.Generic;
using UnityEngine;

namespace PKW_Tilemap
{
    public static class WallGenerator
    {
        public static void GenerateWall(HashSet<Vector2Int> _walkSet, TilemapVisualizer _tilemapVisualizer)
        {
            HashSet<Vector2Int> _wallSet = GetWallSet(_walkSet, Direction2D.directions);

            foreach (Vector2Int _pos in _wallSet)
            {
                _tilemapVisualizer.PaintSingleWallTile(_pos);
            }
        }

        private static HashSet<Vector2Int> GetWallSet(HashSet<Vector2Int> _walkSet, List<Vector2Int> _directions)
        {
            HashSet<Vector2Int> _wallSet = new HashSet<Vector2Int>();

            foreach (Vector2Int _pos in _walkSet)
            {
                foreach (Vector2Int _dirPos in _directions)
                {
                    Vector2Int _neightbor = _pos + _dirPos;
                    if (!_walkSet.Contains(_neightbor))
                    {
                        _wallSet.Add(_neightbor);
                    }
                }
            }
            return _wallSet;
        }
    }
}

 

생성될 타일의 포지션 정보들을 계산해 줍니다

using System.Collections.Generic;
using UnityEngine;

namespace PKW_Tilemap
{
    public static class ProcedualTilemap
    {
        //시작 지점에서 walkLength만큼 랜덤하게 이동
        public static HashSet<Vector2Int> GetRandomWalkPos(Vector2Int _startPos, int _walkLength)
        {
            HashSet<Vector2Int> _walkSet = new HashSet<Vector2Int>();
            Vector2Int _nextPos = _startPos;

            for (int i = 0; i < _walkLength; i++)
            {
                _nextPos += Direction2D.GetRandomDirection();
                _walkSet.Add(_nextPos);
            }
            return _walkSet;
        }

        public static List<Vector2Int> GetRandomWalkCollider(Vector2Int _startPos, int _colliderLength)
        {
            List<Vector2Int> _colliderList = new List<Vector2Int>();
            Vector2Int _direction = Direction2D.GetRandomDirection();

            _colliderList.Add(_startPos);

            for (int i = 0; i < _colliderLength; i++)
            {
                _startPos += _direction;
                _colliderList.Add(_startPos);
            }

            return _colliderList;
        }

        public static HashSet<Vector2Int> GetRandomSquareCollider(Vector2Int _startPos, int _width, int _height)
        {
            HashSet<Vector2Int> _squareList = new HashSet<Vector2Int>();

            int _halfWidth = _width / 2;
            int _halfHeight = _height / 2;

            for (int x = -_halfWidth; x <= _halfWidth; x++)
            {
                for (int y = -_halfHeight; y <= _halfHeight; y++)
                {
                    _squareList.Add(_startPos + new Vector2Int(x, y));
                }
            }

            return _squareList;
        }
    }

    public static class Direction2D
    {
        public static List<Vector2Int> directions = new List<Vector2Int>
        {
            new Vector2Int(0, 1),  // 위
            new Vector2Int(0, -1), // 아래
            new Vector2Int(1, 0),  // 오른쪽
            new Vector2Int(-1, 0)  // 왼쪽
        };

        public static Vector2Int GetRandomDirection()
        {
            return directions[Random.Range(0, directions.Count)];
        }
    }

}

 

 

이분의 강의를 토대로 직사각형의 타일맵을 제작해 보았습니다 한번 보시는걸 추천합니다.

728x90