1

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

我的游戏包含一个与平台部分交互的玩家精灵……一个标准的平台游戏。我通过将薄物理块连接到更大的平台物理主体(大白块)来设置我的游戏

每个细块代表:

  • A面 - 绿色
  • 左墙 - 黄色
  • 右墙 - 红色

我可以配置使用自定义类打开或关闭这些。

  • 蓝色的水滴是杀死玩家的死区

在此处输入图像描述


错误描述


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

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

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

联系 deadZone 时,我的 GameScene 类中的 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) 
        }
    }

请注意该功能不完整...(缺少诸如 Player - Wall Left (见下文)、 Player - Wall Right 之类的内容)

我知道 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 类中调用包含此代码的函数:

           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 sprite 移回起点。也许玩家在移回墙壁时碰到了墙壁?

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

我有点迷路了,一直试图修复它一段时间。


这是一个演示该错误的视频。 YouTube 链接


视频中的第一级

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


第二级

然后视频显示它发生在具有墙壁物理体的下一个级别。

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

ETC


什么没有奏效


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

然后在 Player 类中使用 revive() 将其更改为 true

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

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

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

somePlayer.physicsBody = nil

3. 尝试重置玩家的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. 为每个 Player 使用 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. 我在 Player 类中尝试了一个“重置物理函数”......从 Player 类中的 revive() 函数调用。

同时仍然在DeadZone 联系人上设置somePlayer.physicsBody = nil

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 链接


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

我猜测某种方式可以“重置”或“刷新”SpriteKit 认为正在被联系的内容……但是这种情况发生的方式和原因也让我感到困扰。

4

1 回答 1

0

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

如果有人有更好的解决方案。不过,请务必发布它们,我想要一个不那么凌乱的解决方案,也许更有意义的东西,因为这个解决方案对我如何设计事物施加了一些限制。


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

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

  1. 在某些错误非常顽固的关卡中,我创建了一个“WallPhysicsOnNode”,它会在玩家通过它时触发墙物理重新打开。我将它们放置在游戏中可以跳墙的第一个实例附近。

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


在此处输入图像描述

我在 didMove(to:) 中为 wallPhysicsOnNodePosition 赋值

 self.enumerateChildNodes(withName: "//*") {
            node, stop in

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

  1. 我在 Player 类中使用了以下新变量

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)")
                    }
                }
            }

还有一点……但你几乎可以弄清楚……

使用 Swift 的defer { }能力确实是我解决方案的关键。

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

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


再次,该解决方案远非理想……它有点乱,我可能会稍后清理它……但它工作得很好。

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

希望有更优雅的东西......因为这是一个真正的“胶带解决方案”。但是,至少它有效。

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

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

于 2017-06-17T00:05:23.150 回答