在集成第三方库时,这是一个非常常见的问题,尤其是作为端口的库(如 Box2DAS3),它们保持母语言的编码和命名约定,而不是与目标语言完全集成(例如: Box2DAS3 使用getFoo()
andsetFoo()
而不是.foo
getter/setter)。
为了快速回答您的问题,不,制作包装类不会对性能产生重大影响;不会比您在自己项目的类层次结构中看到的更多。当然,如果您对 500 万次迭代的循环进行计时,您可能会看到一两毫秒的差异,但在正常使用中,您不会注意到它。
“更多的内存消耗以适应额外级别的类结构。”
与任何具有类继承的语言一样,vtable 将在幕后使用,因此您的内存/性能会略有增加,但可以忽略不计。
“由于初始化额外级别的成员而创建新对象时的性能影响?”
不超过正常的实例化,所以除非您创建大量对象,否则无需担心。
性能方面,您通常应该没有问题(除非您确实遇到问题,否则优先考虑可读性和可用性而不是性能),但我会将其更多地视为架构问题,考虑到这一点,我会考虑扩展/修改外部库的类的负面影响通常分为 3 个方面,具体取决于您想要做什么:
修改库
由于 Box2DAS3 是开源的,因此没有什么能阻止您跳入其中并将所有类/函数名称重构为您心中的内容。我有时认真考虑过这样做。
优点:
- 你可以修改你想要的 - 函数,类,你命名它
- 您可以添加所需的任何缺失部分(例如像素米转换助手)
- 您可以解决任何潜在的性能问题(我注意到如果以“AS3”方式完成一些事情可以做得更好更快)
缺点:
- 如果您打算使您的版本保持最新,则需要手动合并/转换任何更新和更改。对于流行的图书馆,或者那些变化很大的图书馆,这可能是一个巨大的痛苦
- 这非常耗时 - 除了修改之外,您还需要对正在发生的事情有一个很好的了解,这样您就可以在不破坏功能的情况下进行任何更改
- 如果有多个人在使用它,他们就不能过多地依赖外部文档/示例,因为内部可能已经改变
扩展类
在这里,您只需创建自己的包装类,它扩展基本 Box2D 类。您可以根据需要添加属性和函数,包括实现您自己的命名方案,该方案可以转换为基类(例如MyBox2DClass.foo()
,可以简单地作为 的包装器Box2DClass.bar()
)
优点:
- 您只实现所需的类,并进行必要的更改
- 您的包装类仍然可以在基本 Box2D 引擎中使用 - 即您可以将
MyBox2DClass
对象传递给采用 a 的内部方法,Box2DClass
并且您知道它会起作用
- 在所有三种方法中,这是最少的工作量
缺点:
- 同样,如果您打算使您的版本保持最新,您需要检查任何更改都不会破坏您的课程。不过一般问题不大
- 如果您创建自己的函数来调用它们的 Box2D 等效函数(例如“我应该使用
setPos()
还是SetPosition()
?),可能会给课堂带来混乱。即使您自己工作,当您在 6 个月后回到课堂时,您会忘记
- 您的类将缺乏连贯性和一致性(例如,某些函数使用您的命名方法(
setPos()
),而其他函数使用 Box2D(SetPosition()
))
- 你被 Box2D 困住了;没有大量开发人员就无法更改物理引擎,具体取决于您的类在整个项目中的使用方式。如果您不打算切换,这可能没什么大不了的
与您自己的课程组合
您创建自己的类,这些类在内部拥有一个 Box2D 属性(例如MyPhysicalClass
将有一个属性b2Body
)。您可以根据需要自由实现自己的接口,并且只实现必要的。
优点:
- 您的课程更干净,并且非常适合您的引擎。仅公开您感兴趣的功能
- 你不依赖于 Box2D 引擎;例如,如果你想切换到 Nape,你只需要修改你的自定义类;你的引擎和游戏的其余部分都被遗忘了。其他开发人员也无需学习 Box2D 引擎即可使用它
- 当您在那里时,您甚至可以实现多个引擎,并使用切换或界面在它们之间切换。同样,您的引擎和游戏的其余部分都没有注意到
- 与基于组件的引擎很好地配合使用 - 例如,您可以拥有一个 Box2DComponent 来保存一个
b2Body
属性
缺点:
- 更多的工作不仅仅是扩展类,因为您实际上是在引擎和 Box2D 之间创建一个中间层。理想情况下,在您的自定义类之外,不应该有对 Box2D 的引用。工作量取决于你在课堂上需要什么
- 额外的间接级别;通常它不应该是一个问题,因为 Box2D 将直接使用您的 Box2D 属性,但是如果您的引擎经常调用您的函数,那么这是一个额外的步骤,性能明智
在这三者中,我更喜欢组合,因为它提供了最大的灵活性并保持引擎的模块化特性不变,即您拥有核心引擎类,并且您可以使用外部库扩展功能。您可以毫不费力地切换库这一事实也是一个巨大的优势。这是我在自己的引擎中采用的技术,我还将它扩展到其他类型的库 - 例如 Ads - 我有我的引擎 Ad 类,可以根据需要与 Mochi、Kongregate 等集成 -我的游戏的其余部分并不关心我使用的是什么,这让我在整个引擎中保持我的编码风格和一致性,同时仍然是灵活和模块化的。
----- 更新 20/9/2013 -----
大更新时间!所以我回去对尺寸和速度进行了一些测试。我使用的类太大,无法在这里粘贴,因此您可以在http://divillysausages.com/files/TestExtendClass.as下载它
在其中,我测试了许多类:
- 一个
Empty
实例;一个仅扩展Object
和实现空getPostion()
函数的类。这将是我们的基准
- 一个
b2Body
实例
- 一个
Box2DExtends
实例;一个扩展b2Body
并实现一个getPosition()
只返回GetPosition()
的函数的类(b2Body
函数)
- 一个
Box2DExtendsOverrides
实例;一个扩展b2Body
和覆盖GetPosition()
函数的类(它只是返回super.GetPosition()
)
- 一个
Box2DComposition
实例;一个类,它有一个b2Body
属性和一个getPosition()
返回b2Body's
GetPosition()
- 一个
Box2DExtendsProperty
实例;扩展b2Body
并添加新Point
属性的类
- 一个
Box2DCompositionProperty
实例;同时具有b2Body
属性和Point
属性的类
所有测试都是在独立播放器 FP v11.7.700.224、Windows 7 中在一台不太出色的笔记本电脑上完成的。
测试1:尺寸
AS3 有点烦人,如果你调用getSize()
,它会给你对象本身的大小,但是任何内部属性也Objects
只会导致4 byte
增加,因为它们只计算指针。我明白他们为什么这样做,只是让获得合适的尺寸有点尴尬。
于是我转向flash.sampler
包裹。如果我们对对象的创建进行采样,并将对象中的所有大小相加NewObjectSample
,我们将得到对象的完整大小(注意:如果您想查看创建的内容和大小,log
请在测试中的调用中注释文件)。
- Empty 的大小为 56 // 扩展 Object
- b2Body的尺寸是568
- Box2DExtends 的大小为 568 // 扩展 b2Body
- Box2DExtendsOverrides 的大小为 568 // 扩展 b2Body
- Box2DComposition 的大小为 588 // 具有 b2Body 属性
- Box2DExtendsProperty 的大小为 604 // 扩展 b2Body 并添加 Point 属性
- Box2DCompositionProperty 的大小为 624 // 具有 b2Body 和 Point 属性
这些大小都以字节为单位。一些值得注意的点:
- 基本
Object
大小是40
字节,所以只有类,其他什么都不是16
字节。
- 添加方法不会增加对象的大小(无论如何它们都是在类的基础上实现的),而属性显然会
- 只是扩展类并没有添加任何东西
- 额外的
20
字节Box2DComposition
来自16
类和4
指向b2Body
属性的指针
- 对于
Box2DExtendsProperty
等,你有16
类Point
本身,4
指向Point
属性的指针,以及8
每个x
和y
属性Numbers
=36
字节之间的差异Box2DExtends
因此,显然大小的差异取决于您添加的属性,但总而言之,可以忽略不计。
测试二:创作速度
为此,我简单地使用getTimer()
了 ,带有 的循环10000
,它本身循环10
(所以 100k)次来获得平均值。System.gc()
在每组之间调用以最小化由于垃圾收集而导致的时间。
- Empty 的创建时间是 3.9ms (av.)
- b2Body 的创建时间是 65.5ms (av.)
- Box2DExtends 的创建时间为 69.9 毫秒(平均)
- Box2DExtendsOverrides 的创建时间为 68.8 毫秒(平均)
- Box2DComposition 的创建时间是 72.6ms (av.)
- Box2DExtendsProperty 的创建时间是 76.5ms (av.)
- Box2DCompositionProperty 的创建时间为 77.2ms (av.)
这里没有一大堆需要注意的地方。扩展/组合类需要稍长一些,但它就像0.000007ms
(这是 100,000 个对象的创建时间),所以它真的不值得考虑。
测试 3:通话速度
为此,我getTimer()
再次使用1000000
, 本身循环10
(所以 10m)次来获得平均值。System.gc()
在每组之间调用以最小化由于垃圾收集而导致的时间。所有对象都getPosition()/GetPosition()
调用了它们的函数,以查看覆盖和重定向之间的区别。
- 空的 getPosition() 时间是 83.4ms (av.) // 空的
- b2Body 的 GetPosition() 时间为 88.3ms (av.) // 正常
- Box2DExtends 的 getPosition() 时间为 158.7ms (av.) // getPosition() 调用 GetPosition()
- Box2DExtendsOverrides 的 GetPosition() 时间为 161ms (av.) // 覆盖调用 super.GetPosition()
- Box2DComposition 的 getPosition() 时间是 160.3ms (av.) // 调用 this.body.GetPosition()
- Box2DExtendsProperty 的 GetPosition() 时间为 89 毫秒(平均)// 隐式超(即未覆盖)
- Box2DCompositionProperty 的 getPosition() 时间为 155.2ms (av.) // 调用 this.body.GetPosition()
这个让我有点吃惊,时间差约为 2 倍(尽管仍然是0.000007ms
每次通话)。延迟似乎完全取决于类继承——例如Box2DExtendsOverrides
简单的调用,但速度是从其基类继承的super.GetPosition()
的两倍。Box2DExtendsProperty
GetPosition()
我想这与函数查找和调用的开销有关,尽管我查看了swfdump
在 FlexSDK 中使用生成的字节码,它们是相同的,所以要么是在骗我(或者不包括它),或者我缺少一些东西:)虽然步骤可能相同,但它们之间的时间可能不同(例如,在内存中,它跳转到您的类 vtable,然后跳转到基类 vtable 等)
的字节码var v:b2Vec2 = b2Body.GetPosition()
很简单:
getlocal 4
callproperty :GetPosition (0)
coerce Box2D.Common.Math:b2Vec2
setlocal3
而var v:b2Vec2 = Box2DExtends.getPosition()
(getPosition()
返回GetPosition()
)是:
getlocal 5
callproperty :getPosition (0)
coerce Box2D.Common.Math:b2Vec2
setlocal3
对于第二个示例,它没有显示对 的调用GetPosition()
,所以我不确定他们是如何解决的。如果有人想解释一下,可以下载测试文件。
需要记住的几点:
GetPosition()
并没有真正做任何事情;它本质上是一个伪装成函数的吸气剂,这就是“额外类步惩罚”看起来如此之大的原因之一
- 这是在 10m 的循环上,您不太可能在游戏中这样做。每次通话的罚款并不值得担心
- 即使您确实担心惩罚,请记住这是您的代码和 Box2D 之间的接口;Box2D 内部将不受此影响,只有对您的界面的调用
总而言之,我希望扩展我自己的一个类也能得到相同的结果,所以我不会真的担心。实施最适合您的解决方案的架构。