首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Swift,SpriteKit:在更新方法中有一个庞大的for-循环的低FPS

Swift,SpriteKit:在更新方法中有一个庞大的for-循环的低FPS
EN

Stack Overflow用户
提问于 2020-09-11 12:30:17
回答 2查看 332关注 0票数 2

使用下面的代码使用Sprite Kit拥有非常低的FPS (~7fps到~10 FPS)是否正常?

用例:

我画的只是从下到上的线(1024 * 64行)。我有一些增量值,它决定了每一帧的单行位置。这些行表示我的CGPath,每个帧都分配给SKShapeNode。没别的了。我想知道SpriteKit (也许是Swift)的性能。

你有什么改善表现的建议吗?

屏幕:

代码:

代码语言:javascript
运行
复制
import UIKit
import SpriteKit

class SKViewController: UIViewController {
    
    @IBOutlet weak var skView: SKView!
    
    var scene: SKScene!
    var lines: SKShapeNode!
    
    let N: Int = 1024 * 64
    var delta: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        scene = SKScene(size: skView.bounds.size)
        scene.delegate = self
        
        skView.showsFPS = true
        skView.showsDrawCount = true
        skView.presentScene(scene)
        
        lines = SKShapeNode()
        lines.lineWidth = 1
        lines.strokeColor = .white
        
        scene.addChild(lines)
    }
}

extension SKViewController: SKSceneDelegate {
    func update(_ currentTime: TimeInterval, for scene: SKScene) {
        let w: CGFloat = scene.size.width
        let offset: CGFloat = w / CGFloat(N)
        
        let path = UIBezierPath()
        
        for i in 0 ..< N { // N -> 1024 * 64 -> 65536
            let x1: CGFloat = CGFloat(i) * offset
            let x2: CGFloat = x1
            let y1: CGFloat = 0
            let y2: CGFloat = CGFloat(delta)

            path.move(to: CGPoint(x: x1, y: y1))
            path.addLine(to: CGPoint(x: x2, y: y2))
        }
        
        lines.path = path.cgPath
        
        // Updating delta to simulate the changes
        //
        if delta > 100 {
            delta = 0
        }
        delta += 1
    }
}

谢谢并致以最良好的问候,艾芭^_^

EN

回答 2

Stack Overflow用户

发布于 2020-09-11 14:38:18

CPU

65536是一个相当大的数字。让CPU理解这么多循环总是会导致慢的结果。例如,即使我做了一个测试命令行项目,它只测量运行空循环所需的时间:

代码语言:javascript
运行
复制
while true {
    let date = Date().timeIntervalSince1970


    for _ in 1...65536 {}

    let date2 = Date().timeIntervalSince1970
    print(1 / (date2 - date))
}

这将导致~17 fps。我甚至没有应用CGPath,而且它已经相当慢了。

调度队列

如果您想将游戏保持在60 use,但您的CGPath的呈现可能仍然很慢,您可以使用DispatchQueue

代码语言:javascript
运行
复制
var rendering: Bool = false // remember to make this one an instance value

while true {
    let date = Date().timeIntervalSince1970

    if !rendering {
        rendering = true
        let foo = DispatchQueue(label: "Run The Loop")
        foo.async {
            for _ in 1...65536 {}
            let date2 = Date().timeIntervalSince1970
            print("Render", 1 / (date2 - date))
        }
        rendering = false
    }
}

这保留了自然的60 you体验,您可以更新其他对象,但是SKShapeNode对象的呈现仍然相当缓慢。

GPU

如果您想加快呈现速度,我建议在GPU而不是CPU上运行它。GPU (图形处理单元)更适合这一点,并能处理巨大的循环,而不干扰游戏。这可能需要您将其编程为SKShader,其中有教程。

票数 1
EN

Stack Overflow用户

发布于 2020-11-01 01:03:54

检查分区的数目

没有任何iOS设备的屏幕宽度超过3000像素或1500个点(视网膜屏幕有逻辑点和物理像素,其中一个点相当于2或3个像素,取决于缩放因子;iOS适用于点,但您也必须记住像素),甚至接近的是那些在景观模式下具有最大屏幕的屏幕(iPad Pro 12.9和iPhone Pro Max)。

一个典型的设备在纵向方向将小于500点和1500像素宽。

您将此宽度划分为65536个部分,并最终得到像0.00、0.05、0.10、0.15、.、0.85这样的像素(非偶数点)坐标,这些坐标实际上将指向相同的像素20次(我的结果是在iPhone模拟器中舍入)。

您的代码在完全相同的物理位置上绘制20到60条直线,相加在一起!为什么要这么做?如果将N设置为w并对offset使用1.0,则在60个FPS时将得到相同的可见结果。

重新考虑方法

尽管如此,这个实现仍然有一些缺点,即使你大大减少了每个帧要完成的工作量。不建议在update(_:)中推进动画帧,因为在FPS上没有保证,而且您通常希望您的动画遵循设定的时间表,即在1秒内完成,而不是60帧。(如果FPS降至10,60帧动画将在6秒内完成,而1秒动画仍将在1秒内完成,但帧速率较低,即跳过帧。)

显然,动画所做的是在屏幕上绘制一个矩形,其宽度填充屏幕,其高度从0增加到100点。我要说的是,实现这一目标的一个更“标准”的方法是:

代码语言:javascript
运行
复制
let sprite = SKSpriteNode(color: .white, size: CGSize(width: scene.size.width, height: 100.0))
sprite.yScale = 0.0
scene.addChild(sprite)
sprite.run(SKAction.repeatForever(SKAction.sequence([
    SKAction.scaleY(to: 1.0, duration: 2),
    SKAction.scaleY(to: 0.0, duration: 0.0)
])))

请注意,我使用SKSpriteNode是因为据说SKShapeNode受到了bug和性能问题的困扰,尽管人们在过去几年中报告了一些改进。

但是,如果由于某些特殊的需要,您坚持要重新绘制sprite每一个帧的整个纹理,那么这可能确实是定制着色器…的一部分。但这需要学习一种全新的方法,更不用说一种新的编程语言了。

您的着色器将在GPU 上为每个像素执行。我重复一遍:代码将对每一个像素执行--这与SpriteKit的世界大相径庭。

着色器将访问一组要处理的值,例如一个名为v_tex_coord的变量中的标准化坐标集(在(0.00.0)到(1.01.0)之间)和u_time中的系统时间(自着色器运行以来已经运行了几秒),它需要通过将该值存储在变量gl_FragColor中来确定所述像素的颜色值需要- and设置。

可能是这样的:

代码语言:javascript
运行
复制
void main() {
       
// default to a black color, or a three-dimensional vector v3(0.0, 0.0, 0.0):
    vec3 color = vec3(0.0); 

// take the fraction part of the time in seconds;
// this value will go from 0.0 to 0.9999… every second, then drop back to 0.0.
// use this to determine the relative height of the area we want to paint white:
    float height = fract(u_time); 

// check if the current pixel is below the height of the white area:
    if (v_tex_coord.y < height) { 

// if so, set it to white (a three-dimensional vector v3(1.0, 1.0, 1.0)):
        color = vec3(1.0); 
    }
 
    gl_FragColor = vec4(color,1.0); // the fourth dimension is the alpha
}

将其放入一个名为shader.fsh的文件中,创建一个全屏雪碧mySprite,并将着色器分配给它:

代码语言:javascript
运行
复制
mySprite.shader = SKShader.init(fileNamed: "shader.fsh")

一旦你显示精灵,它的着色器将处理所有的渲染。但是,请注意,结果您的sprite将失去一些SpriteKit功能。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63847138

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档