swift - 多次调用 SpriteKit 碰撞
问题描述
我是 SpriteKit 的新手,正在通过制作一个飞扬的小鸟风格游戏来学习。
我正在使用其 PhyshicsBody 绑定到纹理的鸟节点。
问题是,有时当鸟击中得分节点时,它会记录多次碰撞。我添加了一个 if 以在第一次调用该函数时删除节点,但我仍在注册多个冲突。
有了那个 if 语句,我会认为这会阻止这个问题,为什么这仍然会发生?
感兴趣的功能:
func didBegin(_ contact: SKPhysicsContact) {
if moving.speed > 0 {
if ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory {
// Bird has contact with score entity
if(contact.bodyB.node != nil && contact.bodyA.node != nil){
score += 1
scoreLabelNode.text = String(score)
if (contact.bodyA.node?.name == "scoreArea") {
contact.bodyA.node?.removeFromParent()
} else if contact.bodyB.node?.name == "scoreArea" {
contact.bodyB.node?.removeFromParent()
}
// Add a little visual feedback for the score increment
scoreLabelNode.run(SKAction.sequence([SKAction.scale(to: 1.5, duration:TimeInterval(0.1)), SKAction.scale(to: 1.0, duration:TimeInterval(0.1))]))
}
} }
完整代码:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate{
let verticalPipeGap = 250.0
var bird:SKSpriteNode!
var skyColor:SKColor!
var pipeTextureUp:SKTexture!
var pipeTextureDown:SKTexture!
var movePipesAndRemove:SKAction!
var moving:SKNode!
var pipes:SKNode!
var canRestart = Bool()
var scoreLabelNode:SKLabelNode!
var score = NSInteger()
var contactState = false
let birdCategory: UInt32 = 1 << 0
let worldCategory: UInt32 = 1 << 1
let pipeCategory: UInt32 = 1 << 2
let scoreCategory: UInt32 = 1 << 3
override func didMove(to view: SKView) {
canRestart = true
// setup physics
self.physicsWorld.gravity = CGVector( dx: 0.0, dy: -5.0 )
self.physicsWorld.contactDelegate = self
// setup background color
skyColor = SKColor(red: 81.0/255.0, green: 192.0/255.0, blue: 201.0/255.0, alpha: 1.0)
self.backgroundColor = skyColor
moving = SKNode()
self.addChild(moving)
pipes = SKNode()
moving.addChild(pipes)
// ground
let groundTexture = SKTexture(imageNamed: "land")
groundTexture.filteringMode = .nearest // shorter form for SKTextureFilteringMode.Nearest
let moveGroundSprite = SKAction.moveBy(x: -groundTexture.size().width * 2.0, y: 0, duration: TimeInterval(0.02 * groundTexture.size().width * 2.0))
let resetGroundSprite = SKAction.moveBy(x: groundTexture.size().width * 2.0, y: 0, duration: 0.0)
let moveGroundSpritesForever = SKAction.repeatForever(SKAction.sequence([moveGroundSprite,resetGroundSprite]))
for i in 0 ..< 2 + Int(self.frame.size.width / ( groundTexture.size().width * 2 )) {
let i = CGFloat(i)
let sprite = SKSpriteNode(texture: groundTexture)
sprite.setScale(2.0)
sprite.position = CGPoint(x: i * sprite.size.width, y: sprite.size.height / 2.0)
sprite.run(moveGroundSpritesForever)
moving.addChild(sprite)
}
// skyline
let skyTexture = SKTexture(imageNamed: "sky")
skyTexture.filteringMode = .nearest
let moveSkySprite = SKAction.moveBy(x: -skyTexture.size().width * 2.0, y: 0, duration: TimeInterval(0.1 * skyTexture.size().width * 2.0))
let resetSkySprite = SKAction.moveBy(x: skyTexture.size().width * 2.0, y: 0, duration: 0.0)
let moveSkySpritesForever = SKAction.repeatForever(SKAction.sequence([moveSkySprite,resetSkySprite]))
for i in 0 ..< 2 + Int(self.frame.size.width / ( skyTexture.size().width * 2 )) {
let i = CGFloat(i)
let sprite = SKSpriteNode(texture: skyTexture)
sprite.setScale(2.0)
sprite.zPosition = -20
sprite.position = CGPoint(x: i * sprite.size.width, y: sprite.size.height / 2.0 + groundTexture.size().height * 2.0)
sprite.run(moveSkySpritesForever)
moving.addChild(sprite)
}
// create the pipes textures
pipeTextureUp = SKTexture(imageNamed: "PipeUp")
pipeTextureUp.filteringMode = .nearest
pipeTextureDown = SKTexture(imageNamed: "PipeDown")
pipeTextureDown.filteringMode = .nearest
// create the pipes movement actions
let distanceToMove = CGFloat(self.frame.size.width + 2.0 * pipeTextureUp.size().width)
let movePipes = SKAction.moveBy(x: -distanceToMove, y:0.0, duration:TimeInterval(0.01 * distanceToMove))
let removePipes = SKAction.removeFromParent()
movePipesAndRemove = SKAction.sequence([movePipes, removePipes])
// spawn the pipes
let spawn = SKAction.run(spawnPipes)
let delay = SKAction.wait(forDuration: TimeInterval(2.0))
let spawnThenDelay = SKAction.sequence([spawn, delay])
let spawnThenDelayForever = SKAction.repeatForever(spawnThenDelay)
self.run(spawnThenDelayForever)
// setup our bird
let birdTexture1 = SKTexture(imageNamed: "bird-02")
birdTexture1.filteringMode = .nearest
let birdTexture2 = SKTexture(imageNamed: "bird-02")
birdTexture2.filteringMode = .nearest
let anim = SKAction.animate(with: [birdTexture1, birdTexture2], timePerFrame: 0.2)
let flap = SKAction.repeatForever(anim)
bird = SKSpriteNode(texture: birdTexture1)
bird.position = CGPoint(x: self.frame.size.width * 0.35, y:self.frame.size.height * 0.6)
bird.run(flap)
bird.physicsBody = SKPhysicsBody(texture: bird.texture!,
size: bird.texture!.size())
bird.setScale(0.4)
bird.physicsBody?.isDynamic = true
bird.physicsBody?.allowsRotation = false
bird.physicsBody?.categoryBitMask = birdCategory
bird.physicsBody?.collisionBitMask = worldCategory | pipeCategory
bird.physicsBody?.contactTestBitMask = worldCategory | pipeCategory
self.addChild(bird)
// create the ground
let ground = SKNode()
ground.position = CGPoint(x: 0, y: groundTexture.size().height)
ground.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.frame.size.width, height: groundTexture.size().height * 2.0))
ground.physicsBody?.isDynamic = false
ground.physicsBody?.categoryBitMask = worldCategory
self.addChild(ground)
// Initialize label and create a label which holds the score
score = 0
scoreLabelNode = SKLabelNode(fontNamed:"MarkerFelt-Wide")
scoreLabelNode.position = CGPoint( x: self.frame.midX, y: 3 * self.frame.size.height / 4 )
scoreLabelNode.zPosition = 100
scoreLabelNode.text = String(score)
self.addChild(scoreLabelNode)
}
func spawnPipes() {
let pipePair = SKNode()
pipePair.position = CGPoint( x: self.frame.size.width + pipeTextureUp.size().width * 2, y: 0 )
pipePair.zPosition = -10
let height = UInt32( self.frame.size.height / 4)
let y = Double(arc4random_uniform(height) + height)
let pipeDown = SKSpriteNode(texture: pipeTextureDown)
pipeDown.setScale(2.0)
pipeDown.position = CGPoint(x: 0.0, y: y + Double(pipeDown.size.height) + verticalPipeGap)
pipeDown.physicsBody = SKPhysicsBody(rectangleOf: pipeDown.size)
pipeDown.physicsBody?.isDynamic = false
pipeDown.physicsBody?.categoryBitMask = pipeCategory
pipeDown.physicsBody?.contactTestBitMask = birdCategory
pipePair.addChild(pipeDown)
let pipeUp = SKSpriteNode(texture: pipeTextureUp)
pipeUp.setScale(2.0)
pipeUp.position = CGPoint(x: 0.0, y: y)
pipeUp.physicsBody = SKPhysicsBody(rectangleOf: pipeUp.size)
pipeUp.physicsBody?.isDynamic = false
pipeUp.physicsBody?.categoryBitMask = pipeCategory
pipeUp.physicsBody?.contactTestBitMask = birdCategory
pipePair.addChild(pipeUp)
let contactNode = SKNode()
contactNode.name = "scoreArea"
contactNode.position = CGPoint( x: pipeDown.size.width + bird.size.width / 2, y: self.frame.midY )
contactNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize( width: pipeUp.size.width, height: self.frame.size.height ))
contactNode.physicsBody?.isDynamic = false
contactNode.physicsBody?.categoryBitMask = scoreCategory
contactNode.physicsBody?.contactTestBitMask = birdCategory
pipePair.addChild(contactNode)
pipePair.run(movePipesAndRemove)
pipes.addChild(pipePair)
}
func resetScene (){
// Move bird to original position and reset velocity
bird.position = CGPoint(x: self.frame.size.width / 2.5, y: self.frame.midY)
bird.physicsBody?.velocity = CGVector( dx: 0, dy: 0 )
bird.physicsBody?.collisionBitMask = worldCategory | pipeCategory
bird.speed = 1.0
bird.zRotation = 0.0
// Remove all existing pipes
pipes.removeAllChildren()
contactState = false
// Reset _canRestart
canRestart = false
// Reset score
score = 0
scoreLabelNode.text = String(score)
// Restart animation
moving.speed = 1
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if moving.speed > 0 {
for _ in touches { // do we need all touches?
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
bird.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 120))
}
} else if canRestart {
self.resetScene()
}
}
override func update(_ currentTime: TimeInterval) {
/* Called before each frame is rendered */
let value = bird.physicsBody!.velocity.dy * ( bird.physicsBody!.velocity.dy < 0 ? 0.003 : 0.001 )
bird.zRotation = min( max(-1, value), 0.5 )
}
func didBegin(_ contact: SKPhysicsContact) {
if moving.speed > 0 {
if ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory {
// Bird has contact with score entity
if(contact.bodyB.node != nil && contact.bodyA.node != nil){
score += 1
scoreLabelNode.text = String(score)
if (contact.bodyA.node?.name == "scoreArea") {
contact.bodyA.node?.removeFromParent()
} else if contact.bodyB.node?.name == "scoreArea" {
contact.bodyB.node?.removeFromParent()
}
// Add a little visual feedback for the score increment
scoreLabelNode.run(SKAction.sequence([SKAction.scale(to: 1.5, duration:TimeInterval(0.1)), SKAction.scale(to: 1.0, duration:TimeInterval(0.1))]))
}
} else {
moving.speed = 0
bird.physicsBody?.collisionBitMask = worldCategory
bird.run( SKAction.rotate(byAngle: CGFloat(Double.pi) * CGFloat(bird.position.y) * 0.01, duration:1), completion:{self.bird.speed = 0 })
// Flash background if contact is detected
self.removeAction(forKey: "flash")
self.run(SKAction.sequence([SKAction.repeat(SKAction.sequence([SKAction.run({
self.backgroundColor = SKColor(red: 1, green: 0, blue: 0, alpha: 1.0)
}),SKAction.wait(forDuration: TimeInterval(0.05)), SKAction.run({
self.backgroundColor = self.skyColor
}), SKAction.wait(forDuration: TimeInterval(0.05))]), count:4), SKAction.run({
self.canRestart = true
})]), withKey: "flash")
}
}
}
}
解决方案
对于刚开始使用物理进行游戏开发的人们来说,这是一个常见问题。
func didBegin(_ contact: SKPhysicsContact) 每秒被调用 60 次(最佳)。所以这意味着当你的“鸟”与一个物体碰撞时,它每秒会被检测到 60 次。您需要在某处放置一个标志以确定鸟是否与物体发生碰撞,因此如果标志为真,则不再进行这些碰撞检查。
看到你自己的类中没有鸟,你可以在你的 GameScene 中放置一个变量来存储这个碰撞。
private var isDead = false
然后当对象与管道碰撞时将其设置为 true
isDead = true
最后在检查碰撞时使用它
if moving.speed > 0 && !isDead {
}
或者我更喜欢在你的 didBegin 函数的开头。这种方式可以节省您和“如果”嵌套在您的代码中
guard moving.speed > 0, !isDead else { return }
推荐阅读
- spring - 如何区分提供 404 的 Apache Tomcat 和我在 tomcat 上返回 404 的应用程序?
- r - 如何在 R 中转换超过 3 列的数据框?
- java - 即使用 VBA 编写,如何使用按钮单击执行 Java 程序
- spring-boot - /env/reset 和 /refresh 之间的区别
- javascript - 如何从事件处理程序内部设置上下文(使用新的 ReactJs 上下文 API)?
- audio - Javafx 如何设置媒体播放列表
- java - 为单个 jaxb 实例传递两个不同的类
- android - GridView Docs 代码不运行,为什么?
- python - Python 中 Tkinter 的问题
- asp.net-mvc - 在 MVC5 的 url 中保留默认控制器名称