반응형
SMALL
오늘로 랜덤 맵 생성 프로토타입은 마지막이다.
맵의 생성 모습을 좀 더 유연하게 확인하기 위해 랜덤 맵 생성기를 제작하였다.
우선 제작 구현사항은 아래와 같다.
- 지정된 배열의 크기(레벨에 따라 증가한다) 안에서 절반의 인덱스를 랜덤으로 골라 해당 인덱스 들을 각각 비교하여 가장 거리가 먼 두점을 골라 보스방과 시작방으로 설정 이후 4번째 로 가중치가 높은 방을 선정하여 안전 가옥으로 설정해주었다.
- 이후 A*알고리즘을 사용하여 시작방 -> 보스방으로 가는 길에서 가장 가중치가 높은 방들만을골라 가장 먼 길을 생성
- 이후 안전가옥 -> 시작방 으로도 같은 알고리즘 적용
- 이후 생성된 리스트에 일반룸 생성(복도들)
- 랜덤한 확률로 (영상에서는 토글로 대체) 히든 룸 생성 (일반룸 하나를 랜덤하게 대체한다)
- 이후 2차원 배열에 룸 정보를 담는다
- 0 : 룸 없음
- 1 : 일반룸
- 2 : 시작룸
- 3 : 안전가옥
- 4 : 보스방
- 5 : 히든룸
- 룸 정보를 기반으로 포탈을 생성, 포탈은 옆에 있는 방이 어떤 방이냐에 따라 모습이 달라진다 (여기서는 색깔이 다른 포탈들로 대체)
제작한 룸 생성기 영상
2차원 배열로 맵의 정보를 저장하니 맵을 관리하기가 훨씬 편하다.
단점이라면 많은 for문의 향연....
코드는 아래와 같다.
using UnityEngine;
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using static AstarLogic;
using System.Collections;
using Unity.VisualScripting;
using UnityEditor.Experimental.GraphView;
public class MapGenerator : MonoBehaviour
{
public AstarLogic astarLogic;
public int maxRow;
public int maxCol;
public int mapDist = 32;
public int mapCount;
private Vector2[,] mapVectorPos;
private List<GameObject> normalRoomList;
private List<GameObject> wholeRoomList;
public GameObject startRoom;
public GameObject BossRoom;
public GameObject normalRoom;
public GameObject safeHouseRoom;
public GameObject hiddemRoom;
public GameObject portal;
public GameObject bossPortal;
public GameObject safeRoomPortal;
public Vector2 startRoomPos;
public Vector2 BossRoomPos;
public Vector2 safeHousePos;
public Vector2 hiddemRoomPos;
private int[,] mapLocation;
public bool isHiddenRoom = false;
public Action<int> sendMapCount;
public void Init()
{
StringBuilder sb = new StringBuilder();
// 맵의 정보를 저장할 2차원 배열 선언
this.mapLocation = new int[maxRow, maxCol];
// 맵의 벡터 정보를 저장하는 배열 생성
this.mapVectorPos = new Vector2[maxRow, maxCol];
// 전체 맵을 저장하는 배열 생성
this.wholeRoomList = new List<GameObject> ();
//일반룸들만을 모아둔 리스트 생성
this.normalRoomList = new List<GameObject>();
int x = 0;
int y = 0;
for (int i = 0; i < this.maxRow; i++)
{
for (int j = 0; j < this.maxCol; j++)
{
this.mapVectorPos[i, j] = new Vector2(x, y);
sb.Append(string.Format("{0}.{1}({2},{3})\t", i, j, x, y));
x += this.mapDist;
}
y -= this.mapDist;
x = 0;
sb.Append('\n');
}
Debug.LogFormat(sb.ToString());
sb.Clear();
//랜덤 포인트 만들기
this.MakeRanLocation();
// 만든 랜덤 포인트 들을 임시리스트에 저장
List<Vector2> temp = new List<Vector2>();
for (int i = 0; i < this.maxRow; i++)
{
for (int j = 0; j < this.maxCol; j++)
{
if (this.mapLocation[i,j] == 1)
{
temp.Add(new Vector2(j * this.mapDist, i * -this.mapDist));
sb.Append(string.Format("{0}.{1}({2},{3})\t", i, j, j * this.mapDist, i * -this.mapDist));
}
}
}
Debug.LogFormat("====생성된 랜덤 좌표들 갯수 :{0}====", temp.Count);
Debug.LogFormat(sb.ToString());
sb.Clear();
// 리스트에서 가장 거리가 먼 두 점을찾아 내림차순으로 넣은 Vecotr2 리스트 생성
var furthestPoints = FindFurthestPoints(temp);
//스타트 Vector2 포지션
this.startRoomPos = furthestPoints[0];
// 보스룸 Vector2 포지션
this.BossRoomPos = furthestPoints[1];
//안전가옥 Vector2 포지션
this.safeHousePos = furthestPoints[furthestPoints.Count-2];
//시작룸 생성
var startRoomGo = GameObject.Instantiate(this.startRoom);
startRoomGo.transform.position = this.startRoomPos;
this.wholeRoomList.Add(startRoomGo);
//보스룸 생성
var bossRoomGo = GameObject.Instantiate(this.BossRoom);
bossRoomGo.transform.position = this.BossRoomPos;
this.wholeRoomList.Add(bossRoomGo);
//안전가옥 생성
var safeHouseGo = GameObject.Instantiate(this.safeHouseRoom);
safeHouseGo.transform.position = this.safeHousePos;
this.wholeRoomList.Add(safeHouseGo);
//임시 리스트 초기화
temp.Clear();
temp.Add(this.startRoomPos);
temp.Add(this.BossRoomPos);
temp.Add(this.safeHousePos);
//2차원 맵 배열에서 실제로 방이 없는 곳 초기화
for (int i = 0; i < this.maxRow; i++)
{
for (int j = 0; j < this.maxCol; j++)
{
if (this.mapLocation[i, j] == 1)
{
if(!temp.Contains(new Vector2(j * this.mapDist, i * -this.mapDist)))
{
this.mapLocation[i, j] = 0;
}
}
}
}
int startY = 0;
int startX = 0;
int bossY = 0;
int bossX = 0;
int safeY = 0;
int safeX = 0;
for (int i = 0; i < this.maxRow; i++)
{
for (int j = 0; j < this.maxCol; j++)
{
if (this.mapVectorPos[i,j] == this.startRoomPos)
{
this.mapLocation[i,j] = 2;
Debug.LogFormat("<color=green>시작룸 위치 : {0},{1} vectorList 값 : {2}</color>", i,j, this.mapVectorPos[i, j]); ; ;
startY = i;
startX = j;
}
else if(this.mapVectorPos[i,j] == this.BossRoomPos)
{
this.mapLocation[i, j] = 4;
Debug.LogFormat("<color=red>보스룸 위치 : {0},{1} vectorList 값 : {2}</color>", i, j, this.mapVectorPos[i, j]);
bossY = i;
bossX = j;
}
else if(this.mapVectorPos[i, j] == this.safeHousePos)
{
this.mapLocation[i, j] = 3;
Debug.LogFormat("<color=blue>안전가옥 위치 : {0},{1} vectorList 값 : {2}</color>", i, j, this.mapVectorPos[i, j]);
safeY = i;
safeX = j;
}
}
}
List<AStarNode> toBossList = new List<AStarNode>();
this.astarLogic.Init(this.maxRow, this.maxCol, startX, startY, bossX, bossY,out toBossList);
toBossList.ForEach(t => { Debug.LogFormat("보스 방까지의 노드 리스트 :{0} , {1}", t.y, t.x); });
List<AStarNode> toSafeHouseList = new List<AStarNode>();
this.astarLogic.Init(this.maxRow, this.maxCol, safeX, safeY, startX, startY, out toSafeHouseList);
toSafeHouseList.ForEach(t => { Debug.LogFormat("안전 가옥 부터 스타트 까지의 노드 리스트 :{0} , {1}", t.y, t.x); });
//보스방 까지의 리스트 생성
foreach (AStarNode node in toBossList)
{
var checkVec = new Vector2 (node.x * this.mapDist, node.y * -this.mapDist);
if (checkVec == this.startRoomPos || checkVec == this.BossRoomPos || checkVec == this.safeHousePos) continue;
else
{
//생성후 포지션 잡아주기
var go = GameObject.Instantiate(this.normalRoom);
go.transform.position = new Vector2(node.x * this.mapDist, node.y * -this.mapDist);
//2차원 맵 배열에 표시
this.mapLocation[node.y, node.x] = 1;
//일반룸 리스트에 목록 추가
this.normalRoomList.Add(go);
this.wholeRoomList.Add(go);
sb.Append(String.Format("({0},{1}) {2}", node.y, node.x, checkVec));
}
checkVec = Vector2.zero;
}
Debug.Log("====보스방까지 생성된 일반룸 목록 ====");
Debug.Log(sb.ToString());
sb.Clear();
foreach (AStarNode node in toSafeHouseList)
{
var checkVec = new Vector2(node.x * this.mapDist, node.y * -this.mapDist);
if (this.mapLocation[node.y,node.x] != 0 || checkVec == this.startRoomPos || checkVec == this.BossRoomPos || checkVec == this.safeHousePos) continue;
else
{
//생성후 포지션 잡아주기
var go = GameObject.Instantiate(this.normalRoom);
go.transform.position = new Vector2(node.x * this.mapDist, node.y * -this.mapDist);
//2차원 맵 배열에 표시
this.mapLocation[node.y, node.x] = 1;
//일반룸 리스트에 목록 추가
this.normalRoomList.Add(go);
this.wholeRoomList.Add(go);
sb.Append(String.Format("({0},{1}) {2}", node.y, node.x, checkVec));
}
checkVec = Vector2.zero;
}
Debug.Log("====다시 시작지점까지 생성된 일반룸 목록 ====");
Debug.Log(sb.ToString());
sb.Clear();
if (this.isHiddenRoom){
this.MakeHiddenRoom();
}
for (int i = 0; i < this.maxRow; i++)
{
for (int j = 0; j < this.maxCol; j++)
{
sb.Append(string.Format("<color=pink>({0},{1}) : {2}</color>\t", i,j,this.mapLocation[i, j]));
if (this.mapLocation[i,j] !=0)
{
this.mapCount++;
}
}
}
Debug.LogFormat("====최종 배열====");
Debug.LogFormat(sb.ToString());
this.MakePortal();
this.sendMapCount(this.mapCount);
}
private void MakeRanLocation( )
{
int cunt = 0;
while (cunt !=(this.maxCol*this.maxRow)/2)
{
var Y = UnityEngine.Random.Range(0, this.maxRow);
var X = UnityEngine.Random.Range(0, this.maxCol);
if (this.mapLocation[Y, X] == 1)
{
continue;
}
else
{
this.mapLocation[Y, X] = 1;
cunt++;
}
}
}
private List<Vector2> FindFurthestPoints(List<Vector2> points)
{
// 리스트에서 가능한 모든 두 점 쌍을 생성한다.
var pairs = from p1 in points
from p2 in points
where p1 != p2
select new { p1, p2 };
// 각 쌍의 거리를 계산
var distances = pairs.Select(pair => new { pair.p1, pair.p2, distance = Vector2.Distance(pair.p1, pair.p2) });
// 거리가 가장 먼 두 점을 찾는다.
var furthestPoints = distances.OrderByDescending(pair => pair.distance).First();
var top2List = new List<Vector2> { furthestPoints.p1, furthestPoints.p2 };
//새로운 리스트 생성
var sortedList = new List<Vector2>();
sortedList.Add(top2List[0]);
sortedList.Add(top2List[1]);
sortedList.AddRange(points.Except(top2List).OrderBy(p => Vector2.Distance(top2List[0],p)));
return sortedList;
}
private void MakeHiddenRoom()
{
//노말룸리스트인덱스 중 랜덤 인덱스 설정
var num = UnityEngine.Random.Range(0, this.normalRoomList.Count - 1);
//랜덤 인덱스위치에 히든룸 생성
var hiddenGO = UnityEngine.Object.Instantiate(this.hiddemRoom);
this.normalRoomList.Add(hiddenGO);
this.wholeRoomList.Add(hiddenGO);
//히든룸 포지션에 랜덤룸 포지션 담기 및 위치 변경
this.hiddemRoomPos = this.normalRoomList[num].transform.position;
hiddenGO.transform.position = this.hiddemRoomPos;
//기존 위치에 있던 노말룸 제거
Debug.LogFormat("파괴된 노말룸 포지션 {0}", this.hiddemRoomPos);
Destroy(this.normalRoomList[num]);
this.normalRoomList.Remove(this.normalRoomList[num]);
}
private void MakePortal()
{
//포탈 생성
for (int i = 0; i < this.maxRow; i++)
{
for (int j = 0; j < this.maxCol; j++)
{
// 기준방 발견
if (this.mapLocation[i, j] != 0)
{
//배열을 벗어나지 않는다면 윗칸 보기
if (i - 1 >= 0)
{
//벗어나지 않았는데 방이 있다면
if (this.mapLocation[i - 1, j] != 0)
{
//생성된 룸 리스트에서 현재 검색중인 배열과 같은 위치에 있는 룸 찾기
foreach (var room in this.wholeRoomList)
{
if ((Vector2)room.transform.position == new Vector2(j * 32, i * -32))
{
GameObject go = null;
//찾으면 포탈 생성해서 해당 포인트에 자식으로 넣어주고 로컬 포지션 0으로 초기화
if (this.mapLocation[i - 1, j] == 4)
{
go = Instantiate(this.bossPortal, room.transform.GetChild(0)); // 보스룸
}
else if (this.mapLocation[i - 1, j] == 3)
{
go = Instantiate(this.safeRoomPortal, room.transform.GetChild(0)); //상점
}
else
{
go = Instantiate(this.portal, room.transform.GetChild(0)); // 일반룸
}
go.transform.localPosition = Vector2.zero;
}
}
}
}
//배열을 벗어나지 않는다면 아랫칸 보기
if (i + 1 < this.maxRow)
{
//벗어나지 않았는데 방이 있다면
if (this.mapLocation[i + 1, j] != 0)
{
//생성된 룸 리스트에서 현재 검색중인 배열과 같은 위치에 있는 룸 찾기
foreach (var room in this.wholeRoomList)
{
if ((Vector2)room.transform.position == new Vector2(j * 32, i * -32))
{
GameObject go = null;
//찾으면 포탈 생성해서 해당 포인트에 자식으로 넣어주고 로컬 포지션 0으로 초기화
if (this.mapLocation[i + 1, j] == 4)
{
go = Instantiate(this.bossPortal, room.transform.GetChild(2)); // 보스룸
}
else if (this.mapLocation[i + 1, j] == 3)
{
go = Instantiate(this.safeRoomPortal, room.transform.GetChild(2)); //상점
}
else
{
go = Instantiate(this.portal, room.transform.GetChild(2)); // 일반룸
}
go.transform.localPosition = Vector2.zero;
}
}
}
}
//배열을 벗어나지 않는다면 오른쪽 칸 보기
if (j + 1 < this.maxCol)
{
//벗어나지 않았는데 방이 있다면
if (this.mapLocation[i, j + 1] != 0)
{
//생성된 룸 리스트에서 현재 검색중인 배열과 같은 위치에 있는 룸 찾기
foreach (var room in this.wholeRoomList)
{
if ((Vector2)room.transform.position == new Vector2(j * 32, i * -32))
{
GameObject go = null;
//찾으면 포탈 생성해서 해당 포인트에 자식으로 넣어주고 로컬 포지션 0으로 초기화
if (this.mapLocation[i, j + 1] == 4)
{
go = Instantiate(this.bossPortal, room.transform.GetChild(1)); // 보스룸
}
else if (this.mapLocation[i, j + 1] == 3)
{
go = Instantiate(this.safeRoomPortal, room.transform.GetChild(1)); //상점
}
else
{
go = Instantiate(this.portal, room.transform.GetChild(1)); // 일반룸
}
go.transform.localPosition = Vector2.zero;
}
}
}
}
//배열을 벗어나지 않는다면 왼쪽 칸 보기
if (j - 1 >= 0)
{
//벗어나지 않았는데 방이 있다면
if (this.mapLocation[i, j - 1] != 0)
{
//생성된 룸 리스트에서 현재 검색중인 배열과 같은 위치에 있는 룸 찾기
foreach (var room in this.wholeRoomList)
{
if ((Vector2)room.transform.position == new Vector2(j * 32, i * -32))
{
GameObject go = null;
//찾으면 포탈 생성해서 해당 포인트에 자식으로 넣어주고 로컬 포지션 0으로 초기화
if (this.mapLocation[i, j - 1] == 4)
{
go = Instantiate(this.bossPortal, room.transform.GetChild(3)); // 보스룸
}
else if (this.mapLocation[i, j - 1] == 3)
{
go = Instantiate(this.safeRoomPortal, room.transform.GetChild(3)); //상점
}
else
{
go = Instantiate(this.portal, room.transform.GetChild(3)); // 일반룸
}
go.transform.localPosition = Vector2.zero;
}
}
}
}
}
}
}
}
}
상세 내용은 주석참고.
반응형
LIST
'프로젝트 > 건즈앤 레이첼스' 카테고리의 다른 글
[유니티 프로젝트] 랜덤 생성맵 포탈 구현 (맵간의 이동) 및 미니맵 포지션 구현 (0) | 2023.03.19 |
---|---|
[유니티 프로젝트] 랜덤맵 미니맵 제작 (0) | 2023.03.18 |
[유니티 프로젝트] A*알고리즘을 이용한 랜덤 맵 생성 .3 (0) | 2023.03.16 |
[유니티 프로젝트] A* 알고리즘을 활용한 랜덤 맵생성 (1) | 2023.03.15 |
[유니티 프로젝트] 이차원 배열과 A* 알고리즘을 사용한 랜덤 맵 생성 .2 (0) | 2023.03.14 |