SpriteKit 游戏中的高 CPU 和帧率下降
High CPU and frame rate drop in SpriteKit game
我正在使用 SpriteKit 制作我的第一款游戏,其中敌人从屏幕的一侧进入屏幕并从另一侧离开屏幕。我注意到在游戏后期渲染不同类型的敌人时,FPS 下降并且 CPU 使用率接近 100%(~95-99%)。我使用了两种仪器调试工具得到了这些结果:
在时间分析器工具中,我发现 main
我认为是 appDelegate
的主体是问题所在。我知道这看起来很明显,但我很惊讶地看到GameScene
中的 update
之类的功能几乎不是问题。有没有办法对此进行更深入的研究?如果这是我的功能之一导致了问题,我觉得好像更容易评估我搞砸的地方。
我还使用了分配工具,发现大量内存用于构建新的外星人精灵:
这是我添加普通外星人的代码:
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([=13=])}`
之前:class GameScene: SKScene, SKPhysicsContactDelegate {
因此可以从我的 Sprite 类 访问,行如下:
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)
}
如果有更好的预分配纹理方法,我很想知道,如果能进一步了解如何评估这些其他问题,那就太棒了!
一般来说,您会发现 main 包含最高的 CPU 使用率,因为通常大多数事情都是 运行 来自主线程。您将需要向下钻取以找到您已经完成的实际违规者。请记住,这都是相对于您在 Instruments 中使用的 运行 完成的。所以如果你没有足够的数据点,你可以转移注意力。这方面的一个例子是游戏加载。这可能是一项昂贵的操作。在某些情况下,它可能看起来相对较快(比如加载 1-5 秒),但是当您查看您的分析时,您可能会发现它消耗了大量时间。但是,如果您发现在 运行 时间内您的 FPS 很高,那么加载的开销是值得的。
转到您的场景,一个问题是您 运行 这花了多长时间?另一个问题是 addNormAlien 实际调用的频率是多少?是每一帧吗?您的 addNormAlien 有 2 个部分正在消耗时间,即创建和添加到层次结构。在不知道 Apple 如何实施这些项目的情况下,它们实际上有点像黑盒子,但很明显,创造的某些方面是昂贵的。同样,不了解游戏的时间特性使得很难完全确定这一点。回到我之前提到的,如果所有这些工作都在加载期间完成,那么这些数字就可以是一个转移注意力的问题。因此,您需要确保您的测试 运行 足以代表游戏会话。
一个建议是创建一个现有的已构建外星人节点池。然后在需要时从池中拉出一个对象(顺便说一句,您可能也应该包括您的 normAlien 方法)。这是顺便说一句,有多少游戏做到了。他们根据使用加载时间招致 "penalty" 对象创建的前提,在 运行 时间内创建他们将需要的项目的预分配版本,保持 运行 时间相对空闲的开销。但是请注意,这有一个技巧。从池中提取空闲对象时,您需要进行一些最小的初始化。以前你会一直依赖 constructor/initializer。但是因为这是一个已经创建的对象,你不会有那么奢侈(记住你在 运行ning 时试图消除不必要的开销)。这意味着您确实需要对此保持警惕,否则它会产生一些难以跟踪的错误。
我在您的其他帖子中提到的另一件事是禁用某些东西。例如,有一个 运行Action 发生在副本看起来很昂贵的地方。
关于纹理大小。多大才算大?一般来说,这是个坏主意。您这样做会招致处罚。但是,根据大小、扩展位置等,开销可以忽略不计。一个简单的测试是 运行 一个版本,其中纹理大小合适而不是大版本。如果你总是按比例缩小,就没有理由使用大的。
无论如何,这里有很多事情需要研究,这就是为什么游戏性能调整可能很棘手,以及为什么一种游戏的方法可能不适用于另一种游戏。只有你知道你是如何建造它的细微差别。
添加了基于信息的问题更新。
关于您对问题的更新。在我看来,您现在正在将范围扩大到不相关的项目(预加载纹理),并且还在寻找灵丹妙药或一站式解决所有问题的方法。有none。如前所述,补救这些类型的事情可能很棘手并且高度依赖于游戏。只有您知道您的游戏,并且只有您可以 运行 在 Instruments 中使用它。有一个人使用的策略。其中大部分来自经验和一些问题(例如,为什么初始化需要这么长时间?)。如果这会导致更多 SO 问题,则中断该问题。
关于预加载纹理,您需要为此创建一个新问题。想一想您在许多游戏中看到的加载屏幕。为什么你认为它们存在?在后台加载诸如纹理和其他数据之类的东西。是的,它增加了开发的复杂性,但它是为用户提供更好体验所必需的。预加载是通过 preload
完成的。您在代码片段中引用的只是数据的初始化,稍后可用于引用纹理实例。
我建议您做的是找到隔离部分代码的方法,以确定性能受到影响的来源。例如,您检查了节点数。 20不是很大。 PI 会惊讶于动态创建它们会有这样的开销,但您的时间分析确实表明可能会有一些显着的开销。如果 init 开销确实很大,您可以通过简单地让您的游戏每帧分配新的外星人来衡量它。您应该可以 运行 几秒钟而不会 运行 内存不足。然后,您可以使用它来开始删除部分代码,以查看这对性能有何影响。您应该希望在这里看到,其中大部分确实变成了反复试验。没有银牌 bullet/set 答案,需要一定的心态才能善于优化。
我在你的其他问题之一中提到了这一点,但如果你在模拟器上这样做,这不是调整性能的正确方法。必须在设备上完成。
我正在使用 SpriteKit 制作我的第一款游戏,其中敌人从屏幕的一侧进入屏幕并从另一侧离开屏幕。我注意到在游戏后期渲染不同类型的敌人时,FPS 下降并且 CPU 使用率接近 100%(~95-99%)。我使用了两种仪器调试工具得到了这些结果:
在时间分析器工具中,我发现 main
我认为是 appDelegate
的主体是问题所在。我知道这看起来很明显,但我很惊讶地看到GameScene
中的 update
之类的功能几乎不是问题。有没有办法对此进行更深入的研究?如果这是我的功能之一导致了问题,我觉得好像更容易评估我搞砸的地方。
这是我添加普通外星人的代码:
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([=13=])}`
之前:class GameScene: SKScene, SKPhysicsContactDelegate {
因此可以从我的 Sprite 类 访问,行如下:
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)
}
如果有更好的预分配纹理方法,我很想知道,如果能进一步了解如何评估这些其他问题,那就太棒了!
一般来说,您会发现 main 包含最高的 CPU 使用率,因为通常大多数事情都是 运行 来自主线程。您将需要向下钻取以找到您已经完成的实际违规者。请记住,这都是相对于您在 Instruments 中使用的 运行 完成的。所以如果你没有足够的数据点,你可以转移注意力。这方面的一个例子是游戏加载。这可能是一项昂贵的操作。在某些情况下,它可能看起来相对较快(比如加载 1-5 秒),但是当您查看您的分析时,您可能会发现它消耗了大量时间。但是,如果您发现在 运行 时间内您的 FPS 很高,那么加载的开销是值得的。
转到您的场景,一个问题是您 运行 这花了多长时间?另一个问题是 addNormAlien 实际调用的频率是多少?是每一帧吗?您的 addNormAlien 有 2 个部分正在消耗时间,即创建和添加到层次结构。在不知道 Apple 如何实施这些项目的情况下,它们实际上有点像黑盒子,但很明显,创造的某些方面是昂贵的。同样,不了解游戏的时间特性使得很难完全确定这一点。回到我之前提到的,如果所有这些工作都在加载期间完成,那么这些数字就可以是一个转移注意力的问题。因此,您需要确保您的测试 运行 足以代表游戏会话。
一个建议是创建一个现有的已构建外星人节点池。然后在需要时从池中拉出一个对象(顺便说一句,您可能也应该包括您的 normAlien 方法)。这是顺便说一句,有多少游戏做到了。他们根据使用加载时间招致 "penalty" 对象创建的前提,在 运行 时间内创建他们将需要的项目的预分配版本,保持 运行 时间相对空闲的开销。但是请注意,这有一个技巧。从池中提取空闲对象时,您需要进行一些最小的初始化。以前你会一直依赖 constructor/initializer。但是因为这是一个已经创建的对象,你不会有那么奢侈(记住你在 运行ning 时试图消除不必要的开销)。这意味着您确实需要对此保持警惕,否则它会产生一些难以跟踪的错误。
我在您的其他帖子中提到的另一件事是禁用某些东西。例如,有一个 运行Action 发生在副本看起来很昂贵的地方。
关于纹理大小。多大才算大?一般来说,这是个坏主意。您这样做会招致处罚。但是,根据大小、扩展位置等,开销可以忽略不计。一个简单的测试是 运行 一个版本,其中纹理大小合适而不是大版本。如果你总是按比例缩小,就没有理由使用大的。
无论如何,这里有很多事情需要研究,这就是为什么游戏性能调整可能很棘手,以及为什么一种游戏的方法可能不适用于另一种游戏。只有你知道你是如何建造它的细微差别。
添加了基于信息的问题更新。
关于您对问题的更新。在我看来,您现在正在将范围扩大到不相关的项目(预加载纹理),并且还在寻找灵丹妙药或一站式解决所有问题的方法。有none。如前所述,补救这些类型的事情可能很棘手并且高度依赖于游戏。只有您知道您的游戏,并且只有您可以 运行 在 Instruments 中使用它。有一个人使用的策略。其中大部分来自经验和一些问题(例如,为什么初始化需要这么长时间?)。如果这会导致更多 SO 问题,则中断该问题。
关于预加载纹理,您需要为此创建一个新问题。想一想您在许多游戏中看到的加载屏幕。为什么你认为它们存在?在后台加载诸如纹理和其他数据之类的东西。是的,它增加了开发的复杂性,但它是为用户提供更好体验所必需的。预加载是通过 preload
完成的。您在代码片段中引用的只是数据的初始化,稍后可用于引用纹理实例。
我建议您做的是找到隔离部分代码的方法,以确定性能受到影响的来源。例如,您检查了节点数。 20不是很大。 PI 会惊讶于动态创建它们会有这样的开销,但您的时间分析确实表明可能会有一些显着的开销。如果 init 开销确实很大,您可以通过简单地让您的游戏每帧分配新的外星人来衡量它。您应该可以 运行 几秒钟而不会 运行 内存不足。然后,您可以使用它来开始删除部分代码,以查看这对性能有何影响。您应该希望在这里看到,其中大部分确实变成了反复试验。没有银牌 bullet/set 答案,需要一定的心态才能善于优化。
我在你的其他问题之一中提到了这一点,但如果你在模拟器上这样做,这不是调整性能的正确方法。必须在设备上完成。