유튜브 동영상 주소
(1) https://youtu.be/UVDL7GcfyxQ
(2) https://youtu.be/KOYVL8zBMkg
(3) https://youtu.be/CmanWPPZOXk
(4) https://youtu.be/cABpLNyQtCA
(5) https://youtu.be/ms6ps5rdrp4
(6) https://youtu.be/x_ZRhoVgaeE
5장. 슛 골인 게임
[학습 목표]
각 개체의 속성을 정의하고 프로그래밍할 수 있다.
중심 좌표와 클리핑 개념을 이해하고 캐릭터에 적용하여 프로그래밍할 수 있다.
전체 제작 로드맵을 보면서 단계별로 게임의 모듈을 완성할 수 있다.
단계별로 제작한 소스를 이용하여 전체 프로그램을 완성할 수 있다.
01 기획
02 실행 화면
03 게임 제작 로드맵
04 단계별 프로그래밍
01 기획
스토리
제한 시간 안에 목표한 공을 넣어라.
게임 방식
왼쪽 키는 j, 오른쪽 키는 l, 슛 키는 k이다. 스테이지마다 정해진 목표의 골인 수가 있으며 제한 시간 안에 골인을 완료해야 한다.
제한 사항
공은 한 번에 한 개씩 슛할 수 있고 슛한 공이 소멸될 때까지 새로운 공이 생성되지 않는다. 게임 난이도는 스테이지마다 골대의 길이와 공의 속도로 조절한다.
기획 화면
[그림 5-1] 슛골인 게임 기획 화면
02 실행 화면
[그림 5-2] 게임 시작 화면
[그림 5-3] 스테이지 화면
[그림 5-4] 게임 실행 화면
[그림 5-5] 골인 축하 화면
[그림 5-6] 미션 성공 화면
[그림 5-7] 미션 실패 화면
03 게임 제작 로드맵
제작 로드맵은 게임을 개발하기 위한 전체 설계도와 같다.
제작 로드맵의 각 단계는 전체 게임을 개발하는 과정을 부분적으로 나눈 것으로 각 단계에서 만들어지는 각종 프로그램들은
전체 프로그램을 구성하게 된다.
프로그램 개발에 있어서 가장 중요한 부분은 설계이다.
설계는 전체 프로그램을 무엇을 어떻게 만들어야 하는지에 대한 전체 과정을 볼 수 있는 역할을 하며 아래의 제작 로드맵은
그와 같은 역할을 한다.
그러면 이제 단계별로 프로그래밍하면 전체 프로그램을 완성해 보자.
[STEP 01]
[STEP 02]
[STEP 03]
[STEP 04]
[STEP 05]
[그림 5-8] 게임 제작 로드맵
04 단계별 프로그래밍
STEP 01
[그림 5-9] 1단계 제작 로드맵
■ 주인공
속성 정의
아래 [그림 5-10]의 게임 진행 화면 속에 있는 주인공을 표현하기 위해서는 어떠한 데이터가 필요할까?
기본적으로 주인공은 키보드 입력에 따라 좌우로 이동해야 하므로 좌표가 필요하다.
[그림 5-10] 게임 진행 화면
그리고 이 게임에서 주인공은 적 캐릭터로부터 공격을 받지 않으므로 생명과 같은 속성은 필요하지 않다. 결국 주인공의 속성은
좌표만 있으면 되며 이 좌표를 이용하여 출력하게 된다.
주인공 캐릭터에게 좌표는 두 가지 의미를 가진다. 첫째는 이동 좌표로써의 의미로 ‘주인공 캐릭터를 어디에 출력할 것인가?’ 이다.
둘째는 중심 좌표로써의 의미로 ‘캐릭터를 출력할 때 어디를 기준으로 출력할 것인가?’ 이다.
이와 같이 좌표를 이동 좌표와 중심 좌표로 나눠서 생각하는 이유는 주인공 캐릭터가 길이를 가지기 때문이다.
현재 주인공 캐릭터를 나타내는 특수문자는 한 문자당 가로로 두 컬럼의 길이를 가지므로 아래 [그림 5-11]의 경우는 총 6컬럼의
가로 길이를 가진다.
[그림 5-11] 특수 문자로 구성한 주인공 캐릭터
만약 이동 좌표만을 생각하고 [그림 5-12]와 같이 길이가 다른 두 개의 캐릭터를 같은 좌표에 출력하면 어떻게 될까?
[그림 5-12] 길이가 다른 주인공 캐릭터
실행된 결과는 다음 [그림 5-13] 과 같다.
[그림 5-13 ] 길이가 다른 캐릭터를 좌측으로 이동
왜 이런 결과가 나왔을까? 그것은 출력하는 좌표의 기준이 좌측 상단이기 때문이다.
이 캐릭터의 경우 중심이 머리이므로 길이가 다른 캐릭터라도 같은 좌표에 출력하면 머리가 겹쳐진 형태로 출력되어 같은 좌표에서
공을 슛할 수 있다.
중심 좌표를 기준으로 하지 않고 출력하는 경우에 위의 두 캐릭터를 최대한 좌측으로 움직여 공을 슛한다고 가정해 보자.
이때 팔이 긴 캐릭터를 아무리 좌측으로 이동시킨다고 하더라도 팔이 짧은 캐릭터와 같은 좌표에서 절대 슛 동작을 할 수 없다.
[그림 5-14] 중심 좌표를 고려하지 않고 출력한 경우
이 부분은 가로 세로 길이를 가지는 캐릭터를 출력할 때 더욱 큰 문제가 되는데 아래의 두 그림을 비교해 보자.
[그림 5-15] 출력 기준 좌표가 좌측 상단인 캐릭터를 같은 좌표에 출력한 결과
[그림 5-15]는 캐릭터를 출력할 때 왼쪽 위를 기준으로 출력하므로 크기가 다른 캐릭터에 같은 좌표를 지정하면 서로 다른 위치에
캐릭터가 출력된 것과 같이 보인다.
이렇게 되면 크기에 따라 이동 좌표가 전부 달라지므로 캐릭터마다 서로 다른 좌표계를 사용하는 결과가 된다.
이제 중심 좌표의 개념이 적용된 예를 살펴보자.
[그림 5-16] 중심 좌표인 중앙의 하단을 기준으로 같은 좌표에 출력한 결과
[그림 5-16]과 같이 크기가 다른 캐릭터라도 중심 좌표를 이동 좌표에 맞추고 출력 기준 좌표를 계산하면 크기가 다르다고 해도
같은 좌표에 캐릭터를 출력할 수 있다.
게임에서는 이와 같은 중심 좌표의 개념이 상당히 중요한 역할을 한다.
[그림 5-17] 좌표 개념
일반적으로 서로 다른 크기를 가지는 캐릭터를 출력하기 위해 중심 좌표를 적용할 때는 아래와 같은 툴을 만들어서 설정한다.
[그림 5-18] 스프라이트 툴
이와 같은 사항을 적용하여 주인공 캐릭터의 속성을 정의하면 아래와 같다.
① 중심 좌표 ② 이동 좌표 ③ 출력 기준 좌표 |
[표 5-1] 주인공 속성
typedef struct _PLAYER { int nCenterX, nCenterY; int nMoveX, nMoveY; int nX, nY; } PLAYER; PLAYER g_sPlayer; |
[소스 5-1] 주인공 속성 정의
실제 출력 기준 좌표(nX, nY)는 아래와 같은 식에 의해 구할 수 있다.
( 이동 좌표 x - 중심 좌표 x, 이동 좌표 y - 중심 좌표 y ) |
[식 5-1] 출력 기준 좌표 식
현재 슛 골인 게임에서는 주인공 캐릭터가 모두 같지만 만약 각 동작마다 서로 다른 크기의 캐릭터 모양을 갖는다면
아래 [소스 5-2]와 같이 여러 개의 중심 좌표를 저장할 수 있도록 배열 또는 포인터의 형태로 정의할 수 있다.
typedef struct _POSITION { int nX, nY; } POSITION;
typedef struct _PLAYER { POSITION nCenter[10]; // 캐릭터 10개에 대한 중심 좌표 int nMoveX, nMoveY; // 이동 좌표 int nX, nY; // 출력 기준 좌표 int nIndex; // 중심 좌표 인덱스 } PLAYER;
PLAYER g_sPlayer; |
[소스 5-2] 10개의 중심좌표를 저장하는 주인공 속성 구조체
이동 및 키보드 처리
현재 이 게임은 주인공의 y 좌표가 고정된 상태에서 x 좌표에만 변화를 주어 좌우로 이동하는 게임이다. 좌우 이동 키는 j, l 키이며
x 좌표를 1씩 증감시켜 이동한다.
키보드에 대한 처리는 3장의 [소스 3-6]을 통해 앞서 살펴본 내용을 그대로 적용한다.
[실습 예제 5-1]
주인공 캐릭터를 j키와 l키의 입력에 따라 좌우로 이동하는 프로그램을 작성해 보자.
이때 주인공 캐릭터에 중심 좌표를 적용하여 출력하고 상단에는 아래 [표 5-2]와 같이 주인공 캐릭터의 이동 좌표를 계속적으로
출력하도록 한다.
주인공 이동 좌표 : x, y |
[표 5-2] 출력 정보
기본적인 게임 구조는 3장에서 작성한 프레임워크를 적용하며 이동 거리는 1컬럼으로 하지만 특수 문자는 2컬럼 단위로 이동하므로
[그림 5-19]에서 [그림 5-20과 [그림 5-21]이 될 때에는 2 컬럼씩 이동하도록 한다.
이와 같이 경계 영역에서 캐릭터의 일부를 출력하지 않는 기법을 클리핑(clipping)이라고 하며 3장의 게임 프로그래밍 용어에서
설명했었다.
이와 같은 부분을 오른쪽 경계 영역에도 동일하게 적용한다.
[그림 5-19] 주인공 캐릭터 출력
[그림 5-20] 클리핑 적용(1)
[그림 5-21] 클리핑 적용(2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | #include <stdio.h> #include <conio.h> #include <string.h> #include <stdio.h> #include "Screen.h" #include <windows.h>
typedef struct _PLAYER { int nCenterX, nCenterY; int nMoveX, nMoveY; int nX, nY; } PLAYER;
PLAYER g_sPlayer; char g_strPlayer[] = "┗━●━┛"; // 주인공 캐릭터 int g_nLength; // 주인공 캐릭터 전체 길이 // Note: 초기화 void Init() { g_sPlayer.nCenterX = 4; // 주인공 캐릭터의 중심 좌표 g_sPlayer.nCenterY = 0; // 주인공 캐릭터의 중심 좌표 g_sPlayer.nMoveX = 20; // 주인공 캐릭터의 이동 좌표 초기화 g_sPlaye.nY = g_sPlayer.nMoveY = 22; // 주인공 캐릭터의 이동 좌표 초기화 g_sPlayer.nX = g_sPlayer.nMoveX - g_sPlayer.nCenterX; // 주인공 캐릭터의 출력 기준 좌표 g_nLength = strlen( g_strPlayer ); // 주인공 캐릭터의 전체 길이 } // Note: 데이터 갱신 void Update() { } // Note: 출력 void Render() { char string[100] = { 0, }; ScreenClear();
// Note: 렌더링 시작 // Note: 2 컬럼씩 클리핑 if( g_sPlayer.nX < 0 ) // 왼쪽 클리핑 처리 ScreenPrint( 0, g_sPlayer.nMoveY, &g_strPlayer[g_sPlayer.nX*-1]); else if( g_sPlayer.nMoveX + (g_nLength - g_sPlayer.nCenterX + 1) > 79 ) { strncat( string, g_strPlayer, g_nLength - (( g_sPlayer.nMoveX + g_sPlayer.nCenterX + 1) - 79 ) ); ScreenPrint( g_sPlayer.nX, g_sPlaye.nY, string ); }else{ // 1 컬럼씩 이동 ScreenPrint( g_sPlayer.nX, g_sPlaye.nY, g_strPlayer ); }
sprintf( string, "주인공 이동 좌표 : %d, %d", g_sPlayer.nMoveX, g_sPlaye.nY ); ScreenPrint( 0, 0, string );
// Note: 렌더링 끝 ScreenFlipping(); }
void Release() { }
int main(void) { int nKey, nRemain;
ScreenInit(); Init(); // 초기화
while( 1 ) { if( _kbhit() ) { nKey = _getch(); if( nKey == 'q' ) break; switch( nKey ) { case 'j' : g_sPlayer.nMoveX--; // 전체 길이 - ( 중심 좌표 + 1 )은 남은 길이 nRemain = g_nLength - (g_sPlayer.nCenterX + 1); // 2컬럼씩 이동하기 위한 부분 ( 팔이 걸친 경우 ) if( g_sPlayer.nMoveX - g_sPlayer.nCenterX < 0 || g_sPlayer.nMoveX + nRemain > 79 ) g_sPlayer.nMoveX--; g_sPlayer.nX = g_sPlayer.nMoveX - g_sPlayer.nCenterX; break; case 'l' : g_sPlayer.nMoveX++; // 전체 길이 - ( 중심 좌표 + 1 )은 남은 길이 nRemain = g_nLength - (g_sPlayer.nCenterX + 1); // 2컬럼씩 이동하기 위한 부분 ( 팔이 걸친 경우 ) if( g_sPlayer.nMoveX + nRemain > 79 || g_sPlayer.nMoveX - g_sPlayer.nCenterX < 0 ) ) g_sPlayer.nMoveX++; g_sPlayer.nX = g_sPlayer.nMoveX - g_sPlayer.nCenterX; break; } }
Update(); // 데이터 갱신 Render(); // 화면 출력 }
Release(); // 해제 ScreenRelease(); return 0; } |
[소스 5-3] 전체 소스
24행은 이동 좌표 y와 출력 좌표 y를 초기화 하는 부분이다.
현재 주인공 캐릭터의 세로 길이가 1 컬럼이고 중심 좌표 y가 0이므로 이동 좌표 y와 출력 기준 좌표 y는 같다.
그래서 24행과 같이 초기화를 하고 있다.
25행은 캐릭터의 중심 좌표와 이동 좌표를 통해 출력 기준 좌표를 계산하고 있다.
40행에서 49행은 주인공 캐릭터의 출력 기준 좌표가 경계 영역을 넘었는지 조사하여 클리핑 적용 여부를 판단하는 부분이다.
클리핑은 주인공 캐릭터의 특수 문자를 저장하고 있는 g_strPlayer[] 배열의 인덱스를 이용하면 쉽게 할 수 있다.
예를 들어 왼쪽 클리핑의 경우는 g_strPlayer[] 배열의 출력할 시작 인덱스를 조정하면 된다.
41행에 &g_strPlayer[g_sPlayer.nX*-1]과 같이 되어 있는 것은 왼쪽 클리핑의 시작은 g_sPlayer.nX가 음수가 될 때 시작되고
이 좌표 값에 -1을 곱하면 좌표가 2, 4 등이 된다.
이 값은 g_strPlayer[]의 시작 인덱스로 사용하면 일정 부분에서부터 출력되므로 클리핑과 같은 효과를 가져온다.
왼쪽 클리핑의 경우 특수 문자 ●는 [그림 5-22]와 같이 왼쪽이 중심이므로 이동 좌표 x가 0이면 [그림 5-23]과 같이 왼쪽 경계
영역에 닿게 되지만 이동 좌표 x가 78일 때는 특수 문자 ●는 오른쪽 경계 영역에 닿게 된다.
왜냐하면 특수 문자는 2컬럼이므로 아래 [그림 5-22]의 ‘중심 좌표 + 1’ 되는 컬럼이 오른쪽 경계 영역에 먼저 닿기 때문이다.
[그림 5-22] 특수 문자 ●
[그림 5-23] 경계 영역과 클리핑(clipping)
44행의 strncat() 함수는 오른쪽 경계 영역에 주인공 캐릭터가 닿았을 때 출력하려는 문자를 일부 복사하기 위한 함수로 사용한다.
이와 같이 해주는 이유는 문자열의 끝은 널문자로 구분되기 때문이다.
이때 복사 영역을 결정해야하는 데 복사 영역은 g_nLength - (( g_sPlayer.nMoveX + g_sPlayer.nCenterX + 1) - 79 )에 의해
구할 수 있다.
이 계산에서 g_sPlayer.nMoveX + g_sPlayer.nCenterX + 1 은 최우측 좌표를 구하게 된다. 여기에 79을 빼주면 경계 영역을
벗어난 컬럼을 구할 수 있다.
이 컬럼만큼 주인공 캐릭터의 전체 길이에서 빼주면 출력하기 위한 주인공 캐릭터의 최대 인덱스를 구하게 되며 이 인덱스를
이용하여 주인공 캐릭터의 일부를 복사하게 된다.
81행과 91행의 nRemain 변수는 중심 좌표의 이후의 길이를 구한 변수이다.
이 값은 84행과 93행에서 주인공 캐릭터의 최우측 좌표가 경계 영역에 닿았는지를 조사하기 위해 이동 좌표에 이 값을 더한다.
83행과 84행, 93행과 94행을 보면 같은 조건식임을 알 수 있다.
하지만 조건식의 우선순위가 서로 반대로 되어 있는 것은 왼쪽 이동일 때는 최우선적으로 조사해야 하는 부분은 왼쪽 경계 영역에
닿을 가능성이므로 이 부분을 조사하는 식이 먼저 오는 것이며 93행과 94행은 그 반대이므로 같은 조건식임에도 불구하고 조건의
우선순위를 다르게 배치한 것이다.
■ 공
속성 정의
전체 흐름과 개체 제어를 동기화하는 방법으로 시간을 사용한다.
이 방법을 간단히 요약하면 시스템으로부터 시간을 밀리세컨드(millisecond) 단위로 읽어 현재 시각과 이전 이동 시각의 차이로
이동과 진행을 결정하는 방법이다.
속성을 정의하기 전에 이와 같은 설명을 하는 것은 공은 키보드 입력에 따라 이동하는 것이 아니라 스스로 이동하기 때문이다.
그러면 이제 공의 속성을 정의하여 보자.
첫째는 공 상태이다.
공 상태는 슛 상태와 준비 상태가 있다.
슛 상태는 공이 아래에서 위를 향해 이동하는 상태를 말하며 준비 상태는 주인공 캐릭터가 공을 잡고 있는 상태를 말한다.
준비 상태에서 공을 주인공 캐릭터가 잡고 있는 것처럼 출력하기 위해서는 주인공 캐릭터와 공의 이동 좌표가 같으면 된다.
둘째는 이동 좌표이다.
이동 좌표는 실제 공이 출력되는 기준 좌표가 된다.
왜냐하면 공에는 중심 좌표의 개념이 없기 때문이다.
셋째는 이동 시간 간격이다.
이 속성은 이동을 언제 할 것인지를 결정하는 부분으로 이동 속력과도 연관된다.
속력은 단위 시간당 이동 거리이므로 이 속성은 단위 시간에 해당이 된다.
각 스테이지마다 골대의 길이와 공의 이동 시간 간격에 변화를 주면 각 스테이지를 다양하게 구성할 수 있다.
넷째는 이전 이동 시각이다.
현재 이동을 했다면 이 순간부터 다음 이동까지의 시간 차이를 측정해야 하며 이 시간을 경과 시간이라고 한다.
모든 개체의 이동 여부는 이 경과 시간과 이동 시간 간격을 비교하여 결정한다. 이와 같은 과정을 적용하기 위해서는 현재의
이동 시간을 이전 이동 시간으로 저장하고 현재 시각을 계속 읽어 이전 이동 시간과 비교하여 이동 여부를 결정하면 된다.
현재 이 속성은 시간차를 이용하는 모든 개체에 공통적으로 들어가는 속성중 하나이다.
지금까지 언급한 속성을 정리하면 다음과 같다.
① 공 상태 ② 이동 좌표 ③ 이동 시간 간격 ④ 이전 이동 시각 |
[표 5-3] 공 속성
typedef struct _BALL { int nIsReady; // 준비상태(1), 슛상태(0) int nMoveX, nMoveY; // 이동 좌표 clock_t MoveTime; // 이동 시간 간격 clock_t OldTime; // 이전 이동 시각 } BALL; |
[소스 5-4] 속성 구조체
이동 및 키보드 처리
키보드 처리 부분을 먼저 살펴보면 k 키가 입력되면 공은 자신의 상태 속성을 준비 상태에서 슛 상태로 바꿔 이동하게 된다.
앞의 게임 기획서의 제안사항을 보면 공은 연속적으로 슛할 수 있는 것이 아니라 한 번에 한 개씩 슛할 수 있다고 되어 있다.
이것은 공이 슛된 상태에서는 또 다른 공을 슛할 수 없다는 것이다. 그래서 키 입력 처리를 할 때는 슛 상태와 준비 상태를 먼저
파악하여 슛 상태에서 또 슛 동작이 되지 않게 해야 한다.
이동에 있어서 공이 준비 상태라면 이것은 주인공 캐릭터가 잡고 있는 상태이므로 주인공 캐릭터의 이동 좌표를 공의 이동 좌표에
대입하여 항상 같이 이동하도록 하면 된다.
만약 슛 상태라면 이동 시간 간격에 따라 y 좌표를 일정한 거리만큼 빼주면 공은 아래에서 위로 이동하게 된다.
[실습 예제 5-2]
[실습 예제 5-1]에서 작성한 소스에 추가적으로 k키가 입력되면 주인공 캐릭터가 공을 슛하는 프로그램을 작성해 보자.
단, 공이 슛 상태에서 공의 이동 좌표가 경계 영역 y 좌표 0과 같으면 충돌로 판단하고 공의 상태를 준비 상태로 바꾼다.
그리고 공의 초기 상태는 항상 준비 상태이다.
[그림 5-24] 준비 상태 출력
[그림 5-25] 슛한 상태 출력
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | #include <stdio.h> #include <conio.h> #include <string.h> #include <windows.h> #include <time.h> #include "Screen.h"
typedef struct _PLAYER { int nCenterX, nCenterY; int nMoveX, nMoveY; int nX, nY; } PLAYER;
typedef struct _BALL { int nIsReady; // 준비 상태(1), 슛상태(0) int nMoveX, nMoveY; // 이동 좌표 clock_t MoveTime; // 이동 시간 간격 clock_t OldTime; // 이전 이동 시각 } BALL;
BALL g_sBall; PLAYER g_sPlayer; char g_strPlayer[] = "┗━●━┛"; int g_nLength;
void Init() { g_sPlayer.nCenterX = 4; g_sPlayer.nCenterY = 0; g_sPlayer.nMoveX = 20; g_sPlayer.nMoveY = 22; g_sPlayer.nX = g_sPlayer.nMoveX - g_sPlayer.nCenterX; g_nLength = strlen( g_strPlayer );
// Note: 공의 초기화 --> 추가된 부분 g_sBall.nIsReady = 1; g_sBall.nMoveX = g_sPlayer.nMoveX; g_sBall.nMoveY = g_sPlayer.nMoveY - 1; g_sBall.MoveTime = 100; }
void Update() { // Note: 공의 이동 처리 --> 추가된 부분 clock_t CurTime = clock(); if( g_sBall.nIsReady == 0 ) // 이동 중일 때 { // 이동 시간 간격에 의한 이동 if( (CurTime - g_sBall.OldTime) > g_sBall.MoveTime ) { if( g_sBall.nMoveY - 1 > 0 ) { g_sBall.nMoveY--; // 다음 이동 시각과 비교하기 위해 현재 시간을 이전 시간 변수에 저장 g_sBall.OldTime = CurTime; }else{ g_sBall.nIsReady = 1; g_sBall.nMoveX = g_sPlayer.nMoveX; g_sBall.nMoveY = g_sPlayer.nMoveY - 1; } } }else{ g_sBall.nMoveX = g_sPlayer.nMoveX; } } }
void Render() { char string[100] = { 0, }; ScreenClear();
// Note: 렌더링 시작
// Note: 2 컬럼씩 클리핑 if( g_sPlayer.nX < 0 ) // 왼쪽 클리핑 처리 ScreenPrint( 0, g_sPlayer.nMoveY, &g_strPlayer[g_sPlayer.nX*-1]); else if( g_sPlayer.nMoveX + (g_nLength - (g_sPlayer.nCenterX + 1)) > 79 ) { strncat( string, g_strPlayer, g_nLength - (( g_sPlayer.nMoveX + g_sPlayer.nCenterX + 1) - 79 ) ); ScreenPrint( g_sPlayer.nX, g_sPlayer.nMoveY, string ); }else{ // 1 컬럼씩 이동 ScreenPrint( g_sPlayer.nX, g_sPlayer.nMoveY, g_strPlayer ); }
ScreenPrint( g_sBall.nMoveX, g_sBall.nMoveY, "⊙" ); sprintf( string, "주인공 이동 좌표 : %d, %d", g_sPlayer.nMoveX, g_sPlayer.nMoveY ); ScreenPrint( 0, 0, string );
// Note: 렌더링 끝 ScreenFlipping(); }
void Release() { }
int main(void) { int nKey, nRemain;
ScreenInit(); Init(); // 초기화
while( 1 ) { if( _kbhit() ) { nKey = _getch(); if( nKey == 'q' ) break;
switch( nKey ) { case 'j' : // [실습 예제 5-1]과 동일 break; case 'l' : // [실습 예제 5-1]과 동일 break; case 'k' : if( g_sBall.nIsReady ) { g_sBall.nMoveX = g_sPlayer.nMoveX; g_sBall.nMoveY = g_sPlayer.nMoveY - 1; g_sBall.OldTime = clock(); g_sBall.nIsReady = 0; } break; } }
Update(); // 데이터 갱신 Render(); // 화면 출력 }
Release(); // 해제 ScreenRelease(); return 0; } |
[소스 5-5] 공과 주인공을 이동하기 위한 전체 소스
47행부터 66행까지는 이동에 따른 좌표의 변화에 대한 내용들이다.
각 개체의 이동 동기화는 시간 차이를 이용하므로 현재 시각과 이전 시각의 시간 간격에 따라 이동이 결정된다.
50행은 이와 같은 내용을 코드로 옮긴 것이다.
참고로 50행의 조건을 아래와 같이 작성해도 같은 의미가 되는데 C언어에서는 연산자 우선순위가 있어서 아래와 같은 순서에 의해 계산되지만 괄호를 분명하게 하는 것이 좋다.
계산 순서 : ➀ → ➁ |
우선순위 | 연산자 |
1 | (), [], ->, . |
2 | sizeof, & ++, ==, ~, !, *(포인터연산자), +(부호), -(부호) |
3 | *, /, % |
4 | +, - |
5 | <<, >> |
6 | <, <=, >=, > |
7 | ==, != |
8 | & |
9 | ^ |
10 | | |
11 | && |
12 | || |
13 | ?: |
14 | =, *=, +=, /=, %=, &=, ^=, |=, <<=, =>> |
15 | , (콤마) |
[표 5-4] 연산자 우선순위
124행부터 130행은 공이 준비 상태일 때 k 키가 눌려지면 공을 초기화하는 부분이다.
공이 슛 상태일 때는 g_sBall.nIsReady가 0이므로 k키가 여러 번 눌려진다고 해도 이 조건식에 의해 공은 초기화 되지 않는다.