转自:http://www.cnblogs.com/hmxp8/archive/2012/02/21/2361211.html
掌握一款庞大的引擎,要一下子掌握真的很难,慢慢地从Editor,Script,各个模块细节,再到源码,一步一个脚印必须扎实,还有,必须学会温故而知新,有很多东西一开始一阅而过,以为简单,实则不然,今天,就不想从最基本的CodeOrientation中学到了很多~
下面就总结一下相关资料:
阅读一款3D引擎的方法备忘
(转自平民程序 - linghuye‘s blog)
要带着引擎系统所共有的问题去阅读,不要被具体引擎的实现思路牵着走,要思考这个引擎如何实现或绕过(如不考虑低端机)这些必然要面临的问题.
首先,最重要的必须,在Debug模式下调试起来一个主场景程序,然后沿着代码流走几遍,在代码里闲逛,生成第一印象,留意基础工具设施类(如字符串内存管理),大约3小时.
然后带着如下每一个问题(不分先后)去走流程,走逻辑,弄清引擎的业务处理方法.
1.SetStreamSource,SetIndices,DrawIndexedPrimitive的调用是在哪里(哪个cpp哪个类的哪个函数),整个工程有几个DrawIndexedPrimitve,DrawPrimitveUp,DrawXXX,被统一管理了吗,还是零零散散地分布在各个cpp各个类?
2.纹理资源是如何管理的,句柄,ID,指针, 按使用时间,按使用计数? 模型顶点资源是如何管理的,是否有Buffer分配上的优化(比如顶点缓冲整合)? 渲染的实例数据如何管理,如何与资源概念区分的? 纯3D资源如Shader/RenderTarget是如何管理的?
3.场景管理是怎样搭建的,如何包含区分节点实例数据和资源数据?镜头是如何被各模块访问的?剔除不可见物件的代码行在哪里?兼容各种算法吗,使用了具体的算法是什么?不论使用何种算法,场景管理模块是否提供了清晰的需求定义接口?
4.骨骼动画是在哪里计算的,具体到顶点乘以矩阵是在哪个cpp的哪个代码行里?Socket实现在哪里?如果有高级动画系统,骨架在哪里,有什么功能?动作融合的矩阵间过渡代码在哪一行?
5.异步数据加载是怎样做的,在哪个cpp里,由哪个模块管理?各个资源的加载读取cpp代码行在哪里?
6.渲染管线如何为ShadowMap提供深度图,如何为CubeMap,水面反射提供场景RenderTarget?如何管理固定流水线状态RasterState的?
7.材质管理系统是如何运作的(通常都是technique + multipass),如何并入管线的,如何从理论上保证可以实现各式各样的效果的?如何抽象兼容固定和可编程流水线的? 如果是抽象管理的,如何抽象掉不同的technique下的不同的数据?管线里是否有独立的后期效果,还是用材质表达出来?材质是否可以脚本化?材质是要分组的,排序的代码段在哪个cpp的哪里?
8.特效如粒子系统到最后是使用哪个DrawXXX画出来的? 粒子系统是动态公式计算(不要理会具体公式)还是帧动画?
9.界面2D Texture Draw是如何被支持的?文字渲染是如何被支持的,特别是如何支持中文的,字体资源如何被管理?是否使用Freetype,是否支持东亚文字?
10.物理系统的接口是如何定义引擎的需求的?或者就是乱糟糟地直接用上.
一个引擎的架构图
不尽然相同,但可以很好地参照
(http://blog.csdn.net/Jaredz/article/details/4411614):
注: UE中的RHI即是RenderHardwareInterface,由下面DX,OG等Driver做成实现; UScript的架构不同于普通脚本,本身具有底层语言的特性,诸多的细节是为Editor做出的特殊化处理,实际上与Engine更类似于平行的关系,编译运行,可与Native的C++层相互调用; |
虚幻引擎3的代码讲解 |
概述
本文档是为正在开始使用虚幻引擎进行开发的程序员提供的。它简单地讲解了基础代码的各个方面。它绝不是一个针对引擎的完整指南,而是一个在您编译引擎代码之后的一个很好的准备工作。如果您还没有编译您的项目,请访问首先访问: 入门指南或快速入门页面。
目录结构
当从Perforce上同步了UE3基础代码后,在您的Perforce depot(仓库)的根文件夹中应该有一个称为 UnrealEngine3 的目录。
在"UnrealEngine3"文件夹内有以下目录:
- Binaries - 这个文件中包含二进制对象。比如,编译后的 引擎/游戏 版本、UE3所需要的DLL文件(Direct X、wx 运行时系统等)、编辑器资源文件(图标图像)。
- Development -这是引擎的源码真正存在的地方,在这个文件夹中有:
- Build -这里存储着用于启动在我们的Continuous Integration Server(CIS)[连续集成服务器]上各种类型编译的批处理文件。CIS可以为我们检查编译并尽可能地捕获编译错误。
- Documentation -存储文档文件的文件夹。我们自动生成的windows帮助文档就存储在这里(UnrealEngine3.chm)。这个帮助文档是帮助您快速浏览Unreal源码的很好的资源。
- External -所有不是Epic公司的库源文件存储在这个文件夹中。比如libPNG、 wxWidgets、zlib、Cg等。
- Intermediate -所有的中间编译文件存储在这里。.obj文件、预编译头文件等。
- Src -源码文件和项目的makefiles。主要的UnrealEngine3解决方案存在于文件夹中。在这个解决方案中也包含了每个项目的文件夹。一个实例项目可以像这样进行细分目录:
- Game Folder(游戏文件夹)
- 在工程主文件夹中是工程的visual studio项目文件。
- Classes -保存.uc(UnrealScript)文件的文件夹。
- Inc -保存.h文件的文件夹。
- Src -保存.cpp文件的文件夹。
- Game Folder(游戏文件夹)
- Tools -保存各种引擎相关工具的所有源码的文件夹, 比如UDE、vistual studio macros(vistual studio宏)及一些命令行工具。
- Engine/Game folders -在Binaries 和 Development文件夹之后是文件夹Engine,然后是使用这个引擎的每个游戏的文件夹。
- Config -包含着用于保存那个游戏设置的.ini文件。
- Content -包含那个游戏内容包的文件夹。
- Localization - 包含那个游戏的任何本地化文本的文件夹。
- Logs -包含着当运行那个游戏时产生的日志文件的文件夹。
- Script -包含着那个游戏编译后的UnrealScript代码(.u文件)。
工程结构
为了分离系统并帮助维持跨平台的兼容性,UE3被分割成了多个工程。
基本的结构如下所示(从最层代码到高层层次代码):
- Core
- Engine
- Editor
- UnrealED
注意,所有的这些工程都要建立于彼此之上的,这意味着Engine依赖Core、Editor依赖Engine、UnrealED依赖Editor。
- Core: Core是最低级别的类,它是指一些类似于字符串类、数组类、内存管理器等东西。这里是核心脚本和加载代码所存在的地方。
- Engine: Engine文件夹是渲染和游戏性代码存在的地方。同时包括了和actors、碰撞检测以及很多其它的子系统。这里是大多数‘游戏界面’代码所在的地方。
- Editor: Editor由仅当编辑器运行时使用的类和程序组成。这些类允许用户操作多边形、画刷、地形及执行其它的通常在游戏中不能执行的动作。
- UnrealED: UnrealED是编辑器的真正UI实现,这是编辑器应用程序代码存在的地方。目前我们使用wxWidgets作为我们的UI工具集。任何需要书写的wxWidgets代码都要放到UnrealED中。这里的观念是‘Editor‘工程包含了所有编辑工具的功能性代码,而UnrealED仅是那些功能的UI封装。
也有很多针对其它平台的特定功能,比如D3DDrv(渲染的Direct 3D实现),WinDrv(创建视口、处理事件的Windows实现等)。如果一个工程是跨平台界面的特定平台实现,那么很可能它需要附加后缀Drv。
游戏/引擎 代码
虚幻引擎一般把游戏代码和引擎代码分割为2个不同的程序语言:
- 引擎代码:
- 使用C++语言书写。
- 使用".h" 和".cpp"扩展名,并分别存储在工程的"Inc" 和 "Src"文件夹中。
- 游戏代码:
- 使用Unreal Engine的独特语言:UnrealScript。这种语言在很多方面和Java类似。请参照UnrealScript 参考页面获得更多信息。
- 使用扩展名".uc",存储在工程的"Classes"文件夹中。
- 在执行前进行编译。
Unreal C++标准
- Unreal已经在整个引擎中定义了它自己的一组数据类型集合(
INT
、FLOAT
、UBOOL
等)。应该总是使用这些数据类型,而不是标准的C++数据类型,从而保证跨平台的兼容性。 - Unreal为字符串使用了宽字符(
TCHAR
)。所有的字符串文字都需要在TEXT()
宏进行封装,从而确保它们可以正确地被转换为宽字符。
示例:
const TCHAR* TestString = TEXT("My Test String");
关于虚幻编码规范的更多信息,请参照编码规范页面。
对象命名规则
在类和struct前附加一个字母前缀是很重要的,因为Unreal使用这些前缀让程序员知道应该如何处理一个对象。
- FClassName
- 实例: FString, FVector
- 应用: 任何带有字符前缀‘F’的对象是标准的C++的struct(结构体)和类,这些对象没有必须继承其它任何特殊东西。
- TClassName
- 实例: TArray, TMap
- 应用: 任何带有字符前缀‘T’的对象是模板类,这些对象没有必须继承其它任何特殊东西。
- UClassName
- AClassName
- 实例: AActor, ABrush, APointLight
- 应用: 任何带有字符前缀‘A’的对象是从AActor继承而来的类或struct(结构体)。AActor是基础‘Actor‘类。Actors和一般的UObject是不同的,因为它们在游戏世界中由实际的位置和真实的项目。仅actors可以在网络上进行复制以及使用UnrealScript状态。Actors也可以继承UObject,并且同样地遵循应用到带有U前缀的对象的不能删除规则。
UnrealScript 文件
本文档仅简要地略微谈到了一个新的程序员在使用虚幻引擎时需要处理的某些问题。
关于UnrealScript的全面描述,请参照UnrealScript参考指南页面。
类的选项修饰符
当声明类时,可以指定一些UnrealScript Classes的选项。关于这个功能的详细介绍,请参照UnrealScript参考指南: UnrealScriptReference#Class_overview。
在本讲解中,我们将简要地覆盖这些选项中的一个,因为它很容易使初识虚幻引擎的程序员犯错误。
使用 native 选项修饰符的自动生成头文件
如果为一个类指定了 native
选项,那么UnrealScript编译器将会自动地为那个UrealScript类生成相应的C++类声明。这些头文件具有后缀词"Classes",一般和工程的名称相关。比如"EngineClasses.h"、"EnginePhysicsClasses.h"、"EditorClasses.h"等。
因为当指定 native
时,UnrealScript将会自动生成C++类,所以程序员应该注意不要修改自动生成的头文件,而是在.uc文件中进行修改。
noexport选项修饰符
某些类在指定了 native
的同时也指定了 noexport
选项。这些类不能自动生成相应的C++部分,但是仍然期望C++声明存在。所以程序员必须自己书写一个头文件。在引擎中有很多这种情况的示例(请参照 Development/Src/Editor/Inc/Editor.h 文件中的UEditorEngine)。
noexport
UnrealScript 类需要和它们的C++对应部分保持‘同步’。因此,当修改UnrealScript文件时,程序员应该小心。
关于编译native 类的更多信息,请参照编译Native 类。
UObjects
因为UObjects是由引擎自动管理的,所以可能有一些新程序员不熟悉的独特的微小差别:
- UOject永远不要使用
new
进行创建, 而是用ConstructObject
模板函数。 - UOject永远不要使用
delete
进行删除。垃圾回收器将会自动地处理删除UObjects。 UObject::StaticClass
可以用于获得指向任何UObject静态实例的指针。
AActor
Unreal World对象包含了它内部所有的Actors列表。
- 不能使用
new
或ConstructObject
来创建AActors – 您应该使用SpawnActor
函数。 - 要想销毁一个Actor,请使用定义在UWorld中的
DestroyActor
函数。 - AActors可以包含一组UActorComponents。这些是提供较小的、正交单元的功能的有用对象。这可以使一个光源、粒子系统、声效等。
序列化
虚幻引擎的一个强大功能是它可以对定义在.uc文件中的UObjects的所有属性根据名称自动地进行序列化。这允许您改变属性的顺序、添加新的属性、在类层次中移动属性,并且这些操作不会破坏向后的兼容性。
因为Unreal垃圾回收系统使用序列化来决定哪个 UObjects 是 可获得的 ,所以您需要确保您具有到任何您不想清除的UObjects的序列化引用。在编辑器中创建工具时是可能导致问题的一种情况。我们一般从FSerializableObject接口继承工具,它提供了一个=Serialize= 函数,可以通过实现这个函数来保存到在工具中创建的UObjects的引用。
void FSomeTool::Serialize(FArchive& Ar) { Ar << SomeUObjectPointer; }
如果您使用native序列化工具来序列化永久性数据,您需要明确地支持老版本的序列化数据。要想启用这个功能,两个版本号存储在每个包中,一个供引擎使用,一个供授权用户使用。当授权用户在进行序列化改变时,可以更新GPackageFileLicenseeVersion
,并且在反序列化过程中将会检查 Ar.LicenseeVer()
来决定保存的是哪个版本的序列换数据。
配置系统
UE3使用层次化的配置系统,意味着默认值将会从 base(基类) 的配置文件向下传递,并且子类可以覆盖这些基类的配置文件。
所有的配置文件都是.INI文件,并且遵循特殊的命名规则:
- 基类配置文件放置在 UnrealEngine3\Engine\Config文件夹中,文件名具有前缀单词 Base 。
- 每个文件都有它自己的 Config 文件夹和一组.INI配置文件。这些文件的前缀是拥有它们的游戏的名称。
比如,Editor配置文件的结构将是:
- Engine\Config\BaseEditor.ini
- "ExampleGame"的INI文件: ExampleGame\Config\DefaultEditor.ini
- "ExampleGame"的INI文件: ExampleGame\Config\ExampleGameEditor.ini
- "ExampleGame"的INI文件: ExampleGame\Config\DefaultEditor.ini
这个系统是强大的,因为它也用于为脚本中定义的属性填充数据,而这些脚本属性可以非常快速地配置游戏的任何部分。
主Tick(更新)循环
引擎的主Tick循环可以在LaunchEngineLoop.cpp文件中找到: FEngineLoop::Tick()
。这个函数调用UEngine类的 Tick
函数,这样它将会更新游戏世界(UWorld)并监控更新引擎的其它各种子系统。
如果您想获得关于Actor ticking(更新)循环函数是如何工作的,请参照Actor 更新页面
结论
虚幻引擎编程乍一看令人畏惧,但是随着时间的推移,您将会开始理解是某些复杂设计的决定使得它成为了今天的样子。既然您已经学习了虚幻引擎编程的第一个速成教程,那么请随便浏览UDN上提供给程序员的其它技术资源。
这里是为初学者提供的一些有用页面:
虚幻引擎编码规范:
编码规范
如何使用ExampleGame 创建一个新的游戏项目:
ExampleGame指南
如何跟踪ue3中您的内存应用情况:
Memory使用
actor更新循环是如何工作的:
Actor更新
UnrealScript参考指南(这是必读页面):
虚幻脚本概述:
关于编译标记为‘native‘类的更多信息:
编译Native类
虚幻引擎中常见的技术问题(FAQ):
常见技术问题
ClassificationForm | |
---|---|
Relevant topics | UnrealScriptTopics, GettingStartedTopics, TechnicalTopics |
Engine build | None/All |
Applicable platforms | |
Document availability | UE3Licensees |
Access required | |
Origin | CodeOrientation |
Revision | 1.11 |
Language | zh |