如何在SpriteKit中循环延迟?

时间:2018-02-20 16:04:55

标签: swift sprite-kit delay dispatch-queue

我在这里和那里进行了一些搜索但是我没有找到(或理解)如何在SpriteKit中循环延迟。

这是个想法:我有一些SKSpriteNodes按数组排序,我想在屏幕上显示它们,每秒一个。问题是......我根本没有设法做到这一点(对不起,我对此很陌生)。

       let sprite1 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
    sprite1.position = CGPoint(x: 100, y: 100)

    let sprite2 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
    sprite2.position = CGPoint(x: 100, y: 300)

    let sprite3 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
    sprite3.position = CGPoint(x: 300, y: 100)

    let sprite4 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
    sprite4.position = CGPoint(x: 300, y: 300)

    let allSprites : [SKSpriteNode] = [sprite1, sprite2, sprite3, sprite4]
    let delay = 1.0
    var i = 0
    while i <= 3
    {
        let oneSprite = allSprites[i]
        self.addChild(oneSprite)


       DispatchQueue.main.asyncAfter(deadline : .now() + delay) {
            i = i + 1
        }
    }

如果你问:这根本不起作用。似乎DispatchQueue.main.asyncAfter中的内容是不可读的。

所以,如果你能帮我理解原因,那就太好了。我不挑剔,我不能用&#34; for&#34;循环。

此致

1 个答案:

答案 0 :(得分:5)

这是对事件驱动/基于运行循环的/ GUI编程系统的常见初学者误解。一些关键点可帮助您走上正轨:

  • SpriteKit尝试每秒渲染场景60(左右)。
  • 这意味着SpriteKit内部有一个循环,每帧一次,它调用你的代码询问新的(更新),然后运行自己的代码来绘制更改(渲染)。
  • 设置代码,或响应事件而发生的事情(点击/点按/按钮)在此循环外部,但是请输入:事件更改状态,渲染循环对此做出反应。

因此,如果你在更新方法,事件处理程序或初始设置代码中有一个循环,那么在循环中发生的一切都将在SpriteKit有机会绘制任何一个之前发生。也就是说,如果我们暂时忽略&#34;延迟&#34;你的问题的一部分,像这样的循环......

var i = 0
while i <= 3 {
    let oneSprite = allSprites[i]
    self.addChild(oneSprite)
}

...将导致在循环开始之前没有任何精灵可见,并且所有精灵在完成后都可见。循环中没有引入延迟的数量会改变这一点,因为 SpriteKit在你的循环完成之后没有第一次获得绘制的机会

解决问题

在SpriteKit等系统中进行动画制作和延迟的最佳方法是利用它为您提供的声明处理动画的工具。也就是说,你告诉SpriteKit你想要在接下来的几百个帧中发生什么,而SpriteKit就会发生这种情况 - 每次通过更新/渲染循环时,SpriteKit都会确定需要在场景中进行哪些更改。完成你的动画。例如,如果你以60 fps的速度运行,并且你要求某个精灵的淡出动画持续一秒,那么每一帧它会将精灵的不透明度降低1/60

SpriteKit的声明性动画工具是SKAction。对于您想要制作动画的大部分内容,以及将其他动作组合成组和序列的动作都有动作。在您的情况下,您可能需要waitunhide以及run(_:onChildWithName)操作的某种组合:

  1. 为每个精灵命名:

    let sprite1 = // ...
    sprite1.name = "sprite1"
    // etc
    
  2. 将所有节点添加到您的场景中,但保留您想要但尚未隐藏的节点:

    let allSprites : [SKSpriteNode] = [sprite1, sprite2, sprite3, sprite4]
    for sprite in allSprites {
        sprite.isHidden = true
        self.addChild(sprite)
    }
    
  3. 创建一个序列操作,通过告知每个节点取消隐藏来替换延迟,并在场景中运行该操作:

    let action = SKAction.sequence([
        run(.unhide(), onChildNodeWithName: "sprite1"),
        wait(forDuration: 1.0),
        run(.unhide(), onChildNodeWithName: "sprite2"),
        wait(forDuration: 1.0),
        // etc
    ])
    self.run(action)
    
  4. 那应该完成你正在寻找的东西。 (免责声明:使用网络浏览器编写的代码,可能需要调整。)如果您想更好地了解情况,请继续阅读...

    其他替代方案

    如果您真的想了解更新/渲染循环的工作原理,您可以直接参与该循环。我一般不会推荐它,因为它需要更多代码和簿记,但知道它很有用。

    1. 保留要添加或取消隐藏的节点的队列(阵列)。
    2. 在您的场景update方法中,记录已经过了多长时间。
    3. 如果您上次显示节点后至少有1.0秒(或之后的任何延迟),请在阵列中添加第一个节点,然后将其从阵列中删除。
    4. 当数组为空时,您已完成。
    5. 关于DispatchQueue.asyncAfter ...

      这对完成任务并不重要,但有助于理解,以便您以后不会遇到类似的问题:“#as; async&#34;在你打算用来尝试推迟事情的电话中,&#34;异步&#34;。异步意味着您编写的代码不会按照您编写的顺序执行。

      简化说明:请记住,现代CPU有多个核心?当CPU 0正在执行while循环时,它将进入asyncAfter调用并将注释传递给CPU 1,说等待一秒,然后执行附加到该asyncAfter调用的闭包主体。 一旦通过该注释,CPU 0就会快速继续 - 它不会等待CPU 1接收节点并完成注释指定的工作。

      仅仅从阅读代码开始,我就不会100%明白 这种方法如何失败,但它确实失败了。要么你有一个无限循环,因为while条件永远不会改变,因为更新i的工作正在其他地方发生,而不是传播回本地范围。或者i的更改会传播回来,但只有在循环旋转不确定的次数后才等待四个asyncAfter调用完成等待和执行。