2010-11-24 7 views
2

Ich mache ein kleines Spiel in C. Ich versuche, objektorientiert mit Funktionszeigern zu programmieren.
Ich wollte diese Zeit wirklich vorantreiben und nicht übertreiben, Dinge zu generisch zu machen, ich verliere mich oft darin. Die Verwendung von einfachem altem C hat mir sehr geholfen, schneller und besser zu programmieren.Generische/OO-ähnliche Programmierung in C unter Vermeidung von Symbolkonflikten

Derzeit beschreibe ich „Game states“ mit:

/* 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; 

Während Sie diesen Stil nicht schätzen kann oder nicht, ich habe es gewachsen zu mögen und es dient mir bisher gut auf dieser kleinen (privat. .) Projekt.

Ich habe zum Beispiel einen "Übergang" Spielzustand, der einfach von einem Spielzustand zum anderen übergeht.

Allerdings, wenn ich die verschiedenen Spiel-Verbindungszustände zusammen, ich hässliche Dinge wie:

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); 

Auch für jedes Spiel Zustand Ich habe Dinge wie:

menu.c

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

game.c

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

..welche globale Daten sind, die während der Ausführung des Programms auf dem Heap verbleiben. Natürlich bestehen die Felder davon in der Regel aus Hinweisen auf dynamischen Speicher, die sich ändern, wenn sich die Spielzustände ändern.
Während ich zuerst dachte, das wäre verrückt, fing ich an, es zu mögen. Es kann jedoch zu Namespace-Konflikten führen, wenn ein anderes .o verknüpft ist, das ebenfalls ein solches "menu_model" -Symbol hat.

Erste Frage: Ist das verrückt, gibt es eine bessere Möglichkeit, solche Dinge zu tun? Was tun die Leute normalerweise, um diese möglichen Symbolnamenkonflikte zu vermeiden?

Zweite Frage ist, dass ich die verschiedenen ..._ setup_routine/.. draw_routine/.. Funktionen zu veröffentlichen haben „extern ..“ in der eine Quelldatei/Objektdatei, die die folgenden Arten von Funktionen gilt:

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; 
    } 
} 

Weil sonst beim Kompilieren das Symbol "menu_setup_routine" nicht bekannt ist.

Wie auch immer, jeder Rat ist willkommen, ich bin ein bisschen neu in C und obwohl ich wirklich gerne darin programmiere, frage ich mich, ob ich es in diesem Fall richtig benutze.

Antwort

2

Einige nicht-kleine Spiele verwenden ähnliche Paradigma. Das erste Beispiel, das mir in den Sinn kommt, ist Neverball.
Sie möchten vielleicht den Quellcode herunterladen (es ist ein OpenSource-Spiel) und sehen, wie es ihnen geht.

Persönlich denke ich, dass Sie C++ überprüfen sollten. Ich habe nur C benutzt, auch in der Art, wie du es machst, bis vor ein paar Jahren; Dann wurde ich verrückt (hauptsächlich wegen Namenskonflikten), und der Wechsel zu C++ brachte mich dazu, eine neue Welt zu entdecken. Jedenfalls verstehe ich, dass Sie es aus einer Reihe von Gründen vermeiden wollen.


über objecst für Ihre menu_model, deren Namenskonflikte mit anderen menu_model in anderen C-Quelldateien, sollten Sie nur erklären, sie als static:

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

Die menu_model in der C-Quelldatei sichtbar sind Es wird deklariert (Sie können es nicht in anderen C-Quelldateien verwenden, nicht einmal von extern), und sein Name wird nicht mit anderen static Variablen mit demselben Namen kollidieren, die in anderen C-Quelldateien deklariert sind.


Über die zweite Ausgabe gibt es nicht viel zu tun. Funktionen und Variablen, die Sie verwenden, müssen deklariert werden.

+0

Danke, Neverball Quelle ist sehr interessant. Ich vermeide bewusst C++, ich benutze es professionell in der Vergangenheit und mir wird es jetzt langweilig, ich weiß auch nicht, wie der Code in der Hälfte der Zeit in der Maschine aussieht (das ist nicht C++ sondern mein Unwissen). – buddhabrot

2

Ich bin ein bisschen verwirrt, aber ich glaube nicht, dass Sie alle diese und so weiter benötigen, um externe Verknüpfung zu haben. Definieren Sie stattdessen einen struct game_vtable, der einen Funktionszeiger für jede Routine enthält, und lassen Sie dann jedes von "menu" und "debug" Zugriff auf eine Instanz dieser Struktur bereitstellen. Um eine Funktion auf einer Komponente aufrufen, tun Sie so etwas wie:

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

oder

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

oder Sie können VTable-Zeiger um als Parameter anstelle Ihres GameStateType passieren, und vielleicht dadurch loswerden einige deiner switch statements.

Wie bei den Globals - Sie bieten nicht viele Details, aber die Möglichkeit, ein globales Menü zu vermeiden, besteht darin, eines lokal auf hoher Ebene zu erstellen und es an jeden weiterzugeben, der es benötigt. Wenn Sie sich entscheiden, dass Sie das globale bevorzugen, müssen Sie ihm einen eindeutigen Namen geben, wenn es außerhalb seiner TU sichtbar sein soll.

+0

Danke, daran habe ich nicht gedacht. Ich habe auch bemerkt, dass die Neverball Quelle Peoro erwähnt eine ähnliche feste Struktur mit Funktionszeigern verwendet. Ich weiß nicht, warum ich es nicht so gemacht habe. – buddhabrot

+0

Machen Sie die "VTables" nicht global. Stattdessen Zeiger auf sie weitergeben, oder wenn Sie lahm sein möchten, speichern Sie den Zeiger auf den aktuellen in einer globalen Variablen. Dann können Sie Dinge wie 'current_context-> setup();' usw. tun. –

+0

@R ..: tatsächlich, fügen Sie hier übliche Bestürzung über die Verwendung von Globals ein. Wenn die Vtables const sind, dann glaube ich nicht, dass irgendetwas besonders falsch daran ist, dass sie global sind - C++ hat Klassen im globalen Umfang und keine Velociraptoren. Was nicht global sein sollte, ist eine veränderbare Struktur, die die "aktuellen" Funktionen für die üblichen anti-globalen Gründe enthält. –

Verwandte Themen