HL2源码分析(框架篇)

fohoo 2003-10-23 02:16:35
1.程序入口 作者:cproom (cproom)
-------------------------------------------------------------------------
程序入口在launcher_main\main.cpp中,装载launcher.dll

HINSTANCE launcher = LoadLibrary("launcher.dll");

定位并执行LauncherMain函数

LauncherMain_t main = (LauncherMain_t)GetProcAddress( launcher, "LauncherMain" );
return main( hInstance, hPrevInstance, lpCmdLine, nCmdShow );

LauncherMain函数完成部分初始化并进入游戏主函数Run

IEngineAPI *engineAPI = ( IEngineAPI * )LauncherFactory( VENGINE_LAUNCHER_API_VERSION, NULL );
if ( engineAPI )
{
restart = engineAPI->Run( ( void * )hInstance, GetBaseDirectory(), LauncherFactory );
}

最关键的初始化函数是LoadAppSystems(),此函数查找定位各模块(.dll)的CreateInterface函数,这是各模块创建接口实例的工厂函数。此工厂函数的实现依赖于InterfaceReg类和与之配合的几个宏EXPOSE_INTERFACE_FN,EXPOSE_INTERFACE,EXPOSE_SINGLE_INTERFACE_GLOBALVAR,EXPOSE_SINGLE_INTERFACE创建的接口数据库来查找并创建接口。向接口数据库中加入一个接口实例的代码类似

static CEngineClient s_VEngineClient;
IVEngineClient *engineClient = &s_VEngineClient;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineClient, IVEngineClient, VENGINE_CLIENT_INTERFACE_VERSION, s_VEngineClient );

每个模块都有独立的接口数据库,在模块载入时(LoadLibrary)由InterfaceReg的构造函数生成并链接起来。

有了这些工厂函数,就可以通过LauncherFactory函数查询需要的接口实例了。
上面用到的接口实例engineAPI就是这样得到的。

IEngineAPI *engineAPI = ( IEngineAPI * )LauncherFactory( VENGINE_LAUNCHER_API_VERSION, NULL );

这里说不太清楚,大家对照原码就清楚了。欢迎大家继续!


2.模块划分 作者:akun(疑无路)
--------------------------------------------------------------------------
我来说说hl2的模块划分, 每个模块中的接口(其他模块需要使用的)静态实例形成一个单向链表,对外暴露一个接口Factory, 通过这个Factory, 获得各个接口的实例指针。(每个模块dll都有一个CreateInterface的导出函数,通过这个函数获得接口Factory。接口单向链表的查找通过一个预定义的字符串,通过这个字符串还可以实现接口版本控制。具体大家可以参见
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineClient, IVEngineClient, VENGINE_CLIENT_INTERFACE_VERSION, s_VEngineClient );
这个宏。
)

下面就是5大模块了~
1. FileSystem.(Filesystem_stdio.dll)功能肯定是文件系统的包装了,打包解包咯~

2. MaterialSystem. (materialsystem.dll)这里是渲染的实现,包装了dx8,dx7等实现,包括是否可以使用shader, 以及相应的渲染方式。

3. Engine. (Engine.dll)这里就太庞大了,它的接口单向链表里面就有:CEngineTraceServer, CModelRender, CModelInfoServer, CModelInfoClient, CNetworkStringTableContainerClient,CNetworkStringTableContainerServer, CStaticPropMgr, CVEnginServer, CEngineSoundClient, CEnginSoundServer, CShadowManager, CVRednerView等等。太复杂,看名字可以知道大概干吗的,具体一点就不知道了~~^_^。

4. VGui2.(vgui2.dll)
5. vguimatsurface.(vguimatsurface.dll)这2个应该是gui了~


下面是游戏的具体实现了,使用client/server模式,这个模式具体什么样,还没看懂...
client.dll 在源码cl_dll那个目录里面。
server.dll 在hl那个目录。
这2个dll里面要调用上面的几个模块里面的接口来实现n多的功能的...
Tf2, hl2, hl1, cs的游戏规则都在这里实现,譬如Npc, 武器效果。


其他还有一些零散的模块。
tier0(tier0.dll),这个是比较底层的东东了,处理debug信息,时间,内存管理,游戏的录像,播放也在这里。
vphysics. 物理哦~~ivp目录里面是Ipion Virtual Physics SDK,有详细文档使用说明哦。
.......

下面大家补充。

请大家不要mark,不要up这些无意回复在里面。

3.游戏循环 作者:fohoo
--------------------------------------------------------------------------
hl2.exe -> laucher.dll -> engine.dll (swds.dll)

到了engine.run(),然后调RunListenServer
RunListenServer:
{
...
game->init //建主窗口 CreateMainWindow
<Message Loop> //消息泵
}

<Message Loop>
{
...
game->DispatchAllStoredGameMessages()//发布消息给Gui的Controls,进行回调
eng->frame(); //游戏设计中常见的Frame动作
...
}

eng->frame
{
调CHostState::FrameUpdate( float time, int iState ) //状态机的状态循环
{
...
//看代码很清楚了
switch( m_currentState )
{
case HS_NEW_GAME:
State_NewGame();
break;
case HS_LOAD_GAME: //
State_LoadGame();
break;
case HS_CHANGE_LEVEL_MP:
State_ChangeLevelMP();
break;
case HS_CHANGE_LEVEL_SP:
State_ChangeLevelSP();
break;
case HS_RUN:
State_Run( time, iState );
break;
case HS_GAME_SHUTDOWN:
State_GameShutdown();
break;
case HS_SHUTDOWN:
State_Shutdown();
break;
case HS_RESTART:
State_Restart();
break;
}
...
}

State_Run( time, iState )
{
...
Cbuf_execute //当你输入map test时,先放入buffer,然后在这execute
...
}

hl2有一个重要ConCommand的类,可扩充要执行的命令

GUI的很多控件操作也就是将Command串放入command buffer中


每个Game有不同的server.dll,cient.dll
server.dll 设计场景,NPC..
client.dll 玩家的控制,...研究中..

当new game时

engine.dll会回调server.dll中IServerGameDLL接口GameInit来给
每个GAME初始化的机会

回调IServerGameDLL中的LevelInit装BSP地图()

然后回调IServerGameDLL的ServerActivate来启动GAME

...研究...待续...

个人理解,不对之处,敬请指正

大家一起分享提高吧,早日成就中国的CS!!
...全文
321 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
Robin 2003-12-15
  • 打赏
  • 举报
回复
好,不错!继续!
fohoo 2003-12-15
  • 打赏
  • 举报
回复
up
EnigmaXJ 2003-10-29
  • 打赏
  • 举报
回复
整体的结构已经很清楚的了。
一个进行全局模块管理的链表,对几大模块进行管理。
模块通过InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName )进行注册;
取得接口通过void* CreateInterface( const char *pName, int *pReturnCode )进行;
取得模块则是通过LauncherFactory函数;
它的类继承关系实在是复杂,YangLin1ST(杨家枪法第六十七代传人)兄给了我好的启发,多谢!

它在整个程序的运行结构上也就没有太多的特殊地方了。
它一个游戏循环之间,到底做了哪些事情呢?
fohoo 2003-10-29
  • 打赏
  • 举报
回复
没什么人,看来没什么人有兴趣啊

还想抛砖引玉的..

只好自己研究了..



YangLin1ST 2003-10-23
  • 打赏
  • 举报
回复

这几天看一下,有个大体的印象。我认为HALFLIFE2是以物体作为基本单元来组织管理整个场景的,这个基本单元就是 C_BaseEntity,这和 QUAKE2,3不同,QUAKE 将场景和物体分开作为两种截然不同的东西来对待。HALFLIFE2是使用物体作为基本单元的引擎,其中的基本单位是物体,(虽然其地图中的一些物体不参与交互动作,被简化后就“相当于”场景对象了,不过很明显他们还是有本质的不同),这个物体里面包含了各种信息和功能,比如玩家就是这样一个类,它包含了自己渲染的信息,物理模拟的信息,消息处理的机制。真正的引擎框架主要是起一些管理和发送消息,协调各个物体之间关系的功能。

观察其结构,发现大量使用了 继承+组合的方法来架构整个系统,比较意外的是比较多的使用了多重继承的方式。按照一般的观点,应该优先使用组合的方式,这样可以节省很多由虚函数带来的开销。当然,按照面向对象编程的要求,这样多重继承似乎是应该的,不过如果继承的深度太大,大量的多重继承的存在,其中巨大的开销必定会给游戏的运行带来不可忽视的负面影响。这个从它运行时对CPU的需求很高就可以看出来,当然,其中复杂的碰撞检测,物理模拟也是一个方面,另外对骨骼动画的物体使用IK分析也是一个巨大的计算量。VALVA也注意到了这个问题,因此他使用了类似于MFC的消息影射管理机制,甚至还自己实现了对象永久化和ISKINDOF()的判断,和MFC的设计简直就是如出一辙。从效率上来看我们希望类的层次尽可能平坦,最好是连类和继承也不使用,使用C这样的结构化编程来实现引擎(就象ID提倡的那样,显然不是谁都这么有种),这样的效率无疑要高。从工程上讲,我们需要清晰的继承和组合的关系,这样开发起来就舒服得多。

由于里面继承-组合层次比较深,所以代码看起来不是很舒服,尤其是我们这种没有编译就看代码的这种情况。在服务器端和客户端物体的功能是有一些不同的,HARFLIFE2用了一种灵活的办法,主要是在编译上做手脚,使统一个类可以在客户端和服务器端使用,多数功能相同,少数功能不同。

此外,HARFLIFE2 对于面向对象的概念,真的是非常遵守。每一种武器,到最后都派生出一个具体的类和它对应。个人以为这样不是很好,我在这里更倾向于使用脚本或者说就是简单的属性配置。你看其中有一把AK47,枪口跳动是由具体的函数来处理的,而且各种参数是以常数的形式给出在程序的源代码中:

void CAK47::AK47Fire( float flSpread, float flCycleTime )
{
if ( !CSBaseGunFire( flSpread, flCycleTime ) )
return;

CCSPlayer *pPlayer = GetPlayerOwner();

if (pPlayer->GetAbsVelocity().Length2D() > 0 )
KickBack ( 1.5, 0.45, 0.225, 0.05, 6.5, 2.5, 7 );
else if ( !FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) )
KickBack ( 2, 1.0, 0.5, 0.35, 9, 6, 5 );
else if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING ) )
KickBack ( 0.9, 0.35, 0.15, 0.025, 5.5, 1.5, 9 );
else
KickBack ( 1, 0.375, 0.175, 0.0375, 5.75, 1.75, 8 );
}
这样如果软件发布后,想修改似乎也不是一件容易的事情,如果想增加或者减少一种武器,也不是很方便。当然这只是个人观点,呵呵。

对了,那个叫 3Dgame 的朋友,我的电脑怎么也不能给你回信息,不如你直接和我聊吧,QQ 3750333


cnpeople 2003-10-23
  • 打赏
  • 举报
回复
嗯,不错!实话说我不是做游戏的,但我很感兴趣!研究的程度跟楼上的老兄差不多了!希望大家继续发表!
fohoo 2003-10-23
  • 打赏
  • 举报
回复
附录1:HL2的接口设计 作者:jinq0123(www.gameres.com) fohoo
-----------------------------------------------------------------------------
jinq0123 发表于: 2003-10-22 16:33:16

InterfaceReg链表用于CreateInterface()返回特定的接口。
但是InterfaceReg是如何初始化的?

看来起作用的只有EXPOSE_INTERFACE
#define EXPOSE_INTERFACE(className, interfaceName, versionName) \
static void* __Create##className##_interface() {return (interfaceName *)new className;}\
static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName );

如果我写一句:
EXPOSE_INTERFACE(CMyClass, IMyClass, "MyName");

应该宏扩展成:
static void* __CreateCMyClass_interface()
{
 return (IMyClass *) new CMyClass;
}
static InterfaceReg __g_CreateCMyClass_reg(__CreateCMyClass_interface, "MyName" );;

这样有什么用?
InterfaceReg是否因此添加了一项
{"MyName", __CreateCMyClass_interface}?
难道末尾的双分号不会出错?
第二个static语句定义了什么东西?

--------------------------------------------------------------------------------
jinq0123

Re:InterfaceReg链表是如何创建的?

static InterfaceReg __g_CreateCMyClass_reg(__CreateCMyClass_interface, "MyName" );;

定义了一个InterfaceReg变量__g_CreateCMyClass_reg,
构造函数就将自己添加入链表。
--------------------------------------------------------------------------------
jinq0123

Re:InterfaceReg链表是如何创建的?

// A single class can support multiple interfaces through multiple inheritance

But we can not do this in a file:
class CMy: public IOne, public ITwo
{
 ...
}
EXPOSE_INTERFACE(CMy, IOne, "One");
EXPOSE_INTERFACE(CMy, ITwo, "Two");

--------------------------------------------------------------------------------
fohoo 发表于: 2003-10-22 18:04:12

Re:InterfaceReg链表是如何创建的?

楼主分析不错

我贴点代码大家看看:

总的来就是一个InterfaceReg类(结构)的单向链表

s_pInterfaceReg是表头,一开始初始化为NULL,做为空表标识
然后每个InterfaceReg类实例在构造时,设置m_pNext = s_pInterfaceRegs(指向前一个InterfaceReg类实例
),再重置表头
s_pInterfaceRegr指向当前的InterfaceReg类实例

InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName ) :
m_pName(pName)
{
m_CreateFn = fn; //保存接口指针
m_pNext = s_pInterfaceRegs;
s_pInterfaceRegs = this;
}


void* CreateInterface( const char *pName, int *pReturnCode )
{
InterfaceReg *pCur;

//遍历链表
for(pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext)
{
if(strcmp(pCur->m_pName, pName) == 0)
{
if ( pReturnCode )
{
*pReturnCode = IFACE_OK;
}
return pCur->m_CreateFn(); //找到则返回注册的接口指针
}
}

if ( pReturnCode )
{
*pReturnCode = IFACE_FAILED;
}
return NULL;
}

8,302

社区成员

发帖
与我相关
我的任务
社区描述
游戏开发相关内容讨论专区
社区管理员
  • 游戏开发
  • 呆呆敲代码的小Y
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧