quake2服务端和客户端代码浅析

// 以下代码摘自quake2-v3.21版本. 

 

//-------------------------------------------------------------------------------

// 服务端由服务端引擎和Game模块组成(quake.exe game.dll)

// 服务端的帧循环,这儿将关键调用摘出.

//-------------------------------------------------------------------------------

SV_Frame

       // 处理客户端玩家命令

       SV_ReadPackets

              SV_ExecuteClientMessage

                     SV_ClientThink (cl, &newcmd);

                            // game模块针负责计算玩家在游戏世界的表现

                            ge->ClientThink (cl->edict, cmd);

       // game

       SV_RunGameFrame ();

              // 在game模块实现了游戏世界各对象与游戏世界的交互,包括物理交互

              G_RunEntity (ent);

              // 保存游戏对象帧相对信息

              ClientEndServerFrames

                     ClientEndServerFrame

                            G_SetClientFrame

 

       // 交服务器引擎负责将游戏对象信息以帧间差分形式发给客户端玩家

       SV_SendClientMessages

              SV_SendClientDatagram (c);

                     SV_BuildClientFrame (client);

                     SV_WriteFrameToClient (client, &msg);

                            SV_WritePlayerstateToClient (oldframe, frame, msg);

                            SV_EmitPacketEntities (oldframe, frame, msg); 

 

// 游戏逻辑模块的帧结构,下面是关键调用

SV_RunGameFrame ()

       // 计算游戏逻辑帧,一般100ms一次, 首选完成游戏对象和游戏世界的交互,

       // 然后运行游戏对象的AI, 最后更新游戏对象的状态

       G_RunEntity (ent)

              SV_RunThink (ent)

                     monster_think (edict_t *self)

                            M_MoveFrame (self);

 

//---------------------------------------------------------------------------------------------------

// 客户端由客户端引擎和ref_gl.dll模块组成(quake.exe ref_gl.dll)

// 下面是Q2的客户端引擎,主要完成对服务器发过来的数据帧解析,游戏

// 世界的渲染,游戏客户端的输入处理,向服务器的命令发送,游戏对象的

// 动画绘制。

//---------------------------------------------------------------------------------------------------- 

// 客户端的帧结构如下

CL_Frame

       // 解析服务器发过来的数据

       CL_ReadPackets ();

              CL_ParseFrame

                     // 根据上帧间差值更新游戏对象状态

                     CL_ParsePlayerstate (old, &cl.frame);

                     CL_ParsePacketEntities (old, &cl.frame);

       //  响应客户端操作,向服务端发送玩家自身动作命令

       CL_SendCommand ();      

       // 在这儿实现客户端平滑运动,采用预测方法不等服务器返回状态即更新自已状态,

       // 前提是在超时时间到达之前,否则帧会卡住..

       CL_PredictMovement ();

              //  玩家运动核心函数,调用引擎检测游戏对象和游戏世界间的碰撞和反应,

              //  是Q2的碰撞检测引擎的高级调用接口。

              Pmove

                     CL_PMTrace

                            // 最后分解为BOX和游戏世界的连续碰撞检测和碰撞反应.

                            CM_BoxTrace

                                   //  这儿是检测游戏对象间的碰撞。这儿的游戏对象主要指玩家,

                                   //  游戏对象的组织采用相当于KD树的组织方法,将游戏世界进

                                   //  行按平行坐标轴方向均匀划分,虽然在数据存储上利用了静态场景

                                   //  的BSP场景数据存储,但是确是一个动态的游戏对象组织方法,也

                                   //  是现今游戏普遍采用的方法(动态场景和静态场景分开管理)。在 每

                                   //  次可移动对象更新时先将其从场景树中取出(unlinkentity),更新完成

                                   //  后再linkentity(重新加入动态对象场景树)。

                                   CL_ClipMoveToEntities

       //--------------------------------------------------------------------------------------------------

       //  通过这个调用进行各种客户端的显示图像的绘制,主要是关于场景绘制,游戏对象的

       //  绘制。M2模型动画是关键帧动画。早期卡马克就想到了在Q2中采用3D的显示方法,

       //  即在显   示图像时采用绘制 视角相差15度的两幅视图,通 过 3D眼 镜  实现3D效果。

       //---------------------------------------------------------------------------------------------------

       SCR_UpdateScreen ();

              V_RenderView

                     CL_AddEntities

                            cl.lerpfrac = 1.0 - (cl.frame.servertime - cl.time) * 0.01;

                            CL_CalcViewValues ();

                            CL_AddPacketEntities (&cl.frame);

 

                     re.RenderFrame (&cl.refdef);

                            R_DrawWorld ();

                                   currentmodel = r_worldmodel;

                                   currententity = &ent;

                                   ent.frame = (int)(r_newrefdef.time*2);

                                   //------------------------------------------------------

                                   // 进行游戏世界的绘制

                                   // 这儿展示了BSP场景在场景渲染加速方面的使用方法,如果仔细

                                   // 分析会发现Q2的BSP场景在绘制时实现了back face culling(背

                                   // 面剔除), 而且几乎没有任何代价。而且可以做到从前往后渲染

                                   // 场景。当然,Q2场景渲染的速度快的方法功劳还是首推他的场

                                   // 景 PVS(场景可视的图形的集合)信息,此信息的预生成和优

                                   // 化存储和快速解码也是值的深入学习的。当然现在BSP的主要

                                   // 不是用在渲染方面,更多用在了碰撞检测方面,光线跟踪方面,

                                   // 这点也在Q2中展示出了具体的实现方法。当然下面只是渲染的

                                   // 部分内容。

                                   R_RecursiveWorldNode (r_worldmodel->nodes);

                           

                            // M2模型绘制, 精灵等的图元,老的M2关键帧动画作为学习动画

                            // 原理,动作插值入门资料还可以。

                            R_DrawEntitiesOnList ();

                                   currententity = &r_newrefdef.entities[i];

                                   currentmodel = currententity->model;

                                   R_DrawSpriteModel (currententity);

                                   R_DrawBrushModel (currententity);

                                          R_DrawInlineBModel ();

                                   R_DrawAliasModel (currententity);

                                          GL_DrawAliasFrameLerp (paliashdr, currententity->backlerp);

 

//--------------------------------------------------------------------------------------------

// 模型动画控制: 由于Q2采用关键帧动画,在服务器上按照服务器设定的帧速进行动画

// 的控制,默认为每帧100ms, 动画信息由各个monster文件单独标明。其中monster AI

// 也由该该文件标明

//---------------------------------------------------------------------------------------------

// 下面指出在Q2中当同时出现各种动画要求时的动画优先级

// 动作转换优先级: ANIM_DEATH -> ANIM_ATTACK -> ANIM_PAIN -> ANIM_JUMP ->ANIM_WAVE -> ANIM_BASIC

 

// 下面是各个动作单独摘要

ANIM_BASIC(默认动作, 在没有其它动画播放要求时的动画)

 

动画的更新:

// 先检测当前是否为其它几种动作,若没有做其它的动作,则目前只会是这stand 和run几// 种动作中的一种,根据xyspeed(水平移动速度)区分 stand 和 run。

G_SetClientFrame (edict_t *ent)

              client->anim_priority = ANIM_BASIC 

 

//  ANIM_WAVE(应该不是游泳的动作,而是从空中落到地上的缓冲动作,振动效果)

//  紧跟着ANIM_JUMP动作后的下一个动作

       G_SetClientFrame (edict_t *ent)

              ent->client->anim_priority = ANIM_WAVE; 

 

//  ANIM_JUMP(跳跃动作)

//  条件为(!ent->groundentity), 即是不是在空中,如果在空中漂浮则施展该动作

       G_SetClientFrame (edict_t *ent)

              client->anim_priority = ANIM_JUMP;

 

ANIM_PAIN(受伤动作, 换武器时的动作和受伤动作采用同一模型动作)

// 以下三种情况均可以导致该动画的播放

       P_DamageFeedback (edict_t *player)

       ChangeWeapon (edict_t *ent)

       G_SetClientFrame (edict_t *ent) // 由此处完成ANIM_PAIN的下一帧。

 

// 以下几种情况可以引起该动作的播放

ANIM_ATTACK(攻击动作)

       Weapon_Generic(...)

       weapon_grenade_fire (edict_t *ent, qboolean held)

       Weapon_HyperBlaster_Fire (edict_t *ent)

       Machinegun_Fire

       Chaingun_Fire (edict_t *ent)

       G_SetClientFrame (edict_t *ent) // 由此处完成ANIM_PAIN的下一帧。

 

//  以下几种情况可以引起该动作的播放

ANIM_DEATH(死亡动作)

       ThrowClientHead (edict_t *self, int damage))

       player_die(...)

       G_SetClientFrame (edict_t *ent) // 由此处完成ANIM_PAIN的下一帧。

 

//  以下几种情况可以引起该动作的播放

ANIM_REVERSE(主要用于枪械的反复动作)

       weapon_grenade_fire (edict_t *ent, qboolean held)

       Weapon_Generic(...)

       G_SetClientFrame (edict_t *ent) // 由此处完成ANIM_PAIN的下一帧。 

 

//------------------------------------------------------

// 物理系统分析

//-----------------------------------------------------

Q2的物理系统放在了game模块,即游戏逻辑模块,实现了几种最基本的物模运动模型

       MOVETYPE_PUSH:

       MOVETYPE_STOP:

       MOVETYPE_NONE:

       MOVETYPE_NOCLIP:

       MOVETYPE_STEP:

       MOVETYPE_TOSS:

       MOVETYPE_BOUNCE:

       MOVETYPE_FLY:

       MOVETYPE_FLYMISSILE: 

其中最重要的是MOVETYPE_STEP, 是玩家的物理运动模型,其实现为

SV_Physics_Step

  SV_FlyMove (ent, FRAMETIME, mask);

其中SV_FlyMove的实现是核心函数。是该函数实现了角色和世界间平滑的连续碰撞检测。其实现方法仍然对现在的游戏开发有很大的参考价值。是个很好的学习材料。

主要做下面工作:

检查是否在地面上

增加重力加速度

增加摩擦力(角速度,直线移动速度)

设定没有站在地面上,根据现在的速度向前运动,遇到障碍则延障碍滑动

 

下面是monster(怪物)的物理运动模型M_walkmove

详细实现在SV_movestep中:

物体总共三种类型
1)陆地行走的物体
2)空中飞行的物体
3)水中游动的物体

 

 可以成功移动的前提条件是满足下面之一:
1)物体在地面上
2)物体是可飞行的或可在水面游动的

 

对于陆地的物体:
检查如果满足下面的条件之一就禁止移动:
1)目的地在物体中
2)目的地是水中
3)前面是陡峭的地方且物体不允许离开地面

 

碰撞检测方法利用引擎提供的trace方法.值的注意的是怪物和人类玩家的运动方式是不一样的,也就是说怪物不会撞到墙上,再按墙面滑动,因为怪物发现前面距离墙面已经很近了,

以至于再走一步就撞墙了这样它就会停止向前运动.就防止撞墙了. 

 

总结:

       以上分析了Q2的几个模块内的帧循环调用顺序,和功能的摘要。Q2的清晰的组织结构,优秀的C语言编码风格。展现了计算机图形学在3D游戏中的灵活运用,到目前为止仍是游戏开发人员的极好的学习资料。

作者:Perit 整理于2010.6 

转载于:https://www.cnblogs.com/Perit/articles/1759577.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值