SpriteKit游戏中CPU占用率高和帧率下降问题

3
我正在使用SpriteKit制作我的第一个游戏,其中敌人从一侧进入屏幕,从另一侧离开。我注意到,在游戏后期渲染不同类型的敌人时,FPS下降,CPU使用率接近100%(约为95-99%)。我使用了两个仪器调试工具,并得出以下结果:
在时间分析器工具中,我发现mainappDelegate的主体,这是问题所在。我知道这似乎很明显,但我惊讶地发现像GameScene中的update函数几乎不是问题所在。有没有办法深入研究这个问题?如果是我的一个函数引起了问题,我觉得评估我搞砸了哪里会更容易些。
我还使用了分配工具,并发现大量内存用于构建新的外星人精灵:enter image description here

enter image description here

这是我添加普通外星人的代码:

func addNormAlien(){

    let mult = normAlienMultiplers

    let alienInst = normAlien(startPos:CGPoint(x: 10,y: 10), speed: random(UInt32(10),max: UInt32(50))*mult[0])

    let yStart = random(UInt32(alienInst.size.height/2), max: UInt32(size.height-alienInst.size.height))
    alienInst.position = CGPoint(x:size.width+alienInst.size.width/2, y:CGFloat(yStart))

    addChild(alienInst)
    totalNodes+=1
}

这在某种程度上是否可以预料,因为实例化新的精灵很昂贵(尽管我很少同时使用超过20个),我只是这样做来确保它们随机分布。
我也没有找到有关纹理大小的太多信息 - 是否有惯例可用?目前,我发现自己制作的精灵纹理相当大,然后使用setScale缩小它们,而不是在图像编辑器中进行缩小。
这些纹理和动画还在我的GameScene文件的开头被(预加载?)加载,例如:
let laserTexture = SKTextureAtlas(named:"Sprites").textureNamed("laserTexture")`

并且

let shipFrames = ["ship0","ship1","ship2","ship3","ship4","ship5","ship6","ship7","ship8","ship9","ship9","ship9","ship8","ship7","ship6","ship5","ship4","ship3","ship2","ship1","ship0"].map{textureAtlas.textureNamed($0)}`

在这行代码之前:class GameScene: SKScene, SKPhysicsContactDelegate {

因此,我的精灵类使用类似下面的代码来访问它们:

    super.init(texture: shipStartTexture, color: UIColor.clearColor(), size: shipStartTexture.size())

并且

func animateShip1() {
    let animate = SKAction.animateWithTextures(shipFrames, timePerFrame: 0.1)
    let forever = SKAction.repeatActionForever(animate)
    self.runAction(forever)
}

如果有更好的预分配纹理的方法,我很想知道,关于如何评估其他问题的更多见解也将非常棒!
1个回答

2
一般来说,你会发现主线程(main)的CPU使用率最高,因为大多数任务都是从主线程运行的。你需要深入挖掘,找到真正的罪魁祸首,这一点你已经做到了。请记住,这一切都是相对于你在Instruments中使用的运行时来完成的。因此,如果你没有足够的数据点,你可能会得到一些误导性的信息。例如游戏加载,这可能是一个昂贵的操作。在某些情况下,它可能看起来相对快速(比如1-5秒),但是当你查看你的性能分析时,你可能会发现它消耗了很多时间。然而,如果你在运行时发现帧率很高,那么加载的开销是值得的。
针对你的情况,有一个问题是你运行了多长时间?另一个问题是addNormAlien实际上被调用了多少次?是每一帧吗?你的addNormAlien有两个部分在消耗时间,即创建和添加到层级结构中。不知道Apple是如何实现这些项目的,它们实际上是一个黑盒子,但是很明显,创建方面存在一些昂贵的方面。同样,不知道你的游戏的时间特性使得这很难完全确定。回到我之前提到的,如果所有这些工作都在加载时完成,那么这些数字可能是一个误导性的信息。因此,你需要确保你的测试运行足够代表一个游戏会话。
一个建议是创建一个已经构建好的外星人节点池。然后在需要时从池中取出一个对象(顺便说一句,你可能也应该包括你的normAlien方法)。这是很多游戏所做的。他们基于使用加载时间来承担对象创建的“惩罚”,在运行时保持相对自由的前提下,创建运行时需要的预先分配的项目版本。但是请注意,这里有一个诀窍。当从池中获取一个空闲对象时,你需要进行一些最小的初始化操作。以前你可能依赖于构造函数/初始化器。但是由于这是一个已经创建好的对象,你将没有这样的豪华条件(记住你正在尝试在运行时删除不必要的开销)。这意味着你需要对此保持警惕,否则可能会引起一些难以跟踪的错误。
另一件事,就像我在你之前的帖子中提到的那样,就是禁用某些功能。例如,发生了看起来很昂贵的复制的runAction。
关于纹理大小。什么样的大小算是大?一般来说,这是一个不好的想法。你正在承担一些惩罚。然而,根据你缩放的大小、位置等情况,开销可能是可以忽略不计的。一个简单的测试方法是运行一个纹理大小正确的版本和一个较大的版本。如果你总是缩小,那就没有理由使用较大的版本。
总之,这里有很多事情需要考虑,这就是为什么游戏性能调优可能很棘手,也是为什么一个游戏的方法可能不适用于另一个游戏。只有你知道如何构建它的细微差别。
关于您对问题的更新。在我看来,您现在正在扩大范围,涉及到了无关的内容(预加载纹理),并且还在寻找一种解决所有问题的银弹或一站式答案。然而并没有这样的解决方案。如前所述,解决此类问题可能会很棘手,并且高度依赖于游戏本身。只有您了解自己的游戏,并且只有您可以在Instruments中运行它。有一些策略可以使用。其中许多都是通过经验和一些问题提问(例如:为什么初始化需要这么长时间?)得出的。如果这导致更多的SO问题,则请分开提问。
关于预加载纹理,您需要为此创建一个新问题。想象一下您在许多游戏中看到的加载屏幕。你认为它们存在的原因是什么?在后台加载像纹理和其他数据等内容。是的,它增加了开发复杂性,但是为了为用户提供更好的体验,这是必需的。预加载是通过preload完成的。您在代码片段中引用的只是数据的初始化,稍后可以用来引用纹理实例。
我建议您找到隔离代码部分以确定性能损失来源的方法。例如,您已经检查了节点计数。20并不是很大。动态创建它们可能会有如此大的开销,这可能令人惊讶,但是您的时间分析表明可能存在一些重大开销。如果初始化开销确实很大,您可以通过简单地使游戏每帧只分配新的外星人来测量它。您应该能够运行几秒钟而不会耗尽内存。然后,您可以使用它来开始删除代码的某些部分,以查看它对性能的影响。您应该能够在这里看到很多都是试错的过程。没有银弹/固定答案,需要一种特定的思维方式才能优化得好。
我在您的另一个问题中提到过这一点,但是如果您在模拟器上进行调试,则不是调整性能的正确方法。必须在设备上完成。

我刚刚更新了我的帖子,加入了我的纹理预分配 - 希望这是正确的方法?我认为runAction是纹理精灵的动画。我也想知道如何创建那个“池”的现有对象?那时我是否能够给它们不同的坐标/速度?或者我会在构建它们后分配这些属性?而且,在运行一些基本动画的同时进行预加载的方法是否存在?在游戏开始之前加载精灵时,一切都会卡住,并且淡入到GameScene的动画不会运行。 - muZero
另外,addNormAlien 每 1-2 秒钟调用一次,这已经运行了约 5 分钟。 - muZero
如果仅仅是调用了1-2秒,那么一定有问题,除非你的addNormAlien过于占用CPU资源。我在我的答案中添加了更多信息。 - Mobile Ben

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