使用 SpriteKit 在 Swift 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)

应该是:

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

第一个建议 - 不要在SpriteKit中使用NSTimer(又名计时器(。它不与游戏循环配对,可能会在不同情况下引起不同的问题。阅读更多 这里 (答案由LearnCocos2D发布(

因此,请执行此操作:

 let wait = SKAction.wait(forDuration: 1)
 run(wait, completion: {
     [unowned self] in
      self.fireBullet()
 })

我注意到的是,如果我在模拟器中运行您的代码,我会得到您描述的行为。 didBegin(contact:)是随机发射的。尽管如此,对我来说,这并没有发生在设备上,设备测试才是最重要的。

现在,当我删除Timer并对一切正常SKAction(s)做同样的事情时,意味着每次都会检测到接触。

您是否尝试过添加

.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
如果您

执行以下操作,SpriteKit 物理引擎将正确计算碰撞:

1( 将子弹物理体的"使用精确碰撞检测"属性设置为 true。这将更改此正文的碰撞检测算法。您可以在此处的"使用碰撞和接触"一章中找到有关此属性的更多信息。

2( 使用应用脉冲或应用力方法移动子弹。如果您通过手动更改身体的位置来移动身体,碰撞检测将无法正确唤醒。你可以在这里找到更多信息,"让物理物体运动"一章。

最新更新