프로그래밍/SGDK 메가드라이브 개발

메가드라이브 게임 만들기 #6 - 구조체에 대해

windship 2019. 6. 24. 23:24

 도트 먹기 게임의 연구 목적으로, 메가드라이브에 팩맨을 제멋대로 이식하고 있습니다만, 깊게 연구하면 할 수록, 정말 대단한 게임이었구나 하는 것을 실감하고 있습니다. 단순한 게임인 것처럼 보이지만, 실은 꽤나 복잡한 구조로 만들어져 있는 게임입니다.

 

 

 메가드라이브 실기 + 브라운관 환경에서 촬영한 영상입니다.

 

 <고생했던 점>

 

* 속도가 미묘하게 달라지는 캐릭터를 제대로 맵의 길에 올려서 벽에 파묻히거나 하지 않고 움직이게 하는 것

* 팩맨의 숏컷

* 몬스터는 방향을 전환하기 3~4프레임 전쯤에 이동하는 방향으로 눈을 향하고 있다

* 메가드라이브의 PSG(DCSG)로 딱딱 떨어지는 좋은 소리를 내는 것

* 세로 해상도가 부족하므로 맵을 스크롤할 필요가 있었다

 

등이 있습니다만, 메가드라이브의 처리가 빠른 것과 개발 환경인 SGDK가 매우 잘 되어 있어서 최적화를 그리 신경쓰지 않아도 느려지거나 하는 일 없이 움직일 수 있었습니다.

 

 잊어버리지 않도록, 제가 프로그래밍한 내용을 정리해 두려고 합니다. 이번에는 구조체에 대해서입니다.

 게임을 만들 때에는 변수를 잔뜩 사용하게 됩니다만, 대충 변수를 막 늘려 가다 보면 수가 늘어남과 동시에 머릿 속이 혼란스러워져 대처하기가 어렵게 됩니다.

 C++에서는 클래스를 준비하여, 클래스별로 변수를 관리하면 혼란을 막을 수 있지만, SGDK는 C언어이므로 저의 경우 구조체를 이용해서 변수를 기능별로 정리하여 관리하고 있습니다. 만들었던 구조체를 소개합니다.

 

<Col구조체>

 

 Collision의 줄임말로 "Col"이라고 했는데, 캐릭터의 좌표와 충돌 판정용의 정보를 멤버로 가진 구조체입니다.

 게임에 등장하는 플레이어, 몬스터, 과일은 이 구조체에서 좌표와 충돌 판정용의 범위를 관리합니다. 팩맨의 캐릭터는 16x16도트가 기준으로 되어 있어, 그 중심부를 캐릭터의 좌표로 하여 계산하고 있습니다.

 캐릭터끼리의 미묘한 이동속도를 표현하기 위해서, 단위는 16도트의 고정소수점을 이용했습니다.

 각각의 캐릭터는 충돌 판정용의 테두리(??)의 좌표인 iX, iY, iW, iH를 전부 설정해놓고 있습니다. 이 범위를 늘리거나 줄이거나 하여 충돌 판정의 빡빡하거나 여유있는 정도를 조정할 수 있습니다.

 

* 충돌 판정에 사용하고 있는 ??의 좌표를 계산하는 함수

 

void    Colli_updateParemeter(Col *col) 
{
    col->x0 = fix16ToInt(col->fPosX) + col->iX;
    col->y0 = fix16ToInt(col->fPosY) + col->iY;
    col->x1 = col->x0 + col->iW;
    col->y1 = col->y0 + col->iH;
}

 

 이 함수를 이용하여, 중심 좌표에서 충돌 판정용 박스의 좌표 x0, y0, x1, y1을 계산합니다. 충돌 판정에 캐릭터가 닿았는지는 매 프레임마다 계산할 필요가 있습니다. 

 

*1. fix16은 SGDK에 준비되어 있는 16비트용 고정 소수점 수

*2. s16은 SGDK에 준비되어 있는 16비트의 정수형으로 short와 같습니다.

*3. fix16ToInt()는 16비트의 고정 소수점을 정수형으로 변환하는 함수 매크로입니다.

 

* 충돌 판정용 함수

 

u8      Colli_check(Col *col_1, Col *col_2) 
{
    if(col_1->x0 < col_2->x1 && col_2->x0 < col_1->x1 &&
       col_1->y0 < col_2->y1 && col_2->y0 < col_1->y1) return TRUE;
    else return FALSE;
}

 

 이 함수에 판정하고 싶은 2개의 Col구조체(col_1과 col_2)의 포인터를 넘기면, 바로 Colli_updateParemeter(Col *col)로 계산한 2개의 사각형 좌표가 겹쳐있는지 어떤지를 판단합니다. 겹쳐 있다면 TRUE, 겹치지 않았다면 FALSE를 돌려줍니다.

 

스프라이트의 표시 위치를 지정하는 함수

 

void    HC_setSpritePosition(u16 index, fix16 x, fix16 y, s16 scrolly)
{
    VDPSprite *sprite = &vdpSpriteCache[index];

    sprite->y = fix16ToInt(y) + 0x80 - 8 - scrolly;
    sprite->x = fix16ToInt(x) + 0x80 - 8;
}

 

 스프라이트는 통상 VBlank의 사이에 설정되어 있습니다만, SGDK에는 스프라이트를 표시하기 위한 캐쉬가 준비되어 있어, VBlank에 들어가기 전에, vdpSpriteCache[]에 수치를 넣어, VBlank에서 정리하여 VDP에 데이터를 전송합니다.

 스프라이트의 좌표는 왼쪽 위가 (0x80, 0x80)의 정수형이므로, 매번 고정소수점 값을 정수로 변환할 필요가 있습니다. 매번 변환하는 것은 귀찮으므로 이와 같은 독자적인 함수를 만들어, 직접 스프라이트의 캐쉬에 써넣도록 하고 있습니다. 또한, 알기 쉽게 화면 왼쪽 위를 (0, 0)으로 하고 있습니다.

 

* s15 scrolly는, 화면의 스크롤용 변수입니다.

 

<Game구조체>

멤버로 하이스코어나 크레딧, 게임 모드 등의 변수가 있습니다.

 

<Player 구조체, PlayerProperty 구조체>

플레이어의 위치, 충돌 판정, 잔기나 라운드 등

 

<Enemy 구조체>

 몬스터 용의 구조체입니다. 4마리 있으므로 사용할 때에는 배열로 선언하고 있습니다.

 

<Fruit 구조체>

 

과일 용입니다. 

 

이게 거의 전부입니다.

 

 소스를 공개하기 위해서, 캐릭터나 음악은 전부 다른 오리지널로 해 봤습니다. 디자인이나 음악이 바뀐 것만으로, 완전히 재미없게 되어 버리는 것은 왜일까요?

 

 소스 : CookieMan.zip

 

SGDK 1.22를 사용해 컴파일하면 돌아갈거라고 생각합니다.