-
반응형
일단 유닛이라는 클래스를 만들어주고 유닛을 플레이어와 몬스터가 상속받게끔 만들어주려고합니다
internal class Pos { public Pos(int y, int x) { Y = y; X = x; } public int Y; public int X; } internal class Unit { protected Map _board; public int PosY { get; set; } public int PosX { get; set; } public void Initialize(int posY, int posX, Map board) { PosY = posY; PosX = posX; _board = board; } }
Unit을 상속받는 uMonster 클래스
internal class uMonster : Unit { Random _random = new Random(); MonsterMap _mboard; public void Initialize(int posY, int posX, MonsterMap mmap) { PosY = posY; PosX = posX; _mboard = mmap; BFS(); } List<Pos> _points = new List<Pos>(); void BFS() { // 상 하 좌 우 로 이동할때의 좌표 변화 int[] deltaY = new int[] { -1, 0, 1, 0 }; int[] deltaX = new int[] { 0, -1, 0, 1 }; // 이미 방문한 위치 표시 bool[,] found = new bool[_mboard.Size, _mboard.Size]; // 어디서부터 왔는지 추적하기 경로 Pos[,] parent = new Pos[_mboard.Size, _mboard.Size]; // 현재 위치를 큐에 넣고 방문했으니 true로 (Initialize에서 지정된 시작지점을 넣어줌) Queue<Pos> q = new Queue<Pos>(); q.Enqueue(new Pos(PosY, PosX)); found[PosY, PosX] = true; parent[PosY, PosX] = new Pos(PosY, PosX); // 대기열에 있으면 while (q.Count > 0) { // 대기열에서 제일 오래기다린 좌표 뽑기 Pos pos = q.Dequeue(); int nowY = pos.Y; int nowX = pos.X; for (int i = 0; i < 4; i++) { // 다음 좌표는 현재 좌표에서 상하좌우로 이동한 좌표를 더한 값 int nextY = nowY + deltaY[i]; int nextX = nowX + deltaX[i]; // 다음 좌표가 보드의 위치를 벗어났을때 if (nextX < 0 || nextX >= _mboard.Size || nextY < 0 || nextY >= _mboard.Size) continue; // 다음 좌표가 보드 타입의 벽일때 if (_mboard.board[nextY, nextX] == Map.BoardType.WALL) continue; // 이미 방문한 좌표일때 if (found[nextY, nextX]) continue; // 위와 같은 상황들을 다 제외한 다음 좌표를 q에 넣고(예약) // found를 true처리 그리고 최단 경로를 저장할 parent에 저장 q.Enqueue(new Pos(nextY, nextX)); found[nextY, nextX] = true; // 새로 예약한 지점의 부모님 now parent[nextY, nextX] = new Pos(nowY, nowX); } } // 내가 방문한 모든 지점마다 내가 어디서 왔는지 추적 CalcPathFromParent(parent); } void CalcPathFromParent(Pos[,] parent) { int EndY = _mboard._DestY; int EndX = _mboard._DestX; // 목적지에서 시작점까지의 경로 추적 List<Pos> GoToStart = new List<Pos>(); while (parent[EndY, EndX].Y != EndY || parent[EndY, EndX].X != EndX) { GoToStart.Add(new Pos(EndY, EndX)); Pos pos = parent[EndY, EndX]; EndY = pos.Y; EndX = pos.X; } // Add를 한번더 한 이유 : 시작점을 넣어줘야함 GoToStart.Add(new Pos(EndY, EndX)); // 시작점에서 목적지까지의 경로 저장 _points.AddRange(GoToStart); // 목적지에서 시작점까지의 경로를 역순으로 저장 -> 시작점부터 목적지까지 경로 List<Pos> Reverse = new List<Pos>(); for (int i = GoToStart.Count - 1; i >= 0; i--) { Reverse.Add(GoToStart[i]); } // 경로를 5배로 늘려줍니다. for (int i = 0; i < 5; i++) { _points.AddRange(Reverse); _points.AddRange(GoToStart); } // 경로방향을 랜덤으로 뒤집기 Random random = new Random(); if (random.Next(0, 2) == 0) _points.Reverse(); } const int MOVE_TICK = 50; int _sumTick = 0; int _lastIndex = 0; public void Update(int deltaTick) { if (_lastIndex >= _points.Count) { return; } _sumTick += deltaTick; if (_sumTick >= MOVE_TICK) { _sumTick = 0; PosY = _points[_lastIndex].Y; PosX = _points[_lastIndex].X; _lastIndex++; } } }
MonsterMap에 몬스터가 도착해야할 지점을 넣어야해서 코드를 수정했습니다 _DestY와 _DestX를 추가하였습니다
internal class MonsterMap : Map { public int _DestY { get; private set; } public int _DestX { get; private set; } public void Initialize(int size, int y, int x) { if (size % 2 == 0) return; board = new BoardType[size, size]; Size = size; _DestY = y; _DestX = x; GenerateBySideWinder(); } void GenerateBySideWinder() { // 길을 다 막아버리는 작업 for (int y = 0; y < Size; y++) { for (int x = 0; x < Size; x++) { if (x % 2 == 0 || y % 2 == 0) board[y, x] = BoardType.WALL; else board[y, x] = BoardType.NONE; } } //랜덤으로 우측 혹은 아래로 길을 뚫는 작업 Random rand = new Random(); for (int y = 0; y < Size; y++) { int count = 1; for (int x = 0; x < Size; x++) { // 막힌 부분 if (x % 2 == 0 || y % 2 == 0) continue; // 가장자리 부분 if (y == Size - 2 && x == Size - 2) continue; // y축 맨끝 부분은 다 뚫어둠 (외각에서 보드를 뚫을수도 있음) if (y == Size - 2) { board[y, x + 1] = BoardType.NONE; continue; } // x축 맨끝부분은 다 뚫어둠 if (x == Size - 2) { board[y + 1, x] = BoardType.NONE; continue; } // 1/2확률로 우측길을 뚫음 if (rand.Next(0, 2) == 0) { board[y, x + 1] = BoardType.NONE; count++; } // 아래로 길을 뚫음 else { int randomIndex = rand.Next(0, count); // 벽 빈공간 막힌공간 이런식으로 이어지기 때문에 인덱스를 *2 // 빈 공간에서 뚫어야 하기 때문 board[y + 1, x - randomIndex * 2] = BoardType.NONE; count = 1; } } } } public void Render(uMonster monster) { // 원래의 콘솔 색깔을 저장해둡니다 ConsoleColor prevColor = Console.ForegroundColor; for (int y = 0; y < Size; y++) { // 커서 위치 지정 Console.SetCursorPosition(15, y + 5); for (int x = 0; x < Size; x++) { Console.ForegroundColor = MAPcolor(board[y, x]); if (y == monster.PosY && x == monster.PosX) { Console.ForegroundColor = ConsoleColor.Gray; Console.Write(MONSTER); } else Console.Write(RECTANGLE); } Console.WriteLine(); } // 원래 콘솔 색깔로 다시 설정 Console.ForegroundColor = prevColor; } }
Map클래스에도 에도 몬스터를 출력할 변수를 만들었습니다
internal class Map { public enum BoardType // 보드 타입 { EAST, WEST, NORTH, SOUTH, NONE, WALL, } public BoardType[,] board { get; protected set; } // 보드 타입을 넣을 배열 protected const char RECTANGLE = '■'; // 보드 출력시 사각형으로 출력 protected const char MONSTER = '★'; // 몬스터 출력시 별로 출력 public int Size { get; protected set; } // 보드 사이즈 protected ConsoleColor MAPcolor(BoardType type) // 보드 타입에 따른 출력색깔 { switch (type) { case BoardType.WALL: return ConsoleColor.Red; case BoardType.EAST: case BoardType.WEST: case BoardType.NORTH: case BoardType.SOUTH: return ConsoleColor.Yellow; default: return ConsoleColor.Green; } } }
Main 함수입니다
static void Main(string[] args) { MonsterMap map = new MonsterMap(); map.Initialize(15, 13, 13); uMonster monster = new uMonster(); monster.Initialize(1, 1, map); Console.CursorVisible = false; const int WAIT_TICK = 1000 / 30; int lastTick = 0; while (true) { #region 프레임 관리 // FPS 프레임(60프레임 OK, 30프레임 이하 NO) int currenTick = System.Environment.TickCount; // 현재 시간, 시스템이 시작된 이후의 경과된 m/s // int ela // psedTick = currenTick - lastTick; // 경과한 시간 = 현재 시간 - 지난 시간 // 만약에 경과한 시간이 1 * 1000/30초보다 작다면 (밀리 새컨드 단위라서 * 1000) if (currenTick - lastTick < WAIT_TICK) continue; // 현재시간 - 이전시간 int deltaTick = currenTick - lastTick; lastTick = currenTick; #endregion // 입력 // 로직 monster.Update(deltaTick); // 렌더링 map.Render(monster); } }
아래는 실행 동영상 예시입니다
저도 공부하면서 올리는거라 코드 가독성이나 효율이 떨어지거나 잘못된 오류가 있을수도 있습니다 감사합니다
반응형'C#콘솔프로젝트(BFS,DFS,A*알고리즘을 이용한 몬스터 경로)' 카테고리의 다른 글
C#콘솔 플레이어와 몬스터 맵 만들기 (1) - 몬스터맵 만들기 (4) 2024.03.29