디시인사이드 갤러리

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

갤러리 본문 영역

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

∫ 2t dt=t²+c갤로그로 이동합니다. 2009.07.23 00:23:10
조회 259 추천 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/17 - -
공지 프로그래밍 갤러리 이용 안내 [71] 운영자 20.09.28 35763 62
2716308 sqld sqlp 따면 회사에서 인정 받을수 있냐 ㅇㅇ 포항의봄갤로그로 이동합니다. 21:47 0 0
2716307 냥덩이 뽀송할땐 흔들어봥~❤+ ♥성교육이수냥덩♥갤로그로 이동합니다. 21:46 1 0
2716305 다행히 나쁘지 않은데 도리스아sk(223.38) 21:43 6 0
2716304 사이트 git과 jira가 기본이라면 프갤러(118.235) 21:39 11 0
2716303 나님도 sqld 보유자 입니다 ? [5] 피에로가르뎅갤로그로 이동합니다. 21:37 26 0
2716302 여기 대학생있음? 트렌더갤로그로 이동합니다. 21:37 6 0
2716301 프붕이들 장마 대비하고 있노.... 도리스아(220.74) 21:36 4 0
2716300 나님 씻엇어양❤+ ♥성교육이수냥덩♥갤로그로 이동합니다. 21:32 6 0
2716299 국비다니는데 다음주에 그만둘려고 ㅇㅇ갤로그로 이동합니다. 21:32 11 0
2716298 sqld 합격샷입니다♥ [6] 헬마스터갤로그로 이동합니다. 21:28 39 0
2716297 쉬운AI 등장 - 개발자 필요없다. ㅇㅇ(211.47) 21:27 10 0
2716295 lodge 태양광 스피커 발명도둑잡기갤로그로 이동합니다. 21:20 7 0
2716294 미소녀의 출산만 즐기려는 변태인데 어캄 ㅇㅅㅇ 류류(118.235) 21:19 9 0
2716293 코딩하는데 사실 좌절의 연속입니다 헬마스터갤로그로 이동합니다. 21:19 11 0
2716292 야 이거 주작 아니냐 ㅇㅅㅇ [1] 포항의봄갤로그로 이동합니다. 21:19 19 0
2716291 [정보] 너네는 이런짓하지마라 트렌더갤로그로 이동합니다. 21:17 18 0
2716290 미소녀의 태반으로 자위하고 싶음 즐기고 싶음 ㅇㅅㅇ 류류(118.235) 21:12 11 1
2716289 딸 자격증이 늘엇당 ♥성교육이수냥덩♥갤로그로 이동합니다. 21:11 15 0
2716288 우크라이나 망해서 나라 없어지면 무기 대금 못 받을 꺼같은데? 발명도둑잡기갤로그로 이동합니다. 21:11 8 0
2716286 주석지향코딩 너무 재밋어요 [4] 헬마스터갤로그로 이동합니다. 21:10 36 0
2716285 운동을 하니까 젊어진 느낌이야 ㅇㅅㅇ 류류(118.235) 21:07 10 0
2716284 Velveteen-Wasn't Even Close [1] 발명도둑잡기갤로그로 이동합니다. 21:06 13 0
2716283 위지윅 에디터로 개인프로젝트 만드는 중인데 프갤러(1.230) 21:05 34 0
2716282 커뮤니티추천부탁 [14] 트렌더갤로그로 이동합니다. 21:04 49 0
2716281 미래의 어느 역사학자들의 대화라는데 (요약편) 프갤러(211.241) 21:02 7 0
2716280 슬슬 퇴근해야겠군. 프갤러(121.172) 21:02 37 1
2716279 미래의 어느 역사학자들의 대화라는데 프갤러(211.241) 21:00 11 0
2716278 멍유는 그 허벌이라는 정처기도 못 땄잖아 [10] 류류(118.235) 20:56 36 1
2716276 나이를 먹으니 현실 친구는 뜸해지고 [2] 아스카영원히사랑해갤로그로 이동합니다. 20:52 34 0
2716274 안드로이드 신입 없어서 백엔드로 돌렸는데 [2] 프갤러(106.101) 20:47 34 0
2716273 뉴럴 네트워크는 사기인 듯. 프갤러(121.172) 20:46 30 1
2716272 인공지능이 못하는 분야를 정해둬야함 [15] 트렌더갤로그로 이동합니다. 20:43 64 1
2716271 인공지능 땜에 코테의 시대가 끝나나 ㅇㅇ(117.111) 20:39 29 0
2716270 깃갤도 병신들많은듯 [3] 트렌더갤로그로 이동합니다. 20:38 42 0
2716267 근데 개발자대체는 수순이니까 받아들여 [4] 트렌더갤로그로 이동합니다. 20:31 58 0
2716265 쌍기사하려고 보니 존나 어렵네 [4] 프갤러(211.106) 20:28 38 0
2716264 gpt로 알고리즘 계속 짜는데 [1] 프갤러(180.68) 20:27 31 0
2716263 백준이 gpt한테 따이면 코딩왜하냐 ㅇㅅㅇ [4] 트렌더갤로그로 이동합니다. 20:22 46 0
2716261 Ui는 이미 만들어져있는거쓰기로함 트렌더갤로그로 이동합니다. 20:19 10 0
2716259 내일출근이네 [2] 트렌더갤로그로 이동합니다. 20:15 30 0
2716258 나씻주준 ♥성교육이수냥덩♥갤로그로 이동합니다. 20:12 15 0
2716257 딥러닝과 퍼셉트론을 전자회로로 만드는수준 ㅇㅇ(106.101) 20:11 13 0
2716256 프붕이 먹고있는약! [3] ㅇㅇ(115.138) 20:09 38 1
2716255 야 너네 연애 안하냐?! [5] 포항의봄갤로그로 이동합니다. 20:08 47 0
2716254 축축행.. ♥성교육이수냥덩♥갤로그로 이동합니다. 20:08 17 0
2716253 업계에서 살아남기 위한것은 실력이 아니였음 [5] 프갤러(221.168) 20:07 57 0
2716252 배민 23년 영업이익이 7000억이던데 [2] ㅇㅇ(172.226) 20:05 44 0
2716251 openid 구현하는 사람 없음? 앞으로 개인정보때문에 openid [1] ㅇㅇ(58.76) 19:58 22 0
2716250 미래학자 ㅈ병신련아 프갤러(106.102) 19:53 20 0
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2