实体组件系统-BountyHunter游戏(第2部分)(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/the-entity-component-system-bountyhunter-game-part-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 13 分钟阅读 - 6504 个词 阅读量 0实体组件系统-BountyHunter游戏(第2部分)(译文)
原文地址:https://www.codeproject.com/Articles/1216420/The-Entity-Component-System-BountyHunter-game-Part
原文作者:Tobs88
译文由本站 robot-v1.0 翻译
前言
The second part of the series “The Entity-Component-System”. This article is about a game based on the ECS implementation from the first part.
系列"实体组件系统"的第二部分.本文是关于从第一部分开始基于ECS实施的游戏的. 嘿伙计 :)(Hey Folks :)) 从我的上一篇继续写作(Continuing writing from my last) 发布(post) ,在这里我谈到了实体组件系统(ECS)设计模式.现在,我想向您展示如何实际使用它来构建游戏.如果您还没有看过游戏,请查看我借助ECS构建的什么样的游戏.(, where I talked about the Entity-Component-System (ECS) design pattern. Now I want to show you how to actually use it to build a game with it. If you not already have seen it, check out what kinda game I built with the help of my ECS.) https://youtu.be/idYdpPCUsyg(https://youtu.be/idYdpPCUsyg) 我承认这看起来并不多,但是如果您曾经在没有大型精美游戏引擎的帮助下构建自己的游戏,例如(I will admit this does not look much, but if you ever had build your own game without help of a big and fancy game engine, like) 统一(Unity) 要么(or) 虚幻(Unreal) ,您可能会在这里给我一些荣誉;)因此,为了演示我的ECS,我只需要那么多.如果您仍然不知道这个游戏(BountyHunter)是关于什么的,让我通过以下图片为您提供帮助:(, you might give me some credit here ;) So for the purpose of demonstrating my ECS I simply just need that much. If you still have not figured out what this game (BountyHunter) is about, let me help you out with the following picture:)
图01:BountyHunter目标和规则.(Figure-01: BountyHunter objective and rules.)左边的图片可能看起来很熟悉,因为它是您在视频剪辑中看到的游戏的更抽象的视图.重点放在游戏实体上.在右侧,您会找到游戏目标和规则.这几乎是不言而喻的.如您所见,我们在游戏世界中生活着许多实体类型,现在您可能想知道它们的真正组成是什么?当然,组件好.尽管某些类型的组件对于所有这些实体都是通用的,但有些对其他实体却是唯一的.查看下一张图片.(The picture on the left may look familiar as it is a more abstract view of the game you saw in the video clip. Focus is laid on game entities. On the right hand side you will find the game objective and rules. This should be pretty much self-explanatory. As you can see we got a bunch of entity types living in this game world and now you may wonder what they are actually made of? Well components of course. While some types of components are common for all this entities a few are unique for others. Check out the next picture.)
图02:实体及其组件.(Figure-02: Entity and their components.)通过查看此图片,您可以轻松地看到实体及其组件之间的关系(这不是完整的描绘!).所有游戏实体都有(By looking at this picture you can easily see the relation between entities and their components (this is not a complete depiction!). All game entities have the)**变换分量(Transform-Component)**共同点.由于游戏实体必须位于世界中的某个地方,因此它们需要进行变换,以描述实体的位置,旋转和比例.这可能是附加到实体的唯一组件.例如,相机对象确实需要更多组件,尤其是不需要(in common. Because game entities must be somewhere located in the world they have a transform, which describes the entities position, rotation and scale. This might be the one and only component attached to an entity. The camera object for instance does require more components especially not a)**材料成分(Material-Component)**因为它对播放器永远是不可见的(如果将其用于后期效果,则可能不正确).的(as it will be never visible to the player (this might not be true if you would use it for post-effects). The) Bounty
和(and) Collector
另一方面,实体对象确实具有视觉外观,因此需要(entity objects on the other hand do have a visual appearance and therefore need a)**材料成分(Material-Component)**得到显示.它们还可以与游戏世界中的其他对象碰撞,因此具有(to get displayed. They also can collide with other objects in the game world and therefore have a)**碰撞分量(Collision-Component)**附件,描述其物理形式.的(attached, which describes their physical form. The) Bounty
实体还附加了一个组件;的(entity has one more component attached to it; the)终身组件(Lifetime-Component).该组件说明了剩余的生命周期(. This component states the remaining life-time of a) Bounty
对象,当它的寿命结束时,赏金将消失.(object, when it’s life-time is elapsed the bounty will fade away.)
下一个是什么?拥有所有这些不同的实体以及它们各自的组件集合并不能完成游戏.我们还需要一个知道如何开车的人.我说的当然是系统.系统很棒.您可以使用系统将整个游戏逻辑分解成很多小块.每个部分都处理游戏的不同方面.可能或实际上应该有一个(So what’s next? Having all these different entities with their individual gathering of components does not complete the game. We also need someone who knows how to drive each one of them. I am talking about the systems of course. Systems are great. You can use systems to split up your entire game-logic into much smaller pieces. Each piece dealing with a different aspect of the game. There could or actually should be an)输入系统(Input-System),它正在处理所有玩家输入.或一个(, which is handling all the player input. Or a)**渲染系统(Render-System)**将所有形状和颜色显示在屏幕上.一种(that brings all the shapes and color onto screen. A)**重生系统(Respawn-System)**重新生成死亡的游戏对象.我想你明白了.下图显示了所有具体实体,组件和系统类型的完整类图.(to respawn dead game objects. I guess you got the idea. The following picture shows a complete class-diagram of all the concrete entity, component and system types in)赏金猎人(BountyHunter).(.)
图03:BountyHunter ECS类图.(Figure-03: BountyHunter ECS class-diagram.)现在我们有了实体,组件和系统(ECS),但是等等还有更多事件.为了使系统和实体彼此通信,我提供了38个不同事件的集合:(Now we got entities, components and system (ECS), but wait there is more.. events! To let systems and entities communicate with each other I provided a collection of 38 different events:)
GameInitializedEvent GameRestartedEvent GameStartedEvent
GamePausedEvent GameResumedEvent GameoverEvent GameQuitEvent
PauseGameEvent ResumeGameEvent RestartGameEvent QuitGameEvent
LeftButtonDownEvent LeftButtonUpEvent LeftButtonPressedEvent
RightButtonDownEvent RightButtonUpEvent RightButtonPressedEvent
KeyDownEvent KeyUpEvent KeyPressedEvent ToggleFullscreenEvent
EnterFullscreenModeEvent StashFull EnterWindowModeEvent
GameObjectCreated GameObjectDestroyed PlayerLeft GameObjectSpawned
GameObjectKilled CameraCreated, CameraDestroyed ToggleDebugDrawEvent
WindowMinimizedEvent WindowRestoredEvent WindowResizedEvent
PlayerJoined CollisionBeginEvent CollisionEndEvent
还有更多,我还需要做些什么(And there is still more , what else did I need to make)赏金猎人(BountyHunter):(:)
-
通用应用框架(general application framework)–(-) SDL2(SDL2) 用于获取播放器输入并设置基本应用程序窗口.(for getting the player input and setting up the basic application window.)
-
图形(graphics)-我使用了自定义OpenGL渲染器,使渲染到该应用程序窗口成为可能.(- I used a custom OpenGL renderer to make rendering into that application window possible.)
-
数学(math)-对于我使用的固体线性代数(- for solid linear algebra I used) m(glm) .(.)
-
碰撞检测(collision detection)-我用于碰撞检测(- for collision detection I used) box2d(box2d) 物理.(physics.)
-
有限状态机(Finite-State-Machine)-用于简单的AI和游戏状态.(- used for simple AI and game states.) 显然,我不会谈论所有这些机制,因为它们很有价值,我可能稍后再说;)但是,如果您仍然热衷于了解,我不会阻止您并让您离开这个(Obviously I am not going to talk about all these mechanics as they are worth their own post, which I might do at a later point ;) But, if your are enthusiastic to get to know anyway I won’t stop you and leave you with this) 链接(link) .查看我上面提到的所有功能,您可能会发现它们对于您自己的小型游戏引擎是一个不错的开始.这是我的待办事项清单上的其他内容,但实际上并没有因为我想把事情做好而实现.(. Looking at all the features I mentioned above you may realize that they are a good start for your own small game engine. Here are a few more things I got on my todo-list, but actually did not implement just because I wanted to get things done.)
-
编辑(Editor)-管理实体,组件,系统等的编辑器(- an editor managing entities, components, systems and more)
-
保存游戏(Savegame)-使用某些ORM库将实体及其组件持久保存到数据库中(例如(- persist entities and their components into a database using some ORM library (e.g.) 代码合成(codesynthesis) )())
-
重播(Replays)-在运行时记录事件并在以后重播它们(- recoding events at run-time and replay them at a later point)
-
图形用户界面(GUI)-使用GUI框架(例如(- using a GUI framework (e.g.) 火箭弹(librocket) )建立互动游戏菜单() to build an interactive game-menu)
-
资源经理(Resource-Manager)-通过自定义资源管理器同步和异步加载资产(纹理,字体,模型等)(- synchronous and asynchronous loading of assets (textures, fonts, models etc.) through a custom resource manager)
-
联网(Networking)-通过网络发送事件并设置多人游戏模式(- send events across the network and setup a multiplayer mode) 我将把这些待办事项交给您,以此来证明您是一位了不起的程序员;)(I will leave these todo’s up to you as a challenge to proof that you are an awesome programmer ;)) 最后,让我为您提供一些代码,以演示我的ECS的用法.记住(Finally let me provide you some code, which demonstrates the usage of the my ECS. Remember the)
Bounty
游戏实体?赏金是小黄色,大红色,并介于世界中心随机产生的正方形之间.下面的片段显示了该类的类声明的代码(game entity? Bounties are the small yellow, big red and all in between squares spawning somewhere randomly in the center of the world. The following snipped shows the code of the class declaration of the)Bounty
实体.(entity.)
// Bounty.h
class Bounty : public GameObject<bounty>
{
private:
// cache components
TransformComponent* m_ThisTransform;
RigidbodyComponent* m_ThisRigidbody;
CollisionComponent2D* m_ThisCollision;
MaterialComponent* m_ThisMaterial;
LifetimeComponent* m_ThisLifetime;
// bounty class property
float m_Value;
public:
Bounty(GameObjectId spawnId);
virtual ~Bounty();
virtual void OnEnable() override;
virtual void OnDisable() override;
inline float GetBounty() const { return this->m_Value; }
// called OnEnable, sets new randomly sampled bounty value
void ShuffleBounty();
};
</bounty>
该代码非常简单.我通过以下方式创建了一个新的游戏实体(The code is pretty much straight forward. I’ve created a new game entity by deriving from) GameObject<>
(源自((which is derived from) ECS::Entity<>
)()),与班级((, with the class () Bounty
)本身为() itself as).现在,ECS知道了具体的实体类型,并且将创建一个唯一的(静态)类型标识符.我们还将获得便捷的方法(. Now the ECS is aware of that concrete entity type and a unique (static-)type-identifier will be created. We will also get access to the convenient methods) AddComponent<U>
,(,) GetComponent<U>
,(,) RemoveComponent<U>
.除了这些组件(我稍后将向您展示)之外,还有另一个属性.赏金值.我不确定为什么我没有将该属性放到单独的组件中,例如(. Besides the components, which I show you in a second, there is another property; the bounty value. I am not sure why I did not put that property into a separate component, for instance a) BountyComponent
组件,因为那是正确的方法.相反,我只是将赏金值属性作为成员放入(component, because that would be the right way. Instead I just put the bounty value property as member into the) Bounty
上课,对我感到羞耻.但是,这仅显示了此模式的灵活性,对吗? ;)对,组件…(class, shame on me. But hey, this only shows you the great flexibility of this pattern, right? ;) Right, the components …)
// Bounty.cpp
Bounty::Bounty(GameObjectId spawnId)
{
Shape shape = ShapeGenerator::CreateShape<quadshape>();
AddComponent<shapecomponent>(shape);
AddComponent<respawncomponent>(BOUNTY_RESPAWNTIME, spawnId, true);
// cache this components
this->m_ThisTransform = GetComponent<transformcomponent>();
this->m_ThisMaterial = AddComponent<materialcomponent>(MaterialGenerator::CreateMaterial<defaultmaterial>());
this->m_ThisRigidbody = AddComponent<rigidbodycomponent>(0.0f, 0.0f, 0.0f, 0.0f, 0.0001f);
this->m_ThisCollision = AddComponent<collisioncomponent2d>(shape, this->m_ThisTransform->AsTransform()->GetScale(), CollisionCategory::Bounty_Category, CollisionMask::Bounty_Collision);
this->m_ThisLifetime = AddComponent<lifetimecomponent>(BOUNTY_MIN_LIFETIME, BOUNTY_MAX_LIFETIME);
}
// other implementations ...
</lifetimecomponent></collisioncomponent2d></rigidbodycomponent></defaultmaterial></materialcomponent></transformcomponent></respawncomponent></shapecomponent></quadshape>
我使用了构造函数来附加(I’ve used the constructor to attach all the components required by the) Bounty
实体.请注意,这种方法会创建对象的预制对象,并且不灵活,也就是说,您总是会得到(entity. Note that this approach creates a prefabricate of an object and is not flexible, that is, you will always get a) Bounty
具有相同组件的对象.对于这个游戏来说,这是一个足够好的解决方案,可能不是在更复杂的解决方案中.在这种情况下,您将提供一个生产定制的定制实体对象的工厂.正如您在上面的代码中看到的那样,(object with the same components attached to it. Where this is a good enough solution for this game it might be not in a more complex one. In such a case you would provide a factory that produces custom tailored entity objects. As you can see in the code above there are quite a few components attached to the) Bounty
实体.我们有一个(entity. We got a) ShapeComponent
和(and) MaterialComponent
为视觉外观.一种(for the visual appearance. A) RigidbodyComponent
和(and) CollisionComponent2D
用于身体行为和碰撞响应.一种(for physical behavior and collision response. A)**RespawnComponent(RespawnComponent)**为了给(for giving) Bounty
死亡后重生的能力.最后但并非最不重要的一点是(the ability to get respawned after death. Last but not least there is a) LifetimeComponent
它将在一定时间内绑定实体的存在.的(that will bind the existents of the entity on a certain amount of time. The) TransformComponent
自动附加到从(is automatically attached to any entity that is derived from) GameObject<>
.而已.我们刚刚在游戏中添加了一个新实体.(. That’s it. We’ve just added a new entity to the game.)
现在,您可能想看看如何利用所有这些组件.让我举两个例子.首先(Now you probably want to see how to make use of all this components. Let me give you two examples. First the) 刚体组件(RigidbodyComponent) .此组件包含有关某些身体特征的信息,例如摩擦,密度或线性阻尼.此外,它还充当适配器类,用于将box2d物理与游戏配合使用.的(. This component contains information about some physical traits, e.g. friction, density or linear damping. Furthermore it functions as an adapter class which is used to in-cooperate the box2d physics into the game. The) RigidbodyComponent
这非常重要,因为它用于同步物理模拟体的转换(由box2d拥有)和实体(is rather important as it is used to synchronize the physics simulated body’s transform (owned by box2d) and the the entities) TransformComponent
(由游戏拥有).的((owned by the game). The) PhysicsSystem
为此同步过程负责.(is responsable for this synchronization process.)
// PhysicsEngine.h
class PhysicsSystem : public ECS::System<physicssystem>, public b2ContactListener
{
public:
PhysicsSystem();
virtual ~PhysicsSystem();
virtual void PreUpdate(float dt) override;
virtual void Update(float dt) override;
virtual void PostUpdate(float dt) override;
// Hook-in callbacks provided by box2d physics to inform about collisions
virtual void BeginContact(b2Contact* contact) override;
virtual void EndContact(b2Contact* contact) override;
}; // class PhysicsSystem
</physicssystem>
// PhysicsEngine.cpp
void PhysicsSystem::PreUpdate(float dt)
{
// Sync physics rigidbody transformation and TransformComponent
for (auto RB = ECS::ECS_Engine->GetComponentManager()->begin<rigidbodycomponent>(); RB != ECS::ECS_Engine->GetComponentManager()->end<rigidbodycomponent>(); ++RB)
{
if ((RB->m_Box2DBody->IsAwake() == true) && (RB->m_Box2DBody->IsActive() == true))
{
TransformComponent* TFC = ECS::ECS_Engine->GetComponentManager()->GetComponent<transformcomponent>(RB->GetOwner());
const b2Vec2& pos = RB->m_Box2DBody->GetPosition();
const float rot = RB->m_Box2DBody->GetAngle();
TFC->SetTransform(glm::translate(glm::mat4(1.0f), Position(pos.x, pos.y, 0.0f)) * glm::yawPitchRoll(0.0f, 0.0f, rot) * glm::scale(TFC->AsTransform()->GetScale()));
}
}
}
// other implementations ...
</transformcomponent></rigidbodycomponent></rigidbodycomponent>
从上面的实现中,您可能已经注意到了三种不同的更新功能.系统更新后,首先(From the implementation above you may have noticed the three different update functions. When systems get updated, first all) PreUpdate
调用所有系统的方法,然后(methods of all systems are called, then) Update
最后(and last the) PostUpdate
方法.自从(methods. Since the) PhysicsSystem
先于其他(is called before any other) TransformComponent
有关的系统,上面的代码可确保同步转换.在这里您还可以看到(concerned system, the code above ensures a synchronized transform. Here you can also see the) ComponentIterator
在行动.与其问世界上每个实体,不如(in action. Rather than asking every entity in the world, if it has a) RigidbodyComponent
,我们问(, we ask the) ComponentManager
给我们一个(to give us a) ComponentIterator
用于类型(for type) RigidbodyComponent
.有(. Having the) RigidbodyComponent
我们可以轻松地检索实体的ID并询问(we easily can retrieve the entity’s id and ask the) ComponentManager
再一次给我们(once more to give us the) TransformComponent
对于这个ID,太容易了.让我们看看我答应的第二个例子.的(for that id as well, too easy. Let’s check out that second example I’ve promised. The) RespawnComponent(RespawnComponent) 用于要在死亡后重新生成的实体.该组件提供了五个属性,可用于配置实体的重生行为.您可以决定在实体死后自动重生,重生它之前必须经过多少时间以及重生位置和方向.实际的重生逻辑是在(is used for entities which are intended to be respawned after they died. This component provides five properties which can be used to configure the entity’s respawn behavior. You can decide to automatically respawn an entity when it dies, how much time must pass until it get’s respawned and a spawn location and orientation. The actual respawn logic is implemented in the) 重生系统(RespawnSystem) .(.)
// RespawnSystem.h
class RespawnSystem : public ECS::System<respawnsystem>, protected ECS::Event::IEventListener
{
private:
// ... other stuff
Spawns m_Spawns;
RespawnQueue m_RespawnQueue;
// Event callbacks
void OnGameObjectKilled(const GameObjectKilled* event);
public:
RespawnSystem();
virtual ~RespawnSystem();
virtual void Update(float dt) override;
// more ...
}; // class RespawnSystem
</respawnsystem>
// RespawnSystem.cpp
// note: the following is only pseudo code!
voidRespawnSystem::OnGameObjectKilled(const GameObjectKilled * event)
{
// check if entity has respawn ability
RespawnComponent* entityRespawnComponent = ECS::ECS_Engine->GetComponentManager()->GetComponent<respawncomponent>(event->m_EntityID);
if(entityRespawnComponent == nullptr || (entityRespawnComponent->IsActive() == false) || (entityRespawnComponent->m_AutoRespawn == false))
return;
AddToRespawnQeueue(event->m_EntityID, entityRespawnComponent);
}
void RespawnSystem::Update(float dt)
{
foreach(spawnable in this->m_RespawnQueue)
{
spawnable.m_RemainingDeathTime -= dt;
if(spawnable.m_RemainingDeathTime <= 0.0f)
{
DoSpawn(spawnable);
RemoveFromSpawnQueue(spawnable);
}
}
}
</respawncomponent>
上面的代码并不完整,但是掌握了重要的代码行.的(The code above is not complete, but grasps the important lines of code. The) RespawnSystem
正在持有和更新队列(is holding and updating a queue of) EntityId
与他们一起(’s along with their) RespawnComponent
的.当系统收到一个新条目时,新条目将入队.(’s. New entries are enqueued when the systems receives a) GameObjectKilled
事件.系统将检查被杀死的实体是否具有重生能力,即是否存在(event. The system will check if the killed entity has the respawn ability, that is, if there is a) RespawnComponent
附上.如果为true,则将实体get排队重新生成,否则将被忽略.在里面(attached. If true, then the entity get’s enqueued for respawning, else it is ignored. In the) RespawnSystem
的更新方法(称为每帧),系统将减少排队实体的初始重生时间(’s update method, which is called each frame, the system will decrease the initial respawn-time of the queued entitys') RespawnComponents
‘(不确定我是否在这里得到了单引号?).如果重生时间降至零以下,则将重生实体并将其从重生队列中移除.(’ (not sure if I got the single quotes right here?). If a respawn-time drops below zero, the entity will be respawned and removed from the respawn queue.)
我知道这是一个快速浏览,但是我希望我能给您一个大概的了解ECS世界中的工作方式.在结束本文之前,我想与您分享更多我自己的经验.与我的ECS合作非常愉快.向游戏甚至第三方库中添加新内容非常容易.我只是添加了新的组件和系统,将新功能链接到我的游戏中.我从来没有感觉到死胡同.将整个游戏逻辑拆分为多个系统很直观,并且可以使用ECS免费获得.随着所有这些指针-意大利面条-依赖性-混淆的消失,代码看起来更加简洁,并且变得更加可维护.事件源非常强大,对于系统间/实体/…之间的通信很有帮助,但它也是双重的优势,最终会给您带来麻烦.我说的是事件引发条件.如果您曾经使用过Unity或Unreal Engine的编辑器,那么将很高兴拥有它们.这样的编辑器绝对可以提高您的工作效率,因为您能够以更少的时间创建新的ECS对象,而无需手工修改所有这些代码行.但是,一旦您建立了实体,组件,系统和事件对象的丰富基础,将它们连接在一起并从中构建出一些很棒的东西就几乎是孩子的事了.我想我可以继续谈论一会儿ECS有多酷,但我会在这里停止.(I know this was a quick tour, but I hope I could give you a rough idea how things work in the ECS world. Before ending this post I want to share some more of my own experiences with you. Working with my ECS was much a pleasure. It is so surprisingly easy to add new stuff to the game even third-party libraries. I simply added new components and systems, which would link the new feature into my game. I never got the feeling being at a dead end. Having the entire game logic split up into multiple systems is intuitive and comes for free using an ECS. The code looks much cleaner and becomes more maintainable as all this pointer-spaghetti-dependency-confusion is gone. Event sourcing is very powerful and helpful for inter system/entity/… communication, but it is also a double bleeding edge and can cause you some trouble eventually. I am speaking of event raise conditions. If you have ever worked with Unity’s or Unreal Engine’s editor you will be glad to have them. Such editors definitely boost your productivity as your are able to create new ECS objects in much less time than hacking all these line of code by hand. But once you have setup a rich foundation of entity, component, system and event objects it is almost child’s play to plug them together and build something cool out of them. I guess I could go on and talk a while longer about how cool ECS’s are, but I will stop here.)
感谢您的光临,并走了这么远:)(Thanks for swinging by and making it this far :))
干杯,Tobs.(Cheers, Tobs.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C++ Design Architect game 新闻 翻译