SpriteKit物理引擎在iOS 9上导致帧率急剧下降

4
我在场景中遇到了巨大的FPS下降问题,其中有一些静态SKSpriteNode节点,它们具有使用SKPhysicsBody init(polygonFrom: CGPath)定义的物体,还有一些简单的动态SKSpriteNode节点,其物体是用init(rectangleOf: CGSize)定义的。
动态节点被发射穿过场景并最终停止,具体取决于物理规则。每个动态节点都与其他动态节点以及先前提到的静态节点发生碰撞。
游戏以60fps流畅运行,直到屏幕上的动态节点数量达到约30个或更多。此后,FPS急剧下降至约10fps。
注意1:该问题仅存在于iOS 9上,而非iOS 10(由于不支持iOS 8,因此我没有进行测试)。
注意2:绘制调用不会因动态节点数量增加而增加,因此OpenGL方面似乎没有问题。
注意3:我禁用了contactTestBitMask以确保这不是问题的原因。

我使用Instruments进行了时间分析器,并关注了FPS下降的部分,发现了以下奇怪的事情:

Time Profiler details

有趣的是,问题段落中超过50%的时间都花在了PhysicsKit上。请注意,无论碰撞是否发生或动态节点是否静止不动,结果始终如一。这必须是问题的原因,但由于所有这些都发生在系统库(PhysicsKit)中,我真的不知道在我的代码库中寻找问题的位置。感谢任何帮助!

你尝试过清理项目吗?Shift-CMD-K - Nik
5
SpriteKit 的版本之间一直存在着非常奇怪的不一致性,即使是在小的点版本之间也是如此。苹果从来没有发布变更或其他有用的发行说明,他们很少在自己的论坛上回复长篇关于性能问题的帖子。我告诉你这些信息是为了让你不要认为这是一个完全的异常情况。最新的 SpriteKit 中存在内存错误和音频问题的报告,这些问题本应该被基本单元测试发现,并且自 iOS 10 的早期测试版就已经存在了。由于 SpriteKit 的用户不是很多,在苹果公司对其进行的关注和关心也不是很多。 - Confused
@Nik 是的,很多次。 - damirstuhec
如果你想的话,有一种方法可以将Box2D导入到SpriteKit中。 - Coldsteel48
@NSDawg 我不确定我是否想要这样做。 - damirstuhec
2个回答

2

你的问题无法避免,正如nateslager已经提到了明显的问题,我将告诉你一个解决方案。

你需要将场景分成四个或更多象限,并使用某种重叠来保留这些象限的categoryBitMask,因为物理对象可以具有多个categoryBitMask。现在,您需要每帧更新这些类别,因此我喜欢覆盖位置属性并添加didSet以告诉节点它们所在的象限。

现在,根据您如何使用这些标志,这可能会变得非常复杂,因为您不能执行类似于quadrant4&bad的操作,因为它仍然会注册所有良好和不良。相反,您需要将quadrant4bad作为1个类别。其想法是减少在所有身体之间进行的检查次数。

例如: 我们有一个场景,范围为(-5,-5)至(5,5),大小为(11,11)

象限1为(-5,0),(0,0),(-5,-5),(0,-5)
象限2为(0,0),(5,0),(0,-5),(5,-5)
象限3为(-5,5),(0,5),(-5,0),(0,0)
象限4为(5,0),(5,5),(0,0),(5,0)
象限C为(-2.5,2.5),(2.5,2.5),(-2.5,-2.5),(2.5,2.5)

enum PhysicsCategory : UInt
{
   case good = 0b1
   case quadrant1Bad = 0b10
   case quadrant2Bad = 0b100
   case quadrant3Bad = 0b1000
   case quadrant4Bad = 0b10000
   case quadrantCBad = 0b100000
}

//we set up a good player at position 0,0 so he is in the center quadrant
good.contactBitMask = quadrantC


//we set up a enemy  at position -5, -5 so he is in the first quadrant
bad1.contactBitMask = quadrant1

//we set up a enemy  at position 5, -5 so he is in the second quadrant
bad2.contactBitMask = quadrant2

//we set up a enemy  at position -5, 5 so he is in the third quadrant
bad3.contactBitMask = quadrant3

//we set up a enemy  at position 5, 5 so he is in the fourth quadrant
bad4.contactBitMask = quadrant4

//we set up a enemy  at position -2.5, 2.5 so he is in the third quadrant and center quadrant (remember, they have a width and height to them, so they will be in more than 1 quadrant
badC.contactBitMask = quadrant3 | quadrantC

现在我们的游戏正在运行时,Good 只会检查他所在象限内的敌人,因此只有 QuadrantC 会被检查。
如果 Good 移动到 Quadrant3,则仅进行两次检查,Bad3 和 BadC。
这将帮助您减少对物理引擎后端的调用,从而减少延迟。
如果您使用了许多不同的类别,那么我建议您在敌人不在某个象限时关闭其 categoryBitMasks。这也将减少物理引擎所做的检查次数。

谢谢您的回复。这绝对是个好主意,但看着我的游戏,我不确定它是否有帮助,因为一个目标是在静态节点上堆叠动态节点。因此,我想计算不会有太大帮助。我将作为最后的手段尝试一下,并让您知道结果。顺便说一下,我曾经尝试过在动态节点“停止”后关闭 categoryBitMasks,但似乎并没有影响计算。 - damirstuhec
如果您有静态节点,则将所有物理体组合成一个物体,并将其分配给父节点,您可以使用contactPoint来确定哪个节点被击中。 - Knight0fDragon
KnightOfDragon,你如何将多个静态物理体合并为单个物理体? - claassenApps
这是一个很棒的解决方案!谢谢! - Coldsteel48
@Knight0fDragon 我已经放置了一个移动摄像机节点,这会导致静态节点最终离开屏幕并在此之后被清除。因此,不断为当前可见的静态节点生成统一的主体将是一件非常麻烦的事情。 - damirstuhec
显示剩余6条评论

0
即使您只有“几个”多边形物理体,每增加一个矩形物理体,无论它是否移动,都需要在每一帧中计算它与每个多边形物理体之间的距离(因此需要使用b2Distance()函数)。尝试减少多边形物理体的复杂性/数量以提高帧率。
我怀疑性能突然下降与缓存(b2SimplexCache)有关,该缓存存储这些距离达到其最大容量(可能约为30个值),此时缓存未命中会降低帧率。iOS 10必须增加了此缓存。另一种解决方案是在iOS 9中以某种方式增加此函数的缓存,但我不确定如何做到这一点。

谢谢你的回答,但我无法进一步简化主体。对于静态节点来说,它们并不那么复杂。而动态节点只有矩形主体。 - damirstuhec
你使用了多少个多边形物理体?它们是什么? - Ralph
我同时使用了大约4个多边形物理体。它们被应用于代表房屋的SKSpriteNode节点上。 - damirstuhec
一次只有四个物体,肯定有某种错误影响了你的性能。你能否在iOS 10的测试项目中增加物体数量,看看何时开始出现相同的行为/问题?@damirstuhec - Confused
我在iOS 10上进行了测试,使用了4个多边形物体,并且我需要至少80个节点才能开始看到帧率下降。下降只持续了几帧,不像在iOS 9上那样显著。 - damirstuhec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接