완성본
전체 코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class WallGenerator
{
public static void CreateWalls(HashSet<Vector2Int> floorPositions, TilemapVisualizer tilemapVisualizer)
{
var basicWallPositions = FindWallsInDirections(floorPositions, Direction2D.cardinalDirectionsList);
foreach (var position in basicWallPositions)
{
tilemapVisualizer.PaintsingleBasicWall(position);
}
}
private static HashSet<Vector2Int> FindWallsInDirections(HashSet<Vector2Int> floorPositions, List<Vector2Int> directionList)
{
HashSet<Vector2Int> wallPositions = new HashSet<Vector2Int>();
foreach (var position in floorPositions)
{
foreach (var direction in directionList)
{
var neighbourPosition = position + direction;
if (floorPositions.Contains(neighbourPosition) == false)
wallPositions.Add(neighbourPosition);
}
}
return wallPositions;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class TilemapVisualizer : MonoBehaviour
{
[SerializeField]
private Tilemap floorTilemap, wallTilemap;
[SerializeField]
private TileBase floorTile, wallTop;
public void PaintFloorTiles(IEnumerable<Vector2Int> floorPositions)
{
PaintTiles(floorPositions, floorTilemap, floorTile);
}
internal void PaintsingleBasicWall(Vector2Int position)
{
PaintSingleTile(wallTilemap, wallTop, position);
}
private void PaintTiles(IEnumerable<Vector2Int> positions, Tilemap tilemap, TileBase tile)
{
foreach(var position in positions)
{
PaintSingleTile(tilemap, tile, position);
}
}
private void PaintSingleTile(Tilemap tilemap, TileBase tile, Vector2Int position)
{
var tilePosition = tilemap.WorldToCell((Vector3Int)position);
tilemap.SetTile(tilePosition, tile);
}
public void Clear()
{
floorTilemap.ClearAllTiles();
wallTilemap.ClearAllTiles();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="SimpleRandomWalkParameters_",menuName = "PCG/SimpleRandomWalkData")]
public class SimpleRandomWalkSO : ScriptableObject
{
public int iterations = 10, walkLenght = 10;
public bool startRandomlyEachIteration = true;
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
public class SimpleRandomWalkDungeonGenerator : AbstractDungeonGenerator
{
[SerializeField]
protected SimpleRandomWalkSO randomWalkParameters;
protected override void RunProceduralGeneration()
{
HashSet<Vector2Int> floorPositions = RunRandomWalk(randomWalkParameters, startPosition);
tilemapVisualizer.Clear();
tilemapVisualizer.PaintFloorTiles(floorPositions);
WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);
}
protected HashSet<Vector2Int> RunRandomWalk(SimpleRandomWalkSO parameters, Vector2Int position)
{
var currentPosition = position;
HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>();
for (int i = 0; i < parameters.iterations; i++)
{
var path = ProceduralGenerationAlgorithms.SimpleRandomWalk(currentPosition, parameters.walkLenght);
floorPositions.UnionWith(path);
if (parameters.startRandomlyEachIteration)
currentPosition = floorPositions.ElementAt(Random.Range(0, floorPositions.Count));
}
return floorPositions;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(AbstractDungeonGenerator), true)]
public class RandomDungeonGeneratorEditor : Editor
{
AbstractDungeonGenerator generator;
private void Awake()
{
generator = (AbstractDungeonGenerator)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if(GUILayout.Button("Create Dungeon"))
{
generator.GenerateDungeon();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class ProceduralGenerationAlgorithms
{
public static HashSet<Vector2Int> SimpleRandomWalk(Vector2Int startPosition, int walkLenght)
{
HashSet<Vector2Int> path = new HashSet<Vector2Int>();
path.Add(startPosition); //시작
var previousPosition = startPosition;
for (int i = 0; i < walkLenght; i++)
{
var newPosition = previousPosition + Direction2D.GetRandomCardinalDirection();
path.Add(newPosition);
previousPosition = newPosition;
}
return path;
}
public static List<Vector2Int> RandomWalkCorridor(Vector2Int startPosition, int corriderLength)
{
List<Vector2Int> corridor = new List<Vector2Int>();
var direction = Direction2D.GetRandomCardinalDirection();
var currentPosition = startPosition;
corridor.Add(currentPosition);
for (int i = 0; i < corriderLength; i++)
{
currentPosition += direction;
corridor.Add(currentPosition);
}
return corridor;
}
public static List<BoundsInt> BinarySpacePartitioning(BoundsInt spaceToSplit, int minwidth, int minHeight)
{
Queue<BoundsInt> roomsQueue = new Queue<BoundsInt>();
List<BoundsInt> roomList = new List<BoundsInt>();
roomsQueue.Enqueue(spaceToSplit);
while(roomsQueue.Count > 0)
{
var room = roomsQueue.Dequeue();
if(room.size.y >= minHeight && room.size.x >= minwidth)
{
if (UnityEngine.Random.value < 0.5f)
{
if(room.size.y >= minHeight * 2)
{
SplitHorizontally(minHeight, roomsQueue, room);
}else if(room.size.x >= minwidth * 2)
{
SplitVertically(minwidth, roomsQueue, room);
}else if(room.size.x >= minwidth && room.size.y >= minHeight)
{
roomList.Add(room);
}
}
else
{
if (room.size.x >= minwidth * 2)
{
SplitVertically(minwidth, roomsQueue, room);
}
else if (room.size.y >= minHeight * 2)
{
SplitHorizontally(minHeight, roomsQueue, room);
}
else if (room.size.x >= minwidth && room.size.y >= minHeight)
{
roomList.Add(room);
}
}
}
}
return roomList;
}
private static void SplitVertically(int minwidth, Queue<BoundsInt> roomsQueue, BoundsInt room)
{
var xSplit = Random.Range(1, room.size.x);
BoundsInt room1 = new BoundsInt(room.min, new Vector3Int(xSplit, room.size.y, room.size.z));
BoundsInt room2 = new BoundsInt(new Vector3Int(room.min.x + xSplit, room.min.y, room.min.z),
new Vector3Int(room.size.x - xSplit, room.size.y, room.size.z));
roomsQueue.Enqueue(room1);
roomsQueue.Enqueue(room2);
}
private static void SplitHorizontally(int minHeight, Queue<BoundsInt> roomsQueue, BoundsInt room)
{
var ySplit = Random.Range(1, room.size.y); //최소높이 = y - 높이
BoundsInt room1 = new BoundsInt(room.min, new Vector3Int(room.size.x, ySplit, room.min.z));
BoundsInt room2 = new BoundsInt(new Vector3Int(room.min.x, room.min.y + ySplit, room.min.z),
new Vector3Int(room.size.x, room.size.y - ySplit, room.size.z));
roomsQueue.Enqueue(room1);
roomsQueue.Enqueue(room2);
}
}
public static class Direction2D
{
public static List<Vector2Int> cardinalDirectionsList = new List<Vector2Int>
{
new Vector2Int(0,1), //위
new Vector2Int(1,0), //오른쪽
new Vector2Int(0, -1), //아래
new Vector2Int(-1, 0) //왼쪽
};
public static Vector2Int GetRandomCardinalDirection()
{
return cardinalDirectionsList[Random.Range(0, cardinalDirectionsList.Count)];
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using UnityEngine;
public class CorridorFirstDungeonGenerator : SimpleRandomWalkDungeonGenerator
{
[SerializeField]
private int corridorLength = 14, corridorCount = 5;
[SerializeField]
private float roomPercent = 0.8f;
protected override void RunProceduralGeneration()
{
CorridorFirstGeneration();
}
private void CorridorFirstGeneration()
{
HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>();
HashSet<Vector2Int> potentialRoomPositions = new HashSet<Vector2Int>();
CreateCorridors(floorPositions, potentialRoomPositions);
HashSet<Vector2Int> roomPositions = CreateRooms(potentialRoomPositions);
List<Vector2Int> deadEnds = FindAllDeadEnds(floorPositions);
CreatRoomsAtDeadEnd(deadEnds, roomPositions);
floorPositions.UnionWith(roomPositions);
tilemapVisualizer.PaintFloorTiles(floorPositions);
WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);
}
private void CreatRoomsAtDeadEnd(List<Vector2Int> deadEnds, HashSet<Vector2Int> roomFloors)
{
foreach (var position in deadEnds)
{
if(roomFloors.Contains(position) == false)
{
var room = RunRandomWalk(randomWalkParameters, position);
roomFloors.UnionWith(room);
}
}
}
private List<Vector2Int> FindAllDeadEnds(HashSet<Vector2Int> floorPositions)
{
List<Vector2Int> deadEnds = new List<Vector2Int>();
foreach (var position in floorPositions)
{
int neighboursCount = 0;
foreach (var direction in Direction2D.cardinalDirectionsList)
{
if (floorPositions.Contains(position + direction))
neighboursCount++;
}
if (neighboursCount == 1)
deadEnds.Add(position);
}
return deadEnds;
}
private HashSet<Vector2Int> CreateRooms(HashSet<Vector2Int> potentialRoomPositions)
{
HashSet<Vector2Int> roomPositions = new HashSet<Vector2Int>();
int roomToCreateCount = Mathf.RoundToInt(potentialRoomPositions.Count * roomPercent);
List<Vector2Int> roomToCreate = potentialRoomPositions.OrderBy(bool2x2 => Guid.NewGuid()).Take(roomToCreateCount).ToList();
foreach (var roomPosition in roomToCreate)
{
var roomFloor = RunRandomWalk(randomWalkParameters, roomPosition);
roomPositions.UnionWith(roomFloor);
}
return roomPositions;
}
private void CreateCorridors(HashSet<Vector2Int> floorPositions, HashSet<Vector2Int> potentialRoomPositions)
{
var currentPosition = startPosition;
potentialRoomPositions.Add(currentPosition);
for (int i = 0; i < corridorCount; i++)
{
var corridor = ProceduralGenerationAlgorithms.RandomWalkCorridor(currentPosition, corridorLength);
currentPosition = corridor[corridor.Count - 1];
potentialRoomPositions.Add(currentPosition);
floorPositions.UnionWith(corridor);
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class RoomFirstDungeonGenerator : SimpleRandomWalkDungeonGenerator
{
[SerializeField]
private int minRoomWidth = 4, minRoomHeight = 4;
[SerializeField]
private int dungeonWidth = 20, dungeonHeight = 20;
[SerializeField]
[Range(0, 10)]
private int offset = 1;
[SerializeField]
private bool randomwalkRooms = false;
protected override void RunProceduralGeneration()
{
CreatRooms();
}
private void CreatRooms()
{
var roomsList = ProceduralGenerationAlgorithms.BinarySpacePartitioning(new BoundsInt((Vector3Int)startPosition, new Vector3Int
(dungeonWidth, dungeonHeight, 0)), minRoomWidth, minRoomHeight);
HashSet<Vector2Int> floor = new HashSet<Vector2Int>();
if (randomwalkRooms)
{
floor = CreateRoomsRandomly(roomsList);
}
else
{
floor = CreateSimpleRooms(roomsList);
}
List<Vector2Int> roomCenters = new List<Vector2Int>();
foreach (var room in roomsList)
{
roomCenters.Add((Vector2Int)Vector3Int.RoundToInt(room.center));
}
HashSet<Vector2Int> corridors = ConnectRooms(roomCenters);
floor.UnionWith(corridors);
tilemapVisualizer.PaintFloorTiles(floor);
WallGenerator.CreateWalls(floor, tilemapVisualizer);
}
private HashSet<Vector2Int> CreateRoomsRandomly(List<BoundsInt> roomsList)
{
HashSet<Vector2Int> floor = new HashSet<Vector2Int>();
for (int i = 0; i < roomsList.Count; i++)
{
var roomBounds = roomsList[i];
var roomCenter = new Vector2Int(Mathf.RoundToInt(roomBounds.center.x), Mathf.RoundToInt(roomBounds.center.y));
var roomFloor = RunRandomWalk(randomWalkParameters, roomCenter);
foreach (var position in roomFloor)
{
if(position.x >= (roomBounds.xMin + offset) && position.x <= (roomBounds.xMax - offset) && position.y >= (roomBounds.yMin -
offset) && position.y <= (roomBounds.yMax - offset))
{
floor.Add(position);
}
}
}
return floor;
}
private HashSet<Vector2Int> ConnectRooms(List<Vector2Int> roomCenters)
{
HashSet<Vector2Int> corridors = new HashSet<Vector2Int>();
var currentRoomCenter = roomCenters[Random.Range(0, roomCenters.Count)];
roomCenters.Remove(currentRoomCenter);
while (roomCenters.Count > 0)
{
Vector2Int closest = FindClosetPointTo(currentRoomCenter, roomCenters);
roomCenters.Remove(closest);
HashSet<Vector2Int> newcorridor = CreateCorridor(currentRoomCenter, closest);
currentRoomCenter = closest;
corridors.UnionWith(newcorridor);
}
return corridors;
}
private HashSet<Vector2Int> CreateCorridor(Vector2Int currentRoomCenter, Vector2Int destination)
{
HashSet<Vector2Int> corridor = new HashSet<Vector2Int>();
var position = currentRoomCenter;
corridor.Add(position);
while (position.y != destination.y)
{
if(destination.y > position.y)
{
position += Vector2Int.up;
}
else if(destination.y < position.y)
{
position += Vector2Int.down;
}
corridor.Add(position);
}
while (position.x != destination.x)
{
if(destination.x > position.x)
{
position += Vector2Int.right;
}else if(destination.x < position.x)
{
position += Vector2Int.left;
}
corridor.Add(position);
}
return corridor;
}
private Vector2Int FindClosetPointTo(Vector2Int currentRoomCenter, List<Vector2Int> roomCenters)
{
Vector2Int closest = Vector2Int.zero;
float distance = float.MaxValue;
foreach (var position in roomCenters)
{
float currentDistance = Vector2Int.Distance(position, currentRoomCenter);
if (currentDistance < distance)
{
distance = currentDistance;
closest = position;
}
}
return closest;
}
private HashSet<Vector2Int> CreateSimpleRooms(List<BoundsInt> roomsList)
{
HashSet<Vector2Int> floor = new HashSet<Vector2Int>();
foreach (var room in roomsList)
{
for (int col = offset; col < room.size.x - offset; col++)
{
for (int row = offset; row < room.size.y - offset; row++)
{
Vector2Int position = (Vector2Int)room.min + new Vector2Int(col, row);
floor.Add(position);
}
}
}
return floor;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class AbstractDungeonGenerator : MonoBehaviour
{
[SerializeField]
protected TilemapVisualizer tilemapVisualizer = null;
[SerializeField]
protected Vector2Int startPosition = Vector2Int.zero;
public void GenerateDungeon()
{
tilemapVisualizer.Clear();
RunProceduralGeneration();
}
protected abstract void RunProceduralGeneration();
}
'Project T > 진행' 카테고리의 다른 글
[팀 프로젝트] - 랜덤 워크와 이진 공간 분할 알고리즘(맵 생성6) (0) | 2021.12.03 |
---|---|
[팀 프로젝트] - 랜덤 워크와 이진 공간 분할 알고리즘(맵 생성5) (0) | 2021.12.02 |
[팀 프로젝트] - 랜덤 워크와 이진 공간 분할 알고리즘(맵 생성4) (0) | 2021.12.01 |
[팀 프로젝트] - 랜덤 워크와 이진 공간 분할 알고리즘(맵 생성3) (0) | 2021.11.30 |
[팀 프로젝트] - 랜덤 워크와 이진 공간 분할 알고리즘(맵 생성2) (0) | 2021.11.29 |