pushButton引擎让你通过组件帮助你很快的构建你的游戏。因为组件都是基于一致的接口,游戏只需要很简单的组织他们就可以了。
1、为什么用组件:
组件是一个更好的方法来开发游戏,他们避免从继承为基础来实现的重大问题。由组件拼凑的游戏自然更加的模块化,可扩展、可维护性更高。在PBE中鼓励这种做法。
第一个用组件构建和发布的重要游戏是Dungeon Siege,“数据驱动的游戏的目标体系”在GDC这一年提出,并给出了深入的讨论。他们主要使用的好处是,他们避免了复杂的对象层次结构,并能强大的管理平面和复杂的游戏世界。最终,他们以21个 C + +组件,148基于脚本的组成部分,大约10,000对象类型。
他们的C + +组件,像“演员”,“谈话”,“信徒”,“物理”等事情。脚本组件的148游戏的具体得多,实施喜欢陷阱和困惑,为电梯运动逻辑的东西,追求跟踪,等他们能够专注于移动游戏的未来,而不是侧重于基础设施的大部分时间,并很快的增加一个新的特点。
Dungeon Siege游戏是很有启发性的,他们避免恶劣纠缠的对象层次和管理,以提供一个令人信服的游戏世界,从此一个类似组件体系结构已开始用于其他的一些游戏中和中间件当中。最显著的是TorqueX和Gamebryo引擎。
2、组件和实体:
一个游戏对象被表示为一个Ientity,他包含了一个或多个IentityComponents。一个实体可以像如下的构建。
<
entity
name
="MyPlayer"
>
<
physicsMoverComponent
name
="mover"
/>
<
SpriteRenderComponent
name
="sprite"
/>
<
PlayerInputComponent
name
="input"
/>
</
entity
>
正如你所看到的,每个组件都被命名了,entity也是一样的。
3、创建一个component
创建一个新类型的component是很简单的,下面就是一个
class SimplestComponent extends EntityComponent
{
}
请注意,我们使用的附加组件的每个类是作为一个组件(即,它扩展EntityComponent或实行IEntityComponent)可用名称公约。这使得它容易调用组件。
一个自定义的component必须是继承子EntityComponent而不能是去实现IentityComponent接口。 EntityComponent中有一些方法需要从写。
我们如何通过反序列化器去实例化一个组件:首先是所有的属性必须设置,如果是在多个template中,属性需要设置多次。然后,所有组件的onAdd()以未指定顺序的方式被调用中。最后onReset()也以同样的方式被调用,用以收集其他组件的引用。
4、访问其他组件
有3中方法可以进行组件间的交互,属性,事件,或接口。所有这些都基于对IEntity查询方法。如果您正在编写一个组件代码,owner成员将返回到IEntity包含该组件的引用。这些方法的工作非常类似于您在访问的另一个实体组件或您的所有者。唯一的区别是,而不是使用所有者,您将使用NameManager或另一种方式来查找一个IEntity。
5、属性
在组件之间共享数据的最佳方式是通过属性。因为它们在编译时不需要任何的组件之间的依赖。事实上,它很容易重新配置数据是如何访问,允许组件被以意想不到的方式使用。他们分享的位置,速度,颜色,健康,和任何其他类型的数据是很重要的。
添加组件的属性跟增加一个新成员变量一样的容易,访问属性可以通过使用简单的字符串,指定哪些部分应该是读或写什么领域,这里有一个单的例子:
代码
class
TestComponent
extends
EntityComponent
{
public
var aProperty:String
=
"
Hello!
"
;
}
//
Inside another component on an entity which has a TestComponent named Test:
var value:String
=
owner.getProperty(
new
PropertyReference(
"
@Test.aProperty
"
)) as String;
Logger.print(
this
, value);
//
Outputs "Hello!"
您也可以访问内部复杂数据类型的字段。假设aProperty是一个点。你可以做以下几点:
var xPosition:Number = owner.getProperty(new PropertyReference("@Test.aProperty.x");
事实上,您可以访问在另一个组件中的任何成员。其他的事,唯一知道的是,在访问数组或字典的项目,您使用相同的语法点:
// Equivalent to accessing anArray[4].x on @Test.
var xPosition:Number = owner.getProperty(new PropertyReference("@Test.anArray.4.x"));
PropertyReference的主要目的是:他使得当组件访问属性和配置变的简单。
代码
class
TestComponent
extends
EntityComponent
{
public
var PositionProperty:PropertyReference
=
new
PropertyReference(
"
@Spatial.position
"
);
public
function DoSomeBehavior():
void
{
var myPos:Point
=
owner.getProperty(PositionProperty) as Point;
}
}
如果您需要得到你的一些其他组件的位置,很容易重新配置TestComponent。此外,你节省了创建一个新的PropertyReference每次要求物业管理费用。一旦所有的数据输入和输出,可配置的一个组成部分,它更容易使用在意外情况下的组件。
6、事件
每IEntity公开了标准Flash的EventDispatcher,所以很容易调度和监听事件。主要是通过owner.eventDispatcher监听和派发事件。
代码
class
TestComponent
extends
EntityComponent
{
//
Declare identifier for the event.
static
public
const
SOME_ACTION:String
=
"
SomeAction
"
;
public
function SomeActionHappens():
void
{
owner.eventDispatcher.dispatchEvent(
new
Event(SOME_ACTION));
}
}
//
Example of listening for an event in a component.
class
TestListenerComponent
extends
EntityComponent
{
protected
function onAdd():
void
{
owner.eventDispatcher.addEventListener(TestComponent.SOME_ACTION, _EventHandler);
}
private
function _EventHandler(e:Event):
void
{
trace(
"
Got an event!
"
);
}
protected
function onRemove():
void
{
//
Notice that we have to unsubscribe the event when the component
//
is removed.
owner.eventDispatcher.removeEventListener(TestComponent.SOME_ACTION, _EventHandler);
}
}
7、直接访问
如果一切都失败了,你可以直接访问其他组件,或者使用一个共同的接口。这是最不可取的选择,因为它引入了编译时间依赖关系。编译时间依赖关系是一个大问题,因为他们可以使它很难使用不同的封装组件。最好是使用事件或属性,因为他们的工作完全按名称,不承担某些接口或父类都存在。
但是,它可以提供更好的性能,它可以在某些情况下的感觉。例如,我们的球员在上面的例子,使用一个接口可能作出有意义的输入组件用于应用力量的推动。有三种方法来直接访问实体中的一个组成部分。使用owner为自己的实体,或查找并使用其他实体从其他地方组成部分。方法:
- lookupComponentByName which looks up a single named component and returns it.
- lookupComponentByType which looks up a single component by type and returns it.
- lookupComponentsByType which finds all components that match the specified type.
代码
//
Interface implemented by another component.
interface
IExampleInterface
{
function getBigArray():Array;
}
class
TestComponent
extends
EntityComponent
{
public
otherComponentName:String
=
"
anotherComponent
"
;
public
function AccessSomeData():
void
{
var otherItem:IExampleInterface
=
owner.lookupComponentByName(otherComponentName, IExampleInterface) as IExampleInterface;
for
each(var i:
*
in otherItem.getBigArray())
trace(i);
}
}
这些功能是很简单的,不过,如果你需要存储一个永久参考到一个组件,一定要明确并重新建立它时onReset()被调用。否则,你可能会访问陈旧的数据或会导致内存泄漏。
代码
//
Just like the previous component, but it keeps the reference around longer.
class
TestComponent
extends
EntityComponent
{
public
var anotherComponent:IExampleInterface;
protected
function onAdd():
void
{
super
.onAdd();
anotherComponent
=
owner.LookupComponentByType(IExampleInterface) as IExampleInterface;
}
protected
function onReset():
void
{
super
.onReset();
anotherComponent
=
owner.LookupComponentByType(IExampleInterface) as IExampleInterface;
}
protected
function onRemove():
void
{
super
.onRemove();
//
Don't forget to clear the reference to prevent memory leaks.
anotherComponent
=
null
;
}
public
function DoSomething():
void
{
for
each(var i:
*
in anotherComponent.getBigArray())
Logger.print(
this
, i);
}
}
-
8、连锁问题
当你只是在level文件当中使用组件的时候,flex编译器并不知道他们被使用了,并且在编译的时候会删除他们,为了防止这种情况的产生,我们需要试用辅助功能PBE.registType来引入对象。
PBE.registerType(com.pblabs.rendering2D.DisplayObjectRenderer);
9、包装组件
组件都是一些AS类,因此打包是很容易的。我们可以将他们打包为swc文件,然后共享。