디시인사이드 갤러리

갤러리 이슈박스, 최근방문 갤러리

갤러리 본문 영역

적분시리즈: 리듬게임을 만들수 있을까? 10. 노트 내려오게 하기

∫ 2t dt=t²+c갤로그로 이동합니다. 2009.07.23 00:23:10
조회 257 추천 0 댓글 12




이제 리듬게임에 핵심이라고 할 수 있는 노트에 대한 처리를 해보자.
먼저 노트에 대한 데이터를 담을 수 있는 구조체를 선언하자.


struct NoteData
{
    int time; //시간
    BYTE type; //종류
    BYTE state; //상태
    WORD data; //데이터
    GObject obj; //그리기객체
};
enum{
    ND_Note1=0,
    ND_Note2,
    ND_Note3,
    ND_Note4,
    ND_Note5,
    ND_Button1Sound,
    ND_Button2Sound,
    ND_Button3Sound,
    ND_Button4Sound,
    ND_Button5Sound,
    ND_LongNote1S,
    ND_LongNote2S,
    ND_LongNote3S,
    ND_LongNote4S,
    ND_LongNote5S,
    ND_LongNote1E,
    ND_LongNote2E,
    ND_LongNote3E,
    ND_LongNote4E,
    ND_LongNote5E,
    ND_Bar,
    ND_Vel,
};

간단하게 설명하자면
노트가 내려올 시간을 담는 time,
노트의 종류를 담는 type,
노트의 처리상태(제때에 눌러졌는가? 아니면 놓쳤는가?)를 담는 state
기타 데이터를 담는 data,
노트의 그리기객체인 obj가 구조체를 구성한다.

type에 대한 열거형값이 아래에 정의되어 있다.
ND_Note1은 제일 왼쪽에서 내려오는 노트,
ND_Note2는 두 번째에서 내려오는 노트,
...
ND_Note5는 제일 오른쪽에서 내려오는 노트이고,
ND_Button1~5Sound는 노트 정보는 아니지만 버튼의 소리를 설정한다.
ND_LongNote1~5S는 기다란 노트의 시작점,
ND_LongNote1~5E는 기다란 노트의 끝점이다.
ND_Bar는 마디구분선,
ND_Vel은 내려오는 속도를 설정한다.

이제 매 프레임 루프를 돌면서 시간이 된 데이터를 처리하고 화면에 보여주면 된다.


class MPlayGame : public MState
{
protected:
    struct{
        POINT frame;
        POINT key[5];
        POINT notelt;
        POINT noterb;
    }m_ui;
    vector<BYTE> m_codedata;
    GObject m_objframe;
    GObject m_button[5];
    vector<NoteData> m_notes;
    size_t m_notebegin;
    size_t m_noteend;
    WORD m_buttonsound[5];
    int m_time;
    float m_vspeed;
    BYTE m_buttonkeycode[5];
    enum{
        Event_Frame=0,
        Event_NoteA,
        Event_NoteB,
        Event_NoteC,
        Event_NoteAEnd,
        Event_NoteBEnd,
        Event_NoteCEnd,
        Event_Button1Down,
        Event_Button5Down=Event_Button1Down+4,
        Event_Button1Up,
        Event_Button5Up=Event_Button1Up+4,
    };
    inline int GetButton(int KeyCode);

    inline void BeginNote(NoteData* nd);
    inline void EndNote(NoteData* nd);
    inline POINT PosNote(NoteData* nd);
    inline void PassNote(NoteData* nd);
    inline void BeginBar(NoteData* nd);
    inline void EndBar(NoteData* nd);
    inline POINT PosBar(NoteData* nd);
    inline void PassButtonSound(NoteData* nd);
    inline void PassVel(NoteData* nd);
    inline void ButtonDown(int Button);
    inline void ButtonUp(int Button);
public:
    MPlayGame(GGame* pGame);   
    ~MPlayGame();
    void OnProc();
    void OnDraw();
    void OnPostProc();
    void OnLost();
    void ();
    void (int KeyCode);
    void (int KeyCode);
    void OnDrawSkip();
    GRET LoadOC(MemFile* file);

};

MPlayGame클래스에 이것저것이 많이 추가되었다.

m_button[5]은 5개의 버튼이 눌렸을때 나타나는 그래픽의 그리기객체이고,
m_notes는 노트데이터를 담는 변수이다.

m_notebegin과 m_noteend는 노트를 검색할 범위를 나타낸다.
노트데이터가 많으면 매 프레임마다 전체를 검색하는데 시간이 꽤 걸린다. 쓸데없는데에 CPU를 낭비하지 말자.
노트데이터를 시간순으로 정렬해 놓는다면, 시간에 따라서 지나간 부분, 아직 오지 않은 부분은 버리고 검색할 수 있으므로 좀 더효율적이다. 여기서 검색을 시작하는 위치가 m_notebegin이고 검색을 끝내는 위치가 m_noteend이다.

m_buttonsound[5]는 5개의 버튼이 눌렸을때 나는 효과음의 인덱스를 저장한다. 물론 아직 사운드부분이 완성되지 않아서 실질적으로는 쓸모가 없다.

m_time은 현재 진행되고 있는 음악의 시간이고,
m_vspeed는 노트가 떨어지는 속도이다.
m_buttonkeycode[5]는 5개의 버튼이 대응되는 키의 키코드이다.

그리고 그 아래에는 Event_어쩌구 해서 그리기코드에 정의되어있는 이벤트들이 열거되어있다.

그 아래 inline함수로 내부 구현 함수들이 정의되어있다.

GetButton함수는 키코드를 통해 이 키가 1~5 버튼 중 어느 버튼에 해당하는지 리턴한다.

Begin/EndNote는 노트가 보이기 시작할때, 사라졌을때 호출되는 함수이다.
Begin/EndBar는 마디구분줄이 보이기 시작할때, 사라졌을때 호출되는 함수,
PosNote/Bar는 노트와 마디구분줄을 그릴때 위치를 얻기 위해 호출되는 함수이다.
PassNote/ButtonSound/Vel은 노트/버튼소리변경/속도변경 이벤트가 지나갈때 호출되는 함수,
ButtonDown/Up은 버튼이 눌려졌을때, 떼어졌을때 호출되는 함수이다.


int MPlayGame::GetButton(int KeyCode)
{
    for(int i=0;i<5;i++)
    {
        if(m_buttonkeycode[i]==KeyCode)return i;
    }
    return -1;
}

GetButton함수 구현을 보자. m_buttonkeycode에서 검색을 해서 해당하는 KeyCode가 있으면 그 버튼의 번호를 리턴하고, 없으면 -1을 리턴한다.


void MPlayGame::OnProc()
{
    int vfstart=m_ui.notelt.y-m_ui.key[0].y-50;
    int vfend=m_ui.noterb.y-m_ui.key[0].y+50;
    while(m_noteend<m_notes.size() &&
        m_vspeed*(m_time-m_notes[m_noteend].time)>vfstart)
    {
        switch(m_notes[m_noteend].type)
        {
        case ND_Note1:
        case ND_Note2:
        case ND_Note3:
        case ND_Note4:
        case ND_Note5:
            BeginNote(&m_notes[m_noteend]);
            break;
        case ND_Bar:
            BeginBar(&m_notes[m_noteend]);
            break;
        }
        m_noteend++;
    }
    for(size_t i=m_notebegin;i<m_noteend;i++)
    {
        if(m_vspeed*(m_time-m_notes[i].time)>vfend)
        {
            switch(m_notes[i].type)
            {
            case ND_Note1:
            case ND_Note2:
            case ND_Note3:
            case ND_Note4:
            case ND_Note5:
                EndNote(&m_notes[i]);
                break;
            case ND_Bar:
                EndBar(&m_notes[i]);
                break;
            }
            m_notebegin++;
        }
        if(m_time>m_notes[i].time && m_notes[i].state==0)
        {
            switch(m_notes[i].type)
            {
            case ND_Note1:
            case ND_Note2:
            case ND_Note3:
            case ND_Note4:
            case ND_Note5:
                PassNote(&m_notes[i]);
                break;
            case ND_Button1Sound:
            case ND_Button2Sound:
            case ND_Button3Sound:
            case ND_Button4Sound:
            case ND_Button5Sound:
                PassButtonSound(&m_notes[i]);
                break;
            case ND_Vel:
                PassVel(&m_notes[i]);
                break;
            }
            m_notes[i].state=1;
        }
    }
}

먼저 m_noteend부분부터 검색을 시작하여 보여져야하는 노트/들은 BeginNote/Bar함수를 호출해서 드러낸다.
그 뒤 m_notebegin부분에서 검색하여 지나간 노트/들은 EndNote/Bar함수를 호출해서 숨긴다.
그리고 처리시간이 된 노트/들은 PassNote/ButtonSound/Vel함수를 호출해서 처리할 이벤트를 처리한다.


void MPlayGame::(int KeyCode)
{
    int ibut=GetButton(KeyCode);
    if(ibut>=0)ButtonDown(ibut);
}

void MPlayGame::(int KeyCode)
{
    int ibut=GetButton(KeyCode);
    if(ibut>=0)ButtonUp(ibut);
}

키이벤트처리는 간단하게 GetButton함수로 해당하는 버튼이 있는지 확인하여 ButtonDown/Up함수를 호출해준다.


void MPlayGame::OnDraw()
{
    m_objframe.Proc(m_pGame, m_ui.frame, m_ui.key);
    for(size_t i=m_notebegin;i<m_noteend;i++)
    {
        POINT t={0, (int)(m_vspeed*(m_time-m_notes[i].time))};
        switch(m_notes[i].type)
        {
        case ND_Note1:
        case ND_Note2:
        case ND_Note3:
        case ND_Note4:
        case ND_Note5:
            m_notes[i].obj.Proc(m_pGame, m_ui.frame+PosNote(&m_notes[i])+t, NULL);
            break;
        case ND_Bar:
            m_notes[i].obj.Proc(m_pGame, m_ui.frame+PosBar(&m_notes[i])+t, NULL);
            break;
        }
    }
    for(int i=0;i<5;i++)
    {
        m_button[i].Proc(m_pGame, m_ui.frame+m_ui.key[i], NULL);
    }
}

그리는 부분에서는 노트데이터의 그리기객체와 버튼의 그리기객체를 처리한다.


void MPlayGame::OnPostProc()
{
    m_time++;
}

후처리부분에서는 m_time을 1증가시킨다.


void MPlayGame::BeginNote(NoteData *nd)
{
    nd->obj.Init(&m_codedata[0]);
    switch(nd->type)
    {
    case ND_Note1:
    case ND_Note5:
        nd->obj.Jump(Event_NoteA*3);
        break;
    case ND_Note2:
    case ND_Note4:
        nd->obj.Jump(Event_NoteB*3);
        break;
    case ND_Note3:
        nd->obj.Jump(Event_NoteC*3);
        break;
    }
}

void MPlayGame::EndNote(NoteData *nd)
{
    nd->obj.End();
}

노트가 화면에 보일때는 그리기객체를 초기화하고 적당한 이벤트로 점프하여 그리기를 준비하고,
노트가 사라질때는 그리기객체를 소멸시킨다.


POINT MPlayGame::PosNote(NoteData *nd)
{
    switch(nd->type)
    {
    case ND_Note1:
        return m_ui.key[0];
    case ND_Note2:
        return m_ui.key[1];
    case ND_Note3:
        return m_ui.key[2];
    case ND_Note4:
        return m_ui.key[3];
    case ND_Note5:
        return m_ui.key[4];
    }
    return POINT();
}

노트의 위치를 정하는 함수에서는 노트의 상대적 위치를 리턴해준다.


void MPlayGame::ButtonDown(int Button)
{
    m_button[Button].Init(&m_codedata[0]);
    m_button[Button].Jump((Event_Button1Down+Button)*3);
    for(size_t i=m_notebegin;i<m_noteend;i++)
    {
        if(m_notes[i].type==ND_Note1+Button && (m_time-m_notes[i].time)>-15 && (m_time-m_notes[i].time)<0)
        {
            m_notes[i].state=1;
            switch(m_notes[i].type)
            {
            case ND_Note1:
            case ND_Note5:
                m_notes[i].obj.Jump(Event_NoteAEnd*3);
                break;
            case ND_Note2:
            case ND_Note4:
                m_notes[i].obj.Jump(Event_NoteBEnd*3);
                break;
            case ND_Note3:
                m_notes[i].obj.Jump(Event_NoteCEnd*3);
                break;
            }
            break;
        }
    }
}

void MPlayGame::ButtonUp(int Button)
{
    m_button[Button].Jump((Event_Button1Up+Button)*3);
}

버튼이 눌러졌을때는 버튼 이펙트의 그리기 객체를 초기화하고,
15프레임이내의 시간에 노트가 있으면 노트가 눌러졌음을 처리한다.

이제 그리기코드를 프로그래밍해보자.

goto FrameInit
goto NoteA
goto NoteB
goto NoteC
goto NoteAEnd
goto NoteBEnd
goto NoteCEnd
goto Key1Down
goto Key2Down
goto Key3Down
goto Key2Down
goto Key1Down
goto Key1Up
goto Key2Up
goto Key3Up
goto Key2Up
goto Key1Up

FrameInit:
setdata 0 9 469
setdata 1 59 469
setdata 2 110 460
setdata 3 194 469
setdata 4 245 469
setdata 5 0 0
setdata 6 0 600

Frame:
setdrawmode normal RGBA
draw 0 0 0 0xffffffff
frameend
goto Frame

NoteA:
setdrawmode normal RGBA
draw 1 0 0 0xffffffff
setdrawmode add RGBA
draw 1 0 0 0xffffffff
frameend
setdrawmode normal RGBA
draw 1 0 0 0xffffffff
setdrawmode add RGBA
draw 1 0 0 0xe0ffffff
frameend
......
goto NoteA

NoteAEnd:
setdrawmode normal RGBA
draw 1 0 0 0xff707070
frameend
goto NoteAEnd

NoteB:
setdrawmode normal RGBA
draw 2 0 0 0xffffffff
setdrawmode add RGBA
draw 2 0 0 0xffffffff
frameend
setdrawmode normal RGBA
draw 2 0 0 0xffffffff
setdrawmode add RGBA
draw 2 0 0 0xe0ffffff
frameend
......
goto NoteB

NoteBEnd:
setdrawmode normal RGBA
draw 2 0 0 0xff707070
frameend
goto NoteBEnd

NoteC:
setdrawmode normal RGBA
draw 3 0 0 0xffffffff
setdrawmode add RGBA
draw 3 0 0 0xffffffff
frameend
setdrawmode normal RGBA
draw 3 0 0 0xffffffff
setdrawmode add RGBA
draw 3 0 0 0xe0ffffff
frameend
......
goto NoteC

NoteCEnd:
setdrawmode normal RGBA
draw 3 0 0 0xff707070
frameend
goto NoteCEnd

Key1Down:
setdrawmode add RGBA
draw 4 -13 -10 0x20ff0000
frameend
setdrawmode add RGBA
draw 4 -13 -10 0x60ff0000
frameend
......
Key1DownLoop:
setdrawmode add RGBA
draw 4 -13 -10 0xffff0000
frameend
setdrawmode add RGBA
draw 4 -13 -10 0xf0ff0000
frameend
setdrawmode add RGBA
draw 4 -13 -10 0xe0ff0000
frameend
goto Key1DownLoop

Key1Up:
setdrawmode add RGBA
draw 4 -13 -10 0xffff0000
frameend
setdrawmode add RGBA
draw 4 -13 -10 0xf0ff0000
frameend
......
setdrawmode add RGBA
draw 4 -13 -10 0x10ff0000
frameend
end

Key2Down:
setdrawmode add RGBA
draw 4 -13 -10 0x200000ff
frameend
setdrawmode add RGBA
draw 4 -13 -10 0x600000ff
frameend
......
Key2DownLoop:
setdrawmode add RGBA
draw 4 -13 -10 0xff0000ff
frameend
setdrawmode add RGBA
draw 4 -13 -10 0xf00000ff
frameend
setdrawmode add RGBA
draw 4 -13 -10 0xe00000ff
frameend
goto Key2DownLoop

Key2Up:
setdrawmode add RGBA
draw 4 -13 -10 0xff0000ff
frameend
setdrawmode add RGBA
draw 4 -13 -10 0xf00000ff
frameend
......
setdrawmode add RGBA
draw 4 -13 -10 0x100000ff
frameend
end

Key3Down:
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0x2000ff00
frameend
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0x6000ff00
frameend
......
Key3DownLoop:
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0xff00ff00
frameend
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0xf000ff00
frameend
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0xe000ff00
frameend
goto Key3DownLoop

Key3Up:
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0xff00ff00
frameend
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0xf000ff00
frameend
......
setdrawmode add RGBA
draws 4 -13 -10 1.6 1.6 0x1000ff00
frameend
end

굉장히 길다. 하지만 별뜻은 없고 언제 어떤 그림을 그려야 하는지 지정한 것이다. 윗부분의 goto 리스트들이 열거형으로선언했던 Event_Frame, Event_NoteA... 들에 대응하는 부분이다. 즉 그리기객체에서 Jump(Event_Frame*3 ) 하면 그리기코드의 FrameInit:으로 가게되는 것이다.

자세한 것은 첨부파일에 있는 acc 폴더안에 들어있다.
acc안에 있는 프로그램들로 sprites.txt, textures.txt, obj.txt를 변환하여 실행파일과 같은 폴더에 넣고 실행해보자. (변환하는 방법은 8강에서 설명했다.)

14119F104A672DCC07AC29
우왕! 드디어 노트가 내려온다.

추천 비추천

0

고정닉 0

0

댓글 영역

전체 댓글 0
등록순정렬 기준선택
본문 보기

하단 갤러리 리스트 영역

왼쪽 컨텐츠 영역

갤러리 리스트 영역

갤러리 리스트
번호 제목 글쓴이 작성일 조회 추천
설문 힘들게 성공한 만큼 절대 논란 안 만들 것 같은 스타는? 운영자 24/06/10 - -
152485 "취업했는데 잘간건지 좀 봐줘여" 는 봅니다 [7] 캐꼬꼬닭(112.216) 09.10.15 214 0
152484 금연 40시간 돌파 [7] 유리한갤로그로 이동합니다. 09.10.15 150 0
152483 님들아 컴공과도 대학원나온가이랑 안나온가이 취업하면 연봉차이심함? [6] ㅇㅇ(114.205) 09.10.15 354 0
152480 모두 몸짱이 되어 보아요 [1] 유리한갤로그로 이동합니다. 09.10.15 115 0
152478 나의 컴퓨터 수리기사 경험담 [13] 아주아슬갤로그로 이동합니다. 09.10.15 231 0
152477 나 대학원가고싶어 [8] 개쉛기갤로그로 이동합니다. 09.10.15 220 0
152476 폭풍같이 글싸며 퇴갤 [5] algo갤로그로 이동합니다. 09.10.15 78 0
152475 어휴.... 추천사하니까 말인데 [3] algo갤로그로 이동합니다. 09.10.15 69 0
152474 이건 꼭 사야해! [4] Gromit갤로그로 이동합니다. 09.10.15 135 0
152473 이 씨발 [13] 개쉛기갤로그로 이동합니다. 09.10.15 187 0
152472 내가 좋아하는 회사... [5] 물속의다이아갤로그로 이동합니다. 09.10.15 163 0
152471 여자친구 만드는법 [2] ㅇㅇㅃ갤로그로 이동합니다. 09.10.15 101 0
152470 나도 마우스 인증 [7] LightEach갤로그로 이동합니다. 09.10.15 192 0
152469 대한민국 1% 손들어라 [12] 아주아슬갤로그로 이동합니다. 09.10.15 216 0
152468 취업했는데 잘간건지 좀 봐줘여 [15] ㅇㅇㅇㅇ(121.131) 09.10.15 329 0
152467 동작 그만 [9] algo갤로그로 이동합니다. 09.10.15 128 0
152466 입장 바꾸는 날 같은거 있었으면 좋겠다 [4] prismatic갤로그로 이동합니다. 09.10.15 105 0
152465 급 설문조사 [12] 참치갤로그로 이동합니다. 09.10.15 107 0
152463 나도 마우스 인증 (2) [3] 삼류(150.183) 09.10.15 117 0
152460 아 아 아 아 [6] 참치갤로그로 이동합니다. 09.10.15 73 0
152459 소설책에 관한 질문... [5] 물속의다이아갤로그로 이동합니다. 09.10.15 77 0
152458 님들아 질문좀 [8] ㅂㅈㄷㄱ(202.30) 09.10.15 84 0
152457 닥눈삼 3분하고 질문하면 까일지 모르겟지만... 메모리 변조... [1] 체다치즈(125.131) 09.10.15 73 0
152455 앱스토어에 프로그램 짤려면 어떤 언어를 배워야 돼? [4] 허무주의자갤로그로 이동합니다. 09.10.15 135 0
152454 뇌잘 왔음 [5] prismatic갤로그로 이동합니다. 09.10.15 100 0
152453 시간은 금이요. [7] yundream(211.189) 09.10.15 143 0
152452 이건 뭐하자는건지 ㅋㅋㅋ [9] rntjr갤로그로 이동합니다. 09.10.15 152 0
152451 안녕하세요. 늅늅 [4] 기름호랑이갤로그로 이동합니다. 09.10.15 95 0
152450 nProtect 가 표창을 받았답니다...... [5] 후시기바나(110.76) 09.10.15 188 0
152449 대학 다닐 때 이야기 하나... [3] 물속의다이아갤로그로 이동합니다. 09.10.15 101 0
152448 횽님들 봐주세욤,ㅠ [2] 살암살려,(203.250) 09.10.15 74 0
152447 XE사용하고 있는데 [1] Vita500갤로그로 이동합니다. 09.10.15 82 0
152445 학교 도서관은 도서 신청이 왜케 더디냐... [9] 혼아갤로그로 이동합니다. 09.10.15 95 0
152444 닥눈삼 여겼다고 욕먹을거같어!!!! 횽님들 알켜주세요;;; [12] 미애남편갤로그로 이동합니다. 09.10.15 128 0
152443 GPL 3.0 상업적으로 갖다쓰면 깜빵가나여?? [4] 아잉따잉갤로그로 이동합니다. 09.10.15 162 0
152442 횽들 개발 서적 좀 읽어? [12] 물속의다이아갤로그로 이동합니다. 09.10.15 211 0
152440 앜ㅋㅋㅋㅋ변듣봌ㅋㅋㅋㅋㅋㅋㅋㅋ [1] 유리한갤로그로 이동합니다. 09.10.15 103 0
152439 개쉛기횽을 위한 표절 짤방 [1] 아주아슬갤로그로 이동합니다. 09.10.15 102 0
152438 빵굽는 개발자. 간지좀 나냐? [7] 유리한갤로그로 이동합니다. 09.10.15 219 0
152437 회사 따돌림이라고 하긴 뭣하지만... [6] 아주아슬갤로그로 이동합니다. 09.10.15 187 0
152436 Resharper는 사랑이긴한데... ㅇㄴㅣㅏ갤로그로 이동합니다. 09.10.15 55 0
152434 지상 최대의 난적 [파일명검색] [8] 검색가(211.62) 09.10.15 143 0
152432 잉카 인터넷 표창 [5] Mr.Bation갤로그로 이동합니다. 09.10.15 134 0
152431 회사에도 따돌리고 그런거 있어여?? [8] ㅇㅇ(124.254) 09.10.15 146 0
152430 KT와이브로 에그 문의... [3] 물속의다이아갤로그로 이동합니다. 09.10.15 100 0
152429 일찍일어나는 새는 빨리 뒤진당. [4] yundream(211.189) 09.10.15 131 0
152428 아주쓸쓸횽을 위한 스페셜 짤방 [7] 개쉛기갤로그로 이동합니다. 09.10.15 140 0
152427 인터넷 개통 기념 -- 톰캣의 어원을 알아냈어! 아주아슬갤로그로 이동합니다. 09.10.15 149 0
152426 nhn도 멤버십 생겼네 [5] 빕뱟뱟갤로그로 이동합니다. 09.10.15 177 0
152425 잘못된 옛말 , '일찍 일어나는 새가 먹이를 쳐 얻는다' [4] 개쉛기갤로그로 이동합니다. 09.10.15 162 0
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2