[Unity] 문명 개발일지 01. 헥사 타일
- ⭐ Game Programming/Unity
- 2022. 9. 6.
다시 유니티
언리얼로 개인 프로젝트를 진행하였는데 회사에서 작업하듯 블루프린트가 아닌 네이티브 코드 개발로만 진행하려니 진행이 더디고 맞는 애셋을 찾기 힘들어 다시 유니티로 새로운 프로젝트를 진행하였다.
시작하며
문명의 시스템을 따라 4X 장르의 게임을 만들어보려고 한다. 그래서 우선 기본적인 문명 시스템 부터 개발하고자 하였다. 문명의 가장큰 특징은 6각(헥사) 타일이다. 유니티에서는 헥사 타일맵 기능을 제공하고 있으니 해당기능을 활용해보자.
헥사 타일맵
유튜브 Unity 공식 채널에 헥사 타일맵 튜토리얼 영상이 있어 해당 영상을 따라 진행하였다.
https://www.youtube.com/watch?v=bcPqdCSGCls
- 유니티 무료 애셋을 활용 하여 타일 팔레트를 생성
- 코드로 땅과 물을 번갈아가며 생성
- 키보드로 유닛 이동
- 마우스 오버시 타일 셀렉트 연출
- 마우스 드래그로 화면 이동
- 타일별 레이어 분할 관리
클릭 이동
클릭으로 유닛 선택, 해제 할수 있도록 했으며, 각 타일 정보를 배열로 처리하였다.
A Star 알고리즘을 활용 하여 경로 탐색 기능을 구현하였다.
주요 개발 내용
- 타일은 3차원이 아닌 2차원 좌표로 처리
3차원으로 할 경우 경로 탐색 알고리즘이 복잡해져 2차원 좌표로 처리하였다. 유니티의 헥사 타일맵 기능도 2차원 좌표계로 처리되고 있다.
- A Star 알고리즘을 활용한 경로 탐색
가중치를 기준으로 OpenList를 만들어서 하나씩 탐색해 나가는 기본 A Star 알고리즘을 구현하였다. 헥사 타일이기에 Y가 홀수 일때와 짝수 일때 주변 타일의 X좌표가 다르다는 점을 유의해야 했다.
public bool GetPath(ObjectData inMoveObjectData, Vector3Int inStart, Vector3Int inEnd, ref List<Vector3> OutPath)
{
OutPath.Clear();
if (inMoveObjectData == null)
return false;
bool bFind = false;
int[] oddDirX = new int[] { 0, 1, 1, 1, 0, -1 }; //홀수
int[] evenDirX = new int[] { -1, 0, 1, 0, -1, -1 }; //짝수
int[] DirY = new int[] { -1, -1, 0, 1, 1, 0 };
int[] cost = new int[] { 7, 7, 10, 7, 7, 10 };
bool[,] closed = new bool[m_WorldSizeY, m_WorldSizeX];
for (int y = 0; y < m_WorldSizeY; y++)
for (int x = 0; x < m_WorldSizeX; x++)
closed[y, x] = false;
List<PathNode> openList = new List<PathNode>();
Vector2Int[,] parent = new Vector2Int[m_WorldSizeY, m_WorldSizeX];
openList.Add(new PathNode(inStart.x, inStart.y, CalcCost(inStart.x, inStart.y, inEnd.x, inEnd.y), 0));
parent[inStart.y, inStart.x] = new Vector2Int(-1, -1);
while (openList.Count > 0)
{
PathNode CurrentNode = openList[0];
openList.RemoveAt(0);
if (closed[CurrentNode.Y, CurrentNode.X])
continue;
closed[CurrentNode.Y, CurrentNode.X] = true;
if(CurrentNode.X == inEnd.x && CurrentNode.Y == inEnd.y)
{
bFind = true;
break;
}
for( int i = 0; i < DirY.Length; i++)
{
int nextY = CurrentNode.Y + DirY[i];
int nextX = (CurrentNode.Y % 2 == 0) ? CurrentNode.X + evenDirX[i] : CurrentNode.X + oddDirX[i];
//유효범위
if (nextX < 0 || nextX >= m_WorldSizeX || nextY < 0 || nextY >= m_WorldSizeY)
continue;
if (closed[nextY, nextX])
continue;
//이동 불가 조건 처리 시작
{
//1. 유닛끼리 겹칠 수 없다.
GameObject FindUnit = m_GameObjectManager.GetUnitByPos(new Vector3Int(nextX, nextY), true);
if (FindUnit != null)
{
closed[nextY, nextX] = true;
continue;
}
}
//이동 불가 조건 처리 끝
int g = CurrentNode.G + cost[i];
int h = CalcCost(nextX, nextY, inEnd.x, inEnd.y);
int openIndex = openList.FindIndex(item => item.X == nextX && item.Y == nextY);
if (openIndex != -1 && openList[openIndex].F < g + h)
continue;
openList.Add(new PathNode(nextX, nextY, g + h, g));
parent[nextY, nextX] = new Vector2Int(CurrentNode.X, CurrentNode.Y);
}
openList.Sort((PathNode A, PathNode B) => A.F.CompareTo(B.F));
}
if(bFind)
{
Vector2Int currentPos = new Vector2Int(inEnd.x, inEnd.y);
List<Vector2Int> FindPath = new List<Vector2Int>();
while(currentPos != parent[inStart.y, inStart.x])
{
FindPath.Add(currentPos);
currentPos = parent[currentPos.y, currentPos.x];
}
for( int i = FindPath.Count - 1; i >= 0; i-- )
{
OutPath.Add(GetTileCenterPos(new Vector3Int(FindPath[i].x, FindPath[i].y)));
}
}
//TODO : 역으로 계산
//if(bFind)
//{
// Queue<Vector3> reversePath;
// int reversePathLength = 0;
//}
return bFind;
}
경로 표시
모든 유닛에게는 턴마다 이동할수 있는 타일 수가 정해져 있다. 그래서 경로 및 턴 수를 표시하도록 하였다.
- 라인 렌더러를 이용해 경로 표시
- 한 타일 이동시마다 이동한 라인 렌더러 삭제
- 이동 타일 수를 계산하여 턴수 표시
주요 개발 내용
- 게임 매니저 클래스에 턴이라는 개념을 추가
- 유닛마다 한 턴에 이동할 수 있는 타일 수 정보 추가
- 여러턴에 걸친 이동 명령에 대한 로직 정리
- 이번턴안에 갈수 있는 타일 만큼 이동
- 다음 턴이 되었을때 명령 가능한 유닛에서 제외됨
- 다음 턴인 상태에서 수동으로 눌러서 명령을 취소하거나 다른 명령을 내릴수 있음
- 다음 턴에서 그 다음턴으로 넘기기 했을때 다른 명령이 내려지지 않았다면 다음 이동을 시작함
- 턴수를 표시하기 위해 UI 작업
- TextMeshPro 한글 폰트 처리
- 라인 렌더러 활용 타일 한칸 마다 라인 렌더러의 Position으로 처리
개척자의 도시 건설
- 선택한 유닛의 전용 UI 추가
- 도시 데이터 클래스 추가
요약
- 유니티의 헥사 타일맵을 이용하여 타일 생성
- A Star 알고리즘을 활용한 경로 탐색
- 라인 렌더러를 활용한 경로 표시
- 기반 데이터 클래스들 생성
반응형