2010-11-24 1 views
2

C로 약간의 게임을 만들고 있습니다. 함수 포인터를 사용하여 객체 지향 방식으로 프로그래밍하려고합니다.
나는이 시간을 앞당기 고 지나치게 일반화하기를 과장하지 않으려 고 종종 이것을 잃어 버렸다. 평범한 구식 C를 사용하면 프로그램을 더 빠르고 더 잘 할 수있었습니다. 당신이 또는이 스타일을 감사하지 않을 수 있지만심볼 충돌을 피하면서 C로 일반/OO와 유사한 프로그래밍

/* macros */ 

#define SETUP_ROUTINE(component) component##_##setup_routine 
#define DRAW_ROUTINE(component) component##_##draw_routine 
#define EVENT_ROUTINE(component) component##_##event_routine 
#define UPDATE_ROUTINE(component) component##_##update_routine 
#define TEARDOWN_ROUTINE(component) component##_##teardown_routine 

#define SETUP_ROUTINE_SIGNATURE void 
#define DRAW_ROUTINE_SIGNATURE void 
#define EVENT_ROUTINE_SIGNATURE SDL_Event evt, int * quit 
#define UPDATE_ROUTINE_SIGNATURE double t, float dt 
#define TEARDOWN_ROUTINE_SIGNATURE void 

/* data */ 

typedef enum GameStateType { 
    GAME_STATE_MENU, 
    GAME_STATE_LEVELSELECT, 
    ... 
} GameStateType; 

typedef struct GameState { 
    GameStateType state; 
    GameStateType nextState; 
    GameStateType prevState; 

    void (*setup_routine)(SETUP_ROUTINE_SIGNATURE); 
    void (*draw_routine)(DRAW_ROUTINE_SIGNATURE); 
    void (*event_routine)(EVENT_ROUTINE_SIGNATURE); 
    void (*update_routine)(UPDATE_ROUTINE_SIGNATURE); 
    void (*teardown_routine)(TEARDOWN_ROUTINE_SIGNATURE); 

} GameState; 

, 내가 좋아하는 성장 하 고 그것을이 작은 (개인에 지금까지 저를 잘 제공 :

은 현재 내가 사용하는 "게임 주"에 대해 설명합니다. .) 프로젝트.

예를 들어, 한 게임 상태에서 다른 게임 상태로 간단히 전환하는 "전환"게임 상태가 있습니다. 각 게임 상태에 대해 내가 좋아하는 일이,

extern GameState GAME; /* The 'singleton' "game" */ 

extern void menu_setup_routine(SETUP_ROUTINE_SIGNATURE); 
extern void menu_draw_routine(DRAW_ROUTINE_SIGNATURE); 
extern void menu_event_routine(EVENT_ROUTINE_SIGNATURE); 
extern void menu_update_routine(UPDATE_ROUTINE_SIGNATURE); 
extern void menu_teardown_routine(TEARDOWN_ROUTINE_SIGNATURE); 

extern void debug_setup_routine(SETUP_ROUTINE_SIGNATURE); 
extern void debug_draw_routine(DRAW_ROUTINE_SIGNATURE); 
extern void debug_event_routine(EVENT_ROUTINE_SIGNATURE); 
extern void debug_update_routine(UPDATE_ROUTINE_SIGNATURE); 
extern void debug_teardown_routine(TEARDOWN_ROUTINE_SIGNATURE); 

또한 : 나는 다른 게임을 함께 명시 연결할 때

그러나, 내가 좋아하는 추한 것들을 얻을

menu.c을

struct MenuModel menu_model; /* The singleton 'menu' model */ 

game.c

struct GameModel game_model; /* The singleton 'game' model */ 

.. 프로그램 실행 전반에 걸쳐 힙에 남아있는 글로벌 데이터 조각입니다. 물론 이러한 필드는 대개 동적 메모리에 대한 포인터로 구성되며 게임 상태가 변경 될 때 어떤 내용이 변경됩니다.
처음에는 이것이 제 정신이 아니니 나는 그것을 좋아하기 시작했습니다. 그러나 다른 ".o"가 링크되어있는 경우에도 "menu_model"기호가있는 경우 네임 스페이스 충돌이 발생할 수 있습니다.

첫 번째 질문 :이 정신 나간가? 이런 일을하는 더 좋은 방법이 있습니까? 이러한 가능한 심볼 이름 충돌을 피하기 위해 사람들은 보통 무엇을합니까?

두 번째 질문은 내가 다음과 같은 유형의 함수를 보유하고있는 하나의 소스 파일/오브젝트 파일에 사용 ..._ setup_routine/.. draw_routine/.. 기능 "통근을 .."다른를 다시 게시해야한다는 것입니다 :

void (*get_setup_routine(GameStateType state))(SETUP_ROUTINE_SIGNATURE) { 
    switch(state) { 
     case GAME_STATE_MENU: 
      return SETUP_ROUTINE(menu); 
      break; 
     case GAME_STATE_LEVELSELECT: 
      return SETUP_ROUTINE(level_select); 
      break; 
     default: /* ... */ break; 
    } 
} 

그렇지 않으면 컴파일 할 때 심볼 "menu_setup_routine"을 알 수 없기 때문에.

어쨌든, 어떤 조언도 환영합니다. 저는 C에 조금 익숙합니다. 프로그래밍을 정말 좋아하지만,이 경우 제대로 사용하고 있는지 궁금합니다.

답변

2

일부 비 - 소형 게임은 유사한 패러다임을 사용합니다. 내 마음 속에 떠오르는 첫 번째 예는 Neverball입니다.
소스 코드 (OpenSource 게임)를 다운로드하고 어떻게 진행되는지 볼 수 있습니다.

개인적으로 나는 C++을 점검해야한다고 생각합니다. 나는 C 만 사용 했었습니다. 여러분이하고있는 방식으로 몇 년 전까지 만 사용 했었습니다. 그때 나는 (주로 이름 충돌 때문에) 미쳐 버렸고 C++로 전환하면 새로운 세계를 발견하게되었습니다. 어쨌든 나는 당신이 여러 가지 이유로 그것을 피하기를 원한다는 것을 이해합니다.menu_model는 C 소스 파일에서 볼 수

static struct MenuModel menu_model; /* The singleton 'menu' model */ 

그건 : objecst 소개


menu_model, 그 다른 C 소스 파일에서 다른 menu_model와 이름 충돌, 당신은 단지 static로를 선언해야 좋아 (다른 C 소스 파일에서는 사용할 수 없으며 extern을 사용하지 않아도됩니다.) 그 이름은 다른 C 소스 파일에 선언 된 동일한 이름을 가진 다른 static 변수와 충돌하지 않습니다.


두 번째 문제는별로 할 일이 아닙니다. 사용하는 함수 및 변수는 선언해야합니다.

+0

감사합니다. Neverball 소스는 매우 흥미 롭습니다. 나는 C++을 피할 수없이 피하고, 과거에는 전문적으로 사용했고, 지금은 지루해하고 있으며, 컴퓨터에서 코드가 어떤 모습인지 반각 쯤 알 수 없다. (이것은 C++의 잘못이 아니라 지식에 대한 지식이 부족하다.). – buddhabrot

2

저는 조금 혼란 스럽지만, 외부 연결을 위해 모든 사람들이 필요하다고 생각하지 않습니다. menu_setup_routine 등등. 대신, 각 루틴에 대해 하나의 함수 포인터를 포함하는 struct game_vtable을 정의한 다음 "메뉴"와 "디버그"각각에 해당 구조체의 인스턴스에 대한 액세스를 제공하십시오. 구성 요소에 함수를 호출하려면, 당신은 뭔가를 할 :

// vtable is a global symbol 
component##_##vtable.setup 

또는

// vtable is acquired from a function 
component##_##getvtableptr()->setup 

또는 당신은 당신의 GameStateType 대신에 매개 변수로 주위의 vtable 포인터를 전달할 수 있고, 어쩌면하여 제거 일부 스위치 문.

글로벌에 관해서는 세부 사항을 많이 제공하지 않지만 글로벌 메뉴를 피하는 방법은 로컬에서 높은 수준으로 로컬 메뉴를 만든 다음이를 필요로하는 모든 사람에게 전달하는 것입니다. 글로벌을 선호한다고 결정하면 TU 밖에서 볼 수있는 고유 한 이름을 지정해야합니다.

+0

고마워, 나는 이것을 생각하지 않았다. 나도 언급 한 Neverball 소스 peoro가 함수 포인터를 사용하여 비슷한 고정 구조를 사용한다는 것을 알았습니다. 왜 내가 그렇게하지 않았는지 모르겠다. – buddhabrot

+0

"vtables"를 전역으로 만들지 마십시오. 대신 포인터를 주위에 전달하거나, 절름발이가되고 싶으면 현재 변수에 대한 포인터를 전역 변수에 저장하십시오. 그런 다음'current_context-> setup();'과 같은 일을 할 수 있습니다. –

+0

@R .. : 실제로, 전역의 사용에 관해서 여기 보통의 경악을 삽입하십시오. vtable이 const 인 경우 전역에있어 특별히 문제가 있다고 생각하지 않습니다. C++에는 전역 범위의 클래스가 있으며 벨로시 랩터는 없습니다. 전역 적이 지 말아야 할 것은 반세기적인 일반적인 이유 때문에 "현재"함수를 포함하는 가변적 인 구조체입니다. –