지난 시간에는 키보드 입력 및 블럭에 관련하여 알아보았습니다.
오늘은 Console 상에서 도형 및 프레임(화면)에 대하여 어떻게 출력할지에 대한 고찰을 해보겠습니다.
화면 출력
테트리스는 기본적으로 네모난 틀 안에서 시작합니다.
기본적으로 가로로 10칸, 세로로 20칸으로 구성됩니다.
(프레임 제외)
이 공간을 이차원 배열로 표현하면
// Origin Map
const int ORIGIN_MAP[MAP_HEIGHT][MAP_WIDTH] =
{
{1,1,1,1,1,1,1,1,1,1,1,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,1,1,1,1,1,1,1,1,1,1,1,},
};
이렇게 됩니다.
그럼 이 맵을 출력해봅시다.
(코드는 지난 시간에 사용했던 코드에 덧붙여서 사용합니다.)
// 아래의 전역 상수 및 변수를 선언 합니다.
// Map Width (constexpr)
constexpr int MAP_WIDTH = 12;
// Map Height (constexpr)
constexpr int MAP_HEIGHT = 22;
// 기본 프레임
const int ORIGIN_MAP[MAP_HEIGHT][MAP_WIDTH] =
{
{1,1,1,1,1,1,1,1,1,1,1,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,1,1,1,1,1,1,1,1,1,1,1,},
};
// Block Type
const char BLOCK_TYPES[][4] =
{
" ", // 빈 공간
"▣", // 프레임
"□" // 블록
};
// Console Data
stConsole g_console;
이제 그림을 그리는 Render 함수
를 만들어 봅시다.
Render 함수
는 다음과 같습니다.
/**
@brief Rendering function
@param nXOffset X Offset (그림을 그릴 때 왼쪽에서부터)
@param nYOffset Y Offset (그림을 그릴 때 위쪽에서부터)
@return
*/
void Render(int nXOffset = 0, int nYOffset = 0)
{
COORD coord{ 0, };
int nXAdd = 0;
DWORD dw = 0;
// Map 그리기
{
for (int nY = 0; nY < MAP_HEIGHT; ++nY)
{
nXAdd = 0;
for (int nX = 0; nX < MAP_WIDTH; ++nX)
{
coord.X = nXAdd + nXOffset;
coord.Y = nY + nYOffset;
// 커서의 위치를 이동
SetConsoleCursorPosition(g_console.hBuffer[g_console.nCurBuffer], coord);
// 출력 버퍼의 해당 커서 위치에 출력
WriteFile(g_console.hBuffer[g_console.nCurBuffer], BLOCK_TYPES[ORIGIN_MAP[nY][nX]], sizeof(BLOCK_TYPES[ORIGIN_MAP[nY][nX]]), &dw, NULL);
// X 위치 이동
nXAdd += 1;
// 굴림체 폰트에서는 특수문자의 경우 특수문자 하나가 띄어쓰기 2개와 크기가 같습니다.
// (보이는 것만 그렇고 실제로는 특수문자도 공간을 하나만 차지합니다.)
// 그렇기 때문에 띄어쓰기가 나올 경우 2칸을 움직이게 합니다.
if (ORIGIN_MAP[nY][nX] == 0)
nXAdd += 1;
}
}
}
}
그리고 메인 함수
를 다음과 같이 작성합니다.
int main(void)
{
InitGame();
while (true)
{
// X로 3칸, Y로 한칸 내려서 그립니다.
Render2(3, 1);
ClearScreen();
BufferFlip();
Sleep(1);
}
DestroyGame();
return 0;
}
이제 출력을 해봅시다.
이렇게 잘 출력이 되는 것을 볼 수 있습니다.
도형의 출력
그럼 플레이어(도형)를 출력하기 위해서는 어떻게 하면 될까요?
바로 맵 데이터 위에 도형을 넣으면 됩니다.
// Origin Map
const int ORIGIN_MAP[MAP_HEIGHT][MAP_WIDTH] =
{
{1,1,1,1,1,1,1,1,1,1,1,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,2,2,0,0,0,0,1,},
{1,0,0,0,0,2,2,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,0,0,0,0,1,},
{1,0,0,0,0,0,0,2,0,0,0,1,},
{1,0,0,0,0,0,2,2,0,0,0,1,},
{1,2,2,2,2,2,2,0,0,0,0,1,},
{1,1,1,1,1,1,1,1,1,1,1,1,},
};
이런 식으로 말이죠.
그럼 이 맵을 가지고 있을 배열을 하나 만들어봅시다.
// Map Data (전역 변수)
int g_nArrMap[MAP_HEIGHT][MAP_WIDTH] = { 0, };
그리고 InitGame 함수
에서 해당 배열에 초기 맵을 그려주도록 배열을 복사합시다.
void InitGame(bool bInitConsole = true)
{
// Initialize Console Data
if (bInitConsole)
{
// 생략
}
// Map 할당
{
int nMapSize = sizeof(int) * MAP_WIDTH * MAP_HEIGHT;
memcpy_s(g_nArrMap, nMapSize, ORIGIN_MAP, nMapSize);
}
}
그리고 위에서 작성한 Render 함수
도 변경합니다.
/**
@brief Rendering function
@param nXOffset X Offset (그림을 그릴 때 왼쪽에서부터)
@param nYOffset Y Offset (그림을 그릴 때 위쪽에서부터)
@return
*/
void Render(int nXOffset = 0, int nYOffset = 0)
{
COORD coord{ 0, };
int nXAdd = 0;
DWORD dw = 0;
// Map 그리기
{
for (int nY = 0; nY < MAP_HEIGHT; ++nY)
{
nXAdd = 0;
for (int nX = 0; nX < MAP_WIDTH; ++nX)
{
coord.X = nXAdd + nXOffset;
coord.Y = nY + nYOffset;
// 커서의 위치를 이동
SetConsoleCursorPosition(console.hBuffer[console.nCurBuffer], coord);
// 출력 버퍼의 해당 커서 위치에 출력
WriteFile(console.hBuffer[console.nCurBuffer], BLOCK_TYPES[g_nArrMap[nY][nX]], sizeof(BLOCK_TYPES[g_nArrMap[nY][nX]]), &dw, NULL);
// X 위치 이동
nXAdd += 1;
// 굴림체 폰트에서는 특수문자의 경우 특수문자 하나가 띄어쓰기 2개와 크기가 같습니다.
// (보이는 것만 그렇고 실제로는 특수문자도 공간을 하나만 차지합니다.)
// 그렇기 때문에 띄어쓰기가 나올 경우 2칸을 움직이게 합니다.
if (g_nArrMap[nY][nX] == 0)
nXAdd += 1;
}
}
}
}
이제 이 맵 배열에 도형을 넣어봅시다.
CalcPlayer 함수
를 하나 만들어줍시다.
추후에 이 함수는 플레이어(도형)의 좌표를 계산하여 해당하는 맵의 위치에 도형을 그리는 역할을 하게 될겁니다.
지금은 간단하게 플레이어(도형)를 그리기만 해봅시다.
/**
@brief Function that puts blocks into a map to match the player's position.
@param
@return
*/
void CalcPlayer()
{
// 현재 플레이어(도형)의 좌표
COORD playerCursor{ 5, 2 };
// BLOCK_HEIGHT = 4, BLOCK_WIDTH = 4
for (int nY = 0; nY < BLOCK_HEIGHT; ++nY)
{
for (int nX = 0; nX < BLOCK_WIDTH; ++nX)
{
// 1번 블록(J 블록)을 맵 데이터에 입력
if (BLOCKS[1][(nY * BLOCK_HEIGHT) + nX])
g_nArrMap[playerCursor.Y + nY][playerCursor.X + nX] = BLOCKS[1][(nY * BLOCK_HEIGHT) + nX];
}
}
}
여기서 플레이어(도형)의 좌표는
저 부분(빨간 부분)을 의미 합니다.
int main(void)
{
InitGame();
while (true)
{
CalcPlayer();
// X로 3칸, Y로 한칸 내려서 그립니다.
Render2(3, 1);
ClearScreen();
BufferFlip();
Sleep(1);
}
DestroyGame();
return 0;
}
이렇게 메인함수에 함수를 추가하고 실행해보면
이렇게 출력함을 알 수 있습니다.
이제 우리는 기본적인 부분을 배웠습니다.
- 콘솔 화면 갱신 (더블 버퍼링)
- 테트리스의 기본적인 설계
- 키보드 입력
- 블럭과 블럭 회전
- 화면 출력
물론 더 필요한 부분들이 있습니다.
- 플레이어(도형)의 낙하
- 플레이어(도형)의 충돌 판정
- 블록 랜덤 생성 및 다음 블럭 생성
- 블록 라인 체크 및 제거
- 게임 오버 조건
나머지 부분은 실제로 테트리스를 만들어 보면서 추가적인 것들을 같이 구현하며 알아보도록 합시다.
'Study > C++' 카테고리의 다른 글
[C++/Console] 테트리스 만들어 보기 - 5 (충돌 판정 - 벽, 블럭) (0) | 2023.02.20 |
---|---|
[C++/Console] 테트리스 만들어 보기 - 4 (플레이어(블럭) 움직임) (0) | 2023.02.09 |
[C++/Console] 테트리스 만들어 보기 - 2 (키보드 입력 및 블럭) (0) | 2023.01.29 |
[C++/Console] 테트리스 만들어 보기 - 1 (간단 소개 및 더블 버퍼링) (1) | 2023.01.28 |
[C++] ImGui - C++용 GUI Library (0) | 2023.01.07 |