초모롱마
GNEX 환경에서 개발한 아케이드 장르의 타임 어택 모바일 게임
| 구분 | 내용 |
|---|---|
| 플랫폼 | GNEX |
| 개발 도구 및 사용 언어 | GNEX SDK, Mobile C |
| 개발 기간 | 2004년 11월 ~ 2004년 12월 |
1. 프로그램 소개
각 스테이지의 목적지인 꼭대기 까지 최대한 빠른 시간내에 도달하는 게임.
에베레스트 산 등정을 소재로 하여 유명한 산악인이었던 어빙을 모티브로 하였다.
게임은 크게 시나리오 모드와 아케이드 모드로 나누어 진다.
아케이트 모드는 실제 게임 플레이를 하는 종스크롤 아케이드 게임 모드이고, 시나리오 모드는 다음 스테이지로 넘어가기 위한 시나리오 진행을 위한 RPG 모드이다.
로고 화면
당시 학교 친구들 사이에서 전작인 이니셜B에 대한 반응이 나쁘지 않아 친구들을 좀더 웃겨 보려고 만든 로고다.
만화 이니셜D의 로고를 페러디 한 것이다.
프롤로그 화면
1
2
3
위대한 대자연의 상징 에베레스트.
1953년 5월 29일 11시 에드먼드 힐러리에 의해 인류는 에베레스트 등정 시대를 맞이하게 된다.
게임 메뉴 화면
메뉴
설정
도움말
게임 설명
조작 방법
시나리오 모드 화면
시나리오 모드는 RPG 형식으로 진행 된다.
맵에 배치되어 있는 NPC를 찾아 대화를 해야만 다음 스테이지로 넘어갈 수 있다.
아케이드 모드 화면
단순히 스테이지 별로 게임을 플레이 하는 모드다.
매번 스테이지를 클리어 할 때마다 다시 그 다음 스테이지를 위한 시나리오 모드로 진입하게 된다.
해당 스테이지를 클리어하면 또다시 그 다음 스테이지를 위한 시나리오 모드로 진입 하게 되는데 게임은 이렇게 시나리오 모드와 아케이드 모드를 번갈아 가며 진행 되며, 모든 스테이지를 클리어하면 엔딩 화면으로 넘어 가게 된다.
엔딩 화면
1
2
3
4
5
6
7
1921년과 1922년 연이은 두 번의 실패 이후 3번째 도전에 나선 말로리에게 당대의 세인들이 물었다.
"도대체 무엇 때문에 그렇게 에베레스트에 오르려고 합니까?"
이때 말로리는 그에 대한 대답으로 유명한 말을 남겼다.
“산이 거기 있어 산을 오른다”
THE END
2. 프로그램 구조 개요
프로그램은 이니셜B와 같이 switch-case 문을 통해 프로그램의 상태를 변경하는 방식으로 실행 된다.
A. 게임 모드 변경
게임은 시나리오 모드와 아케이드 모드를 번갈아 가면서 진행하는 구조이다.
그리고 실제로는 이 두 모드 외에도 인트로와 메뉴, 엔딩, 게임 오버 또한 게임 모드 값이다.
어찌되었든 시나리오 모드는 일종의 RPG 모드이고 아케이드 모드는 수직 스크롤 아케이드 게임 모드이기 때문에 조작 방법 등이 다르다.
그렇기 때문에 키 처리 함수와 충돌체크의 일부분이 다르게 적용 되는데 이를 위해 2개의 상태 값을 갖는 변수를 선언해 두고 키 처리 함수와 충돌 체크 함수가 내부에서 그 흐름이 2개로 분기 하도록 구현 되었다.
B. 주요 자료 구조들
1) OBJECT 구조체
1
2
3
4
5
struct OBJECT {
int nImgNum;//이미지 번호
COORD coord;//오브젝트 출력 좌표
int nEmergScrollNum;//출현 위치
};
플레이어 캐릭터가 밟고서 점프 할 수 있는 얼음 덩어리와 시나리오 모드에 나오는 NPC에 대한 정보를 가지고 있다.
2) STAGE 구조체
1
2
3
4
5
struct STAGE {
OBJECT object[50];//해당 스테이지에 등장하는 얼음 덩어리 및 NPC 정보
int nMaxScroll;//목표 스크롤 맵 번호, 몇장의 스크롤 맵으로 구성되어 있는가를 뜻한다.
int nTimeLimit; //해당 스테이지의 제한 시간
};
스테이지의 정보를 가지고 있으며 다음과 같이 게임에서 제공되는 스테이지의 최대 개수 만큼 배열로 묶인다.
1
2
#define MAX_STAGE 5
STAGE Stage[MAX_STAGE];
3) CURRENT_STAGE 구조체
1
2
3
4
5
6
7
struct CURRENT_STAGE {
int nScrollCnt;//현재 화면 스크롤 횟수 0:스테이지 가장 처음 화면, 1:스테이지 2번째 화면
int nStageNum;//현재 진행중인 스테이지 번호
OBJECT* pObject;//현재 충돌체크 및 그려야 하는 오브젝트들에 대한 포인터
int nMaxScroll;
int nTimeLimit;
};
현재 스테이지의 정보를 갖는 구조체로 실제로 게임이 돌아갈때 해당 되는 스테이지 값들이 이 구조체 변수로 복사 되어 활용 된다.
이러한 복사 작업은 스테이지가 변경될 때마다 이루어지며 다음과 같은 방식으로 처리 된다.
1
2
3
4
5
6
7
8
9
10
STAGEINFO CurrentStage;
void ChangeStage(int p_nStageNum)//인자값 : 진행 해야 할 스테이지 번호
{
CurrentStage.nStageNum = p_nStageNum;
CurrentStage.pObject = Stage[CurrentStage.nStageNum].object;
CurrentStage.nMaxScroll = Stage[CurrentStage.nStageNum].nMaxScroll;
CurrentStage.nTimeLimit = Stage[CurrentStage.nStageNum].nTimeLimit;
InitPlayer();//스테이지 시작전 플레이어의 데이터들 초기화
}
4) PLAYER 구조체
1
2
3
4
5
6
struct PLAYER {
COORD coord;
STATES State;//플레이어의 현재 상태
int nHeightOfJump;//플레이어 캐릭터가 도약을 시작한 y좌표, 후에 착지 이벤트 GROUND가 발생하게 되면 착지 지점의 높이에서 이 값을 뺀다.
//그리고 그 결과값이 -부호이면서 절대값 크기에 따라 게임 오버 처리와 게임 플레이 기회를 의미하는 몫이 차감 된다.
};
플레이어 캐릭터와 관련된 데이터들을 관리 하기 위한 구조체.
다음과 같이 사용 된다.
PLAYER Player; Player.State = ASCENDING;
C. 맵 스크롤 방식
게임은 기본적으로 수직 방향으로 맵이 스크롤 되는 방식이다.
RPG 방식으로 진행 되는 시나리오 모드는 화면 스크롤을 아예 하지 않는다.
맵 스크롤은 플레이어 캐릭터의 머리 부분(캐릭터의 y좌표)이 기기 화면 최상단부를 의미하는 0값 이하로 떨어졌을때 nScrollCnt 값이 하나 증가하며 위쪽으로, 캐릭터의 발쪽 부분(캐릭터의 y좌표 - 캐릭터 이미지의 높이)의 좌표 값이 화면 최하단부의 값인 SCREEN_HEIGHT 이상일때 nScrollCnt값이 하나 감소하며 아래쪽으로 스크롤 되는 방식으로 이루어진다.
한 번에 맵이 스크롤 되는 단위가 화면 전체 사이즈이기 때문에 스크롤 한 번에 화면이 한 번씩 통째로 바뀌게 된다.
따라서 각 오브젝트들의 좌표 값 또한 해당 스크롤 맵에서의 좌표를 그대로 사용하며 단순히 nScrollCnt 값에 따라 출력과 충돌 체크 처리의 유무가 정해지게 된다.
D. 오브젝트 출력
초모롱마는 플레이어가 점프를 하여 공중에 떠있는 오브젝트들을 밟아 정상 까지 올라가는 방식으로 진행 되는 게임이다.
그렇기 때문에 오브젝트의 이미지 출력 처리와 플레이어 캐릭터간의 충돌체크가 본 게임에서 가장 중요한 부분이다.
오브젝트의 출력과 충돌 체크 모두 오브젝트 배열을 포인터를 통해 순차적으로 순회 하는 방식으로 이루어진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define NON_IMG 0
void DrawObjects()
{
OBJECT *pCurrentObject = CurrentStage.pObject;
while(1)
{
if(pCurrentObject->nImgNum > NON_IMG)//NON_IMG : 0으로 정의되어 있으며 아무것도 표시할 것이 없다는 의미의 이미지 번호 값
{
if(pCurrentObject->nEmergScrollNum == CurrentStage.nScrollCnt)//현재 수직 스크롤된 화면에 등장할 오브젝트가 있으면
{
Draw(pCurrentObject->coord.x, pCurrentObject->coord.y, ObjectImg[pCurrentObject->nImgNum]);
}
}
else//이미지 번호 0은 오브젝트 배열의 실질적인 끝을 의미하기도 하므로 루프를 종료 시킴
break;
pCurrentObject++;
}
}
E. 충돌 체크
플레이어의 캐릭터는 기본적으로 DeadPoint(y축 좌표가 스크린의 최대 높이 값이고, 게임의 스크롤 카운트 값이 0인 게임 상에서의 제일 밑 바닥(데드 지점)의 지면)와 오브젝트를 밟아 나가며 점프를 한다.
그렇기 때문에 충돌 체크는 오브젝트와 DeadPoint의 좌표 값을 비교하는 방식으로 이루어진다.
즉, 캐릭터가 오브젝트 위에 서있으려면 해당 오브젝트와의 충돌체크 판정이 참이 나와야만 한다는 말이다.
1
2
3
4
5
6
7
8
9
10
11
bool CheckCollision(OBJECT* p_pCurrentObject)
{
if((Player.coord.x >= p_pCurrentObject->coord.x && Player.coord.y >= p_pCurrentObject->coord.y)
&&
(Player.coord.x <= p_pCurrentObject->coord.x + OBJECT_WIDTH && Player.coord.y <= p_pCurrentObject->coord.y + OBJECT_HEIGHT))//오브젝트와 비교
return true;//충돌 발생 알림
else if(Player.coord.y >= DEAD_POINT)//데드 포인트와 비교
return true;
else
return false;//출동 발생 X를 알림
}
F. 플레이어 캐릭터의 상태 변화
플레이어 캐릭터는 게임 진행 중에 4가지 상태 중 하나로 시시각각 변화한다.
이는 플레이어 캐릭터가 상태 값 4가지 중에 하나를 현재 상태 값으로 갖는다는 말과 같다.
| 상태 | 설명 |
|---|---|
| Walking | 도보 중인 상태 |
| Ascending | 점프 최대점 까지 상승 중인 상태 |
| Descending | 지면으로 하강 중인 상태 |
| Landing | 착지 동작 중인 상태 |
플레이어 캐릭터의 상태가 현재 상태에서 다른 상태로 바뀌려면 그에 맞는 이벤트가 발생해야 한다.
발생 할 수 있는 이벤트는 총 5가지다.
| 이벤트 | 설명 |
|---|---|
| Jump | 점프 키를 눌렀을때 발생 |
| Peak | 점프 최대 높이에 이르렀을때 발생 |
| Ground | 캐릭터가 지면에 닿았을때(오브젝트나 Dead Point와의 충돌체크 판정이 참일때) 발생하지만 보통 이걸 매번 체크하고 있는 것은 부담이기 때문에 Walking 상태와 Descending 상태에서만 이를 체크 한다 |
| Not Ground | 캐릭터가 지면을 밟고 있지 않을때 발생 |
| Stable Posture | 착지 동작을 무사히 마쳤다 |
발생하는 이벤트에 따른 캐릭터의 상태 변화는 다음과 같이 처리 한다.
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
#define DEAD_HEIGHT -70//낙차 최대 허용 값으로 이 값 보다 더 -가 되면 게임 오버 처리
#define MAX_EVENTS 5//발생 이벤트 개수
#define MAX_STATES 4//플레이어의 상태 개수
enum EVENTS{JUMP, PEAK, GROUND, NOT_GROUND, STABLE_POSTURE};
enum STATES{WALKING, ASCENDING, DESCENDING, LANDING};
void ProcessEvents(EVENTS p_Event)
{
//현재 도보 중인 상태인데 지면이 없어 실족한 경우
if(Player.State == WALKING && p_Event == NOT_GROUND)
{
Player.State = DESCENDING;//플레이어의 상태를 하강 상태로 변경
Player.nHeightOfJump = Player.coord.y;//낙차 값 계산을 위해 도약 좌표 저장
}
else if(Player.State == WALKING && p_Event == JUMP)
{
Player.State = ASCENDING;
}
else if(Player.State == ASCENDING && p_Event == PEAK)
{
Player.State = DESCENDING;
}
else if(Player.State == DESCENDING && p_Event == GROUND)
{
Player.State = LANDING;
Player.nHeightOfJump -= (Player.coord.y) + ( * SCREEN_HEIGHT);//낙차 값 계산
if(Player.nHeightOfJump <= DEAD_HEIGHT )//낙차 범위가 허용치 이상이면
{
nGameMode = GAMEOVER_MODE;//게임 오버
}
}
else if(Player.State == LANDING && p_Event == STABLE_POSTURE)
{
Player.State = WALKING;
}
}
