목차

1. 화면을 그린 후 지우고 다시 출력하면 깜빡이는 문제점

2. 게임 프로그래밍에서의 버퍼 개념

3. 페이지 전환을 통해 깜빡이는 문제 해결하기


1. 화면을 그린 후 지우고 다시 출력하면 깜빡이는 문제점

C언어게임은 콘솔창이므로 콘솔창에서는 고속으로 화면을 지우고 그리게 하는 부분이 없어 지우고 다시 그리는 과정에서 깜빡임이 발생하게 된다.

 

아래 구조는 간단하게 구조체를 하나 만들고 방향키로 캐릭터가 움직이도록 구현하였다.

#include <stdio.h>
#include<Windows.h>
void gotoxy(int x, int y)   //gotoxy api함수

{
	COORD pos = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

typedef struct _PLAYER
{
	int x, y; 
}PLAYER;

PLAYER player;

void init() {
	player.x = 20;
	player.y = 10;
}
void update() {
}
void render()
{
	system("cls");
	gotoxy(player.x, player.y);
	printf("♀");
}

void release() {
}

int main(void) {
	int nKey;
    
	init();

	while (1) {
		if (_kbhit()) { //키보드가 눌렸는지 체크 
			nKey = _getch(); //눌린값 대입
			if (nKey == 'q') //q를 눌렀다면 break
				break;
			else if (nKey == 224 || nKey == 0) {
				nKey = _getch();
			switch (nKey) {
			case 75: //왼쪽 방향키를 눌렀다면 
				player.x--; 
				break;
			case 77:  //오른쪽 방향키를 눌렀다면
				player.x++; 
				break;
			
			case 72:  //위쪽 방향키를 눌렀다면
				player.y--; 
				break;
			
			case 80:  //아래쪽 방향키를 눌렀다면
				player.y++; 
				break;
		}
			}
		}
		update();
		render();
	}
	release();
	return 0;
}

이동하기 전 위치의 문자를 보이지 않게 하기 위해 system("cls")로 지우고 다시 새로운 위치에 문자를 출력시키기 때문에

어쩔수없이 화면이 지워졌다가 다시 그려짐으로 인해서 화면이 깜빡이게 된다.

 

그렇다고 system("cls")를 해주지 않으면 다음과 같이 잔상이 남게 된다

 

 

이런 화면깜빡임은 보는 사람에게 너무 피로감을 느끼게 한다.  이런 문제를 해결하는 방법을 알아보자.


2. 게임 프로그래밍에서의 버퍼 개념

먼저 해결하는 방법을 알기 위해서는 게임 프로그래밍에서 쓰는 버퍼 개념을 알아야한다.

 

1) 전위 버퍼(primary buffer)

전위 버퍼를 전위면이라고도 하는데 화면과 일대일 대응되는 메모리르 ㄹ말하며 그래픽 카드의 메인 메모리의 일부분이다.

현재 모니터의 해상도를 변경하면 전위 버퍼에 해당되는 메모리도 변경된다.

 

2) 후위 버퍼(back buffer)

전위 버퍼와 동일한 특성을 가진 메모리를 말한다.

전위 버퍼는 모니터와 일대일 대응되므로 컴퓨터와 동작함과 동시에 그래픽 카드에 생성이 되지만 후위 법퍼는 따로 생성이 된다.

 

후위 버퍼는 전위 버퍼와 특성이 같으므로 전위 버퍼와 연결하여 사용하며 주로 시스템 메모리 보다는 그래픽 카드 메모리에 생성하여 빠른 화면 전환을 하기 위해 사용된다.

 

3) 이중 버퍼링(double buffering)

이중 버퍼링은 두 개의 버퍼를 이용하여 화면을 전환하는 방법을 말한다.

사용되는 두 개의 버퍼는 전위 버퍼와 후위 버퍼를 말하며 후위 버퍼의 내용을 전위 버퍼에 복사하여 출력하는 것을 말한다.

 

4) 페이지 전환(Page flipping)

이중 버퍼링에도 문제가 있어 오늘날 모든 게임에서 사용되는 방식이 바로 페이지 전환 방식이다.

실제로 전위 버퍼와 후위 버퍼간의 메모리 복사가 아닌 화면과 일대일 대응하는 메모리의 시작주소를 바꾸는 방식이다.

 

즉, 전위 버퍼를 쓰고 있다가 메모리의 시작 주소를 후위 버퍼로 바꿔서 후위 버퍼를 보여줬다가 다시 시작 주소를 바꿔서 전위 버퍼를 보여줬다가 번갈아가며 출력하게 하는 것이다.


3. 페이지 전환을 통해 깜빡이는 문제 해결하기

위에 봤던 간단한 예제를 버퍼 예제로 바꿔보자.

 

일단 기본 구조는 다음과 같다.

잘 모르겠다면 아래 링크를 잠깐 갔다 오자.

C언어로 게임만들기 1탄 - 게임의 기본구조 :: 잡코딩 정보 블로그 (tistory.com)

 

일단 코드를 복붙하고 따라해보자.

총 3개의 파일이 필요하다

1) main.c

#include <stdio.h>
#include<Windows.h>
#include "Screen.h"

typedef struct _PLAYER
{
	int x, y; //출력기준좌표
}PLAYER;

PLAYER player;

void init() {
	player.x = 20;
	player.y = 10;
}
void update() {
}
void render()
{
	   ScreenClear(); //대기하고 있는 화면 버퍼를 지운다.
	  ScreenPrint(player.x, player.y, "♀"); //(player.x, player.y) 좌표에 문자를 출력한다.
		ScreenFlipping(); //활성화된 화면 버퍼와 비활성화된 화면 버퍼의 상태를 바꾼다
}

void release() {
}

int main(void) {
	int nKey, nRemain;

	ScreenInit();//버퍼를 2개 생성한다.
	init();

	while (1) {
		if (_kbhit()) { //키보드가 눌렸는지 체크 
			nKey = _getch(); //눌린값 대입
			if (nKey == 'q') //q를 눌렀다면 break
				break;
			else if (nKey == 224 || nKey == 0) {
				nKey = _getch();
			switch (nKey) {
			case 75: //왼쪽 방향키를 눌렀다면 
				player.x--; 
				break;
			case 77:  //오른쪽 방향키를 눌렀다면
				player.x++; 
				break;
			
			case 72:  //위쪽 방향키를 눌렀다면
				player.y--; 
				break;
			
			case 80:  //아래쪽 방향키를 눌렀다면
				player.y++; 
				break;
		}
			}
		}
		update();
		render();
	}


	release();
	ScreenRelease(); //화면 버퍼 초기화 함수에서 생성한 두 개의 화면 버퍼를 모두 해제한다.
	return 0;
}

 

2) Screen.c (작명은 아무렇게나 해도 상관없다)

#include <stdio.h>
#include<Windows.h>

static int g_nScreenIndex;
static HANDLE g_hScreen[2];

void ScreenInit()
{
    CONSOLE_CURSOR_INFO cci;

    //화면 버퍼 2개를 만든다.
    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);
}

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

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

void ScreenRelease()
{
    CloseHandle(g_hScreen[0]);
    CloseHandle(g_hScreen[1]);
}

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);

}

 

3) Screen.h (작명은 아무렇게나 해도 된다)

void ScreenInit();
void ScreenFlipping();
void ScreenClear();
void ScreenRelease();
void ScreenPrint(int x, int y, char* string);
void SetColor(unsigned short color);

 

위에 깜빡이는 예제와 거의 똑같은데 다른 것은 윈도우 API함수가 들어간 것이다.

파일이 준비돼었으니 차근차근 보도록하자.

 

Screen.c 코드는 이해할필요없다. 그냥 가져다 쓰면 된다.

 

<1> ScreenInit()로 전위 버퍼와 백 버퍼로 사용할 화면 버퍼를 두 개 생성한다.

 

<2> ScreenClear()로 하나의 화면 버퍼가 활성화되어 출력되고 있는 동안에 다음 장면을 위한 화면 버퍼는 지운다.

 

 

<3>  ScreenPrint()로 대기 화면 버퍼의 x, y 좌표에 문자열을 출력해준다.

전위버퍼/후위버퍼

 

<4> ScreenFlipping()로 활성화된 화면 버퍼와 비활성화된 화면 버퍼의 상태를 바꾼다.

전위버퍼/후위버퍼

 

아주 빠른 속도로 일어나서 그냥 이동하는 것 처럼 보이지만 이런식으로 빠르게 바꿔치기 돼서 화면에 보이게 된다.

이렇게 코드를 짜면 아래와 같이 깜빡이지 않는 편안함을 느낄 수 있다


참고문헌

C를 이용한 게임프로그래밍-이태성

 

 

 

+ Recent posts