One of the most critical problems of the game engine is the scenario management technology. The most basic part is scenario segmentation. Several problems to be solved in scenario segmentation are as follows:
- Whether the game scenario is a loading task or a real-time stream loading is required
- How to load a part of a game when the scenario is too large to be loaded at a time?
- How to define a part loaded at a time and based on what principles
- In split scenarios, how does a dynamic object move between shards when moving (especially similar to the collision detection function)
- How does the editor create a scenario, how to dynamically manage the size of the scenario, and whether scenario merging and splicing are supported?
- What should I do in physical system scenarios? Is it consistent with that in graphic scenarios?
The purpose of this article is to discuss the above issues and give my current understanding.
World description
Describing the world is the layer that defines the game scenario. If the game scenario is small and it is just a single load, you only need one REE or another messy [split tree. Let's first define a name for this method, "idiot-single scenario-single split tree ]. If the scenario cannot be loaded at a time, there are different options. Select 1. in a very large scenario, only one REE is used for representation. However, the depth of octree may vary depending on the scenario size. In a very large scenario, this depth will be terrible, we define this method as "brute force-single scenario-single split tree ]. Select 2. it is the optimization of selecting 1. For the child nodes of REE, compression or dynamic allocation is performed, instead of allocating a huge split tree that is extremely scary at a time, we define this method as "smart-single scenario-single split tree ]. Select 3. this is also the most common practice. A scenario is divided into multiple sub-scenarios, that is, multiple small levels. When the game is in progress, only a part of the level is loaded. for the level of the activity status, it is also a part of loading. The logic definition of the world description is like this. As for collision detection and visibility analysis, there is no difference with a single scenario. Everything is in a tree, we define this method as "Multi-scenario-single split tree ]. Select 4. the logical definition of world description is the same as that of choice 3. It also has multiple levels, but the split tree also has multiple levels. In addition, the split tree basically corresponds to the level one by one, we define this method as "Multi-scenario-Multi-split tree ]. Select 5. in some cases, the intermediate transition method is used to solve the problem, mainly for indoor and outdoor treatment methods, even in the same region, there may also be two different split trees, which we define as [moth-single scene-multiple split trees ]. Of course, there are not many such methods, but the methods that have not been listed are not much different. The methods that have already been listed are basically the most typical of several arrangement combinations. Next, we will describe the methods for Division:
[Idiot-single scenario-single split tree]: moving a dynamic object only requires moving between nodes in the [split tree] and layer nodes. The requirements of the editor are relatively simple. When creating a scenario, you must specify the surrounding body for the entire scenario. You can choose to specify that the object will not be allowed to be removed from the surrounding body.
[Brute force type-single scenario-single split tree]: the movement of dynamic objects also only needs to process the movement between nodes in the [split tree, however, when the depth of the tree increases, the calculation amount is the increase of the geometric series, and the base of this geometric series is not necessarily 2, sometimes 8, if you use REE. When the editor controls the scene size, it is not easy to do. Does the editor support dynamic expansion, or must it be fixed?
[Smart-single scenario-single split tree]: compared with the former, it is optimized for a large tree. Generally, it is a [dynamic expansion] method, the depth of the tree is maximized only in the place where the camera is located. The big node is invisible and the child node is not allocated. When one node is out of sight, also, he and his child nodes are released to minimize the overall space of the tree. In this way, the complexity is much lower during visibility judgment and collision detection, and the complexity is big-Oh (N). Specifically, the polynomial coefficient of N, it should be the number k of tree branches (8 for the eight-tree and K for the KD-tree), the product of a constant C related to the line of sight. The problems in the editor are the same as those in the former.
[Multi-scenario-single split tree]: the advantage of this division method is that the split tree is just a container, and the systems in multiple scenarios constantly get and put things in it, the job of the tree itself is simple, that is, visibility judgment and collision detection.
[Multi-scenario-Multi-split tree]: Not necessarily a split tree, but multiple split trees. Therefore, the complexity of the analysis algorithm is reduced, which is complicated. The beauty of this method is that every tree can be complete. Therefore, for developers, this place is easy to implement and does not need to be dynamically expanded, you can also perform Linear Compression and select the most suitable tree for different types of sub-scenarios (indoor and outdoor). For complex scenarios, you can select a deeper tree and a simple scenario, you only need sparse, shallow trees. Of course, there are also many shortcomings. The core point is the moving of objects. This problem is not only difficult to solve in the editor, but also complicated during game operation. For example, if an object moves from a sub-level to another sub-level, it needs to process the object and then split the tree. What if the object is in both scenarios, based on which principle should we select the priority and put it in the segmentation tree of which scenario? Can it be easily attributed to the previous scenario? Is there any special situation? When a game is running, if an object moves to another scene, the scene in which the object was previously located is classified as an inactive level during running, followed, when this inactive level is loaded again, will one more object come out, that is, the object that shouldn't appear in the same place?
[Demon moth-single scene-multiple split trees]: To handle indoor and outdoor scenes, two trees are required. But why not simply turn this into two sub-scenarios, you can also import data separately in the editor once to match the relative position. You can even learn about cryengine and divide it into layers, even if it appears in the editor at the same time, there is also a strict division. Therefore, this is just an excessive solution. We can continue to make it into a [multi-scenario-Multi-split tree]. If it is only a single scenario, it will have the disadvantages of dividing all single scenarios, is this solution even more amazing than all the disadvantages of Multi-Tree Splitting? But it does make a lot of commercial engines really do this, but now the engine is getting fewer and fewer.
In the above discussion, I did not mention the physical aspect. I have not yet figured out that various choices are related to the special requirements of developers. For me, I use a third-party physical engine, havok and physx, both engines tend to make game Object Management-graphical object management-physical object management, all of which are divided using the same scenario. Therefore, game world is divided into multiple levels, each level corresponds to a graphics scene and a physics scene (or physics Island). In this way, the physical coordinate description of an object and the graphic coordinate description can be very simple and unified, the problems to be handled are the same. You can try to ensure that changes occur in the same place.
Detailed information about the segmentation tree algorithm octree
(Picture from http://www.gamasutra.com/features/19970801/octree.htm)
REE divides the plane of space and is orthogonal to the coordinate axis. For each node, it is an AABB (axis-Aligned-bound-box), and each node is inside, there are eight completely equal child nodes.
In this article, we only talked about the separation of space, but not the visibility judgment. Therefore, in terms of space separation, REE is not so advantageous. In addition, many times, the space utilization is not ideal, especially when the scenario is wide (in the horizontal direction) and the depth (in the vertical direction) is relatively small. Space-related algorithms:
1. Add static and dynamic objects:
Add a static object, first calculate the final AABB of the object, and then check whether the AABB is inside the root node of REE Ree, continue to judge whether the AABB is in each subnode. If so, continue to judge...
If an AABB is not in the REE at the beginning, it should be an error for a REE that cannot be scaled. Therefore, the editor is creating and moving static objects (or even dynamic objects) make sure that the object is not removed from the root node range.
Add static object pseudocode:
Bool octreenode: addobject (Object * object)
{
If (this-> AABB. Contains (Object-> AABB) // is the AABB of this object within the range of the current node?
{
For (INT I = 0; I <8; I ++) // continue to judge whether the object is in each subnode.
{
If (Children [I]-> addobject (object) // If a subnode [accepts] This object, the task is completed.
{
Return true;
}
}
// Although the object is in the current node, but no child node can completely contain this object, put it in the current
This-> addobjectimpl (object );
Return true;
}
Return false;
}
2. How simple it is to move a dynamic object...
Void octreeroot: updateobject (Object * object)
{
// Open the refrigerator door
Rootnode-> removeobject (object );
// Put the elephant in
Bool result = rootnode-> addobject (object );
// Close the refrigerator door
Object-> setupdateresult (result );
}
There is only one thing about space division. Each engine has some differences in details, but the core idea is so simple.
BSP
BSP details are discussed in detail in the quake3 chapter, which is omitted here.
KD-tree
KD-tree is generally used to divide collision detection (and ray tracing) optimization. KD-tree is rarely used in graphic scenarios. KD-tree is similar to BSP in many places. Sometimes it is difficult to tell whether it is BSP or KD-tree. In fact, some tools generate BSP Based on the KD-tree algorithm.
Case study-engineunreal engine 3
Since the publication of Unreal Engine 3 in, it has taken a long time and the details of algorithms have changed a lot. I mainly analyze the version of 04 years. This version can be obtained from the Internet (in short, for example, verycd). I don't know if it can be compiled, but there is definitely no way to run it, because there was no art resource at that time in, it would not be possible to replace the current ue3 game resource. For such reasons, the analysis results cannot be so accurate.
In the 04-year version of ue3, we use the [brute force-single scenario-single split tree], but from the design point of view, we have considered the scheme of extended to [multi-scenario-single split tree] in the future. For version 04 ue3, a level is a ulevel. From the code point of view, a game only has one ulevel. Ulevel has a fprimitivehashbase member. This is the vest of REE, a subclass of fprimitivehashbase, that is, fprimitiveoctree:
All the specific operations on REE are completed in the foctreenode class:
The implementation method of other engines based on REE is the same as this. Each octreenode contains a list of objects stored in this node, followed by eight subnodes.
Core Function addprimitive
First, check whether the object is in the world. When the game is running, use singlenodefilter to add the object.
In singlenodefilter, first check whether the child node can fully contain the object. If the child node cannot contain the object, [try] to own the object. If the child node can contain the object, the child node's singlenodefilter will be recursive.
[Try] The storeactor contains the object. The specific implementation is as follows:
If the current node already contains too many objects and there are no child nodes at the same time (at the same time, this node is larger than the defined minimum-granularity node ), divide the space of this node, and then put the existing objects and new objects to the current node again (because the child nodes have been allocated, so at this time, many objects may be placed in the subnode, while other objects are placed at the subnode again ).
If the sub-node allocation operation of the node does not meet the requirements, or has been previously allocated, the newly added object will be placed in the Object List and the object will be notified, primitive-> octreenode. additem (this) [You are already in me]: P
The above is the main Algorithm related to scenario segmentation of version 04 ue3.
Next, let's take a look at the improvements made by ue3 06 and ue3 09.
In, the development of Unreal Tournament 3 was coming to an end, and the development of Gears of War had been going on for quite a while. Gow features a multi-scenario system.
In the code, the most striking point is the unworld:
Fsceneinterface * scene, scenario management related to visibility and sorting, moved from level to uworld, scene tree separated by scenario, fprimitivehashbase * hash also moved from level to uworld, level, what is left now?
There is still BSP in level, which is mainly used for collision detection, static illumination, rendering, and so on in the interior brush, as well as the objects of kismet visual scripts. Other parts are not directly related to Scenario Management, especially scenario segmentation. However, I am more interested in one of the members:
Obviously, how does unreal handle objects that span borders, especially those dynamic objects? The most important thing is "streaming a level in/out". Aha! Pay attention to the difficulties mentioned above in [multi-scenario-Multi-split tree. I believe it will be quite tangled here (detailed analysis later ).
In level, the only thing that deals with Game objects directly is the level parent class. levelbase:
Therefore, we can get the rough method of scenario segmentation in version 06 unreal, uworld management level (mainly runtime streaming), and some other messy transactions (transactions are quite many and have different categories, this is a common problem of the unreal design strategy. Level is simply provided as a game object (New Game objects are obtained from the Streaming results ).
Unreal 06 to 09 has not changed much in this regard. (PS: Since I have never found a version that can be debugged, further introduction to unreal may take a while. However, unreal is the most important reference engine in this series .)
Cryengine
First, let's talk about cryengine... Ah... Cryengine ~~~~ Among so many engines, cryengine is my favorite design. Well, it even exceeded the nebula device series. What is the most important thing about the 21 st century game engine? Module Design! The most important part of the module design I understand is layering and chunking. Cryengine, C4, and Nebula device 3 are doing well in terms of layering. In terms of chunking, Nd3 began to be messy; C4 was not clear, and there was no SDK at hand. CE is also very reasonable in terms of division. I fully understand ce design. The code from crysis mod SDK only contains some header files. Generally, it is a public interface and can be used to directly load DLL dynamically. For the exposed interface classes, the design is very gorgeous! Well, let's take a look.
In the application layer Scenario Management, there is only a little bit of interface, but there is no way to analyze it:
Quake 3 & Half lifeogregamebryo
Gamebryo is mostly a negative textbook in this series. In this series, there is nothing to say about GB, and there is no scenario division at all. Classes related to the scenario are put in the core appframework, the Entity system; and there is no scenario division, and everything looks like they have been split into scenarios; in other projects, there seems to be no relevant implementation. Most of the time, it seems that the management of this scenario is still inclined to scene graph (in today's opinion, scene graph is the mainstream non-mainstream). It is indeed in line with the image of gamebryo in people's minds.
Cube 2 case study-editorgtk-radiantworld craft (my project) getictorque 3D