【Game Engine Architecture 1】
1、This book is really just the beginning of a fascinating and potentially lifelong journey.
We’ll focus primarily on the engine itself, including a host of low-level foundation systems, the rendering engine, the collision system, the physics simulation, character animation, audio, and an in-depth discussion of what I call the gameplay foundation layer.
This layer includes the game’s object model, world editor, event system and scripting system. We’ll also touch on some aspects of gameplay programming, including player mechanics, cameras and AI.
2、This book is intended to be used as a course text for a two- or three-course college-level series in intermediate game programming.
Industrial-strength 3D game engines are still written primarily in C++.
3、A general-purpose job system can be used to unlock the power of concurrency within a game engine
3.1、常用的引擎:
1)Epic Games’ Unreal Engine 4
2)Valve’s Source engine
3)Crytek’s CRYENGINE® 3,
4)Electronic Arts DICE’s Frostbite™ engine
5)the Unity game engine
4、The role of producer is defined differently by different studios.
In some game companies, the producer’s job is to manage the schedule and serve as a human resources manager.
In other companies, producers serve in a senior game design capacity. Still other studios ask their producers to serve as liaisons between the development team and the business unit of the company (finance, legal, marketing, etc.). Some smaller studios don’t have producers at all. For example, at Naughty Dog, literally everyone in the company, including the two co-presidents, plays a direct role in constructing the game; team management and business duties are shared between the senior members of the studio.
5、A physics simulation may need to be updated 120 times per second in order to remain stable.
6、A rendering engine that was designed to handle intimate indoor environments probably won’t be very good at rendering vast outdoor environments. The indoor engine might use a binary space partitioning (BSP) tree or portal system to ensure that no geometry is drawn that is being occluded by walls or objects that are closer to the camera.
The outdoor engine, on the other hand, might use a less-exact occlusion mechanism, or none at all, but it probably makes aggressive use of level-of-detail (LOD) techniques to ensure that distant objects are rendered with a minimum number of triangles, while using high-resolution triangle meshes for geometry that is close to the camera.
7、MR(Mixed Reality),中文名称混合现实。MR是对VR和AR的进一步发展,以增强用户体验的真实感。
而MR和AR的区别在于:在MR的世界里,呈现在真实世界里的虚拟物体,并不会因为玩家位置改变而发生位移,它们的位置其实是相对固定的。在高级的MR世界里,用户很有可能分不清哪一个是虚拟物体,哪一个是现实物体。
8、Studies have shown that VR running at below 90 frames per second is likely to induce disorientation, nausea, and other negative user effects. This means that not only do VR systems need to render the scene twice per frame, they need to do so at 90+ FPS. This is why VR games and applications are generally required to run on highpowered CPU and GPU hardware.
9、Runtime Game Entine Architecture
1)底层。
2)中底层。
3)渲染层。
4)游戏层。
10、Collision & Physics
• Havok is a popular industrial-strength physics and collision engine.
• PhysX is another popular industrial-strength physics and collision engine, available for free download from NVIDIA.
• Open Dynamics Engine (ODE) is a well-known open source physics/collision package.
11、HUD:heads up display。指的是player头顶的UI。
GUI:graphical user interface。指的是游戏面板UI,如背包、菜单等等。
12、Full-Motion video (FMV) system in this layer. This system is responsible for playing full-screen movies that have been recorded earlier (either rendered with the game’s rendering engine or using another rendering package).
In-game cinematics (IGC) system. This component typically allows cinematic sequences to be choreographed within the game itself, in full 3D. For example, as the player walks through a city, a conversation between two key characters might be implemented as an in-game cinematic. IGCs may or may not include the player character(s). They may be done as a deliberate cut-away during which the player has no control, or they may be subtly integrated into the game without the human player even realizing that an IGC is taking place. Some games, such as Naughty Dog’s Uncharted 4: A Thief’s End, have moved away from pre-rendered movies entirely, and display all cinematic moments in the game as real-time IGCs.
13、Digital Content Creation.
14、Asset Conditioning Pipeline
ACP,将DCC导出的资源,处理成适用于 Engine引用的资源。
14.1、Brush Geometry
Brush geometry is defined as a collection of convex hulls, each of which is defined by multiple planes. Brushes are typically created and edited directly in the game world editor. This is essentially an “old school” approach to creating renderable geometry
15、Skeletal Animation Data
A skeletal mesh is a special kind of mesh that is bound to a skeletal hierarchy for the purposes of articulated animation. Such a mesh is sometimes called a skin.
An unoptimized skeletal animation is defined by a stream of 4 x 3 matrix samples, taken at a frequency of at least 30 frames per second, for each of the joints in a skeleton (of which there can be 500 or more for a realistic humanoid character). Thus, animation data is inherently memory-intensive. For this reason, animation data is almost always stored in a highly compressed format. Compression schemes vary from engine to engine, and some are proprietary. There is no one standardized format for game-ready animation data.
16、 Perforce
Perforce is a professional-grade source control system, with both text-based and GUI interfaces. One of Perforce’s claims to fame is its concept of change lists. A change list is a collection of source files that have been modified as a logical unit. Change lists are checked into the repository atomically—either the entire change list is submitted, or none of it is.
Perforce is used by many game companies, including Naughty Dog and Electronic Arts.
17、Libraries, Executables and Dynamic Link Libraries
When a translation unit is compiled, the resulting machine code is placed in an object file (files with a .obj extension under Windows or .o under UNIX-based operating systems). The machine code in an object file is:
• relocatable, meaning that the memory addresses at which the code resides have not yet been determined
• unlinked, meaning that any external references to functions and global data that are defined outside the translation unit have not yet been resolved.
Object files can be collected into groups called libraries. A library is simply an archive, much like a ZIP or tar file, containing zero or more object files. Libraries exist merely as a convenience, permitting a large number of object files to be collected into a single easy-to-use file.
17.1、_DEBUG & NDEBUG
As a ubiquitous example, the symbol _DEBUG is always defined for a debug build, while in non-debug builds, the symbol NDEBUG is defined instead.
18、Local and Global Optimizations
Local optimizations operate only on small chunks of code known as basic blocks. Roughly speaking, a basic block is a sequence of assembly language instructions that doesn’t involve a branch. Local optimizations include things like:
• algebraic simplification,
• operator strength reduction (e.g., converting x / 2 into x >> 1 because the shift operator is “lower strength” and therefore less expensive than the integer division operator),
• code inlining,
• constant folding (recognizing expressions that are constant at compile time and replacing such expressions with their known value),
• constant propagation (replacing all instances of a variable whose value turns out to be constant with the literal constant itself),
• loop unrolling (e.g., converting a loop that always iterates exactly four times with four copies of the code within the loop, in order to eliminate conditional branching),
• dead code elimination (removal of code that has no effect, such as removing the assignment expression x = 5; if it is immediately followed by another assignment to x like x = y + 1;), and
• instruction reordering (in order to minimize CPU pipeline stalls).
Global optimizations operate beyond the scope of basic code blocks—they take the whole control flow graph of a program into account. An example of this kind of optimization would be common sub-expression elimination. Global optimizations ideally operate across translation unit boundaries, and hence are performed by the linker rather than the compiler. Aptly, optimizations performed by the linker are known as link-time optimizations (LTO).
Some modern compilers like LLVM/Clang support profile-guided optimizations (PGO). As the name implies, these optimizations use profiling information obtained from previous runs of your software to iteratively identify and optimize its most performance-critical code paths.
18.1、Typical Build Configurations
1)debug:关闭优化,开启Debug模块
2)development:开启优化,开启Debug功能。
3)ship:开启优化,关闭Debug功能。
19、Visual Studio Linker Property Page
• General Property Page/Output File. This setting lists the name and location of the final product of the build, usually an executable or DLL.
• General Property Page/Additional Library Directories. Much like the C/C++ Additional Include Directories field, this field lists zero or more directories that will be searched when looking for libraries and object files to link into the final executable.
• Input Property Page/Additional Dependencies. This field lists external libraries that you want linked into your executable or DLL. For example, the OGRE libraries would be listed here if you are building an OGREenabled application..
Note that Visual Studio employs various “magic spells” to specify libraries that should be linked into an executable. For example, a special #pragma instruction in your source code can be used to instruct the linker to automatically link with a particular library. For this reason, you may not see all of the libraries you’re actually linking to in the “Additional Dependencies” field. (In fact, that’s why they are called additional dependencies.) You may have noticed, for example, that Direct X applications do not list all of the DirectX libraries manually in their “Additional Dependencies” field. Now you know why.
20、 suffix of watcher window
• The “,d” suffix forces values to be displayed in decimal notation.
• The “,x” suffix forces values to be displayed in hexadecimal notation.
• The “,n” suffix (where n is any positive integer) forces Visual Studio to treat the value as an array with n elements. This allows you to expand array data that is referenced through a pointer.
• You can also write simple expressions in square brackets that calculate the value of n in a “,n” suffix. For example, you can type something like this:
my_array,[my_array_count]
to ask the debugger to show my_array_count elements of the array named my_array.
21、数据断点
a breakpoint that trips whenever a specific memory address is written to (i.e., changed). These are called data breakpoints, because they are triggered by changes to data, or sometimes hardware breakpoints.
21.1、Memory Leak and Corruption Detection
Nonetheless, having a tool to help you detect potential memory corruption and leak problems certainly can’t hurt. Thankfully, many such tools exist.
My personal favorite is IBM’s Rational Purify, which comes as part of the Purify Plus toolkit. Purify instruments your code prior to running it, in order to hook into all pointer dereferences and all memory allocations and deallocations made by your code. When you run your code under Purify, you get a live report of the problems—real and potential—encountered by your code. And when the program exits, you get a detailed memory leak report. Each problem is linked directly to the source code that caused the problem, make ing tracking down and fixing these kinds of problems relatively easy.
22、Multiple Inheritance
Most C++ software developers avoid multiple inheritance completely or only permit it in a limited form. A common rule of thumb is to allow only simple, parentless classes to be multiply inherited into an otherwise strictly single-inheritance hierarchy. Such classes are sometimes called mix-in classes.
大多数 C++ 开发者禁用多重继承。主流的做法是,只有单一继承链上的类可以多重继承,但只能继承无父类的非本链类型,从而保证不会出现死亡钻石。这样的类叫 mix-in classes。
23、Composition and Aggregation
Composition is the practice of using a group of interacting objects to accomplish a high-level task. Composition creates a “has-a” or “uses-a” relationship between classes. (Technically speaking, the “has-a” relationship is called composition, while the “uses-a” relationship is called aggregation.) For example, a spaceship has an engine, which in turn has a fuel tank. Composition/aggregation usually results in the individual classes being simpler and more focused. Inexperienced object-oriented programmers often rely too heavily on inheritance and tend to underutilize aggregation and composition.
24、C++11 Auto
As an example of a language feature with both benefits and downsides, consider the new C++11 interpretation of the auto keyword. This keyword certainly makes variables and functions more convenient to write. But the Naughty Dog programmers recognized that over-use of auto can lead to obfuscated code: As an extreme example, imagine trying to read a .cpp file written by somebody else, in which virtually every variable, function argument and return value is declared auto. It would be like reading a typeless language such as Python or Lisp. One of the benefits of a strongly-typed language like C++ is the programmer’s ability to quickly and easily determine the types of all variables. As such, we decided to adopt a simple rule:
auto may only be used when declaring iterators, in situations where no other approach works (such as within template definitions), or in special cases when code clarity, readability and maintainability is significantly improved by its use. In all other cases, we require the use of explicit type declarations.
顽皮狗限制使用 auto.
25、template metaprogramming.
Andrei Alexandrescu’s Loki library [3] makes heavy use of template metaprogramming to do some pretty interesting and amazing things. However, the resulting code is tough to read, is sometimes non-portable, and presents programmers with an extremely high barrier to understanding. The programming leads at Naughty Dog believe that any programmer should be able to jump in and debug a problem on short notice, even in code with which he or she may not be very familiar. As such, Naughty Dog prohibits complex template metaprogramming in runtime engine code, with exceptions made only on a case-by-case basis where the benefits are deemed to outweigh the costs.
26、user error & programmer error
user error occurs when the user of the program does something incorrect, such as typing an invalid input, attempting to open a file that does not exist, etc. A programmer error is the result of a bug in the code itself.
It is best to handle user errors as gracefully as possible, displaying some helpful information to the user and then allowing him or her to continue working—or in the case of a game, to continue playing.
Programmer errors, on the other hand, should not be handled with a graceful “inform and continue” policy. Instead, it is usually best to halt the program and provide detailed low-level debugging information, so that a programmer can quickly identify and fix the problem. In an ideal world, all programmer errors would be caught and fixed before the software ships to the public.
27、Exception Handling
Exception handling does add some overhead to the program. The stack frame of any function that contains a trycatch block must be augmented to contain additional information required by the stack unwinding process.
Also, if even one function in your program (or a library that your program links with) uses exception handling, your entire program must use exception handling—the compiler can’t know which functions might be above you on the call stack when you throw an exception.
项目的任意一个文件,如果启用了 try catch,或 throw,则整个项目所有的函数都会被扩展,以添加 exception unwind 信息。从而导致性能下降。
That said, it is possible to “sandbox” a library or libraries that make use of exception handling, in order to avoid your entire game engine having to be written with exceptions enabled. To do this, you would wrap all the API calls into the libraries in question in functions that are implemented in a translation unit that has exception handling enabled. Each of these functions would catch all possible exceptions in a try/catch block and convert them into error return codes. Any code that links with your wrapper library can therefore safely disable exception handling.
要想避免,可以将带 try-catch、throw 的代码,封装到一个库里面。
Clearly there are some pretty strong arguments for turning off exception handling in your game engine altogether. This is the approach employed at Naughty Dog and also on most of the projects I’ve worked on at Electronic Arts and Midway.
28、assert
The standard library’s definition of assert() causes it to be defined in debug builds (builds with the DEBUG preprocessor symbol defined) and stripped in non-debug builds (builds with the NDEBUG preprocessor symbol defined).
29、static_assert
C++11中引入。
30、顽皮狗工作室用lisp方言作为游戏配置脚本。
31、Fixed-Point
32、Floating-Point
m is the fractional value stored in the mantissa.
The precision of a floating-point number increases as the magnitude decreases, and vice versa.
The largest possible floating-point value, FLT_MAX = 3.403 x 10^38.
• An exponent of 255 has a special meaning in the IEEE-754 format—it is used for values like not-a-number (NaN) and infinity —so it cannot be used for regular numbers. Hence the maximum eight-bit exponent is actually 254, which translates into 127 after subtracting the implicit bias of 127.
33、Subnormal Values
The smallest nonzero magnitude we can represent is :
The next smallest valid value is zero, so there is a finite gap between the values -FLT_MIN and +FLT_MIN.
With this extension, any floating-point value with a biased exponent of 0 is interpreted as a subnormal number. The exponent is treated as if it had been a 1 instead of a 0, and the implicit leading 1 that normally sits in front of the bits of the mantissa is changed to a 0. This has the effect of filling the gap between -FLT_MIN and +FLT_MIN with a linear sequence of evenlyspaced subnormal values. The positive subnormal float that is closest to zero is represented by the constant FLT_TRUE_MIN.
exponent值为0的数,即为 subnormal value. 在计算时,将mantissa隐含的前置1给去除。subnormal value 将 -FLT_MIN、FLT_MIN之间的值均分。最接近0的值为 FLT_TRUE_MIN。
如果不使用subnormal,当 a!=b时, a-b==0 也有可能成立。
34、Machine Epsilon
the machine epsilon is defined to be the smallest floating-point value # that satisfies the equation, 1+ Epsilon != 1. For an IEEE-754 floating-point number, with its 23 bits of precision, the value of Epsilon is 2^(-23), which is approximately 1.192x10^(-7). The most significant digit of Epsilon falls just inside the range of significant digits in the value 1.0, so adding any value smaller than # to 1.0 has no effect. In other words, any new bits contributed adding a value smaller than # will get “chopped off” when we try to fit the sum into a mantissa with only 23 bits.
35、Units in the Last Place(ULP)
ULP指的是 mantissa的最后一位的大小。当浮点数值为1时,1ULP大小为 2^(-23)。当浮点数值为2时,1ULP增大2倍(因为整体 mantissa左移了一位),大小为2^(-22)。
The concept of units in the last place illustrates the idea that the precision of a floating-point number depends on its exponent.
It can also be useful for finding the floating-point value that is the next largest representable value relative to a known value, or conversely the next smallest representable value relative to that value.
36、Before C++11, programmers had to rely on non-portable sized types provided by their compiler.
For example, the Visual Studio C/C++ compiler defined the following extended keywords for declaring variables that are an explicit number of bits wide: __int8, __int16, __int32 and __int64.
Most other compilers have their own “sized” data types, with similar semantics but slightly different syntax.
at Naughty Dog we use the following sized types:
• F32 is a 32-bit IEEE-754 floating-point value.
• U8, I8, U16, I16, U32, I32, U64 and I64 are unsigned and signed 8-,16-, 32- and 64-bit integers, respectively.
• U32F and I32F are “fast” unsigned and signed 32-bit values, respectively. Each of these data types contains a value that is at least 32 bits wide, but may be wider if that would result in faster code on the target CPU.
<cstdint>
The C++11 standard library introduces a set of standardized sized integer types. They are declared in the <cstdint> header, and they include the signed types std::int8_t, std::int16_t, std::int32_t and std::int64_t and the unsigned types std::uint8_t, std::uint16_t, std::uint32_t and std::uint64_t, along with “fast” variants (like the I32F and U32F types we defined at Naughty Dog).
37、OGRE’s Primitive Data Types
Ogre::Real 默认是 float,如果 OGRE_DOUBLE_PRECISION 为1,则Ogre::Real 会成为 double。
38、LIttle-endian & Big Endian
1)Little-endian. If a microprocessor stores the least significant byte of a multibyte value at a lower memory address than the most significant byte, we say that the processor is little-endian.
2)Big-endian. If a microprocessor stores the most significant byte of a multibyte value at a lower memory address.
39、
40、