我将在这里为您展示一些内容,并为未来的读者展示更多内容,因此他们只需复制粘贴此代码即可获得一个可行的示例。接下来是这几件事:
1.使用SKAction创建计时器
2.暂停动作
3.暂停节点本身
4. 正如我所说,还有几件事 :)
请注意,所有这些都可以通过不同的方式完成,甚至比这更简单(当涉及到动作和节点的暂停时),但我会向您展示详细的方式,以便您选择最适合您的方式。
初始设置
我们有一个英雄节点和一个敌人节点。敌人节点将每 5 秒在屏幕顶部生成一次,并向下移动,朝玩家投毒。
正如我所说,我们将只使用SKActions,不使用NSTimer,甚至不使用update: 方法。纯粹的行动。因此,在这里,玩家将静止在屏幕底部(紫色方块),而敌人(红色方块)将如前所述向玩家移动并毒死他。
让我们看看一些代码。我们需要为所有这些工作定义常用的东西,比如设置物理类别、初始化和节点定位。我们还将设置敌人生成延迟(8 秒)和中毒持续时间(3 秒)等内容:
//Inside of a GameScene.swift
let hero = SKSpriteNode(color: .purple , size: CGSize(width: 50, height: 50))
let button = SKSpriteNode(color: .yellow, size: CGSize(width: 120, height:120))
var isGamePaused = false
let kPoisonDuration = 3.0
override func didMove(to view: SKView) {
super.didMove(to: view)
self.physicsWorld.contactDelegate = self
hero.position = CGPoint(x: frame.midX, y:-frame.size.height / 2.0 + hero.size.height)
hero.name = "hero"
hero.physicsBody = SKPhysicsBody(rectangleOf: hero.frame.size)
hero.physicsBody?.categoryBitMask = ColliderType.Hero.rawValue
hero.physicsBody?.collisionBitMask = 0
hero.physicsBody?.contactTestBitMask = ColliderType.Enemy.rawValue
hero.physicsBody?.isDynamic = false
button.position = CGPoint(x: frame.maxX - hero.size.width, y: -frame.size.height / 2.0 + hero.size.height)
button.name = "button"
addChild(button)
addChild(hero)
startSpawningEnemies()
}
还有一个名为isGamePaused 的变量,稍后我将对此进行更多评论,但正如您想象的那样,它的目的是跟踪游戏是否暂停以及当用户点击黄色大方形按钮时其值是否发生变化。
辅助方法
我为节点创建做了一些辅助方法。我觉得这对你个人来说不是必需的,因为你看起来对编程有很好的理解,但我会为了完整性和未来的读者而做。所以这是您设置节点名称或其物理类别等内容的地方......这是代码:
func getEnemy()->SKSpriteNode{
let enemy = SKSpriteNode(color: .red , size: CGSize(width: 50, height: 50))
enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.frame.size)
enemy.physicsBody?.categoryBitMask = ColliderType.Enemy.rawValue
enemy.physicsBody?.collisionBitMask = 0
enemy.physicsBody?.contactTestBitMask = ColliderType.Hero.rawValue
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.affectedByGravity = false
enemy.name = "enemy"
return enemy
}
另外,我将敌人的创建与实际生成分开。所以在这里创建意味着创建、设置和返回一个节点,该节点稍后将添加到节点树中。 Spawning 是指使用之前创建的节点将其添加到场景中,并对其运行动作(移动动作),使其可以向玩家移动:
func spawnEnemy(atPoint spawnPoint:CGPoint){
let enemy = getEnemy()
enemy.position = spawnPoint
addChild(enemy)
//moving action
let move = SKAction.move(to: hero.position, duration: 5)
enemy.run(move, withKey: "moving")
}
我认为没有必要在这里讨论产卵方法,因为它非常简单。让我们进一步了解产卵部分:
SKA 动作计时器
这是一种每 x 秒生成敌人的方法。每次我们暂停与“生成”键相关的操作时,它都会暂停。
func startSpawningEnemies(){
if action(forKey: "spawning") == nil {
let spawnPoint = CGPoint(x: frame.midX, y: frame.size.height / 2.0 - hero.size.height)
let wait = SKAction.wait(forDuration: 8)
let spawn = SKAction.run({[unowned self] in
self.spawnEnemy(atPoint: spawnPoint)
})
let sequence = SKAction.sequence([spawn,wait])
run(SKAction.repeatForever(sequence), withKey: "spawning")
}
}
节点生成后,它最终会与英雄发生碰撞(更准确地说,它会发生接触)。这就是物理引擎发挥作用的地方......
检测联系人
当敌人在移动时,它最终会到达玩家,我们将注册该联系人:
func didBegin(_ contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case ColliderType.Hero.rawValue | ColliderType.Enemy.rawValue :
if let projectile = contact.bodyA.categoryBitMask == ColliderType.Enemy.rawValue ? contact.bodyA.node : contact.bodyB.node{
projectile.removeAllActions()
projectile.removeFromParent()
addPoisionEffect(atPoint: hero.position)
}
// Handle more cases here
default : break
//Some other contact has occurred
}
}
联系人检测码借用here (from author Steve Ives).
我不会深入讨论 SpriteKit 中的联系人处理是如何工作的,因为那样我会过多地偏离主题。所以当英雄和射弹之间的接触被注册时,我们做了几件事:
1. 停止对射弹的所有动作,使其停止移动。我们可以通过直接停止移动动作来做到这一点,稍后我将向您展示如何做到这一点。
2.从父级移除弹丸,因为我们不再需要它。
3. 通过添加发射器节点添加中毒效果(我在粒子编辑器中使用 Smoke 模板制作了该效果)。
第三步的相关方法如下:
func addPoisionEffect(atPoint point:CGPoint){
if let poisonEmitter = SKEmitterNode(fileNamed: "poison"){
let wait = SKAction.wait(forDuration: kPoisonDuration)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([wait, remove])
poisonEmitter.run(sequence, withKey: "emitAndRemove")
poisonEmitter.name = "emitter"
poisonEmitter.position = point
poisonEmitter.zPosition = hero.zPosition + 1
addChild(poisonEmitter)
}
}
正如我所说,我会提到一些对您的问题并不重要但在 SpriteKit 中执行所有这些操作时至关重要的事情。 SKEmitterNode 发射完成后不会被移除。它停留在节点树中并消耗资源(以一定百分比)。这就是为什么你必须自己删除它。您可以通过定义两个items 的动作序列来做到这一点。第一个是SKAction,它等待给定的时间(直到发射完成),第二个项目是一个动作,它会在时间到来时从其父级移除发射器。
最后 - 暂停:)
负责暂停的方法称为togglePaused(),当点击黄色按钮时,它会根据isGamePaused 变量切换游戏的暂停状态:
func togglePaused(){
let newSpeed:CGFloat = isGamePaused ? 1.0 : 0.0
isGamePaused = !isGamePaused
//pause spawning action
if let spawningAction = action(forKey: "spawning"){
spawningAction.speed = newSpeed
}
//pause moving enemy action
enumerateChildNodes(withName: "enemy") {
node, stop in
if let movingAction = node.action(forKey: "moving"){
movingAction.speed = newSpeed
}
}
//pause emitters by pausing the emitter node itself
enumerateChildNodes(withName: "emitter") {
node, stop in
node.isPaused = newSpeed > 0.0 ? false : true
}
}
这里发生的事情实际上很简单:我们通过使用先前定义的键(生成)抓取它来停止生成动作,为了停止它,我们将动作的速度设置为零。要取消暂停,我们将执行相反的操作 - 将动作速度设置为 1.0。这也适用于移动动作,但是因为可以移动许多节点,所以我们枚举了场景中的所有节点。
为了向您展示不同之处,我直接暂停SKEmitterNode,因此您在 SpriteKit 中还有另一种暂停内容的方法。当节点暂停时,它的所有动作及其子节点的动作也会暂停。
剩下要提的是,我在touchesBegan 中检测到是否按下按钮,并且每次都运行togglePaused() 方法,但我认为该代码并不是真正需要的。
视频示例
为了做一个更好的例子,我记录了整件事。所以当我点击黄色按钮时,所有动作都会停止。意味着如果存在,产卵、移动和中毒效果将被冻结。通过再次点击,我将取消暂停所有内容。结果如下:
在这里你可以(清楚地?)看到当敌人击中玩家时,我会暂停整个动作,比如在击中发生后 1-1.5 秒。然后我等待大约 5 秒左右,然后我取消暂停所有内容。您可以看到发射器继续发射一两秒钟,然后消失。
请注意,当发射器未暂停时,它看起来并不是真的未暂停:),而是看起来即使发射器暂停了粒子也在发射(实际上是这样)。这是bug on iOS 9.1,我仍在此设备上使用 iOS 9.1 :) 所以在 iOS 10 中,它已修复。
结论
在 SpriteKit 中你不需要 NSTimer 来处理这种事情,因为 SKActions 就是为此而生的。如您所见,当您暂停动作时,整个事情都会停止。产卵停止,移动停止,就像你问的那样......我已经提到有一种更简单的方法可以做到这一切。即使用容器节点。因此,如果您的所有节点都在一个容器中,那么只需暂停容器节点,所有节点、操作和一切都将停止。就那么简单。但我只是想向您展示如何通过按键抓取动作、暂停节点或更改其速度...希望这会有所帮助且有意义!