重置 didBeginContact() ... 或者场景认为玩家死亡时正在联系什么?

Resetting didBeginContact() ... or what the scene thinks is being contacted on Player Death?

我正在处理一个我一直在努力修复的错误,

我的游戏包含一个 Player sprite,它与平台的各个部分交互...一个标准的平台游戏。 我通过将薄物理块附加到更大的平台物理来设置我的游戏 body(白色大块)

每个细块代表:

我可以使用自定义 classes 配置打开或关闭其中的每一个。


错误描述


所以我现在正在处理一个有趣的错误。 它发生的方式是,如果我的 Player 精灵击中死区物理 body(杀死角色的区域)。

角色通过 revive() 函数复活后出现错误。 似乎 didBeginContact 被调用并相信我的角色与墙接触。即使看不到墙物理 body。

只有当我在角色复活后开始快速按下跳跃时才会出现这个错误。在按下跳转之前等待一小段时间不会出现错误。

当联系死区时,我的 GameScene class 中的 didBeginContact() 代码如下所示

@objc(didBeginContact:) func didBegin(_ contact: SKPhysicsContact) {


        let firstBody: SKPhysicsBody
        let secondBody: SKPhysicsBody

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
            firstBody = contact.bodyA
            secondBody = contact.bodyB
        } else {
            firstBody = contact.bodyB
            secondBody = contact.bodyA
        }

    //MARK:  PLAYER with DEADZOne


    if ( firstBody.categoryBitMask == BodyType.player.rawValue && secondBody.categoryBitMask == BodyType.deadZone.rawValue) {

        print("deadzone contact with Player")
        if let somePlayer:Player = firstBody.node as? Player {
            somePlayer.changeRotation(newAngle: 0)
            killPlayer(somePlayer) 
        }
    }

请注意该功能不完整...(缺少播放器 - 左墙(见下文),播放器 - 右墙)

我知道 didBeginContact 被调用是因为调试控制台发出了一条打印语句,我在错误发生时从函数的 Wall - Player 部分中编写了该语句。它打印 "contact with wall didBegin" ,当您接触左墙或右墙时打印。

//MARK:  PLAYER with PLATFORM WALL (LEFT)

if ( firstBody.categoryBitMask == BodyType.player.rawValue && secondBody.categoryBitMask == BodyType.wallLeft.rawValue){


    if let somePlayer:Player = firstBody.node as? Player,
        let theWall:Platform = secondBody.node as? Platform
    {

        if somePlayer.jumpedOccured {

            print("contact with wall didBegin")

            somePlayer.onRightWall = false
            somePlayer.onLeftWall = true


            if somePlayer.isWallJumping {
                somePlayer.stopWallJump()
                playerOnWall(somePlayer, platformNode: theWall)

            } else {
                print("contact with wall didBegin")
                playerOnWall(somePlayer, platformNode: theWall)
            }

        }
    }
}

我的玩家回到起点的方式是通过调用 killPlayer() 将他移回重生点的代码,一个在关卡开始时不可见的精灵,它调用 GameScene 中包含此代码的函数 class:

           var startBackToLocation:CGPoint = CGPoint.zero

            if (somePlayer == thePlayer) {

                if ( respawnPointPlayer1 != CGPoint.zero) {
                    startBackToLocation = respawnPointPlayer1     
                }  

            let wait:SKAction = SKAction.wait(forDuration: somePlayer.reviveTime)
            let move:SKAction = SKAction.move(to: startBackToLocation, duration:0)
            let run:SKAction = SKAction.run {
            somePlayer.playerIsDead = false;
            somePlayer.revive()
        }

        let seq:SKAction = SKAction.sequence([wait, move, wait, run])
        somePlayer.run(seq)

我可以通过简单地增加等待时间来防止错误发生,例如

let wait:SKAction = SKAction.wait(forDuration: 1)

然而,这会降低游戏的流畅性。

我不确定到底是什么问题?

如果我从一个关卡中移除所有墙体物理体,只包含表面物理体,我也可以防止错误发生...

我的猜测是它与 SpriteKit 的渲染循环有关,首先调用 didEvaluateActions... 在调用 didStimulatePhysics...

所以 didEvaluateActions 将 Player 精灵移回起点。 也许玩家在向后移动时碰到了墙?

也许 didStimulatePhysics 还没有完全赶上? 也许循环需要再次 运行 才能识别出玩家的物理 body 已经移动?

我有点迷路了,已经尝试修复了一段时间。


这是演示该错误的视频。 YouTube Link


视频第一级

该视频显示该错误并未发生在没有墙壁物理体的关卡上。


二级

然后视频显示它发生在下一层,它有墙壁物理体。

  1. 第一次死亡后不会出现错误
  2. 第二次死亡后出现错误
  3. 第三次死亡后出现错误
  4. 第四次死亡后出现错误
  5. 第五次死亡后出现错误
  6. 第六次死亡后出现错误

等等


什么没用


1.与 deadZone 接触时将 isDynamic 设置为 false。

然后在播放器中使用 revive() 将其更改为 true Class

// On Contact with DeadZone this code is executed
self.physicsBody?.isDynamic = false 

// On revive()
self.physicsBody?.isDynamic = true 

2。通过在 deadZone 接触上将其设置为 nil 来移除物理 body,然后在 revive() 上重新创建它。奇怪的是,在测试过程中,我可以直观地看到 deadZone 触点上的物理矩形被移除......然后重新出现......它仍然没有解决问题。

somePlayer.physicsBody = nil

3.尝试重置 Player 的 contactTestMask.. 通过在 deadZone 接触上将其设置为零,然后在 revive() 上将其重置为默认值。

例如在 DeadZone 触点上

somePlayer.physicsBody?.contactTestBitMask = 0

例如在 revive()

 self.physicsBody?.contactTestBitMask = BodyType.platformSurface.rawValue | BodyType.platformCeiling.rawValue | BodyType.wallRight.rawValue |  BodyType.wallLeft.rawValue | BodyType.pole.rawValue |  BodyType.portal.rawValue |  BodyType.deadZone.rawValue |  BodyType.coin.rawValue | BodyType.player.rawValue | BodyType.zipLine.rawValue | BodyType.springBoard.rawValue

4.对每个玩家使用 return 语句 - ___ 如果 playerIsDead 联系退出范围。

例如在左墙接触

if somePlayer.playerIsDead == true {return} 

5.直接设置玩家位置而不是在玩家死亡时使用 SKAction.move。

let wait:SKAction = SKAction.wait(forDuration: somePlayer.reviveTime)
let move:SKAction = SKAction.run {
    somePlayer.position = startBackToLocation
}

let run:SKAction = SKAction.run {
    somePlayer.playerIsDead = false;
    somePlayer.revive()
}

let seq:SKAction = SKAction.sequence([wait, move, wait, run])
somePlayer.run(seq)

什么降低了错误的频率


  1. 我在播放器 Class 中尝试 "reset physics function" ... 从播放器 Class. 中的 revive() 函数调用

同时还在设置 somePlayer.physicsBody = 无在死区触点上

func resetPlayerPhysicsBody() {

    let removePhysicsBody: SKAction = SKAction.run {
        self.physicsBody = nil
        self.canJumpAgain = false
    }


    let wait: SKAction = SKAction.wait(forDuration: self.reviveTime)

    let resetToDefaults: SKAction = SKAction.run {
        self.setUpPlayerPhysics() // Recreates initial physics body //
        self.canJumpAgain = true
    }

    let seq:SKAction = SKAction.sequence([removePhysicsBody, wait, resetToDefaults])
    self.run(seq)}

这实际上使错误...发生的频率低得多...它几乎消除了它...但它也有一些奇怪的副作用,比如玩家经常在生成后向右移动一点。

虽然添加了这些代码,但它在我最新的视频中只出现了 3 次...

  1. 视频中 13 秒处死亡后
  2. 在视频第 16 秒处死亡后
  3. 在视频第 1 分 13 秒处死亡后

这是该尝试的视频。 YouTube Link


有人对修复有任何想法吗?

我在猜测某种方式 "reset" 或 "flush" SpriteKit 认为正在联系的内容......但是这种情况发生的方式和原因也让我很烦恼。

所以我终于找到了解决方法... 这不是超级优雅。

如果谁有更好的解决办法。请 post 他们尽管如此,我喜欢一个不那么混乱的解决方案,也许是更有意义的东西,因为这个解决方案对我的设计方式施加了一些限制。


  1. 我在 GameScene 中使用了以下新变量 class

class GameScene: SKScene, SKPhysicsContactDelegate {
    var contactDetectionEnabled = true
    var wallContactDetectionEnabled = true
    var wallPhysicsOnNodePosition:CGPoint?

  1. 在 bug 非常顽固的某些关卡中,我创建了一个 "WallPhysicsOnNode",当玩家通过它时,它会触发墙壁物理重新开启。我把它们放在游戏中第一个可以跳墙的地方。

如果存在,则为wallPhysicsOnNodePosition赋值,否则保持为nil。


我在 didMove(to:)

中为 wallPhysicsOnNodePosition 赋值
 self.enumerateChildNodes(withName: "//*") {
            node, stop in

         if (node.name == "WallPhysicsOnNode") {
                self.wallPhysicsOnNodePosition = node.position
                node.isHidden = true
                print("assigning wallPhysicsOnNodePosition")
         }
 }

  1. 我在播放器中使用了以下新变量 class

class Player: SKSpriteNode {
    var jumpedOccured = false  // turned on via update() while isJumping //
    var fallingOccured = false // turned on via update() while isFalling //

  1. 在 GameScene 中的 didBeginContact 和 didEndContact 中...我使用以下 if 语句来防止在某些情况下使用该函数。

@objc(didBeginContact:) func didBegin(_ contact: SKPhysicsContact) {
   if contactDetectionEnabled {

@objc(didEndContact:) func didEnd(_ contact: SKPhysicsContact) {

    if contactDetectionEnabled {

  1. 在 GameScene 中的 didBeginContact 和 didEndContact 中...我用另一个 if 语句将代码的 Wall - Player contact 部分包围起来,以防止它有时被使用。

 if wallContactDetectionEnabled {

  1. 在 GameScene 中的 didBeginContact 中...在玩家死亡/与 deadZone 接触时,我使用了 defer 语句,以便在退出范围时发生以下情况。

//MARK:  PLAYER with DEADZOne


if ( firstBody.categoryBitMask == BodyType.player.rawValue && secondBody.categoryBitMask == BodyType.deadZone.rawValue){

    print("deadzone contact with Player")
    if let somePlayer:Player = firstBody.node as? Player{

        if somePlayer.playerIsDead {
            return
        }

        defer {
            self.contactDetectionEnabled = false
            self.wallContactDetectionEnabled = false
            killPlayer(somePlayer)
        }     
    }
}

  1. 我的 GameScene 在其 update() 中包含以下内容。这使得墙壁物理仅在玩家通过该节点后才打开......它位于首先可以发生跳墙的位置。

   override func update(_ currentTime: TimeInterval) {
       /* Called before each frame is rendered */

       if (transitionInProgress == false) {
         thePlayer.update()
         thePlayer2.update()

            defer {
                if thePlayer.jumpedOccured && !thePlayer.playerIsDead && thePlayer.touchOccured && thePlayer.fallingOccured && !contactDetectionEnabled {
                        contactDetectionEnabled = true
                }

                if thePlayer.jumpedOccured && !thePlayer.playerIsDead && thePlayer.touchOccured && thePlayer.fallingOccured && !wallContactDetectionEnabled {
                    if let wallOnNodePosUnwrapped = wallPhysicsOnNodePosition {
                        if thePlayer.position.x >= wallOnNodePosUnwrapped.x {
                            wallContactDetectionEnabled = true
                            print("Player passed wallPhysicsOnNodePosition ... so  \(wallContactDetectionEnabled)")
                        }
                    } else if wallPhysicsOnNodePosition == nil {
                        wallContactDetectionEnabled = true
                        print("No wallPhysicsOnNodePosition ... but wallContactDetectionEnabled is \(wallContactDetectionEnabled)")
                    }
                }
            }

还有更多内容...但您几乎可以理解...

Swifts defer { } 能力的使用确实是我解决问题的关键。

wallContactDetectionEnabled 的使用几乎没有必要......但我仍然偶尔会遇到错误,所以我添加了那部分,并在错误似乎仍然存在的情况下可选地使用 wallPhysicsOnNodePosition 来控制它。

但是,对于所有关卡,我将在第一次发生跳墙的位置添加一个 WallPhysicsOnNode。


同样,该解决方案与理想方案相差甚远...它有点乱,我可能会稍后清理它...但它工作得很好。

希望有更好的东西...但是我尝试了很多从逻辑上应该有效但没有成功的方法。直接在上面添加代码在逻辑上不应该是必要的。我很震惊我需要额外的 if 语句来封装 wall-player 联系。没有它,这个错误几乎不会发生......但由于某种原因它偶尔会发生。

希望有更优雅的东西...因为这是真实的 "duct tape solution"。但是,至少它有效。

如果有人有任何意见,请随时分享。 我的回答可能有用……但它只能防止这种情况发生。从技术上讲,它并没有深入了解正在发生的事情。

这可能会导致将来出现问题。