ios - didBegin 函数在 IOS 11.3 上不能正常工作

标签 ios swift sprite-kit

我使用 swift 开发了一款游戏,在我将系统更新到适用于 iPhone 的 IOS 11.3 之前它运行良好。在我的游戏中,当子弹接触到敌人时,两个 SKSpriteNode 会立即被移除,变量“gameScore”会按预期加 1。但是现在,每次子弹与敌人接触时,“gameScore”都会加上远大于 1 的数字(取决于 SKSpriteNode 的速度)。

因此,我猜测在移除SKSpriteNode后didBegin函数仍然被调用。 didBegin 函数似乎有时间延迟。每个人都遇到同样的问题吗?

    func didBegin(_ contact: SKPhysicsContact) {

    var body1 = SKPhysicsBody()
    var body2 = SKPhysicsBody()

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        body1 = contact.bodyA
        body2 = contact.bodyB
    } else {
        body1 = contact.bodyB
        body2 = contact.bodyA
    }

    if body1.categoryBitMask == physicscategories.Player && body2.categoryBitMask == physicscategories.Bullet {

        gameScore += 1
        gameLabel1.text = "SCORE: \(gameScore)"
        body1.node?.removeFromParent()
        body2.node?.removeFromParent()

    }
}  

最佳答案

我找不到我的答案来彻底解释这一点,我认为它在文档中,所以如果有人找到重复项,请随时将其标记为副本。

首先,让我们尝试了解联系人的工作原理。

在物理阶段,会为您的节点创建一个池,其中列出了它接触到的所有接触点。该池将保留您的所有节点。

例如

let pool : [SKPhysicsContact] = [{node1.side1,node2.side1},{node1.side1,node2.side2}]

然后我们遍历所有接触点并调用 didBegin 函数。

for contact in pool
{
   scene.didBegin(contact)
}

现在我们输入您提供的代码:

func didBegin(_ contact: SKPhysicsContact) {

    var body1 = SKPhysicsBody()
    var body2 = SKPhysicsBody()

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        body1 = contact.bodyA
        body2 = contact.bodyB
    } else {
        body1 = contact.bodyB
        body2 = contact.bodyA
    }

    if body1.categoryBitMask == physicscategories.Player && body2.categoryBitMask == physicscategories.Bullet {

        gameScore += 1
        gameLabel1.text = "SCORE: \(gameScore)"
        body1.node?.removeFromParent()
        body2.node?.removeFromParent()

    }
}  

如果我将您的代码内联到 for 循环中,它最终会看起来像这样:

for contact in pool
{

    var body1 = SKPhysicsBody()
    var body2 = SKPhysicsBody()

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        body1 = contact.bodyA
        body2 = contact.bodyB
    } else {
        body1 = contact.bodyB
        body2 = contact.bodyA
    }

    if body1.categoryBitMask == physicscategories.Player && body2.categoryBitMask == physicscategories.Bullet {

        gameScore += 1
        gameLabel1.text = "SCORE: \(gameScore)"
        body1.node?.removeFromParent()
        body2.node?.removeFromParent()

    }
}

现在如您所见,调用 body1.node?.removeFromParent() 不会做任何事情来阻止 for 循环发生两次。所有这一切要做的就是将 parent 设置为 nil,但是联系人和节点仍然存在,使下一个循环成功。

所以我们需要做的是一些如何防止循环再次处理。

现在有几种方法可以做到这一点:

1) 检查parent是否为nil:

func didBegin(_ contact: SKPhysicsContact) {
    guard let _ = contact.bodyA.node.parent else {return}

这行得通,但是如果 bodyA.node 在某个地方变成 nil,我们的代码就会崩溃。

2) 检查节点或父节点是否为零:

func didBegin(_ contact: SKPhysicsContact) {
    guard let _ = contact.bodyA.node?.parent else {return}

现在我们知道我们的代码是安全的。哦不,我们有 2 个不同的节点同时发生碰撞,并且该节点已从场景中移除,我如何处理两者,我只是移除了该节点?

3) 将节点移动到临时位置,检查节点是否在移除节点中,并在结束更新时清理:

let removalNode = SKNode()
func didBegin(_ contact: SKPhysicsContact) {
    var body1 : SKPhysicsBody!
    var body2 : SKPhysicsBody!

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        body1 = contact.bodyA
        body2 = contact.bodyB
    } else {
        body1 = contact.bodyB
        body2 = contact.bodyA
    }


    if body1.categoryBitMask == physicscategories.Player && 
       body2.categoryBitMask == physicscategories.Bullet && 
       body1.node?.parent != removalNode{

           gameScore += 1
           gameLabel1.text = "SCORE: \(gameScore)"
           removalNode.addChild(body1)
           removalNode.addChild(body2)
    }  
}

func didFinishUpdate(){
    removalNode.removeAllChildren()
}

现在让我们看看我们在游戏中放置了一个 token ,我们的游戏规则是如果玩家同时击中 token 和子弹,玩家得分仍会增加 10。使用当前设置,我们现在可以这样做:

let removalNode = SKNode()
func didBegin(_ contact: SKPhysicsContact) {
    var body1 : SKPhysicsBody!
    var body2 : SKPhysicsBody!

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        body1 = contact.bodyA
        body2 = contact.bodyB
    } else {
        body1 = contact.bodyB
        body2 = contact.bodyA
    }


    if body1.categoryBitMask == physicscategories.Player && 
       body2.categoryBitMask == physicscategories.Bullet && 
       body1.node?.parent != removalNode{

           gameScore += 1
           gameLabel1.text = "SCORE: \(gameScore)"
           removalNode.addChild(body1)
           removalNode.addChild(body2)
    }  
    if body1.categoryBitMask == physicscategories.Player && 
       body2.categoryBitMask == physicscategories.Token &&
       body2.node?.parent != removalNode{

           gameScore += 10
           gameLabel1.text = "SCORE: \(gameScore)"
           removalNode.addChild(body2)
    }  

}

func didFinishUpdate(){
    removalNode.removeAllChildren()
}

现在让我们看一下先击中子弹,然后击中 token 的示例。

接触->玩家命中子弹
开始了:
玩家还活着
玩家击中子弹 = true
游戏得分增加
玩家死了
子弹死了
玩家命中标记 = false
结束开始
联系->玩家命中标记
开始了:
玩家死了
玩家击中子弹 = false
玩家命中标记 = true
游戏分数增加10
结束开始

游戏结束,游戏分数增加11,这是我们游戏的规则。

但是如果游戏规则是如果玩家击中子弹和同时击中 token ,那么分数不加怎么办?您可能会想“让我们只检查玩家是否在 removalNode 中而不添加分数。那么您就错了,因为如果管道恰好是 token 然后是子弹怎么办。

contact->player hit token
开始了:
玩家还活着
玩家击中子弹 = false
玩家命中标记 = true
游戏分数增加10
结束开始
contact->player hit bullet
开始了:
玩家还活着
玩家击中子弹 = true
游戏得分增加
玩家死了
子弹死了
玩家命中标记 = false
结束开始

分数现在是 11,而它应该是 1,这违反了我们的游戏规则。

我们如何解决这个问题?

好吧,我们将评分转移到 didFinishUpdate 方法,我们使用 userData 来标记标记:

let removalNode = SKNode()
func didBegin(_ contact: SKPhysicsContact) {
    var body1 : SKPhysicsBody!
    var body2 : SKPhysicsBody!

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        body1 = contact.bodyA
        body2 = contact.bodyB
    } else {
        body1 = contact.bodyB
        body2 = contact.bodyA
    }


    if body1.categoryBitMask == physicscategories.Player && 
       body2.categoryBitMask == physicscategories.Bullet && 
       body1.node?.parent != removalNode{

           removalNode.addChild(body1)
           removalNode.addChild(body2)
    }  
    if body1.categoryBitMask == physicscategories.Player && 
       body2.categoryBitMask == physicscategories.Token &&
       body2.node.parent != removalNode
       //Make sure userData is allocated
       if body1.userData = nil {body1.userData = [String:NSObject]()}
       // If body.userData[tokenScore] is nil, default to 0 then add 10
       body1.userData[tokenScore] = (body1.userData[tokenScore] ?? 0) + 10
       removalNode.addChild(body2)
    }  

}

func didFinishUpdate(){
    if player.parent = removalNode
    {
      gameScore += 1
    }
    else
    {
      //if userdata exists and token score has a value, then add it, otherwise add 0
      gameScore += player.usedData?["tokenScore"] ?? 0
    }
    gameLabel1.text = "SCORE: \(gameScore)"

    removalNode.removeAllChildren()
}

现在我们的游戏规则规定只有当玩家不在移除节点中时才会添加 token 。

或者,如果您不想使用 userData,您始终可以检查“removalNode”子节点中有多少标记,并相应地添加分数。

关于ios - didBegin 函数在 IOS 11.3 上不能正常工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49656063/

相关文章:

swift - 等待 Swift 包中的 URLSession 完成

ios - 如何将 TableView 单元格从最新到最旧而不是从最旧到最新排序

jquery - 无法使用类设置 div 的高度

ios - 检测 Sprite 的蒙面部分(与隐藏部分相对)的触摸,swift,spritekit

iphone - pushViewController导致黑屏

ios - 有没有办法以编程方式缩放 MKMapView 而不会捕捉到其预定义的缩放级别之一?

ios - Sprite 物理在 Swift (SpriteKit) 中显示但未在视觉上显示

swift - 节点未从父级中删除(spritekit)

ios - 刚刚升级到 Xcode 8.3,现在出现错误 : "Type GameScene has no member: updateCounting"

ios - 有什么方法可以让我知道导航推送何时完成?