doom3 代码结构

doom3代码结构 code

原文地址  https://www.iddevnet.com/doom3/code.php

原来 我认为这个网站已经倒闭了,毕竟从Doom4,quake4是2004,2005的,关注的人太少了 ,我觉得这些文章有助于对于doom3,代码的理解,

比单纯的看源代码要好的多了

idcevNet是介绍doom3和quake4的Mod关卡制作的网站,我以前翻阅时候,网站还在....闲杂不知道......... 

但是我也没有太多的时间来翻译这个,我看谷歌翻译的质量可以,

所以我是用谷歌翻译再加上我自己的修正,来进行翻译了,

译文如下

该守则是所有 真正的乐趣 开始 发生 的 地方。

Doom 3 SDK拥有相当于游戏源代码的一半。我将简要介绍 SDK 中 的 主要部分,

并触摸 主引擎 中 的一些 子系统。

编译

您将需要做的第一件事是确保您可以构建和运行代码。

我们使用Visual Studio.Net开发它,它可能或可能不工作在6.0。

理想情况下,您只需双击解决方案文件并点击构建即可。

它将在输出文件夹中创建一个gamex86.dll。

您将要使用批处理文件,后期制作步骤或仅通过更改链接器输出目录

将其移动到您的mod文件夹。

测试您的代码是否正在使用的一种快速方法是通过打开Entity.cpp,

滚动idEntity :: Spawn并在“if

(def)”行之后的开头插入此代码:

gameLocal.Printf(“Hello%s!\ n”,classname);

这应该导致“Hello <classname>!”随时在新的实体中出现在控制台中。

idlib

idLib只是一个存储随机,通用,经常使用的代码的库。它包含字符串代码,解析器,几个哈希表,

数学库,计时器,容器等等。它也是存储所有SSE代码的地方,所以检查idLib / Math / Simd _ *。

*如果你喜欢这样的事情。

游戏系统

游戏代码有很多部分与Doom 3没有任何关系,而是基于游戏代码构建的基础。

GameSys


游戏代码的核心是GameSys。这包含了基本的idClass类,以及事件系统和保存游戏系统。

游戏系统是非常抽象的,并没有真正了解任何建立在其上的实际游戏。

它知道如何做是创建对象,消防事件,并走走对象进行保存和恢复。

当在entitydef中指定“spawnclass”时,它会调用idClass :: CreateInstance来创建该spawn类

的实例。类通过使用Class.h中定义的CLASS_DECLARATION宏来注册到idClass

AI


AI文件夹实际上并不包含怪物的AI(在脚本中定义)。

相反,C ++ AI代码是将脚本与游戏的其余部分联系起来的“胶”。

它包含通过AAS(区域意识系统)的路由和路径,以及事件处理。

核心对象是idAI,它来源于

idActor-> idAFEntity_Gibbable-> idAFEntity_Base->

idAnimatedEntity-> idEntity-> idClass。

在Doom 3中,路由是“如何从地图的一个区域到另一个区域”,

而路径是“如何在该椅子周围导航”。差异是微妙但重要的。

AI是与正在生产的实际游戏最紧密的游戏系统。

一个例子是idAI_Vagary,它包含Vagary在播放器上拾取和抛出对象的特殊代码。

AI_events.cpp包含处理AI脚本中的事件的所有代码。

大多数函数只是调用AI.cpp的存根,其中代码的大部分是。

一个例子是Event_KickObstacles,它刚刚修复了参数并调用了KickObstacles()。

物理学


Doom 3游戏代码附带了整个Doom 3物理系统。

核心引擎本身绝对没有物理代码。

即使LCP解决方案都在idLib中。

物理代码绝对是巨大的,但幸运的是,您不需要在物理学博士中了解

其中的大部分内容,(更重要的是)如何使用它。

idPhysics 对象 是用于 操纵 实体 的 位置 和 方向 的 工具。

物理对象是用于碰撞检测的idClipModels的容器。

物理系统  根据 物理学规律  或 其他规则 处理通过 世界移动 这些 碰撞模型。

每个idEntity都有一个指向idPhysics对象的指针,该对象可以使用idForces移动,

尽管还有其他方法可以移动它。每一帧idEntity从物理系统的原点和轴信息更新其视觉模型。

渲染系统 具有每个 实体 的视觉模型,物理系统具有每个实体的剪辑模型(有时称为战斗模型)。

可以从地图模型(使用标记为剪辑的任何曲面)加载碰撞模型,或者可以从渲染模型生成

(默认情况下,它使用渲染模型中的所有曲面,但可以使用“纹理/普通/碰撞“应用它的材料,

在这种情况下,它使用该简化模型),或者它可以从.cm文件加载(使用DOOMEdit创建)。

idPhysics  有多个子类,它们都实现了专门的物理类型。

例如,门使用idPhysics_Parametric,抹布娃娃使用idPhysics_AF等等。

大多数  物理类型  都是自解释的,并且它们 都在.h文件的顶部有一个简短的描述。

如果您不希望在物体上运行物理,请确保TH_PHYSICS标志已被清除

(例如,当对象进入休息状态时)。如果不能触摸对象,而不是仅将内容掩码设置为0,

 则应该取消链接剪辑模型。这将加速碰撞检测,因为它甚至不会访问该剪辑模型。

你应该确保没有任何可能导致物理学不断运行在物体上的东西的小动作。

有几个控制台命令帮助调试物理减速。最有用的可能是g_showActiveEntities。

anim动画动画


系统不会决定要播放哪些动画(在脚本中完成),而是在动画中进行混合动画,帧之间的拖动,

混合骨骼权重和调用帧命令。

在Doom 3中,有4个不同的动画通道(“躯干”,“腿”,“头”,“眼皮”)以及第五个伪通道(“全”)。

每个动画漫画可以同时播放不同的动画。这样可以避免地震2“射击时出现”问题,

以及Quake3 “我的模式是碎片”的问题。

有一个单一的网格,但每个动画影响一组不同的骨头。

所有这些都在Anim_Blend中处理。它还可以处理两种完全不同的动画之间的混合

(例如当您在武器之间切换时)。

框架命令(在modelDef中定义)从ServiceAnims调用。

每次更新动画时,它会看到它是否通过了定义了frame命令的“边界”。

实际上真的很容易添加自己的帧命令。您只需要将一个项目添加到frameCommandType_t

枚举中,

然后在 AddFrameCommand  和  CallFrameCommands  中 实现 它 的 case 语句。

Script脚本

(很多我看源码,是根据Quake3的 Script 改进而来..只不过加入了c++的一些语法)


类似于Anim,脚本子系统是非常通用的,根本不知道Doom。

脚本系统的主要入口点是通过idProgram。

系统中只有一个程序(它是idGameLocal的成员)。

它扫描脚本目录并编译其中包含的所有脚本。它拥有所有的功能和变量信息。

idProgram 是唯一使用idCompiler的类。

idCompiler是Op代码生成器。

它使用idLib中定义的词法分析器和解析器。

要实际调用脚本函数,创建一个新的idThread(它应该被分配给新的,不是在堆栈上创建的)。

idThread中有静态函数来处理当前活动线程的跟踪。

每个线程都有一个idInterpreter,它包含一个线程的所有堆栈信息和指令指针。

游戏线程不是实际的操作系统线程。

它的工作方式是脚本系统为每个线程提供运行每个帧的机会。

线程运行,直到它获得多帧事件或遇到暂停事件,如sys.pause,sys.waitFor或sys.wait

(还有一个对ai对象的等待命令)。

游戏实用程序文件

Net网络


原始网络代码仍然在引擎内,但Doom 3暴露了以前的id引擎没有的很多网络。

在Doom 3中,您可以将包含您想要的所有内容的自定义位消息雕刻成任意客户端。

这显然是一个非常强大的工具。

大部分的代码都在Game_network.cpp中,但网络代码几乎遍布所有的游戏文件。

两个最重要的功能是WriteToSnapshot和ReadFromSnapshot。这两个功能处理实体的定期更新。

所有的客户端实际上都是运行与服务器完全相同的游戏代码,

所以在理论上他们永远不会失去同步,

但是众所周知,他们确实失去了同步。快照机制将定期捕获客户端以与服务器同步。

Game / GameLocal


idGame是整个游戏dll的界面类。这是核心引擎唯一看到的。

引擎在加载游戏DLL时首先要调用GetGameAPI,将其接口指针传递给一系列内部系统类。

该函数将系统类指针复制到dll中的全局指针,

然后返回一个指向idGameLocal对象(指向idGame)的指针。

idGameLocal是游戏界面的实际实现。

由于gameLocal是游戏中的全局对象,所以很多真正的随机函数最终都会被抛出来。

在idGame中定义了非常重要的功能(如InitFromNewMap,RunFrame和Draw)。

MultiplayerGame定义了所有多人游戏的特定游戏代码,例如保持得分和投票。

Pvs
PVS(潜在可见集)跟踪从其他方面可以看到的区域。

它是从渲染门户的地图加载计算的。

它允许游戏代码确定对象或怪物是否应该处于休眠状态,

网络代码也可以使用它来确定哪些更新发送到哪些客户端。

AF


idAF是一个铰接的图形助手类。

它包含一个指向动画师的指针,它用于围绕对象移动。

它没有做任何解决(这都是在idPhysics_AF中完成的),

但是将解决的AF位置应用于实体模型(记住,物理模型是与渲染系统完全分离的系统)。

IK


idIK是逆运动学助手类。

目前只有Actor角色才能将他们的脚放在地上,但很容易被用于更多的地方。

它与idAF类似。

PlayerIcon


idPlayerIcon是一个帮助类,用于在多人游戏中头部绘制滞后和聊天图标。

PlayerView


idPlayerView是一个帮助器实用程序,从玩家角度呈现场景。

这是PDA被绘制的地方,以及最后一刻的影响,如屏幕血溅,狂战士,影响力和双重视觉观察,

以及屏幕淡入淡出。

SmokeParticles


烟雾 粒子系统 用于该不断变化 以完全非  paramentric 方式位置或取向的颗粒的效果。

这样的一个例子是烟雾的影响

GameEdit


好我说谎,核心引擎也看到了 idGameEdit。

这是一个实用程序类,允许编辑器(如AFEditor)在游戏运行时操纵实体。

比如在控制台中用来进行对于游戏关卡编辑器的加载,灯光编辑器的使用了。

游戏实体

现在我已经浏览了游戏制作的主要系统,我可以开始谈论实际的游戏代码。

我要讨论的其余代码几乎都是从idEntity派生的。

这些是门,怪物,玩家,物品,火车,触发器,灯光和其他众多的东西。

我认为最简单的方法是逐个谈论每个文件。

实体


idEntity是派生所有实体的基类。它处理所有实体常见的大多数“低级”维护功能,如诞生,思考,

运行物理,绑定,播放声音,显示,隐藏等。

Entity.cpp 还包含 idAnimatedEntity,它只是一个具有 关联动画师(可以播放动画)的 实体。

WorldSpawn

Worldspawn是一个代表整个级别本身的实体。

这里唯一的代码是从地图读取一些属性,以设置 重力 和 耐力,并调用 级别 脚本。

相机


idCamera用于客观截图(当客观工具提示弹出时)以及  电影摄影机。

大部分代码涉及以下动画 (在md5camera类中定义)。

摄像机 也可以绑定到移动实体(因为任何实体可以绑定到任何其他实体)。

SecurityCamera


安全摄像机用于制作平移,可拆卸的安全摄像机(如CPU中的)。

他们只是左右移动,找出自己的事情。

脆性断裂脆性


断裂是任何物体在损坏时相当壮观的物体的术语。

通常这意味着玻璃,但从技术上讲,您可以在BrittleFracture对象上放置所需的任何纹理。

脆性断裂会将平面细分成多个碎片,然后跟踪 碎片 之间 的 连接,

因此当两个碎片之间有足够的损坏时,链接 将 断开。

单个玻璃片 通过物理系统 作为 idPhysics_StaticMulti 的一部分。

这使得 破碎玻璃 相当 缓慢。

可以通过将碎片作为粒子进行处理(从而不通过物理系统运行)来加快它的加速,

但是看起来不会那么好。

Fx


FX实体是特殊效果的容器对象。该文件 还包含作为 传送目的地 实体 的idTeleporter。


IdLight实体代表了可以用灯光完成的所有事情:打开和关闭,有声音脉动,衰落进出等。

声音


idSound就像idLight,但是声音

项目


项目是东西玩家可以拿起得到的东西。武器,健康,盔甲等都是物品。

该文件定义了基本idItem(大多数情况下使用),以及

idMoveableItem(用于装甲和其他较大的对象),

idItemPowerup(隐形,berserk等),idObjective(表示一个目标),

idVideoCDItem,idPDAItem和idMoveablePDAItem。它还定义了idItemRemover和idObjectiveComplete,

它们是从玩家iventory中删除项目的实体。

项目和可移动项目之间的区别在于环境(如门)在触摸项目时的反应。

对于常规项目,对象将直接通过它,

但对于可移动项目,项目将被移动器推动。

idMoveabe可移动物体的类


idMoveable  就像一个可移动的项目,你不能拿起来。

例子是 人 们喜欢打汉堡包,箱子 ,苏打水罐,垃圾桶,垃圾桶等。

有两种特殊情况 下 可移动 的 定义:idBarrel和idExplodingBarrel。

我希望这两个行为是显而易见的。

trigger触发器


触发器是触发目标实体或发生事件时调用脚本的实体。

“某事”取决于它是什么样的触发器。

有玩家的触发触发器,怪兽的触发触发器,定时器触发器,伤害触发器,褪色触发器,

仅在触发一定次数后触发的触发器,触发器仅触发某些实体等。

主要的是请记住,

每当触发器被触发时,它将向其目标的任何实体发送一个Activate事件。

target目标


目标被用作触发的目标。有大约30种不同的目标实体类型,它们做的是非常不同的事情,

所以我不会提及它们。

幸运的是,大多数目标往往相当小(只有一个Event_Activate功能),

所以我需要一分钟才能通过Target.h扫描

其他杂

是我们将实体放在其他地方并不合适的地方。

它包含像idVacuumEntity和idPlayerStart这样的东西。

它还包含约30个实体,所以我不会去那些。

AFEntity


AFEntity是一个具有关联关联数字的实体。

它实际上可以是许多不同类型的实体之一。玩家,怪物,车辆,链条(如起重机)

和通用的尸体都是AF实体。

有一个idAFEntity_Base类,其中包含所有AF实体共享的一些基本功能,

然后派生自idAFEntity_Gibbable,idAFEntity_Vehicle,idAFEntity_SteamPipe和

idAFEntity_ClawFourFingers。

除了产生和销毁AF实体之外,AFEntity实际上并不做很多事情,

因为所有的真实工作都是在idAF和idPhysics_AF中完成的。

idClass
+ -Identity
  + -idAnimatedEntity
    | -idWeapon
    | -idAFAttachment
    + -idAFEntity_Base ----使用idAF
      | -idAFEntity_ClawFourFingers
      | -idAFEntity_Vehicle
      | | -idAFEntity_VehicleFourWheels
      | + -idAFEntity_VehicleSixWheels
      + -idAFEntity_Gibbable
        | -idAFEntity_WithAttachedHead
        | -idAFEntity_Generic
        | -idAnimated
        + -idActor ----使用idIK
          | -idPlayer
          + -idAI

Actor

一个演员是需要一些AI的东西。一般来说,一个actor有一个与它相关联的.script,

但并不总是需要一个(一个没有脚本的演员,只要站在那里,眨眼就像在现实生活中)。

idActor源自idAFEntity_Gibbable。

播放器


idPlayer是...播放器。它处理输入处理,项目处理,电影,动画,传送和一整套其他事情

(大概8000行)。

Player.cpp也是idInventory的所在地,它只是玩家库存的辅助类。

射弹


一个物体是一个飞过世界的物体,对任何物体造成伤害。

一般来说,他们被武器开枪(见下文),但不一定是。

例如,“雷约”创造了射弹,但他技术上没有任何武器

(肩上安装的火箭发射器是他的模型的一部分,岩石是由脚本产生的)。

武器


一个idWeapon是玩家在游戏中跑到身边的一个实体。

它可能会也可能不会射出射弹,但大部分时间都是这样。

idWeapon有趣的是每个玩家有一个,而不是一个武器。

当玩家切换武器时,只需将一个新的武器脚本加载到现有的idWeapon中。

引擎 代码

现在我们已经完成了一大堆的源代码,我们来看一些您没有代码的系统。

System


Sys是所有操作系统特定呼叫所在的地方。像锁定内存和诞生 线程 的东西在这里。

所有的函数和类都在sys_public.h中定义在'sys'文件夹中。

游戏中的所有功能都可以通过idSys单例 (通过全局'sys'对象访问)的成员。

Common
idCommon处理启动和关闭所有其他系统(包括游戏系统)。

它还执行错误,警告和调试消息打印

(它将消息路由到日志文件,控制台,早期控制台,专用控制台或任何其他需要它)。


在引擎中,代码看起来像这样:

void main(void){
    Sys_Init();
    共>初始化();
    而(1){
        共>框架();
    }
}

    Sys_Init();
    共>初始化();
    而(1){
        共>框架();
    }
}

命令系统


idCmdSystem处理用户键入任何一个控制台(游戏中,专用或编辑器)的命令。

游戏代码也可以直接将命令传递给命令系统进行处理。

命令系统的每个帧都将解析出命令缓冲区,并通过调用与命令关联的回调函数

来执行任何等待命令。

例如,“quit”将调用Com_Quit_f回调函数(然后调用common-> Quit)。

CVar System


idCVarSystem处理 控制台  变量。

Doom 3中的控制台变量  比 以前的引擎中的控制台变量 更冷。

它们被添加到CVar系统中:

idCVar com_showFPS(“com_showFPS”,“0”,CVAR_BOOL | CVAR_SYSTEM,“”);

idCVar的构造函数将cvar添加到cvar的静态列表中,然后在启动时将其添加到cvar系统中。

然后,您可以通过实例化对象访问cvar。您还可以通过实际cvar系统中的函数来访问cvars,

但是这有点慢(因为它必须每次执行查找,而不是将其存储在cvar对象中)。

文件系统


idFileSystem允许访问文件。

这是非常抽象的,因为您请求的文件可能在真实的文件系统中,

或者它可能在包文件系统中。

它也可能在mod目录或游戏目录中。

当你要求“cheese / gouda.jpg”时,它将搜索游戏目录,然后是 游戏包 文件

,然后是基本目录,然后是 基本包 文件。

如果服务器以纯模式运行,则 文件系统 将只检查 包文件。

文件系统跟踪在服务器上进行纯检查的所有文件,

也可以设置为复制模式(fs_copyfiles)以帮助进行构建。

网络系统


游戏系统真的只能访问网络系统的两个部分:

回读读/写(idGame :: ClientReadSnapshot / idGame :: ServerWriteSnapshot),

并 发送 和 接收 可靠消息(在idNetworkSystem中)的功能。

idNetworkSystem中还有其他一些功能来获取 ping 和 丢包 等 统计 信息。

渲染系统


渲染器分为3个不同的概念部分:

  • RenderWorld与场景图非常相似。它包含场景中的所有渲染模型和纹理,
  • 以一些易于剔除的方式排列。它处理对后端的剔除和调度命令。

  • 后端是一个经过优化的系统,用于渲染三角汤(待定)。
  • 为Doom 3支持的每个主要硬件架构(NV20,R300,ARB,ARB2,CG)
  • 编写了一个单独的后端。

  • 渲染系统管理所有的渲染世界(可以有多个渲染),并且作为渲染系统的入口点。

  • 声音系统


  • 声音系统非常类似于渲染系统,

  • 因为有一个声音世界(或两个或三个)处理所有真正难处理的东西,

  • 声音系统 只是作为一个戒指领袖。

  • 声音世界  跟踪所有发射器的位置,它们在什么状态以及听众的位置。

  • 渲染模型管理器

    idRenderModelManager处理渲染模型的加载和释放。

  • 这些型号可以是各种文件格式,包括BSP,LWO和ASE

  • 。模型管理器首次在请求时缓存模型数据。

  • 每个要用于级别的模型都应该在级别加载期间由模型管理器加载

  • (在BeginLevelLoad和EndLevelLoad调用之间)。

  • 这将确保在实际游戏过程中最小的磁盘命中。

  • user 用户界面管理器

  • idUserInterfaceManager的行为与模型管理器完全相同,除了GUI。

  • decl 声明管理器

  • idDeclManager处理加载,缓存,读取和写入任何种类的声明。

  • 它扫描目录并解析出decils,为每种类型自动创建适当的decl对象。

  • 您可以使用decl管理器注册自己的定制声明类型,以及要扫描的新文件夹。

  • 就像其他管理者一样,它所加载的任何decls都应该在指定的级别加载时间内完成,

  • 以防止不必要的磁盘命中。

  • AAS文件管理器

  • idAASFileManager加载并释放AAS(区域感知系统)文件。

  • 这是在游戏代码中获取idAASFile的唯一方法。

  • 碰撞模型管理器

  • 碰撞模型管理器与渲染模型管理器完全相同,除了物理系统而不是渲染系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值