Общее / ОО-подобное программирование на C, избегая столкновений символов - PullRequest
2 голосов
/ 24 ноября 2010

Я делаю небольшую игру на C. Я пытаюсь программировать объектно-ориентированным способом, используя указатели функций.
Я действительно хотел продвинуться вперед на этот раз и не переусердствовать, делая вещи слишком общими, я часто теряюсьв этом.Использование старого доброго Си очень помогло мне в программировании быстрее и лучше.

В настоящее время я описываю «Состояния игры», используя:

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

Кроме того, для каждого игрового состояния у меня есть такие вещи, как:

меню.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 / .., используя "extern .." в одном из них.исходный файл / объектный файл, который содержит следующие типы функций:

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 ]

2 голосов
/ 24 ноября 2010

Я немного сбит с толку, но я не думаю, что вам нужны все эти menu_setup_routine и так далее, чтобы иметь внешнюю связь.Вместо этого определите struct game_vtable, содержащий один указатель на функцию для каждой подпрограммы, а затем разрешите каждому из «menu» и «debug» предоставить доступ к экземпляру этой структуры.Чтобы вызвать функцию компонента, вы должны сделать что-то вроде:

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

или

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

или вы можете передавать указатели vtable в качестве параметров вместо вашего GameStateTypeтем самым избавьтесь от некоторых ваших операторов переключения.

Что касается глобальных параметров - вы не предоставляете много деталей, но способ избежать глобального меню - это создать его локально, на высоком уровне,и затем передайте это любому, кто нуждается в этом.Если вы решите, что предпочитаете глобальный объект, вы должны дать ему уникальное имя, если он будет виден вне его TU.

2 голосов
/ 24 ноября 2010

Некоторые немелкие игры используют похожую парадигму . Первый пример, который приходит мне в голову: Neverball .
Возможно, вы захотите скачать его исходный код (это игра с открытым исходным кодом) и посмотреть, как они работают.

Лично я думаю, что вы должны проверить C ++. Раньше я использовал только C, также как и вы, несколько лет назад; затем я сошел с ума (в основном из-за столкновений имен), и переключение на C ++ заставило меня открыть новый мир. В любом случае, я понимаю, что вы можете избежать этого по ряду причин.


Об объекте, подобном вашему menu_model, чье имя конфликтует с другими menu_model в других исходных файлах C, вы должны просто объявить их как static:

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

То, что menu_model будет видно в исходном файле C, в котором он объявлен (вы не сможете использовать его в других исходных файлах C, даже при extern его использовании), а его имя не будет Столкновение с другими static переменными с тем же именем, объявленным в других исходных файлах Си.


По второму вопросу делать особо нечего. Используемые функции и переменные должны быть объявлены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...