我在C中做了一个小游戏。我尝试使用函数指针以面向对象的方式编程 我真的很想这次推进而不是过于夸张,我经常迷失在这里。使用普通的旧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);
此外,对于每个游戏状态,我都有以下内容:
menu.c
struct MenuModel menu_model; /* The singleton 'menu' model */
game.c
struct GameModel game_model; /* The singleton 'game' model */
..这是在整个程序执行期间保留在堆上的全局数据片段。当然,这些领域通常包括指向动态记忆的指针,当游戏状态发生变化时,这些指针会随着内容的变化而变化 起初我觉得这很疯狂,我开始喜欢它。但是,如果链接另一个也具有“menu_model”符号的.o,则可能会导致名称空间冲突。
第一个问题:这是疯了吗,有没有更好的办法做这样的事情?人们通常做些什么来避免这些可能的符号名称冲突?
第二个问题是我必须在一个包含以下类型函数的源文件/目标文件中使用“extern ..”重新发布不同的... _ 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有点新,虽然我真的很喜欢编程,但我想知道我是否正确使用它。
答案 0 :(得分:2)
一些非小游戏使用类似的范例。我想到的第一个例子是Neverball 你可能想下载它的源代码(它是一个OpenSource游戏)并看看它们是如何做的。
我个人认为你应该检查C ++。我曾经只使用过C,就像你在做的那样,直到几年前;然后我发疯了(主要是因为名字冲突),转而使用C ++让我发现了一个新的世界。无论如何,我理解你可能出于多种原因想要避免它。
关于与您的menu_model
类似的对象,其名称与其他C源文件中的其他menu_model
冲突,您应该将它们声明为static
:
static struct MenuModel menu_model; /* The singleton 'menu' model */
menu_model
将在其声明的C源文件中可见(您将无法在其他C源文件中使用它,甚至不能在extern
中使用它),并且name不会与其他C static
变量冲突,并且在其他C源文件中声明的名称相同。
关于第二个问题,没什么可做的。必须声明您使用的函数和变量。
答案 1 :(得分:2)
我有点困惑,但我认为你不应该需要所有那些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,从而可以删除一些switch语句。
至于全局变量 - 你没有提供很多细节,但避免全局菜单的方法是在本地创建一个高级别,然后将其传递给任何需要它的人。如果您认为自己更喜欢全局,那么如果它在TU之外可见,则必须给它一个唯一的名称。