CPU-Expensive (frame.contains) 是不是有解决方法?

Posted

技术标签:

【中文标题】CPU-Expensive (frame.contains) 是不是有解决方法?【英文标题】:Is there a work-around for CPU-Expensive (frame.contains)?CPU-Expensive (frame.contains) 是否有解决方法? 【发布时间】:2016-09-16 02:57:58 【问题描述】:

我正在使用 Swift 和 SpriteKit 构建我的第一款游戏,最近我将整个游戏拆开以确定我的 CPU 问题来自哪里(在玩游戏的几分钟内,大约 80-100%)。我最近发现最昂贵的操作之一位于touchesMoved 方法中。这并不奇怪,因为它被非常频繁地调用,并且用户在整个游戏中都用手指来控制主角。但是,我惊讶地发现,特别是一条线路造成了麻烦。这是我的代码:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) 
    for touch : AnyObject in touches 
        let location = touch.location(in: self)

        let touchStartPoint:CGPoint = startingTouches[touch as! UITouch]!

        if(controlBase.frame.contains(touchStartPoint))
            controlStick.position = location
        
    


我用 Instruments 运行了 4 个单独的测试,每个测试都用另一行。我发现没有

   if(controlBase.frame.contains(touchStartPoint))
        controlStick.position = location
    

当我的手指向下时,我在大约 5-10% 的 CPU 上徘徊 - 所以 touchesMoved 仍然被调用。但是,在检查if(controlBase.frame.contains(touchStartPoint)) 时,即使括号内没有任何内容,我也会达到约 20-30% 的 CPU。

以下是 iPhone 上当前设置的屏幕截图:

这是我的 Instruments 日志的图片:最低点是我的手指从屏幕上抬起时,最高点是我的手指移动控制器时。

有没有办法解决这个问题?我正在做这个检查以确保用户虚拟操纵杆上的手柄不能离开它的底座。我只是不确定为什么这条线会如此昂贵,如果这是此检查所固有的,是否有另一种方法可以实现类似的功能。谢谢!

【问题讨论】:

我不确定我是否会担心这个。根据您的仪器日志,您没有使用大量 CPU。你会注意到渲染是你的最高消费者,而不是你的触摸代码。除非您需要向下滚动更多才能显示它。尝试在下次运行时使用 Xcode 中的 CPU 仪表板,看看这些数字的表现如何。还有你的 FPS 是多少? 真的吗?我相信在 Xcode 上,他们建议 20%,除非你说如果 FPS 没有受到严重影响,就没有太多可担心的?我的 FPS 稳定在 60 直到游戏后期(几分钟),当它降级到低 40 秒时——这个例子也没有考虑完整的游戏玩法,只考虑了控制器(这是一个单独的项目,我正在重建应用程序) 现在尝试运行 Spaceships 并查看它的 CPU 使用率(减去几分钟后)。如果你的触摸代码消耗了很多,你会看到它出现在调用树中更高的位置,但事实并非如此。我会说在你的其他东西上取得进展并重新审视。让 FPS 成为您的向导。最后你很可能会得到一个高 CPU 使用率的游戏,除非你正在制作像井字游戏这样的东西;)。显然,下降表明您还有其他挑战。 哦,是的,拥有一些可以从树上下来并给出准确计数的东西可能仍然有用,以防万一您需要计数。这样的代码就派上用场了。 这就是当非游戏引擎公司(例如:Apple)在制作游戏引擎方面有所努力时发生的情况。 :// 【参考方案1】:

啊,好吧,我在另一个线程中为您留下了一些 cmets 后才看到这个。关于您获得的触摸事件的频率,您有点受操作系统的支配。您可以尝试限制在这些时刻占用的带宽。

这里的问题是你的节点树有多大?临时添加一些代码以通过手动遍历树来物理计算节点数可能很有用。您是否还在节点上应用缩放和旋转并使用层次结构?如果是这样,您也应该注意这一点,因为它需要更多的计算。

仍然基于您之前描述的内容,如果这真的占用了您的带宽,我会感到惊讶(假设您说您有 20 个对象)。

或者,如果您真的想要,您可以创建自己的包含。这实际上是我所做的,但其中一部分是对我如何处理我的图形有特殊需求的副产品。话虽如此,我经历了数百个对象,但没有明显的问题。请注意,我不会像您那样仔细检查 CPU 使用情况。我只关注FPS。

这段时间你的 FPS 是多少?此外,您应该在设备上运行它,并查看您的 CPU/GPU 使用情况与帧相关。 %CPU 也可能有点可疑,我觉得基于其他因素,例如您是否正在记录,甚至有时,操作系统还发生了什么。

最后尝试将您的代码移至飞船示例,并在添加更多飞船时注意效果。

添加细节

所以当我最初回答这些问题时,我很少考虑你在做什么。我仍然相信你现在不应该过度尝试优化,但是看看 SKOOP 的答案,特别是

https://github.com/MitrophD/Swift-SpriteKit-Analog-Stick

这是有趣的代码,让我更好地了解了您真正想要做什么以及如何在需要时挤出 CPU 使用率。游戏编码和优化背后的原则之一是懒惰。 “我怎样才能预先计算或做最少的工作?”

在这里,您需要对您的touchesBegintouchedEnd/touchesCancelled 进行一些调整。在进行触摸处理的类中添加一个名为

的成员属性

var controllerTouch : UITouch?

请注意,这是可选的,将代表控制器中的当前触摸。另请注意,如果您正在执行多点触控,则您将其定义为规则:控制器中的第一个触控是控制触控。

这带来了另一个关键点,即规则。您需要定义规则。在这里,我要说的是,活动控制器的触摸超出controllerBase 的范围是可以的(这就像将杆拉到最大)并且控制器实际上应该是一个圆圈。最后,我假设controllerBase 是静态的。

现在,这使我找到了可以最大限度减少所需工作的方法。

touchesBegin

由于这是静态的,我应该使用简单的数学方法来比较触摸的位置与到controllerBase 中心的距离。我可以这样做,因为我们说这是一个圆圈。所以我应该知道半径。因此,我的支票将类似于:

let distance = computeDistance(pt0: controllerBaseCenter, pt1: location)

// We only care about doing this if we are not currently actively tracking
if controllerTouch == nil 
    if distance < controllerBaseRadius 
        // Where touch is the current touch
        controllerTouch = touch
       

注意代码没有经过编译测试或是否有效,而是一个示例,并假设您将根据需要对其进行修复。我假设您将用正确的数学替换computeDistance 来计算距离,这是一件微不足道的事情。你的距离也需要绝对。

还有一件事要记住。由于controllerBase 是静态的,您可以预先计算并将实际中心点(controllerBaseCenter 和半径)存储在变量中。这可以保护您不必在节点上执行locationcontains,从而节省每个循环的浪费touchesStart 运行时间。

touchesEnd/touchesCancelled

在这里你将忽略controllerTouch 如果这是结束/取消的触摸。如有必要,您也可以为通知执行任何其他逻辑。

真正要处理的是

touchesMoved

所以在这里我们知道我们已经通过controllerTouch知道我们是否有一个活动控制器

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) 
    for touch : AnyObject in touches 

        if touch == controllerTouch 
            // Only get location when needed (ie defer until you really need this)
            let location = touch.location(in: self)
            controlStick.position = location // You will need to adjust, see notes below to compensate for controllerBaseCenter if needed
        
    

请注意我的延迟评论。在您的原始代码中,您应该只在需要时完成 location 分配,也就是触摸在 controllerBase.frame 时。

现在,我省略了最后一件事。如何处理控制器可以移动的径向距离取决于您。例如,如果半径为 64,而我的触摸开始于距中心 60 的距离,那么如何处理?您是否将该偏移量视为中心点?如果是这种情况,您还需要跟踪第一次触摸的位置以正确调整数学。

请记住,您需要让玩家轻松不必精确。还要记住,他们不会经常低头查看控制器区域来仔细检查。

【讨论】:

只是为了澄清,因为我含糊不清——游戏的结构是只有一艘宇宙飞船是用户控制的,许多物体是宇宙飞船(用户)射击的外星人。也就是说,我什至没有在屏幕上绘制宇宙飞船——只是在游戏中获取值,因为我有 touchMoved 被调用。节点的数量应该在 3 左右 - 组成虚拟游戏控制器的节点 - 一个 SKSpriteNode 用于基础(只是屏幕角落的一个灰色矩形),一个 SKSpriteNode 用于joyStick - 只是你下方的一个红点手指(如果手指在底座内) 我不太确定您的上一条评论是什么意思。无论如何,“节点的数量应该......”。这里的关键词是应该。以防万一,验证总是值得的。奇怪的事情发生了。您是否碰巧在后台做任何事情,或者您是否正在记录您的触摸?如果你真的只有 3 个从 controlBase.frame 下降的节点,它应该很快。 哦,你是如何测量 CPU 使用率的?使用 CPU 仪表板?尝试用宇宙飞船将其隔离。 我想到的另一件事是,您可以将节点一一添加到 controlBase.frame 上,然后看看会发生什么。 我刚刚添加了底座/手柄设置的屏幕截图以及我的仪器日志。我现在将添加一些代码来获取递归计数。【参考方案2】:

这是处理 Apple 框架时令人费解的方面之一。

没有好的微观例子!

许多游戏引擎和框架提供了一连串小型、实用、相关的示例,有时甚至数百个,展示了如何使用引擎功能的各个方面。

这些然后(也)用作类似单元的测试,用于引擎的每次迭代,并相应地更新。 Apple 的一些 Sprite Kit 演示代码是其原始形式。

而且社区很少。

不幸的是,你最好看看其他人在 github 上是如何做类似事情的,因为 Apple 甚至没有 Sprite Kit 的“最佳实践”文档。

这是 github 上的一个虚拟摇杆示例。

https://github.com/MitrophD/Swift-SpriteKit-Analog-Stick

这里有个有趣的问题:Dpad for sprite kit

其中条件是关于被触摸的节点的名称,而不是在.frame中搜索

//if control pad touched
    if ([node.name isEqualToString:@"controlPadNode"]) 
       ....
    

可能是 .frame 内的搜索成本很高。它(应该)是简单的数学运算,所以我不知道为什么会这样,但是 SpriteKit 和 SceneKit 有很多奇怪的陷阱。

【讨论】:

从更全面的角度来看,一旦您确定了触摸,并且由于您只对宇宙飞船的所有运动使用一次触摸,我不确定您是否需要一个触摸有条件地检查被触摸的内容。您可以(可能)假设它在屏幕上,并使用它在每次移动中的相对位置来控制您的船。 “许多游戏引擎...”APPL 是一种股票,使用徽标和电视广告将 32 美分的耳机标价为 200 美元 full analysis - 上帝保佑他们,我鼓励你购买 APPL 股票. APPL 尝试开发游戏引擎的想法是——可爱!可怜的亲们! (记住“地图”的东西。嘿!) “这是处理 Apple 框架时令人费解的方面之一。”只有一个。只有一个。有很多。我相信你可以列出它们。其他部分是他们纯粹的贪婪,猖獗的玩世不恭,WWDC视频起搏和演示,他们作为一个整体的文档等等等等。建议更好的游戏引擎,亲爱的,请! span>

以上是关于CPU-Expensive (frame.contains) 是不是有解决方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 iframe 元素? [复制]