티스토리 뷰

728x90

1.서론

이번 글에서는 더블버퍼링에 대한 간단한 개념과 구현방법에 대해 알아보겠습니다.

그리고 이를 활용하여 다음포스팅에서 간단한 지렁이게임 구현을 통해 활용하는법 까지 알아보겠습니다.

 

콘솔창에서 출력내용이 밀려나면서 지저분하게 출력되는것을 해결하기 위해 보통 system("cls")를 반본적으로 호출해

콘솔창 화면을 모두 지우고 새로 출력하는 방법을 채택합니다. 하지만 이 방법을 사용하게 되면 아주 빠르게 새로운 화면을 출력해야하는 게임 프로그램의 경우 화면 깜빡임이 발생합니다.

 

화면을 지우고 새로운 내용을 출력하는 동안 약간의 텀이 생기면서 눈으로 출력하는 순간을 인지하게 되기 때문이죠.

 

 이 문제를 해결하기 위해 더블버퍼링을 사용해야 합니다.

 

더블버퍼링이란?

화면을 구성할때 하나의 버퍼를 쓰는게 아니라 2개의 버퍼를 쓰는것입니다. 위에서 말했던 방법은 하나의 화면에서 쓰고 지우고를 반복한다면 더블버퍼링을 적용했을 때는 하나의 화면에 내용을 출력하고 게임 1프레임이 지나가는 동안 다른 화면에 다음 프레임 화면을 출력하고있다가 다음프레임때 미리 구성된 화면을 사용자가 볼 수 있는 화면으로 교체하는것입니다.

 

이렇게 할 경우 한순간에 화면만 교체 되므로 지웠다가 다시 출력하는 과정을 또다른 화면에서 진행하기 때문에 화면 깜빡임이 발생하지 않습니다.

 

2. 구현

더블버퍼링 구현은 윈도우 API를 활용합니다. 콘솔과 관련된 몇가지 함수를 사용하면 쉽게 구현 할 수 있습니다.

static int g_nScreenIndex;//콘솔 접근 인덱스
static HANDLE g_hScreen[2];//콘솔2개
int g_numOfFrame;//누적 프레임
int g_numOfFPS;//분당 프레임

char* FPSTextInfo;//콘솔에 출력할 char 포인터

더블 버퍼링을 위한 전역변수들입니다.

void ScreenInit()
{
	CONSOLE_CURSOR_INFO cci;

	g_hScreen[0] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	g_hScreen[1] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);

	// 커서를 숨긴다.
	cci.dwSize = 1;
	cci.bVisible = FALSE;
	SetConsoleCursorInfo(g_hScreen[0], &cci);
	SetConsoleCursorInfo(g_hScreen[1], &cci);
}

더블버퍼링을 위한 콘솔 2개를 준비하는 함수 입니다.

 

void ScreenFlipping()
{
	SetConsoleActiveScreenBuffer(g_hScreen[g_nScreenIndex]);
	g_nScreenIndex = !g_nScreenIndex;
}

화면에 보여줄 콘솔을 스위칭 하는 함수입니다.  c++에서 0이 아닌 값은 true로 여겨지기 때문에 !연산자를 통해

g_nScreenIndex의 값을 0과1을 반복하면서 바꿔주고 해당 인덱스를

 

g_hScreen[g_nScreenIndex];

식으로 사용하면서 2개의 콘솔을 번갈아가면서 사용해줍니다.

 

void ScreenClear()
{
	COORD Coor = { 0, 0 };
	DWORD dw;
	FillConsoleOutputCharacter(g_hScreen[g_nScreenIndex], ' ', 80 * 40, Coor, &dw);
}

스크린을 빈공간으로 지우는 함수입니다. 현재 콘솔을 다쓰고나면 다음 콘솔로 바꿔주면서 이전 콘솔의 내용은 깨끗하게 지우고 다시 출력하기 위해 사용합니다.

 

void ScreenPrint(int x, int y, char* string)
{
	DWORD dw;
	COORD CursorPosition = { x, y };
	SetConsoleCursorPosition(g_hScreen[g_nScreenIndex], CursorPosition);
	WriteFile(g_hScreen[g_nScreenIndex], string, strlen(string), &dw, NULL);
}

char *포인터 버퍼의 내용을 콘솔에 출력하는 함수입니다. x,y는 출력을 시작할 콘솔의 위치입니다.

콘솔의 좌표는 좌상단을 0,0을 기준으로 합니다.

 

void Render()
{
	ScreenClear();
	if (CurTime - OldTime >= 1000) // 출력 코드
	{
		OldTime = CurTime;
		g_numOfFPS = g_numOfFrame;
		g_numOfFrame = 0;
	}
	sprintf_s(FPSTextInfo, BUF_SIZE, "FPS : %d , deltaTime : %d\n", g_numOfFPS, deltaTime);
	ScreenPrint(0,0, FPSTextInfo);
	g_numOfFrame++;
    
	ScreenFlipping();
}

위에서 만든 함수들을 적용해 콘솔출력을 직접적으로 하는 부분입니다.

처음에 다 사용한 콘솔은 ScreenClear()를 통해 다 지우고 그다음 새로운 내용을 출력합니다.

curTime과 OldTime은 메인함수의 무한루프문을 먼저 보시고 설명하겠습니다.

 

int main(void) {
	FPSTextInfo = new char[BUF_SIZE];
	memset(FPSTextInfo, '\0', sizeof(char)*BUF_SIZE);
	ScreenInit();
	OldTime = clock();

	while (true) {
		CurTime = clock();
		Render();
	}
	return 0;
}

무한루프를 통해 지속적으로 콘솔에 내용을 출력합니다.

clock()은 현재 시간을 구하는 함수이며 time.h를 include 해야합니다.

먼저 버퍼 역할을할 char 포인터를 초기화하고 ScreenInit()을 통해 2개의 콘솔을 생성해줍니다.

그리고 무한루프 안에서 CurTime에 현재시간을 계속해서 갱신해주고 Render()를 호출합니다.

 

Render함수에서 CurTime이 계속 갱신되면서 OldTime과 1초(1000미리세컨)이상 차이날 경우

초당 프레임(g_numOfFPS)의 값을 바꿔주고 누적 프레임(g_numOfFrame)을 0으로 초기화 합니다.

OldTime과 CurTime이 1초이상이 아닐경우에는 지속적으로 누적 프레임(g_numOfFrame)의 값을 1씩 증가합니다.

즉, while문이 한번 돌때마다 프레임1이 증가하게 되고 이를 1초마다 나누어 초당 프레임을 계산하게 됩니다.

 

콘솔에 출력할 내용은 sprintf_s() 를 통해 char 포인터의 값을 채웁니다.

첫번째 인자는 버퍼 역할을 하는 char 포인터, 두번째 인자는 버퍼의 크기(저는 #define을 통해 128로 설정) 3번째 인자는 char 포인터의 값이될 문자열, 그 뒤로는 printf를 사용할때 처럼 출력 내용을 결정해주는 변수들로 채워줍니다.

 

char * 버퍼에 콘솔에 출력할 내용을 먼저 채운후 ScreenPrint()를 통해 콘솔에 최종 출력하는 작업을 하게 됩니다.

그리고 출력을 완료한뒤에 최종적으로 ScreenFlipping()을 호출하여 내용을 채운 콘솔을 사용자가 보게 될 전면 콘솔로 스위칭 해줍니다. 그러면 기존에 화면에 나오던 콘솔은 다시 뒤로가서 기존 내용을 모두 지우고 새로운 내용을 출력하는 작업을 수행합니다.

 

여기까지 기본적인 더블버퍼링을 사용하는 방법에 대해 알아보았습니다. 다음은 더블버퍼링을 활용해 간단한 지렁이게임을 만들어보겠습니다

 

댓글을 통한 질문 환영합니다. 질문 남겨주시고 더 빠른 답변을 원하시면 오픈 채팅방을 이용해주세요.

 

도움이 되셨으면 좋아요버튼 부탁드립니다~!

 

오픈채팅방 주소 : open.kakao.com/o/gDlTpJpc

댓글