Swift 3中使用SpriteKit时的不一致接触检测

3
我在使用SpriteKit的Swift 3时遇到了关于接触检测的问题。接触检测有时可以正常工作,但有时则不能。这似乎是完全随机的。
我有一个黄色的“子弹”,它向上移动以击中名为targetSprite的红色精灵。期望的行为是当子弹击中目标时将其移除,但有时它只是从下面穿过。
我发现很多关于接触检测根本无法工作的问题,但我没有找到任何处理不一致检测的内容。
我该怎么办才能解决这个问题?
以下是代码:
import SpriteKit
import GameplayKit

enum PhysicsCategory:UInt32 {
    case bullet = 1
    case sprite1 = 2
    case targetSprite = 4
    // each new value should double the previous
}

class GameScene: SKScene, SKPhysicsContactDelegate {

// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true

// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil

// set up bullet removal:
var bulletShouldBeRemoved = false


let bulletMask = PhysicsCategory.bullet.rawValue


override func didMove(to view: SKView) {

    // Physics
    targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
    targetSprite.physicsBody?.affectedByGravity = false

    bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
    bullet.physicsBody?.affectedByGravity = false


    // Contact Detection:
    targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue

    targetSprite.physicsBody?.contactTestBitMask =
        //PhysicsCategory.sprite1.rawValue |
        PhysicsCategory.bullet.rawValue

    targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection


    // bullet physics
    bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue

    bullet.physicsBody?.contactTestBitMask =
        PhysicsCategory.targetSprite.rawValue

    bullet.physicsBody?.collisionBitMask = 0 // no collision detection


    // execute once:
    fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
                                           target: self,
                                           selector: #selector(self.fireBullet),
                                           userInfo: nil,
                                           repeats: false)

    // Add sprites to the scene:
    self.addChild(sprite1)
    self.addChild(bullet)
    self.addChild(targetSprite)

    // Positioning
    targetSprite.position = CGPoint(x:0, y:300)
    // Note: bullet and sprite1 are at 0,0 by default

    // Delegate
    self.physicsWorld.contactDelegate = self

}

func didBegin(_ contact: SKPhysicsContact) {

    print("didBegin(contact:))")

    //let firstBody:SKPhysicsBody
   // let otherBody:SKPhysicsBody

    // Use 'bitwise and' to see if both bits are 1:
    if contact.bodyA.categoryBitMask & bulletMask > 0 {

        //firstBody = contact.bodyA
        //otherBody = contact.bodyB
        print("if contact.bodyA....")
        bulletShouldBeRemoved = true
    }
    else {
        //firstBody = contact.bodyB
        //otherBody = contact.bodyA
        print("else - if not contacted?")
    }

    /*
    // Find the type of contact:
    switch otherBody.categoryBitMask {
        case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
        case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
        case PhysicsCategory.bullet.rawValue: print(" bullet hit")

        default: print(" Contact with no game logic")
    }
    */


} // end didBegin()


func didEnd(_ contact: SKPhysicsContact) {
    print("didEnd()")

}

func fireBullet() {

    let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
    bullet.run(fireBulletAction)

}

func showBullet() {

    // Toggle to display or not, every 1 second:
    if isShowingBullet == true {
        // remove (hide) it:
        bullet.removeFromParent()
        // set up the toggle for the next call:
        isShowingBullet = false
        // debug:
        print("if")

    }
    else {
        // show it again:
        self.addChild(bullet)
        // set up the toggle for the next call:
        isShowingBullet = true
        // debug:
        print("else")
    }

}

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

    if bulletShouldBeRemoved {
        bullet.removeFromParent()
    }

}

}

抱歉缩进不一致,我似乎找不到简单的方法来解决这个问题...

编辑:

我发现使用“frame”而不是“centerRect”可以使碰撞区域与精灵大小相同。例如:

    targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)

should be:

    targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
3个回答

4

第一个建议 - 不要在SpriteKit中使用NSTimer(又名Timer)。它与游戏循环不匹配,可能会在不同情况下导致不同问题。在这里阅读更多信息(由LearnCocos2D发布的答案)。

因此,做以下事情:

 let wait = SKAction.wait(forDuration: 1)

 run(wait, completion: {
     [unowned self] in
      self.fireBullet()
 })

我注意到的是,如果我在模拟器中运行你的代码,我会得到你所描述的行为。 didBegin(contact:) 会随机触发。但是,对我来说,在设备上测试才是最重要的,而这种情况在设备上并没有发生。
现在,当我移除了 Timer 并使用 SKAction(s) 做同样的事情时,一切都正常工作,也就是每次都能检测到接触。

谢谢您的建议,我一定会查看这篇文章。我对Swift还比较新手,没有很好的背景知识。 - Omnomnipotent

2

你试过添加吗?

.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true

1
嗨,sicvayne,isDynamic的默认值始终为true,所以在这种情况下,这行代码是不需要的。 - Alessandro Ornano

1

如果您按照以下步骤操作,SpriteKit物理引擎将正确计算碰撞:

1)为子弹的物理体设置"usePreciseCollisionDetection"属性为true。这将更改此物体的碰撞检测算法。您可以在这里找到有关此属性的更多信息,章节为“处理碰撞和联系”。

2)使用applyImpulse或applyForce方法移动您的子弹。如果通过手动更改其位置来移动物体,则碰撞检测将无法正常工作。您可以在这里找到更多信息,章节为“使物理体移动”。


我同意你的第一个说法,但我不能说你的第二个说法真的是正确的,即使它是一个好建议。所以我会这样说:如果您手动移动精灵(通过直接更改精灵的位置属性或间接使用操作更改位置),并且它们不受重力或任何其他力的影响,则接触检测将正常工作。这是在SpriteKit中使用物理引擎(部分)与SKActions结合使用的一种被认可的方式。 - Whirlwind
1
如果你手动移动物体(例如使用SKAction) - 你的物体可能会穿过另一个小物体或在某些碰撞上没有反应,因为物理引擎计算速率与重新绘制帧速率不同。这就是为什么你需要使用冲量和力来移动所有物理对象。 - Petr Lazarev
1
你在哪里看到这个关于绘制速率的文档?因为如果我没记错的话,当节点移动得太快时,无论是通过操作还是力量,即使使用精确的碰撞检测,接触点可能最终无法被检测到。 - Whirlwind
1
从我所看到的情况来看,如果节点移动得太快,无论是通过操作还是力量移动它们,它们都会穿过其他节点。只有速度才是最重要的。我的意思是,你说的那种行为可能很好,但我没有注意到这种行为... - Whirlwind
1
好的,但是正如我之前提到的,在实践中(你可以自己尝试),即使你通过力移动节点,usesPreciseCollisionDetection也无法解决问题。所以我的观点是,无论如何,对于(超级)快速移动的对象,行为都将是相同的。而对于适当快速移动的对象,SKAction实际上会起作用。不过,这是我从过去的测试经验中得出的结论。我最近没有进行类似的测试,所以可能在此期间发生了一些变化。 - Whirlwind
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接