
콘솔 창에서 간단히 실행할 수 있는 게임을 만들기 위한 기능은 대부분 배웠기에 지금까지 배운 것을 활용하여 실제로 만들어보는 프로젝트를 진행할 것이다. 가장 쉬운 예시로는 텍스트로 맵을 구현시킨 미로찾기 등을 생각할 수 있다. 물론 소코반과 같이 플레이어의 움직임에 따라 사물을 밀 수 있는 로직을 구현을 하거나 TRPG 장르처럼 턴을 활용한 간단한 게임 또한 제작이 가능하니 일단 아이디어를 마구 방출해보자.
게임은 실시간으로 동작하는 상호작용이 가능한 컨텐츠로 게임 루프가 반복되는 형태로써 실행된다고 보면 된다.
Process input → update Game → render → Process input → update Game → render ...
이를 활용하여 코드의 기본 틀(frame)을 먼저 작성하므로써 아이디어 구현에 도움이 될 것이다.
static void Main(string[] args)
{
bool gameOver = false;
Start();
while(gameOver == false)
{
Render();
Input();
Update();
}
End();
}
static void Start()
{
}
static void Render()
{
}
static void Input()
{
}
static void Update()
{
}
static void End()
{
}
메인 메서드 내에는 함수를 통해 게임의 실행을 요약한 것으로, 실제로 콘솔 창에 실행시키기 위해서는 이에 맞는 함수와 로직을 구현해야할 것이다.
단, 아무리 비효율적이더라도 지금까지 배웠던 것을 활용하여 구현하는 것을 지향해보자.
함수 구현 과정에서 코드 작성이 길어지는 경우 과정을 나누어 각각을 작은 함수들로 제대로 구현되는지 확인하고, 원래의 큰 함수에서는 구성된 작은 함수들이 어떤 순서로 진행되는지, 어떤 과정을 수행하는지 한 눈에 확인이 가능하도록 하는 것이 좋다.
static void Render(Position playerPos, char[,] map)
{
Console.SetCursorPosition(0, 0);
PrintMap(map);
PrintPlayer(playerPos);
}
이렇게 Render 함수를 PrintMap 함수와 PrintPlayer 함수로 나누게 되면, 제작과정에서 하나의 기능에 집중하여 변수 설정 등이 쉬워지고, 디버깅 과정도 용이해질 것이다.
로직 구현을 끝내고 실제로 프로그램이 구동되는데 아무런 문제가 없다고 하더라도, 다시 한 번 체크해보면서 더 간결하게 표현할 수는 없는지, 반복된 구문이 있는지 확인하는 과정을 거쳐야한다. 이 과정을 통해 코드의 가독성과 유지보수성을 높일 수 있다.
struct Position
{
public int x;
public int y; // 단, y축의 경우 아래 방향이 +
}
static void Start(out Position playerPos,out Position goalPos ,out bool[,] map)
{
Console.CursorVisible = false;
// 플레이어 초기 위치 설정
playerPos.x = 1;
playerPos.y = 1;
// 도착 지점 위치 설정
goalPos.x = 13;
goalPos.y = 8;
}
static void Start(out Position playerPos,out Position goalPos ,out bool[,] map)
{
map = new bool[10,5]
{
{ false,false,false,false, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false, true, true, true, false},
{ false,false,false,false, false},
};
}
static void PrintMap(bool[,] map)
{
for (int y = 0; y < map.GetLength(0); y++)
{
for (int x = 0; x < map.GetLength(1); x++)
{
if (map[y, x] == false)
{
Console.Write('#'); // false는 #으로 출력
}
else
{
Console.Write(' '); // true는 공백으로 출력
}
}
Console.WriteLine();
}
}
#####
# #
# #
# #
# #
# # // 실제로는 정사각형으로 구현.
# #
# #
# #
#####
static void Render(Position playerPos)
{
// 백버퍼 작업(콘솔 지우고 새 도화지로 만들기)
Console.Clear();
// 플레이어 위치로 커서 옮기기
Console.SetCursorPosition(playerPos.x, playerPos.y);
// 플레이어 출력
Console.Write('P');
// 목표 지점 위치로 커서 옮기기
Console.SetCursorPosition(goalPos.x, goalPos.y);
// 목표 지점 출력
Console.Write('G');
}
ConsoleKey 로 입력값 입력static ConsoleKey Input()
{
// 엔터 없이 바로 수행할 수 있도록 콘솔키 기능
return Console.ReadKey(true).Key;
}
static void Update(ConsoleKey key, ref Position playerPos)
{
switch (key)
{
case ConsoleKey.A:
case ConsoleKey.LeftArrow:
playerPos.x--;
break;
case ConsoleKey.D:
case ConsoleKey.RightArrow:
playerPos.x++;
break;
case ConsoleKey.W:
case ConsoleKey.UpArrow:
playerPos.y--;
break;
case ConsoleKey.S:
case ConsoleKey.DownArrow:
playerPos.y++;
break;
}
}
static bool CheckGameClear(Position playerPos, Position goalPos)
{
//플레이어가 골 위치에 도달했을 때
//플레이어의 x위치가 골 x위치와 같으면서
//동시에 플레이어의 y위치가 골 y위치와 같으면
bool success = (playerPos.x == goalPos.x) && (playerPos.y == goalPos.y);
return success;
}
static void End()
{
Console.Clear();
Console.WriteLine("축하합니다. 미로 찾기에 성공하셨습니다.");
}
순서에 따라 기본 로직을 구현했지만, 기본 틀은 앞서 작성한 frame과 같다. frame을 구성하는 함수에 구현을 위한 기본 로직을 pseudo 코드로 작성하고, 이를 실제 코드로 단계 별로 바꾸는 것이 위의 과정이라고 볼 수 있다.