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

Posted

技术标签:

【中文标题】didBegin 函数在 IOS 11.3 上不能正常工作【英文标题】:didBegin function doesn't work well on IOS 11.3 【发布时间】:2018-04-04 16:31:56 【问题描述】:

我使用 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()

    
  

【问题讨论】:

这个问题已经解决了很多次了,我对它在 11.3 之前的工作有很大的怀疑,我想你只是没有注意到差异 【参考方案1】:

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

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

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

例如

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) 检查节点或父节点是否为 nil:

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()

现在让我们看看我们在游戏中放置了一个令牌,我们的游戏规则是如果玩家同时击中令牌和子弹,玩家得分仍然会增加 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()

现在让我们看一下我们先击中子弹,再击记号的例子。

contact->玩家击中子弹 确实开始了: 玩家还活着 玩家击中子弹 = true 游戏分数增加 玩家死了 子弹死了 玩家命中令牌 = false 结束开始 联系人->玩家命中令牌 确实开始了: 玩家死了 玩家击中子弹 = 假 玩家命中令牌 = true 游戏分数增加 10 结束开始

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

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

联系人->玩家命中令牌 确实开始了: 玩家还活着 玩家击中子弹 = 假 玩家命中令牌 = true 游戏分数增加 10 结束开始 联系->玩家击中子弹 确实开始了: 玩家还活着 玩家击中子弹 = 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()

现在我们的游戏规则规定只有当玩家不在removingNode中时才会添加令牌。

或者,如果您不想使用userData,您可以随时检查“removalNode”子项中有多少令牌,并相应地添加分数。

【讨论】:

谢谢老板。问题解决了,你的解释很清楚! 很好的答案,非常彻底

以上是关于didBegin 函数在 IOS 11.3 上不能正常工作的主要内容,如果未能解决你的问题,请参考以下文章

html5 appcache 在 IOS 11.3 上停止工作

didBegin 未检测到两个 SKSpriteNode 之间的接触

Ember Cordova 应用程序卡在 iOS 11.3 的加载屏幕上

Xcode 11.3 / iOS 13.3 NavigationLink 仅推送一次

防止 iOS 11.3 溢出弹跳

iOS 11.3 WKWebView:input.focus() 没有做任何事情