在前面的分析中,我们知道在quake2运行的函数框架中,最终每桢的执行会涉及到game(游戏逻辑)模块和ref_XXX(图形系统)模块的交互,这一节我们来看这两个模块的输出函数。
game模块的输出函数:
在文件game.h中,我们找到下面的代码:
//
// functions exported by the game subsystem,game系统的输出函数
//
typedef struct
{
int apiversion; //api版本
void (*Init) (void); //初始化game系统,分配全局数据
void (*Shutdown) (void); //关闭game系统
// each new level entered will cause a call to SpawnEntities, 加载新的地图后,展开地图中的实体
void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
void (*WriteGame) (char *filename, qboolean autosave); //写入游戏文件
void (*ReadGame) (char *filename); //读取游戏文件
void (*WriteLevel) (char *filename); //写入地图文件
void (*ReadLevel) (char *filename); //读取地图文件
qboolean (*ClientConnect) (edict_t *ent, char *userinfo); //客户端连接
void (*ClientBegin) (edict_t *ent); //客户端开始
void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); //客户端用户信息改变
void (*ClientDisconnect) (edict_t *ent); //客户端断开连接
void (*ClientCommand) (edict_t *ent); //客户端命令
void (*ClientThink) (edict_t *ent, usercmd_t *cmd); //客户端思考
void (*RunFrame) (void); //运行一桢
void (*ServerCommand) (void); //服务器端命令
//全局数据
struct edict_s *edicts; //game中的实体(游戏单元,具体看edict_s定义)
int edict_size; //实体数目
int num_edicts; // current number, <= max_edicts,当前实体数
int max_edicts; //最大实体数
} game_export_t;
game输出函数的实现:
在g_main.c的GetGameAPI函数中,我们找到了game模块输出函数的对应实现函数:
globals.apiversion = GAME_API_VERSION; //值为3
globals.Init = InitGame; //初始化了许多游戏参数和实体,客户端,预先分配了最大树木的实体和客户端
globals.Shutdown = ShutdownGame; //释放游戏和地图中的分配
globals.SpawnEntities = SpawnEntities; //一个地图包里包含实体(Entity),实体中可能需要展开物品(Item),展开某一类别的实体,调用实体的展开函数展开所有的物品,找到团队等。
globals.WriteGame = WriteGame; //将游戏状态和每个客户端写入文件 (game_locals_t和g_client_t)
globals.ReadGame = ReadGame; //读游戏状态和每个客户端
globals.WriteLevel = WriteLevel; //将地图数据和每个实体写入文件 (level_locals_t和edict_t)
globals.ReadLevel = ReadLevel; //读地图数据和每个实体
globals.ClientThink = ClientThink; //复杂的函数,客户端思考,根据状态进行移动,计算移动中和其他实体的碰撞,武器等
globals.ClientConnect = ClientConnect; //建立连接,从game.clients中分一个给edict.client
globals.ClientUserinfoChanged = ClientUserinfoChanged; //更新客户端信息
globals.ClientDisconnect = ClientDisconnect; //客户端断开连接,广播其他客户端它的断开,重置它的edict结构
globals.ClientBegin = ClientBegin; //客户端开始,移动?等待展开(spawn)?新的连接?调用ClientEndServerFrame(客户端:结束服务器端桢)
globals.ClientCommand = ClientCommand; //解析并执行客户端的命令
globals.RunFrame = G_RunFrame; //在客户的服务器端每桢中,游戏逻辑运行的主函数,我们将稍候讨论这个函数
globals.ServerCommand = ServerCommand; //解析并执行服务器端命令
G_RunFrame简述:
服务器端每桢中都运行哪些游戏逻辑呢?让我们看一下:
AI_SetSightClient (); //在这一桢中,选择一个客户端作为怪兽攻击的目标
对每个实体:调用G_RunEntity //根据每个实体的状态,应用相应的物理系统移动每个实体
ClientEndServerFrames (); //将要结束服务器端每桢了,还有许多工作要做:
对每个连接的客户端:
更新移动的状态;
P_WorldEffects (); //应用岩浆,水等效果
更新视角;
计算速度;
P_FallingDamage (ent); //是否打到地面?损坏地面?
P_DamageFeedback (ent); //应用这一桢所有的损坏效果
SV_CalcViewOffset (ent); //计算视角偏移
SV_CalcGunOffset (ent); //计算枪支偏移
SV_CalcBlend (ent); //在视角偏移后重新计算颜色混合,确定看到的内容
G_SetClientEvent (ent); //设置客户端事件
G_SetClientEffects (ent); //设置客户端效果
G_SetClientSound (ent); //设置客户端声音
G_SetClientFrame (ent); //设置客户端桢数
更新统计数据。
这些函数都会影响到客户端的实体(也就是ent,一个edict_s结构体),最终客户端每桢会用到,所以游戏每桢的运行可以总结为:
服务器端每桢处理游戏的整个逻辑,调用上层game模块,
客户端每桢处理游戏的图形渲染效果,调用下层的ref_gl或ref_soft模块。
下一节,我们分析ref_XXX模块。
浙公网安备 33010602011771号