多个 SKSpriteNode 的 SpriteKit 多个 SKActions - 等待全部完成
Posted
技术标签:
【中文标题】多个 SKSpriteNode 的 SpriteKit 多个 SKActions - 等待全部完成【英文标题】:SpriteKit multiple SKActions for multiple SKSpriteNode - wait for all to complete 【发布时间】:2017-09-23 08:08:11 【问题描述】:我正在开发一款 ios 游戏,但遇到了问题。 我需要显示 x 个精灵(每个精灵都有一个比例 SKAction)。 我需要能够等到所有 SKAction 精灵然后再做其他事情。 每个 SKAction 在单独的线程中运行。 我怎么能等呢?
这是一段代码:
for tile in tiles
let randomNum:UInt32 = arc4random_uniform(20) // range is 0 to 99
let randomTime:TimeInterval = TimeInterval(randomNum/10)
let scale = SKAction.scale(by: 1, duration: 2, delay:randomTime , usingSpringWithDamping: 0.1, initialSpringVelocity: 5)
tile.sprite = SKSpriteNode(imageNamed: tile.type.spriteName)
tile.sprite?.size = CGSize(width: TileWidth, height: TileHeight)
tile.sprite?.position = tile.position!
tile.sprite?.scale(to: CGSize(width: 0, height: 0))
cookiesLayer.addChild(tile.sprite!)
tile.sprite?.run(scale)
//TODO code to add to be executed after all SKActions
在所有 SKActions 之后,我怎样才能让我的 TODO 代码成为执行者? 我想并行或一个接一个地运行 SKAction。
谢谢。
【问题讨论】:
SKAction Completion Handlers; usage in Swift的可能重复 【参考方案1】:您可以在 run 方法中使用完成块很容易地做到这一点。
就本示例而言,假设您有一个名为 someSpriteNode 的 SKSpriteNode
并想知道两个操作(在这种情况下为 applyImpulse)何时完成运行:
// 1) Create your actions:
let action1 = SKAction.applyImpulse(CGVector(dx: 1.0, dy: 0.0), duration: 2.0)
let action2 = SKAction.applyImpulse(CGVector(dx: 6.0, dy: 2.0), duration: 1.0)
// 2) Add them to a sequence:
let actionSequence = SKAction.sequence([action1, action2])
// 3) Run the sequence using a completion block:
someSpriteNode?.run(actionSequence, completion:
// All your actions are now finished
// Do whatever you want here :)
)
更新:在执行一组操作时收到通知,所有操作都在同一个节点上运行
那么您可能正在寻找行动组:
// Declare an empty array that will store all your actions:
var actions = [SKAction]()
// Iterate through your nodes:
for _ in 0..<6
// ...
// Generate your random scale, delay, or whatever you need:
let randomScale = CGFloat(GKRandomDistribution(lowestValue: 0, highestValue: 10).nextInt())
// Create your custom action
let scaleAction = SKAction.scale(by: randomScale, duration: 2.0)
// Append your action to the actions array:
actions.append(scaleAction)
// Create an action group using the actions array:
let actionGroup = SKAction.group(actions)
// Run your action group, and do whatever you need inside the completion block:
self.run(actionGroup, completion:
// All your actions are now finished, no matter what node they were ran on.
)
另外,我建议你在游戏中使用GameplayKit
来生成随机数,它肯定会让你的生活更轻松:)
更新 2:在所有操作都在不同节点上运行的情况下,在所有操作都执行时收到通知
使用DispatchGroup
:
// Create a DispatchGroup:
let dispatchGroup = DispatchGroup()
for _ in 0..<6
// ...
let randomWait = Double(GKRandomDistribution(lowestValue: 1, highestValue: 12).nextInt())
let waitAction = SKAction.wait(forDuration: randomWait)
let fadeOutAction = SKAction.fadeOut(withDuration: 2.0)
let fadeInAction = SKAction.fadeIn(withDuration: 2.0)
let sequenceAction = SKAction.sequence([waitAction, fadeOutAction, fadeInAction])
// Enter the DispatchGroup
dispatchGroup.enter()
colorSquares[i].run(sequenceAction, completion:
// Leave the DispatchGroup
dispatchGroup.leave()
)
// Get notified when all your actions left the DispatchGroup:
dispatchGroup.notify(queue: DispatchQueue.main, execute:
// When this block is executed, all your actions are now finished
)
我认为这个解决方案比计数器更优雅:)
【讨论】:
实际上,我每个节点都有一个动作。所以我有 n 个节点,n 个动作。我没有针对单个节点的 n 个操作。 我刚刚为您更新了我的答案,如果您有任何问题,请告诉我! 我用另一种可能的解决方案更新了我的答案,DispatchGroup 对我来说看起来更优雅。我认为这将是解决您的问题的最佳方法! 感谢您的解决方案。我正在测试类似的东西。这是工作错误,我认为这并不理想。我也尝试过使用计数器,它也可以工作,但是使用大量代码我认为它会很混乱。感谢您的2个解决方案。至少我可以做我需要的。 我刚刚实施了您的 DispatchGroup 建议,效果很好 - 谢谢。【参考方案2】:首先,您应该始终创建一个Minimum Verifiable Example。从您的问题中删除不需要的内容,并确保包含我们测试您的代码所需的所有内容。
前提
我假设你有一个类似的 Tile
类
class Tile
var sprite: SKSpriteNode?
还有这样的数组
let tiles:[Tile] = ...
你的目标
-
您希望对瓷砖中每个瓷砖的精灵元素运行一个随机持续时间的动作。
您希望操作同时开始
您希望能够在所有操作完成后运行一些代码
解决方案
// 0. create a maxDuration variable
var maxDuration:TimeInterval = 0
// 1. create all the actions
let actions = tiles.map tile in
return SKAction.run
let randomNum = arc4random_uniform(100)
let randomTime = TimeInterval(randomNum / 10)
let wait = SKAction.wait(forDuration: randomTime)
let scale = SKAction.scale(by: 1, duration: 2)
tile.sprite?.run(scale)
maxDuration = max(maxDuration, randomTime + 2)
// 2. create a wait action for the max duration
let wait = SKAction.wait(forDuration: maxDuration)
// 3. write inside this action the code to be executed after all the actions
let completion = SKAction.run
print("now all the actions are completed")
// 4. create a sequence of wait + completion
let sequence = SKAction.sequence([wait, completion])
// 5. create a group to run in parallel actions + sequence
let group = SKAction.group(actions + [sequence])
// 6. run the group on the node you prefer (it doesn't really matter which node since every inner action is tied to a specific node)
self.run(group)
更新(由@Alex 建议)
var maxDuration:TimeInterval = 0
tiles.forEach tile in
let randomNum = arc4random_uniform(100)
let randomTime = TimeInterval(randomNum / 10)
let wait = SKAction.wait(forDuration: randomTime)
let scale = SKAction.scale(by: 1, duration: 2)
tile.sprite?.run(scale)
maxDuration = max(maxDuration, randomTime + 2)
run(.wait(forDuration: maxDuration))
print("now all the actions are completed")
【讨论】:
好吧,尽管这样做,与从一开始就等待最长持续时间相同,它实际上并没有等待所有操作完成,而是等待最长的一个完成。它更像是一种硬编码的解决方案。不过,我使用 DispatchGroup 的方法完美地解决了这个问题,因为当所有操作都被执行时,你会得到真正的通知。当然,这两种方法都可以(甚至是使用计数器的方法),但毕竟,这都是关于偏好/风格的问题 @Alex 好点!我根据您的建议更新了答案,现在代码更加清晰。关于DispatchGroup
通常将 Grande Central Dispatch 和 SpriteKit 一起使用并不是一个好习惯。
太棒了!关于 DispatchGroup,我从来没有听说这会是一个不好的做法,我也使用过几次,但没有遇到任何相关的问题。您能否进一步解释一下它会在哪里导致问题?
@Alex 非常有趣。特别是您linked 的第一个答案确实解释了如何使用update
方法正确地从后台线程移动到SpriteKit Run Loop。好的,你的回答得到了我的支持 ;-)
让我们continue this discussion in chat.以上是关于多个 SKSpriteNode 的 SpriteKit 多个 SKActions - 等待全部完成的主要内容,如果未能解决你的问题,请参考以下文章
SpriteKit:向场景中添加多个 SKSpriteNode