作者NDark (溺於黑暗)
看板GameDesign
标题Re: [分享] 组件式(Component-Based)游戏引擎简介
时间Tue Aug 31 22:15:40 2010
※ 引述《cjcat2266 (CJ Cat)》之铭言:
: 这是我上个月在PTT Flash板聚所分享的
: "组件式游戏引擎 (Component-Based Game Engine)"
: 主要介绍组件式游戏引擎与继承式游戏引擎的差异
: 还有组件式游戏引擎的各项特色
: 使用的范例语言为ActionScript 3.0
: 在这里跟大家分享一下~ :)
: https://www.youtube.com/view_play_list?p=472DB0C5A3976D80
这边刚好翻了一篇文章
http://wp.me/pBAPd-fj
主要就是说明这个概念.因为还有例子,讲得很清楚.
跟我在讨论会讲的题目有相关
但又有一些差别.我可能还要想再花点时间想清楚.
有兴趣的人可以直接end.抓最後的一些pdf跟投影片来参考.
Evolve Your Hierarchy
元件式的重构
By Mick West
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Refactoring Game Entities with Components
用元件来重构游戏实体
Up until fairly recent years, game programmers have consistently used a deep
class hierarchy to represent game entities. The tide is beginning to shift
from this use of deep hierarchies to a variety of methods that compose a game
entity object as an aggregation of components. This article explains what
this means, and explores some of the benefits and practical considerations of
such an approach. I will describe my personal experience in implementing this
system on a large code base, including how to sell the idea to other
programmers and management.
时至今日,游戏程式设计者往往使用深度的继承来表达游戏实体(译按我喜欢称作game
object)。"趋势"渐渐转换为以各种方式把这个实体物件包装为元件的集成。本篇文章就
是在讨论这个方法,以及他的好处跟实际面。这篇文章还会描述个人实作这种系统的经历
,包含如何推销这个想法给其他程式设计者与管理阶层。
GAME ENTITIES
游戏实体
Different games have different requirements as to what is needed in a game
entity, but in most games the concept of a game entity is quite similar. A
game entity is some object that exists in the game world, usually the object
is visible to the player, and usually it can move around.
在不同的游戏中游戏实体可以有不同的需求,但是概念上是类似的。游戏实体就是存在於
游戏世界的某一个物件,通常可以被玩家看到,也通常可以移动。
(略)
如清单所述游戏实体可以包含:飞弹,车辆,坦克,手榴弹,枪枝,英雄,行人,外星人
,钢铁人飞行装,医疗装备,石头。
(略)
游戏实体可以有以下的功能:执行script,移动,被物理引擎推动,发出粒子,发出区域
音效,被玩家拾取,被玩家损害,爆炸,磁力反应,被玩家瞄准,跟随路径,播放动画。
TRADITIONAL DEEP HIERARCHIES
传统的深度继承
The traditional way of representing a set of game entities like this is to
perform an object-oriented decomposition of the set of entities we want to
represent. This usually starts out with good intentions, but is frequently
modified as the game development progresses - particularly if a game engine
is re-used for a different game. We usually end up with something like figure
1, but with a far greater number of nodes in the class hierarchy.
传统表示游戏实体集合的方式大多遵循物件导向分解的概念。概念上一开始都是好意。但
是随着游戏继续开发或是把就程式想办法套到新游戏上的时候,就会发现问题。这个传统
方式通常会变成像图1一样,有着大量数目的类别继承。
As development progresses, we usually need to add various points of
functionality to the entities. The objects must either encapsulate the
functionality themselves, or be derived from an object that includes that
functionality. Often, the functionality is added to the class hierarchy at
some level near the root, such as the CEntity class. This has the benefit of
the functionality being available to all derived classes, but has the
downside of the associated overhead also being carried by those classes.
随着开发进行,我们通常会增加很多的功能到这些实体上,要不是直接装到这些物件上,
就是要从其他物件那边继承这些功能过来。通常这些功能是在接近root的地方,好处是大
家都能共享这个功能。但是在底层的新物件就会被层层这些重担所包覆。
Even fairly simple objects such as rocks or grenades can end up with a large
amount of additional functionality (and associated member variables, and
possibly unnecessary execution of member functions). Often, the traditional
game object hierarchy ends up creating the type of object known as "the
blob". The blob is a classic "anti-pattern" which manifests as a huge single
class (or a specific branch of a class hierarchy) with a large amount of
complex interwoven functionality.
即便是石头或是手榴弹这种简单物件都会有一卡车继承而来的函式跟无关紧要的变数。通
常我们把这种情况叫做blob(像一团的大型物件)。这种blob通常不是一个好的设计模式,
因为他代表了一个巨大的单一类别或是继承的集合,包含了一堆交杂的函式。
While the blob anti-pattern often shows up near the root of the object
hierarchy, it will also show up in leaf nodes. The most likely candidate for
this is the class representing the player character. Since the game is
usually programmed around a single character, then the object representing
that character often has a very large amount of functionality. Frequently
this is implemented as a large number of member functions in a class such as
CPlayer.
这种blob不只会出现在继承的根部(root),也会出现在继承的底部(leaf)。最常见的
这种blob就是玩家人物物件,因为我们的游戏都是围绕着玩家角色来进行。所以通常被命
名为CPlayer的玩家人物物件就有一卡车函式。
The result of implementing functionality near the root of the hierarchy is an
overburdening of the leaf objects with unneeded functionality. However, the
opposite method of implementing the functionality in the leaf nodes can also
have unfortunate consequence. Functionality now becomes compartmentalized, so
that only the objects specifically programmed for that particular
functionality can use it. Programmers often duplicate code to mirror
functionality already implemented in a different object. Eventually messy
re-factoring is required by re-structuring the class hierarchy to move and
combine functionality.
在根部出现的这种blob就会导致底部的物件有很多无用处的函式。然而若是把这些函式实
做在底部物件,却又会发生功能无法透过继承共享的现象(导致再不同的底部物件中重复
作相同的功能)。最後就会发生必须重构的情况发生。
Take for example the functionality of having an object react under physics as
a rigid body. Not every object needs to be able to do this. As you can see in
figure 1, we just have the CRock and the CGrenade classes derived from
CRigid. What happens when we want to apply this functionality to the
vehicles? You have to move the CRigid class further up the hierarchy, making
it more and more like the root-heavy blob pattern we saw before, with all the
functionality bunched in a narrow chain of classes from which most other
entity classes are derived.
举例来说,若是有一个会受到物理引擎影响的固体类别(CRigid),继承之後变成石头或
手榴弹。若是现在要加一个车辆物件到这个CRigid的继承树上时就会变得这个CRigid必须
往继承的根部移动,让他有更多能够包含车辆使用的功能。慢慢地他就变成所谓的blob。
AN AGGREGATION OF COMPONENTS
元件的集成
The component approach, which is gaining more acceptance in current game
development, is one of separating the functionality into individual
components that are mostly independent of one another. The traditional object
hierarchy is dispensed with, and an object is now created as an aggregation
(a collection) of independent components.
元件的方式慢慢被游戏开发所接受,是一种把功能区别变成单一互相独立元件的方法。传
统的继承被扬弃,物件变成一个元件的集成。
Each object now only has the functionality that it needs. Any distinct new
functionality is implemented by adding a component.
每个物件只有他所需要的功能,每个新功能就只需实做成一个元件。
A system of forming an object from aggregating components can be implemented
in one of three ways, which may be viewed as separate stages in moving from a
blob object hierarchy to a composite object.
若是要把一个已经成为blob的继承系统变成元件式的系统,我们可以用三种方法来进行重
构。
OBJECT AS ORGANIZED BLOB
组织过的blob
A common way of re-factoring a blob object is to break out the functionality
of that object into sub-objects, which are then referenced by the first
object. Eventually the parent blob object can mostly be replaced by a series
of pointers to other objects, and the blob object's member functions become
interface functions for the functions of those sub-objects.
最常见的重构blob物件的方式就是把这些物件依照功能分成小的物件,然後被原来的物件
所使用。最终这个blob就可以被一堆指到其他小物件的指标所取代。这个blob的物件就变
成一个使用其他小物件功能的介面(interface)。
This may actually be a reasonable solution if the amount of functionality in
your game objects is reasonably small, or if time is limited. You can
implement arbitrary object aggregation simply by allowing some of the
sub-objects to be absent (by having a NULL pointer to them). Assuming there
are not too many sub-objects, then this still allows you the advantage of
having lightweight pseudo-composite objects without having to implement a
framework for managing the components of that object.
若是时间有限,或是要重构的物件还算小的时候,这种方法是最合理的方式。这种方式可
以随意的组成你要的物件,尤其是当你不想要某一个功能的时候,就把那个小物件的指标
设为NULL即可。甚至不用实做整个管理物件的系统,很快的就可以建立一个虚拟的组合物
件出来。
The downside is that this is still essentially a blob. All the functionality
is still encapsulated within one large object. It is unlikely you will fully
factor the blob into purely sub-objects, so you will still be left with some
significant overhead, which will weight down your lightweight objects. You
still have the overhead of constantly checking all the NULL pointers to see
if they need updating.
但是实际上这仍是一个所有功能都被包住的blob。只是你必须用是否为NULL的方式检查那
些功能有没有装进来。
OBJECT AS COMPONENT CONTAINER
元件的容器
The next stage is to factor out each of the components (the "sub-objects" in
the previous example) into objects that share a common base class, so we can
store a list of components inside of an object.
下一步就是打造每一个元件(先前说的小物件)为继承一个共通基底的物件,然後我们就
可以在物件里面用一个串列把他用得到的元件都串起来。
This is an intermediate solution, as we still have the root "object" that
represents the game entity. However, it may be a reasonable solution, or
indeed the only practical solution, if a large part of the code base requires
this notion of a game object as a concrete object.
这是一个中间层的解法,我们仍有一个根部的物件来衍生我们的游戏实体。但这可能是唯
一有用且合理的解决方案。
Your game object then becomes an interface object that acts as a bridge
between the legacy code in your game, and the new system of composite
objects. As time permits, you will eventually remove the notion of game
entity as being a monolithic object, and instead address the object more and
more directly via its components. Eventually you may be able to transition to
a pure aggregation.
我们的游戏物件就会变成一个介於原本程式与新的组合元件的介面。只要时间允许,最终
就会移除这一个原本是庞大整块的游戏实体,变为使用这个物件透过他的元件。然後就可
以转为一个单纯的集合体(pure aggregation)。
OBJECT AS A PURE AGGREGATION
单纯的集合体
In this final arrangement, an object is simply the sum of its parts. Figure 2
shows a scheme where each game entity is comprised of a collection of
components. There is no "game entity object" as such. Each column in the
diagram represents a list of identical components, each row can be though of
as representing an objects. The components themselves can be treated as being
independent of the objects they make up.
最後的布置会像这样,一个物件就是他元件的总和。图2展示了一个每一个游戏实体是由
元件组成的结构。没有所谓游戏实体物件这样的东西,每一栏都表示了单一的元件,每一
列表示独立的物件。元件终於可以独立於物件之外。
PRACTICAL EXPERIENCE
实际的实做经验
I first implemented a system of object composition from components when
working at Neversoft, on the Tony Hawk series of games. Our game object
system had developed over the course of three successive games until we had a
game object hierarchy that resembled the blob anti-pattern I described
earlier. It suffered from all the same problems: the objects tended to be
heavyweight. Objects had unnecessary data and functionality. Sometimes the
unnecessary functionality slowed down the game. Functionality was sometimes
duplicated in different branches of the tree.
作者在Neversoft的Tony Hawk游戏系列中实做了一个元件集合的系统。在那之前专案已经
延续了三代的游戏,年代久远的继承架构就变成先前提到的blob。造成那些令人困扰的问
题:物件很大;物件有不相关的资料与功能;不相关的功能减缓游戏执行的速度;功能重
复的在不同的继承树上被实做。
I had heard about this new-fangled "component based objects" system on the
sweng-gamedev mailing list, and decided it sounded like a very good idea. I
set to re-organizing the code-base and two years later, it was done.
作者那时在sweng-gamedev新闻群组听到了这种新奇的元件式的物件系统,因此花了两年
去重新组织这个程式。
Why so long? Well, firstly we were churning out Tony Hawk games at the rate
of one per year, so there was little time between games to devote to
re-factoring. Secondly, I miscalculated the scale of the problem. A
three-year old code-base contains a lot of code. A lot of that code became
somewhat inflexible over the years. Since the code relied on the game objects
being game objects, and very particular game objects at that, it proved to be
a lot of work to make everything work as components.
为什麽花了这麽长的时间才完成?首先Tony Hawk游戏系列进入每年一款的量产,以至於
每次都只有一点时间来作重构。第二,错估了这个问题的大小。一个跑了三年的程式基底
包含了一堆不弹性的程式码,每个物件都很独特,难以解构。
EXPECT RESISTANCE
碰到抗拒
The first problem I encountered was in trying to explain the system to other
programmers. If you are not particularly familiar with the idea of object
composition and aggregation, then it can strike you as pointless, needlessly
complex, and unnecessary extra work. Programmers who have worked with the
traditional system of object hierarchies for many years become very used to
working that way. They even become very good at working that way, and manage
to work around the problems as they arise.
第一个作者碰到的问题就是如何说服这个系统给其他的程式设计人员。假如用不是很熟这
个物件组合与集成的问题的态度去说服其他人,通常会终结於没有意义;不需要这麽复杂
;不想增加其他工作等回应。程式设计师已经很习惯於传统的继承写作方式。甚至很用的
很好。
Selling the idea to management is also a difficult. You need to be able to
explain in plain words exactly how this is going to help get the game done
faster. Something along the lines of:
"Whenever we add new stuff to the game now, it takes a long time to do, and
there are lots of bugs. If we do this new component object thing, it will let
us add new stuff a lot quicker, and have fewer bugs."
推销这个想法给管理阶层也是一个困难。这需要能够用简单的文字去表达这样的新方法是
如何能够加速游戏的完成,就像这样说:"每当我们再游戏中新增一个元素,总是要花很
多时间,同时产生很多错误。假如我们采用元件式的方式,就会让我们更快,更少错误。
"
My approach was to introduce it in a stealth manner. I first discussed the
idea with a couple of programmers individually, and eventually convinced them
it was a good idea. I then implemented the basic framework for generic
components, and implemented one small aspect of game object functionality as
a component.
作者推销的作法是采用低调的作法。先分别对一些程式设计师闲聊这个想法,说服他们这
是个好方法。实做这个架构的基础工作,且修改一个游戏物件为元件式作例子。
I then presented this to the rest of the programmers. There was some
confusion and resistance, but since it was implemented and working there was
not much argument.
然後展示给剩余的程式设计师,有些人会有疑惑跟抗拒,但既然已经有实做的结果了,就
没有太大的争论。
SLOW PROGRESS
步调缓慢
Once the framework was established, the conversion from static hierarchy to
object composition happened slowly. It is thankless work, since you spend
hours and days re-factoring code into something that seems functionally no
different to the code it replaces. In addition, we were doing this while
still implementing new features for the next iteration of the game.
基础的架构已经被建立之後,转换原本的继承架构为元件集合是的过程却十分的缓慢。这
是份不被感激的工作,因为只是花时间重作那些一样功能的东西。更糟的是,同时间还在
开发下一代游戏的功能。
At an early point, we hit the problem of re-factoring our largest class, the
skater class. Since it contained a vast amount of functionality, it was
almost impossible to re-factor a piece at a time. In addition, it could not
really be re-factored until the other object systems in the game conformed to
the component way of doing things. These in turn could not be cleanly
refactored as components unless the skater was also a component.
早期的重点就是解构那个最大的类别-滑板者(skater)。他包含了庞大的功能根本不可
能一口气重构为元件。甚至是必须在其他物件都遵循元件式的架构後才能进行。也就是说
skater这个类别要先变成元件式才能重构其他元件。
The solution here was to create a "blob component." This was a single huge
component, which encapsulated much of the functionality of the skater class.
A few other blob components were required in other places, and we eventually
shoehorned the entire object system into a collection of components. Once
this was in place, the blob components could gradually be refactored into
more atomic components.
解法就是创造一个blob的元件。一个超大的元件,包含了skater的所有功能。当然其他
blob也这样作。最後终於硬塞了整个物件系统到这个元件集合里面。一旦这步骤做完,
blob元件就可以被解构为简单的元件。
RESULTS
结果
The first results of this re-factoring were barely tangible. But over time
the code became cleaner and easier to maintain as functionality was
encapsulated in discreet components. Programmers began to create new types of
object in less time simply by combining a few components and adding a new one.
第一次重构的结果看起来没有什麽显着帮助。但是当功能被塞成离散的元件,程式越来越
清楚简单。
We created a system of data-driven object creation, so that entirely new
types of object could be created by the designers. This proved invaluable in
the speedy creation and configuration of new types of objects.
建立的系统是资料驱动的物件创造系统,因此整个新的物件能够由设计人员来创造,使得
开发速度跟调整变得十分快速。
Eventually the programmers came (at different rates) to embrace the component
system, and became very adept at adding new functionality via components. The
common interface and the strict encapsulation led to a reduction in bugs, and
code that that was easier to read, maintain and re-use.
程式设计人员最终爱上了这种元件系统,也非常适应以元件的方式增加功能。这种设计方
式使得错误率降低,程式可读性上升,容易维护与重新使用。
IMPLEMENTATION DETAILS
实作的细节
Giving each component a common interface means deriving from a base class
with virtual functions. This introduces some additional overhead. Do not let
this turn you against the idea, as the additional overhead is small, compared
to the savings due to simplification of objects.
元件有着共通的介面意思是在继承的基底类别有一些虚拟函式。在建构这部份的时候花了
不少精神。但别让这步骤阻止了重构,因为重构的结果会带来相对很多在开发上的省力。
Since each component has a common interface, it is very easy to add
additional debug member functions to each component. That made it a
relatively simple matter to add an object inspector that could dump the
contents of the components of a composite object in a human readable manner.
Later this would evolve into a sophisticated remote debugging tool that was
always up to date with all possible types of game object. This is something
that would have been very tiresome to implement and maintain with the
traditional hierarchy.
既然元件都是相同介面,当然非常容易增加相同除错的架构上去。用可读的方式把资料
dump出来看。而且可以与复杂的远端除错工具结合,同时保持可以除错最新的物件类别。
若是用继承的方式是十分难以作到的。
Ideally, components should not know about each other. However, in a practical
world, there are always going to be dependencies between specific components.
Performance issues also dictate that components should be able to quickly
access other components. Initially we had all component references going
through the component manager, however when this started using up over 5% of
our CPU time, we allowed the components to store pointers to one another, and
call member functions in other components directly.
原则上,元件彼此之间应该互相不知道。然而实际应用时,某些元件总是必须相依彼此。
效能的考量使得应该要让元件彼此能够互相沟通。最初我们透过元件管理器 reference住
全部的元件,然而这管理器竟然消耗了百分之五的处理时间。因此我们允许元件储存其他
元件的指标直接呼叫成员函式。
The order of composition of the components in an object can be important. In
our initial system, we stored the components as a list inside a container
object. Each component had an update function, which was called as we
iterated over the list of components for each object.
建构元件的顺序是重要的,在初始的系统中,我们用串列把元件储存在容器里。每一个元
件有自己的更新函式,然後走访串列一起更新他们。
Since the object creation was data driven, it could create problems if the
list of components is in an unexpected order. If one object updates physics
before animation, and the other updates animation before physics, then they
might get out of sync with each other. Dependencies such as this need to be
identified, and then enforced in code.
既然物件的创造是由资料所驱动,可能会因为再串列上的顺序导致不预期的问题。譬如说
某些流程应该比某些流程先更新。这种相依性必须考虑进去。
CONCLUSIONS
结论
Moving from blob style object hierarchies to composite objects made from a
collection of components was one of the best decisions I made. The initial
results were disappointing as it took a long time to re-factor existing code.
However, the end results were well worth it, with lightweight, flexible,
robust and re-usable code.
从继承的blob风格改为元件组合式的方式是作者的成功经验之一。最初因为花了很多时间
所以有点失望。然而最後的结果却相当值得,轻量,有弹性,通用,又能重复利用。
Resources
其他资源
Scott Bilas: GDC 2002 Presentation: A Data-Driven Game Object System
http://www.drizzle.com/~scottb/gdc/game-objects.htm
Bjarne Rene: Component Based Object Management. Game Programming Gems 5,
2005, page 25.
Kyle Wilson: Game Object Structure: Inheritence vs Aggregation, 2002,
http://www.gamearchitect.net/Articles/GameObjects1.html
other reference :
Game Object Structure: Inheritance vs. Aggregation ( 2002 )
By Kyle Wilson
http://www.gamearchitect.net/Articles/GameObjects1.html
Ogre Wiki :
Architecture and Design in Games - A list of various must-read articles
http://www.ogre3d.org/tikiwiki/Architecture+and+Design+in+Games
--
"May the Balance be with U"(愿平衡与你同在)
视窗介面游戏设计教学,讨论,分享。欢迎来信。
视窗程式设计(Windows CLR Form)游戏架构设计(Game Application Framework)
游戏工具设计(Game App. Tool Design )
电脑图学架构及研究(Computer Graphics)
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 118.167.138.65
※ 编辑: NDark 来自: 118.167.138.65 (08/31 22:16)
1F:推 newcinka:推 08/31 22:26
※ 编辑: NDark 来自: 118.167.138.65 (08/31 23:18)
2F:→ exe44:之前也想尝试类似的架构, 但是在介面存在与否的问题上总是 09/01 00:27
3F:→ exe44:会有点不顺利的感觉(C++), 或许有些设计机制可以达成, 可是 09/01 00:29
4F:→ exe44:又觉得太过罗嗦? 某些动态语言似乎比较没这方面问题? 09/01 00:30
5F:推 imagefish:好文! 09/01 02:22
6F:推 linjack:推荐这篇文章 09/01 03:05
7F:推 Kendai:推推 09/01 06:01
8F:推 rexrainbow:推! 09/01 13:53
9F:推 ddavid:推 09/01 19:25
10F:推 nobody1:推 09/01 22:20
11F:→ NDark:我的经验是,会好好用继承,设计模式的程式员都很少见了 09/01 22:50
12F:→ NDark:若是用不成熟的态度来实作元件式架构,只会更是一团乱而已. 09/01 22:50
13F:推 Qshi:翻得好顺好读! 感恩 09/08 17:15
14F:推 HalfLucifer:推荐这篇文章!包括Ref那些文章都是极佳的架构好文! 09/10 11:19