콘솔 게임이나 애니메이션 효과를 낼 때 깜박임을 없애는 방법에 대해 얘기해보려 합니다.
[깜박임의 발생 원인]
- system("cls") 명령어를 통해 콘솔의 내용을 지우고 다시 쓰고 하는 방식을 사용할 때
- 화면을 까맣게 지웠다 새로 썼다 하므로 깜박임이 발생
[깜박임 없애는 방법]
1. gotoxy() 함수를 사용해 출력 좌표를 고정시킨 뒤 같은 내용을 반복 출력
- 기존 화면을 지우지 않고 덮어쓰기 출력을 함으로써 깜박임을 최소화
- 출력 사이즈가 커지거나 반복 횟수가 늘어나면 여전히 깜박임 발생
2. 더블 버퍼링 사용
- 일단 다른 곳에 출력을 먼저한 뒤, 현재 화면과 빠르게 바꿔치기 함
- 현재 대부분의 그래픽 인터페이스에서 사용하는 방식
위 두 가지 중 더블 버퍼링 사용에 대해 알아보겠습니다.
인터넷에 나오는 방법들을 찾아보다보니, 대부분 더블 버퍼링을 윈도우 API 함수를 통해 구현하고 있네요. 일단 꽤나 길고 어려운 방식이라.. 굳이 이렇게까지 해야하나 싶었습니다. 더블 버퍼링의 기본 개념은 아래와 같습니다.
물론 상세한 구성은 위 그림보다 훨씬 복잡하겠지만 대충 개념은 위와 같습니다. 요약하면 바로 콘솔에다가 변경사항을 출력하는것이 아니라 현재 보여지고 있는 앞장(front_buffer)은 그대로 둔 상태에서 뒷장(back_buffer)에 미리 그림을 그려두고 눈이 알아채지 못할만한 빠른 속도로 두 그림을 바꿔주는 것이죠. 까만화면 자체가 중간에 나올일이 없으니 당연히 깜박임이 발생하지 않습니다.
저렇게 화면 자체를 그림 같이 만들어서 교체해 주는 방식이 윈도우 API 함수를 통한 더블 버퍼링인 것으로 보입니다. 하지만 정말 C언어로 콘솔에서 게임이나 움직이는 무언가를 계속 만들어 나갈게 아니라면 굳이 이렇게까지 윈도우 API 함수를 깊게 파고 드는 것도 시간낭비가 아닌가 하는 생각이 들어서 조금 다른 방식으로 해결해 보았습니다. 아래 미로 찾기 글을 보시면 깜박임도 해결이 가능한 것을 볼 수 있습니다.
2019/12/09 - [C언어/알고리즘 및 자료구조] - 큐 구조_미로찾기 (최종 ver) [3/3]
제가 선택한 더블 버퍼링 방식은 윈도우 API 함수 없이(물론 커서 없애기나 gotoxy()함수는 윈도우 API를 사용하긴 함), 하나의 배열을 더 사용하는 방식입니다. 아래의 구성과 같이, 다음번에 출력해야할 내용을 기존에 출력된 내용과 비교해서 바뀐 부분만 출력해주는 것입니다.
- 원본 배열과 똑같은 사이즈의 buffer 배열을 하나 만들어 줌
- 원본배열이 변경되어 출력할 때가 되면 기존에 출력했던 내용인 buffer 배열과 비교
- 바뀐 부분만 화면에 출력해줌
- 원본배열을 buffer 배열로 복사해줌
아래와 같은 코드로 간단히 작성이 가능하고, 위의 링크된 글을 보시면 깜박임이 사라진 걸 확인할 수 있습니다. 해당 배열의 위치는 곧, 출력해야할 좌표를 의미하니 배열의 위치를 gotoxy()의 함수로 전달해주면 됩니다.
배열은 순서가 "array[세로축][가로축]" 이고, 좌표는 "gotoxy(가로축, 세로축)"이기 때문에 반대로 해줘야 합니다. 물론 gotoxy()함수를 커스터마이징 할 수있다면 같은 축으로 맞춰줄 수도 있겠네요.
사실 버퍼를 하나만 사용하고 있기 때문에 더블 버퍼링이라고 하기에는 좀 무리가 있긴 하지만 기본 원리는 똑같습니다. 굳이 두 개를 사용할 필요가 없어서 하나만 사용했네요. 콘솔 게임 정도는 복잡하게 윈도우 API 함수를 통한 더블 버퍼링을 사용하는 것 보다, 버퍼 배열을 하나만 더 추가해서 출력함수를 만드는게 더 간단하고 효율적이지 않을까 생각해봅니다.
/* 버퍼로 받아서 출력 */
/* parameter : 미로배열 */
/* return : void */
void buffer_print(char maze[][SIZE])
{
static char front_buffer[SIZE][SIZE] = { ' ' };
/* 현재 미로와 front_buffer(이전 미로)에 있는 미로 비교 */
for (int i = 0; i < SIZE; ++i)
for (int j = 0, j2 = 0; j < SIZE; ++j)
{
if (front_buffer[i][j] != maze[i][j])
{
maze_print(j2, i, maze[i][j]);
// 바뀐 부분 화면에 출력
front_buffer[i][j] = maze[i][j];
// 바뀐 부분은 출력 후 front_buffer에 저장
}
j2 += 2;
// 2byte(두칸)짜리로 출력하기 때문에 x 좌표는 2칸씩 이동시켜야함
}
}
/* 한글자 출력 */
/* prarameter : 좌표 값, 바꿀 문자 */
/* return : void */
void maze_print(int j, int i, char c)
{
gotoxy(10 + j, 5 + i); // 한줄씩 시작지점을 아래로
switch (c)
{
case 48:
printf("□");
break;
case 49:
printf("■");
break;
case 50:
printf("⊙");
break;
case 51:
printf("▩");
break;
case 52:
printf("↘");
break;
case 53:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6);
printf("★");
break;
}
gotoxy(10 + SIZE, 5 + SIZE);
}
'▸C언어 > 개발 TIP' 카테고리의 다른 글
조건문(if)과 증감연산자(i++) 사용시 주의사항 (0) | 2019.12.05 |
---|---|
삼항연산자(3항연산자) 사용법 (if문 대체) (0) | 2019.12.05 |
atoi() 함수 없이 문자열의 숫자를 int타입으로 변환 (0) | 2019.12.05 |
Debug 모드에서 sprintf_s() 함수 사용 시 주의사항 (0) | 2019.12.05 |
댓글