实体组件系统-C ++中的出色游戏设计模式(第1部分)(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/the-entity-component-system-an-awesome-game-design-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 20 分钟阅读 - 9572 个词 阅读量 0实体组件系统-C ++中的出色游戏设计模式(第1部分)(译文)
原文地址:https://www.codeproject.com/Articles/1216418/The-Entity-Component-System-An-Awesome-Game-design
原文作者:Tobs88
译文由本站 robot-v1.0 翻译
前言
This article is about the Entity-Component-System (ECS). It is a design pattern which allows you great flexibility in designing your overall software architecture.
本文是关于实体组件系统(ECS)的.这是一种设计模式,可让您在设计整体软件体系结构时具有极大的灵活性.
介绍(Introduction)
在本文中(原文为(In this article (original) 发布(post) ),我想谈一谈Entity-Component-System((), I want to talk about the Entity-Component-System ()精英(ECS)).您可以在Internet上找到很多有关此问题的信息,因此在这里我将不做详细解释,但是我将讨论有关自己的实现的更多信息.(). You can find a lot of information about the matter on the internet so I am not going to explain in depth here, but I will be discussing more about my own implementation.) 首先是第一件事.您可以在我的网站上找到我的ECS的完整源代码.(First things first. You will find the full source code of my ECS in my) 的github(github) 资料库.(repository.) 实体组件系统(通常在视频游戏中遇到)是一种设计模式,可让您在设计整体软件体系结构时具有极大的灵活性(An Entity-Component-System – mostly encountered in video games – is a design pattern which allows you great flexibility in designing your overall software architecture) [1](https://www.codeproject.com#refs) .诸如Unity,Epic或Crytek之类的大公司将这种模式纳入其框架中,从而为开发人员提供了非常丰富的工具来构建其软件.您可以查看这些帖子以关注有关此问题的广泛讨论(. Big companies like Unity, Epic or Crytek incorporate this pattern into their frameworks to provide a very rich tool for developers to build their software with. You can checkout these posts to follow a broad discussion about the matter) [2,3,4,5](https://www.codeproject.com#refs) .(.) 如果您阅读了我上面提到的文章,您会发现它们都有相同的目标:在实体,组件和系统之间分配不同的关注点和任务.这是此模式中的三大参与者,并且它们之间的耦合相当松散.(If you read the articles I mentioned above, you will notice they all share the same goal: distributing different concerns and tasks between Entities, Components and Systems. These are the three big players in this pattern and are fairly loosely coupled.)实体(Entities)主要用于提供唯一标识符,使环境意识到单个个体的存在,并充当捆绑一组组件的一种根对象.(are mainly used to provide a unique identifier, make the environment aware of the existence of a single individual and function as a sort of root object that bundles a set of components.)组件(Components)只不过是不具有任何复杂逻辑的容器对象.理想情况下,它们是简单的普通旧数据对象(POD).每种类型的组件都可以附加到实体上以提供某种类型的属性.举例来说,可以将"健康组件"附加到实体上,使其具有致命性(are nothing more than container objects that do not possess any complex logic. Ideally, they are simple plain old data objects (PODs). Each type of a component can be attached to an entity to provide some sort of a property. Let’s say for example, a “Health-Component” can be attached to an entity to make it mortal by giving it)健康(health),该值不得超过内存中的整数或浮点值.(, which is not more than an integer or floating point value in memory.) 到目前为止,我碰到的大多数文章都同意实体和组件对象的用途和用途,但是对于系统而言,意见不一.有人建议系统仅了解组件.此外,有人说每种类型的组件都应该有一个系统,例如,“碰撞组件"有一个"碰撞系统”,“健康组件"有一个"健康系统”,等等.这种方法有点僵化,没有考虑不同组件之间的相互作用.限制较少的方法是让不同的系统处理应关注的所有组件.例如,“物理系统"应注意"碰撞组件"和"刚体组件”,因为两者都可能包含有关物理模拟的必要信息.在我的愚见,(Up to this point, most of the articles I came across agree about the purpose and use of entity and component objects, but for systems, opinions differ. Some people suggest that systems are only aware of components. Furthermore, some say for each type of component, there should be a system, e.g., for “Collision-Components” there is a “Collision-System”, for “Health-Components” there is a “Health-System”, etc. This approach is kind of rigid and does not consider the interplay of different components. A less restrictive approach is to let different systems deal with all components they should be concerned with. For instance, a “Physics-Systems” should be aware of “Collision-Components” and “Rigidbody-Components”, as both probably contain necessary information regarding physics simulation. In my humble opinion,)系统(systems)是"封闭环境".也就是说,它们不拥有实体或组件的所有权.他们确实通过独立的管理器对象访问它们,这些管理器对象又将照顾实体和组件的生命周期.(are “closed environments”. That is, they do not take ownership of entities nor components. They do access them through independent manager objects, which in turn will take care of the entities and components life-cycle.) 这就提出了一个有趣的问题:实体,组件和系统之间或多或少相互独立时如何相互通信?取决于实现方式,答案有所不同.至于实现,我将向您展示,答案是事件源(This raises an interesting question: how do entities, components and systems communicate with each other, if they are more or less independent of each other? Depending on the implementation, the answer differs. As for the implementation, I am going to show you, the answer is event sourcing) [6](https://www.codeproject.com#refs) .活动是通过"活动管理器"分发的,每个对活动感兴趣的人都可以听听管理者的意见.如果实体或系统甚至组件具有重要的状态更改以进行通信,例如"位置更改"或"玩家死亡",则它可以告诉"事件管理器".他将广播该事件,并将通知该事件的所有订阅者.这样,一切都可以互连.(. Events are distributed through an “Event-Manager” and everyone interested in events can listen to what the manager has to say. If an entity or system or even a component has an important state change to communicate, e.g., “position changed” or “player died”, it can tell the “Event-Manager”. He will broadcast the event and all subscribers for this event will get notified. This way, everything can be interconnected.) 好吧,我想上面的介绍比我实际计划的要长,但是到了. :)在我们深入研究之前(Well, I guess the introduction above got longer than I was actually planning to, but here we are. :) Before we are going to dive deeper into the) 码(code) ,顺便说一下,它是C ++ 11,我将概述架构的主要功能:(, which is C++11 by the way, I will outline the main features of my architecture:)
- 记忆效率(Memory efficiency)-为了快速创建和删除实体,组件和系统对象以及事件,我不能依赖于标准的新建/删除托管堆内存.解决方案当然是自定义内存分配器.(- To allow a quick creation and removal of entity, component and system objects as well as events, I could not rely on standard new/delete managed heap-memory. The solution for this was of course a custom memory allocator.)
- 记录中(Logging)-要查看发生了什么,我使用了log4cplus(- To see what is going on, I used log4cplus) [7](https://www.codeproject.com#refs) 用于记录.(for logging.)
- 可扩展(Scalable)-易于实现新类型的实体,组件,系统和事件,而无需任何预设上限(系统内存除外)(- It is easy to implement new types of entities, components, systems and events without any preset upper limit except your system’s memory)
- 灵活(Flexible)-实体,组件和系统之间不存在依赖关系(实体和组件确实具有某种依赖关系,但彼此之间不包含任何指针逻辑)(- No dependencies exist between entities, components and systems (entities and components sure do have a sort of dependency, but do not contain any pointer logic of each other))
- 简单的对象查找/访问(Simple object lookup/access)-通过以下方式轻松检索实体对象及其组件(- Easy retrieval of entity objects and their components through an)
EntityId
或组件迭代器迭代某种类型的所有组件(or a component-iterator to iterate over all components of a certain type) - 流量控制(Flow control)-系统具有优先级并且可以相互依赖,因此可以建立执行它们的拓扑顺序(- Systems have priorities and can depend on each other, therefore a topological order for their execution can be established)
- 易于使用(Easy to use)-该库可以轻松地合并到其他软件中;只有一个包括.(- The library can be easily incorporated into other software; only one include.) 下图描述了我的实体组件系统的总体架构:(The following figure depicts the overall architecture of my Entity-Component-System:)
图01:ECS体系结构概述(ECS.dll).(Figure-01: ECS Architecture Overview (ECS.dll).)如您所见,此图片中有四个不同的彩色区域.每个区域都定义了体系结构的模块化部分.在最底部-实际上在上方的图片中;它应该是颠倒的-我们获得了内存管理和日志记录功能(黄色区域).该第一层模块处理的是非常底层的任务.它们由实体组件系统(蓝色区域)和事件源(红色区域)中的第二层模块使用.这些家伙主要处理对象管理任务.第三层模块位于最上方(As you can see, there are four different colored areas in this picture. Each area defines a modular piece of the architecture. At the very bottom - actually in the picture above at the very top; it should be upside down - we got the memory management and the logging stuff (yellow area). This first-tier modules are dealing with very low-level tasks. They are used by the second-tier modules in the Entity-Component-System (blue area) and the event sourcing (red area). These guys mainly deal with object management tasks. Sitting on top is the third-tier module, the) ECS_Engine
(绿色区域).此高级全局引擎对象协调所有第二层模块,并负责初始化和销毁.好的,这是一个简短且非常抽象的概述,现在让我们对其进行详细介绍.((green area). This high-level global engine object orchestrates all second-tier modules and takes care of the initialization and destruction. All right, this was a short and very abstract overview, now let’s get more into the details.)
内存管理器(Memory Manager)
让我们从(Let’s start with the) 内存管理器(Memory-Manager) .它的实现是基于一篇文章(. It’s implementation is based on an article) [8](https://www.codeproject.com#refs) 我发现了(I have found on)** gamedev.net(gamedev.net) **.这个想法是将堆内存的分配和释放保持在绝对最低限度.因此,只有在应用程序启动时,才会分配大量的系统内存(*. The idea is to keep heap-memory allocations and releases to an absolute minimum. Therefore, only at application start, a big chuck of system-memory is allocated with*) 分配(malloc) .现在,此内存将由一个或多个自定义分配器管理.分配器的类型很多(*. This memory now will be managed by one or more custom allocator. There are many types of allocators*) [9](https://www.codeproject.com#refs) (线性,堆栈,空闲列表…),每个元素都有其优缺点(在此不做讨论).但是,即使它们在内部以不同的方式工作,它们都共享一个公共的公共接口:(*( linear, stack, free list…) and each one of them has its pros and cons (which I am not going to discuss here). But even if they internally work in a different way, they all share a common public interface:*)
class Allocator
{
public:
virtual void* allocate(size_t size) = 0;
virtual void free(void* p) = 0;
};
上面的代码段不完整,但概述了两个主要方面(The code snippet above is not complete, but outlines the two major) public
每个具体分配器必须提供的方法:(methods each concrete allocator must provide:)
allocate
-分配一定数量的字节,并将内存地址返回到该块,并且(- which allocates a certain amount of bytes and returns the memory-address to this chunk and)free
-根据给定的地址取消分配先前分配的内存(- to de-allocate a previously allocated chuck of memory given its address) 现在,我们可以做一些很酷的事情,例如将多个分配器链接起来:(Now with that said, we can do cool stuff like chaining-up multiple allocators like that:)
图02:自定义分配器管理的内存.(Figure-02: Custom allocator managed memory.)如您所见,一个分配器可以从另一(父)分配器获取其要管理的内存块,而后者又可以从另一分配器获取其内存,依此类推.这样,您可以建立不同的内存管理策略.对于我的ECS的实施,我提供了一个根堆栈分配器,该分配器获得了初始分配的1GB系统内存卡盘.第二层模块将从此根分配器分配所需的内存,并且仅在应用程序终止时释放它们.(As you can see, one allocator can get its chunk of memory - that it is going to manage - from another (parent) allocator, which in turn could get its memory from another allocator and so on. That way, you can establish different memory management strategies. For the implementation of my ECS, I provide a root stack-allocator that gets an initial allocated chuck of 1GB system-memory. Second-tier modules will allocate as much memory as they need from this root allocator and only will free it when the application gets terminated.)
图03:全局内存的可能分布.(Figure-03: Possible distribution of global memory.) 图03(Figure-03) 显示了如何在第二层模块之间分配全局内存:(shows how the global memory could be distributed among the second-tier modules: “)**全局内存用户A”(Global-Memory-User A")**可以是实体管理器,(could be the Entity-Manager, “)**全局内存用户B”(Global-Memory-User B")组件管理器和(the Component-Manager and)“全局内存用户C”(“Global-Memory-User C”)**系统管理员.(the System-Manager.)
记录中(Logging)
我不会过多地谈论日志记录,因为我只是使用log4cplus(I am not going to talk too much about logging as I simply used log4cplus) [7](https://www.codeproject.com#refs) 为我做这份工作.我所做的只是定义一个(doing this job for me. All I did was defining a) 记录仪(Logger) 基类托管(base class hosting a) log4cplus::Logger
对象和一些包装方法来转发简单的日志调用,例如"(object and a few wrapper methods forwarding simple log calls like “) LogInfo()
“,"(”, “) LogWarning()
“等(”, etc.)
实体管理器,IEntity,实体和公司.(Entity-Manager, IEntity, Entity and Co.)
好的,现在让我们谈谈我的建筑的实质.蓝色区域(Okay, now let’s talk about the real meat of my architecture; the blue area in) 图01(Figure-01) .您可能已经注意到所有管理器对象及其相关类之间的类似设置.看看(. You may have noticed the similar setup between all manager objects and their concerning classes. Have a look at the) 实体管理器(EntityManager) ,(,) 实体(IEntity) 和(and) 实体(Entity) 类.的(classes for example. The) EntityManger
类应该在应用程序运行时管理所有实体对象.这包括创建,删除和访问现有实体对象之类的任务.(class is supposed to manage all entity objects during application run-time. This includes tasks like creating, deleting and accessing existing entity objects.) IEntity
是一个接口类,提供实体对象的最基本特征,例如对象标识符和(静态)类型标识符.这是静态的,因为程序初始化后它不会更改.此类型标识符在多个应用程序运行中也是一致的,并且只有在修改了源代码的情况下才可以更改.(is an interface class and provides the very basic traits of an entity object, such as an object-identifier and (static-)type-identifier. It’s static because it won’t change after program initialization. This type-identifier is also consistent over multiple application runs and may only change, if source code was modified.)
class IEntity
{
// code not complete!
EntityId m_Id;
public:
IEntity();
virtual ~IEntity();
virtual const EntityTypeId GetStaticEntityTypeID() const = 0;
inline const EntityId GetEntityID() const { return this->m_Id; }
};
类型标识符是一个整数值,并且针对每个具体实体类而有所不同.这使我们可以检查(The type-identifier is an integer value and varies for each concrete entity class. This allows us to check the type of an) IEntity
对象在运行时.最后但并非最不重要的是(object at run-time. Last but not least comes the) Entity
模板类.(template class.)
template<class T><class t="">
class Entity : public IEntity
{
// code not complete!
void operator delete(void*) = delete;
void operator delete[](void*) = delete;
public:
static const EntityTypeId STATIC_ENTITY_TYPE_ID;
Entity() {}
virtual ~Entity() {}
virtual const EntityTypeId GetStaticEntityTypeID() const override
{ return STATIC_ENTITY_TYPE_ID; }
};
// constant initialization of entity type identifier
template<class T> const EntityTypeId Entity<T>::STATIC_ENTITY_TYPE_ID =
util::Internal::FamilyTypeID::Get();</class>
该类的唯一目的是初始化具体实体类的唯一类型标识符.我在这里利用了两个事实:第一个常量初始化(This class’s sole purpose is the initialization of the unique type-identifier of a concrete entity class. I made use of two facts here: first constant initialization) [10](https://www.codeproject.com#refs) 静态变量,其次是模板类如何工作的性质.每个版本的模板类(of static variables and second, the nature of how template classes work. Each version of the template class) Entity
会有自己的(will have its own) static
变量(variable) STATIC_ENTITY_TYPE_ID
.依次保证可以在任何动态初始化发生之前对其进行初始化.期限 “(. Which in turn will be guaranteed to be initialized before any dynamic initialization happens. The term “) util::Internal::FamilyTypeID::Get()
“用于实现一种类型计数器机制.每次使用不同的计数器调用计数器时,它都会在内部递增计数器(” is used to implement a sort of type counter mechanism. It internally increments a counter every time it gets called with a different) ,但在使用相同的值调用时始终返回相同的值(*, but always returns the same value when called with the same*)
再次.我不确定该模式是否具有特殊名称,但是它很酷. :)至此,我也摆脱了(again. I am not sure if that pattern has a special name, but it is pretty cool. :) At this point, I also got rid of the) delete
和(and) delete[]
操作员.这样,我确保没有人会意外地打电话给这些家伙.只要您的编译器足够聪明,这也会在尝试使用(operator. This way, I made sure nobody would accidentally call these guys. This also - as long as your compiler is smart enough - would give you a warning when trying to use the) new
要么(or) new[]
实体对象的运算符消失了.这些操作符不打算使用,因为(operator of entity objects as their counterparts are gone. These operators are not intended to be used since the) EntityManager
全班会照顾好这一切.好吧,让我们总结一下我们刚刚学到的东西.管理器类提供基本功能,例如创建,删除和访问对象.接口类用作最基本的类,并提供唯一的对象标识符和类型标识符.模板类确保类型标识符的正确初始化,并删除(class will take care of all this. Alright, let’s summarize what we just learned. The manager class provides basic functionality such as creating, deleting and accessing objects. The interface class functions as the very root base class and provides an unique object-identifier and type-identifier. The template class ensures the correct initialization of the type-identifier and removes the) delete
/(/) delete[]
操作员.管理器,接口和模板类的这种相同模式也用于组件,系统和事件.这些群体不同的唯一但重要的是方法(operator. This very same pattern of a manager, interface and template class is used for components, systems and events as well. The only, but important, thing these groups differ, is the way) manager
类存储和访问其对象.(classes store and access their objects.)
让我们来看看(Let’s have a look at the) EntityManager
全班第一(class first.) 图04(Figure-04) 显示了事物存储的总体结构.(shows the overall structure of how things are stored.)
图04:EntityManager类及其对象存储的抽象视图.(Figure-04: Abstract view of EntityManager class and it’s object storage.)创建新的实体对象时,可以使用(When creating a new entity object, one would use the) EntityManager::CreateEntity<T>(args...)
方法.这个(method. This) public
方法首先使用模板参数,该参数是要创建的具体实体的类型.其次,此方法采用可选数量的参数(可以为空),这些参数被转发给(method first takes a template parameter which is the type of the concrete entity to be created. Secondly, this method takes in an optional amount of parameters (can be empty) which are forwarded to the constructor of) `` .通过可变参数模板转发这些参数(. Forwarding these parameters happens through a variadic template) [11](https://www.codeproject.com#refs) .在创建过程中,以下事件在内部发生…(. During creation, the following things happen internally …)
- 的(The)
ObjectPool
** [12](https://www.codeproject.com#refs) **用于类型的实体对象(for entity objects of type)如果该池不存在,将获取一个新池.(will be acquired, if this pool does not exist, a new one will be created.) - 新的内存将从该池中分配;足以存储(New memory will be allocated from this pool; just enough to store the)目的.(object.)
- 在实际调用构造函数之前(Before actually calling the constructor of) `` ,一个新的(, a new)
EntityId
是从经理那里获得的.该ID将与之前分配的内存一起存储到查找表中,这样,我们以后可以使用该ID查找实体实例.(is acquired from the manager. This id will be stored along with the before allocated memory into a look-up table, this way, we can look-up the entity instance later with that id.) - 接下来,使用C ++放置新运算符(Next the C++ in-placement new operator)** [13](https://www.codeproject.com#refs) **被转发(*is called with the forwarded*)
args
**…(*…*)**作为创建新实例的输入(*as input to create a new instance of*)T.(*T.*) - 最后,该方法返回实体的标识符.(Finally, the method returns the entity’s identifier.)
创建实体对象的新实例后,您可以通过其唯一的属性访问它(After a new instance of an entity object got created, you can get access to it via its unique)目的(object)标识符((identifier ()
EntityId
)和() and)EntityManager::GetEntity(EntityId id)
.要销毁实体对象的实例,必须调用(. To destroy an instance of an entity object, one must call the)EntityManager::DestroyEntity(EntityId id)
方法.(method.) 的(The)ComponentManager
类的工作方式相同,外加一个扩展名.除了用于存储各种组件的对象池外,它还必须提供一种将组件链接到它们自己的实体对象的附加机制.此约束导致第二个查找步骤:首先,我们检查给定的条目是否存在(class works in the same way plus one extension. Besides the object pools for storing all sorts of components, it must provide an additional mechanism for linking components to their owning entity objects. This constraint results in a second look-up step: first, we check if there is an entry for a given)EntityId
,如果有的话,我们将通过在组件列表中查找该实体来检查该实体是否具有某种类型的组件.(, if there is one, we will check if this entity has a certain type of component attached by looking it up in a component-list.)
图05:Component-Manager对象存储概述.(Figure-05: Component-Manager object storage overview.)使用(Using the) ComponentManager::CreateComponent(EntityId id, args...)
方法允许我们向实体添加特定组件.用(method allows us to add a certain component to an entity. With) ComponentManager::GetComponent(EntityId id)
**,(,)**我们可以访问实体的组件,(we can access the entity’s components, where) 指定我们要访问的组件类型.如果该组件不存在,(*specifies what type of component we want to access. If the component is not present,*) `nullptr` 返回.要从实体中删除组件,可以使用(*is returned. To remove a component from an entity, one would use the*) `ComponentManager::RemoveComponent(EntityId id)` ****方法.但是,等等,还有更多.访问组件的另一种方法是使用(*method. But wait, there is more. Another way of accessing components is using the*) `ComponentIterator` .这样,您可以遍历某种类型的所有现有组件(*. This way, you can iterate over all existing components of a certain type*)
.如果像"物理系统"这样的系统想要对所有"刚体组件"施加重力,这可能会很方便.(. This might be handy if a system like the “Physics-System” wants to apply gravity to all “Rigidbody-Components”.)
的(The) SystemManager
class没有用于存储和访问系统的任何额外花哨.一个简单的映射用于存储系统及其类型标识符作为键.(class does not have any fancy extras for storing and accessing systems. A simple map is used to store a system along with its type-identifier as the key.)
的(The) EventManager
类使用管理大量内存的线性分配器.该内存用作事件缓冲区.事件存储在该缓冲区中,并在以后分派.调度事件将清除缓冲区,以便可以存储新事件.每帧至少发生一次.(class uses a linear-allocator that manages a chunk of memory. This memory is used as an event buffer. Events are stored into that buffer and dispatched later. Dispatching the event will clear the buffer so new events can be stored. This happens at least once every frame.)
图06:概述ECS架构概述(Figure-06: Recap ECS architecture overview)我希望到此为止,您对我的ECS中的工作方式有了一个了解.如果没有,不用担心,看看(I hope at this point, you got an idea of how things work in my ECS. If not, no worries, have a look at) 图-06(Figure-06) 让我们回顾一下.你可以看到(and let’s recap. You can see the) EntityId
这非常重要,因为您将使用它来访问具体的实体对象实例及其所有组件.所有组件都知道其所有者,即拥有(is quite important as you will use it to access a concrete entity object instance and all its components. All components know their owner, that is, having a) component
您可以通过询问对象轻松地获得实体(object at hand, you can easily get the entity by asking the) EntityManager
类具有该组件的给定所有者标识.要传递一个实体,您将永远不会直接使用其指针,但可以将事件与(class with the given owner-id of that component. To pass an entity around, you would never use its pointer directly, but you can use events in combination with the) EntityId
.您可以创建一个具体的事件,比如说”(. You could create a concrete event, let’s say “) EntityDied
例如,并且此事件(必须是简单的旧数据对象)具有类型为(” for example, and this event (which must be a plain old data object) has a member of type) EntityId
.现在通知所有事件监听器((. Now to notify all event listeners () IEventListener
)-可能是实体,组件或系统-我们使用() - which could be Entities, Components or Systems - we use) EventManager::SendEvent(entityId)
.另一端的事件接收器现在可以使用提供的(. The event receiver on the other side now can use the provided) EntityId
并问(and ask the) EntityManager
类以获取实体对象或(class to get the entity object or the) ComponentManager
类以获取该实体的特定组件.绕道的原因很简单,在运行应用程序的任何时候,都可以通过某种逻辑删除实体或其组件之一.因为您不会因为额外的清理工作而使代码混乱,所以您依赖于此(class to get a certain component of that entity. The reason for that detour is simple, at any point while running the application, an entity or one of its components could be deleted by some logic. Because you won’t clutter your code by extra clean-up stuff you rely on this) EntityId
.如果经理返回(. If the manager returns) nullptr
为了那个原因(for that) EntityId
,您将知道一个实体或组件不再存在.红场顺便说一句.对应于(, you will know that an entity or component does no longer exists. The red square btw. is corresponding to the one in) 图01(Figure-01) 并标出了ECS的边界.(and marks the boundaries of the ECS.)
引擎对象(The Engine Object)
为了使事情更舒适,我创建了一个引擎对象.引擎对象可确保在客户端软件中轻松集成和使用.在客户端,只需将”(To make things a little bit more comfortable, I created an engine object. The engine object ensures an easy integration and usage in client software. On client side, one only has to include the “)ECS/ECS.h(ECS/ECS.h)“标题并调用(” header and call the) ECS::Initialize()
方法.现在将初始化一个静态全局引擎对象((method. Now a static global engine object will be initialized () ECS::ECS_Engine
),并且可以在客户端使用以访问所有经理类.此外,它还提供了() and can be used at client side to get access to all the manager classes. Furthermore, it provides a) SendEvent
广播事件的方法和(method for broadcasting events and an) Update
方法,它将自动调度所有事件并更新所有系统.的(method, which will automatically dispatch all events and update all systems. The) ECS::Terminate()
应该在退出主程序之前被调用.这将确保释放所有获得的资源.以下代码段演示了ECS全局引擎对象的基本用法.(should be called before exiting the main program. This will ensure that all acquired resources will be freed. The code snippet below demonstrates the very basic usage of the ECS’s global engine object.)
结论(Conclusion)
本文中描述的实体组件系统已完全正常运行并可以使用.但是像往常一样,肯定有一些想法可以改进.以下列表概述了我提出的一些想法:(The Entity-Component-System described in this article is fully functional and ready to use. But as usual there are certainly a few thinks to improve. The following list outlines just a few ideas that I came up with:)
-
使其成为线程安全的(Make it thread-safe)
-
在面临威胁的情况下运行每个系统或一组系统按其拓扑顺序(Run each system or a group of systems in threats w.r.t. to their topological order)
-
重构事件源和内存管理,并将它们作为模块包含在内(Refactor event-sourcing and memory management and include them as modules)
-
序列化(serialization)
-
剖析(profiling)
-
…(…) 希望本文对您有所帮助,并且您和我一样喜欢阅读它. :)如果您想查看我的ECS的运行情况,请查看此演示:(I hope this article was helpful and you enjoyed reading it as much as I did writing it. :) If you want to see my ECS in action, check out this demo:)
-
https://youtu.be/idYdpPCUsyg(https://youtu.be/idYdpPCUsyg) 的(The)
BountyHunter
演示大量使用了ECS并演示了这种模式的优势.如果您想知道如何,请看一下(demo makes heavy use of the ECS and demonstrates the strength of this pattern. If you want to know how, have a look at this) 发布(post) .(.) 至今 …(So far …)
参考文献(References)
- 1 https://zh.wikipedia.org/wiki/实体组件系统(https://en.wikipedia.org/wiki/Entity-component-system)
- 2 http://gameprogrammingpatterns.com/component.html(http://gameprogrammingpatterns.com/component.html)
- 3 https://www.gamedev.net/articles/programming/general-and-gameplay-programming/understanding-component-entity-systems-r3013/(https://www.gamedev.net/articles/programming/general-and-gameplay-programming/understanding-component-entity-systems-r3013/)
- 4 https://github.com/junkdog/artemis-odb/wiki/Introduction-to-Entity-Systems(https://github.com/junkdog/artemis-odb/wiki/Introduction-to-Entity-Systems)
- 5 http://scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf(http://scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf)
- 6 https://docs.microsoft.com/zh-cn/azure/architecture/patterns/event-sourcing(https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
- 7 https://sourceforge.net/p/log4cplus/wiki/Home/(https://sourceforge.net/p/log4cplus/wiki/Home/)
- 8 https://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/(https://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/)
- 9 https://github.com/mtrebi/memory-allocatorshttps://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/(https://github.com/mtrebi/memory-allocatorshttps://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/) 10 http://en.cppreference.com/w/cpp/language/constant_initialization(http://en.cppreference.com/w/cpp/language/constant_initialization)
- 11 https://zh.wikipedia.org/wiki/Variadic_template(https://en.wikipedia.org/wiki/Variadic_template)
- 12 http://gameprogrammingpatterns.com/object-pool.html(http://gameprogrammingpatterns.com/object-pool.html)
- 13 http://en.cppreference.com/w/cpp/language/new(http://en.cppreference.com/w/cpp/language/new)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C++ Design Architect game 新闻 翻译