Collide Once then don't do it again - swift

How do I make my SKnodes collide only once? I have an particle system that is appearing when they collide but if they collide again a bunch appears. I don't want that. Just want one then thats it. How would I go about pulling this effect off?
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 3 || bodyA.categoryBitMask == 3 && bodyB.categoryBitMask == 1 {
print("END GAME")
if let dieexplostionNode = SKEmitterNode(fileNamed: "Explosion.sks"){
dieexplostionNode.targetNode = self
dieexplostionNode.position = player.position
// add 3 actions
let wait = SKAction.wait(forDuration: 1.1)
let addExplosion = SKAction.run {
self.player.addChild(dieexplostionNode)
self.player.alpha = 0
}
let removePlayer = SKAction.run {
self.player.removeFromParent()
self.player.removeAllActions()
dieexplostionNode.removeFromParent()
}
// put them in a sequence.
let seq = SKAction.sequence([addExplosion,wait,removePlayer])
// run the sequence
self.run(seq)
}

Your issue is multiple contacts can happen, so you need to place a check in your code to tell your system you already evaluate a contact and do not want to do any other evaluation. My preferred method is to reserve bit 31 on the category bit mask flag, and if that is set, do not evaulate.
guard bodyA.categoryBitMask < 1 << 31 && bodyB.categoryBitMask < 1 << 31 else {return}
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 3 || bodyA.categoryBitMask == 3 && bodyB.categoryBitMask == 1 {
print("END GAME")
player.categoryBitMask += 1<<31
if let dieexplostionNode = SKEmitterNode(fileNamed: "Explosion.sks"){
dieexplostionNode.targetNode = self
dieexplostionNode.position = player.position
// add 3 actions
let wait = SKAction.wait(forDuration: 1.1)
let addExplosion = SKAction.run {
self.player.addChild(dieexplostionNode)
self.player.alpha = 0
}
let removePlayer = SKAction.run {
self.player.removeFromParent()
self.player.removeAllActions()
dieexplostionNode.removeFromParent()
}
// put them in a sequence.
let seq = SKAction.sequence([addExplosion,wait,removePlayer])
// run the sequence
self.run(seq)
}

Related

iOS Sprites Contact Detection

I'm working on a platform game where the character can climb ladders. Since the total length of each ladder varies, I have several ladders stacked on top of each other in SceneEditor. Upon entering a ladder, the contact delegate works fine and my code allows the character to move up the ladder. The problem I'm having is that as soon as the character moves off the first ladder segment, the didEnd method fires even though the character has entered the next ladder segment. I've gotten around it by given the ladder segments different category masks. Is this the only way to do it?
try overlapping the ladders by 1 rung, and then set a count whenever didBegin contact is fired increase the value by 1 whenever didEnd is called decrease the value by 1. at the end of the didEnd func check if onladderCount == 0. if it is, trigger whatever code is supposed to fire when the player is not on a ladder.
this assumes that you have a ladder class, and will have to put a property in the ladder class for onLadder to ensure that the += 1 isn't called multiple times.
var onladderCount: Int = 0
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if !ladder.onLadder {
ladder.onLadder = true
onladderCount += 1
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if ladder.onLadder {
ladder.onLadder = false
onladderCount -= 1
}
}
if onladderCount == 0 {
//trigger whatever code happens when they leave a ladder
}
}

FPS issues with adding a blur using SKEffectNode

If there a better way of creating a blur effect? It seems like the way I am currently doing it creates FPS issues especially on older phones. It seems like the higher the blurAmount the lower the FPS. Could the blendMode be the reason here?
if effectsNode.parent == nil {
let filter = CIFilter(name: "CIGaussianBlur")
let blurAmount = 15.0
filter!.setValue(blurAmount, forKey: kCIInputRadiusKey)
effectsNode.filter = filter
effectsNode.blendMode = .add
sceneContent.removeFromParent()
effectsNode.addChild(sceneContent)
addChild(effectsNode)
}
When I pause my game, I call blurScreen() which does the following code above. However, it seems like my fps drops over time the longer the game is paused. I tried taking blurScreen() out and the FPS issues went away. How is the FPS dropping over time when blurScreen() is only called once?
EDIT:
func pauseGame() {
sceneContent.isPaused = true
intermission = true
physicsWorld.speed = 0
blurScreen()
}
Here is the code in touchesEnded()
// Tapped pause or pause menu options
if name == "pause" && touch.tapCount == 1 && pauseSprite.alpha == 1.0 && ((!sceneContent.isPaused && !GameData.shared.midNewDay) || (!sceneContent.isPaused && sceneElements[0].editingMode)) {
SKTAudio.sharedInstance.pauseBackgroundMusic()
SKTAudio.sharedInstance.playSoundEffect("Sounds/pause.wav")
pauseSprite.run(SKAction.sequence([SKAction.scale(to: 1.2, duration: 0.10), SKAction.scale(to: 1.0, duration: 0.10)])) { [unowned self] in
self.createPauseMenu()
self.pauseGame()
}
return
}
Update method
override func update(_ currentTime: TimeInterval) {
if GameData.shared.firstTimePlaying && GameData.shared.distanceMoved > 600 && !step1Complete {
tutorial2()
}
// Check for game over
if GameData.shared.hearts == 0 && !gameEnded {
gameOver()
}
// If we're in intermission, do nothing
if intermission || sceneContent.isPaused {
return
}
// some more stuff unrelated to pausing
}
You are running an effect node on the entire scene, that scene is going to be rendering that effect every frame which is going to put a lot of work on your system. If you do not have any animations going on behind it, I would recommend
converting your effect node to a sprite node by doing this
var spriteScene : SKSpriteNode!
func blurScreen() {
DispatchQueue.global(qos: .background).async {
[weak self] in
guard let strongSelf = self else { return }
let effectsNode = SKEffectNode()
let filter = CIFilter(name: "CIGaussianBlur")
let blurAmount = 10.0
filter!.setValue(blurAmount, forKey: kCIInputRadiusKey)
effectsNode.filter = filter
effectsNode.blendMode = .add
strongSelf.sceneContent.removeFromParent()
effectsNode.addChild(strongSelf.sceneContent)
let texture = self.view!.texture(from: effectsNode)
strongSelf.spriteScene = SKSpriteNode(texture: texture)
strongSelf.spriteScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
DispatchQueue.main.async {
strongSelf.sceneContent.removeFromParent()
strongSelf.addChild(strongSelf.spriteScene)
}
}
}

Emitter Collision not working?

I have a question about the collision I am making for my emitter to spawn. When the collision happens, the app crashes. but from the research I've been doing, I don't see what's wrong.
Collision:
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 3 || bodyA.categoryBitMask == 3 && bodyB.categoryBitMask == 1{
print("END GAME")
let dieexplostionNode = SKEmitterNode(fileNamed: "Explode")
player.addChild(dieexplostionNode!)
}
Am I missing something for the emitter to spawn? Every time it collides it crashes.
Based on your comment on the answer above I think an Action is what you need.
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 3 || bodyA.categoryBitMask == 3 && bodyB.categoryBitMask == 1{
print("END GAME")
dieexplostionNode = SKEmitterNode(fileNamed: "Explosion.sks"){
dieexplostionNode.targetNode = self
//dieexplostionNode.position = player.position - no need for this!
// add 3 actions
let wait = SKAction.wait(forDuration: 2)
let addExplosion = SKAction.run {
self.player.addChild(dieexplostionNode!)
}
let removeExplosion = SKAction.run {
dieexplostionNode?.removeFromParent()
}
// put them in a sequence.
let seq = SKAction.sequence([addExplosion,wait,removeExplosion])
// run the sequence
self.run(seq)
}
this will add the dieexplostionNode to the player. then wait for 2 seconds. then remove the dieexplostionNode
Are you confusing SceneKit with SpriteKit?
SCN files/Abreviations are used in SceneKit
If you want to add a SpriteKit Particle Emitter file.
File -> New -> IOS/OSX -> Resource -> SpriteKit Particle File

Creating sound effects for bouncing ball in SpriteKit

My Swift game uses SpriteKit and SKPhysics to animate a small red ball that frequently collides with other physicsBodys over the course of the game. I want to create an natural sounding bounce sound effect whenever the ball collides. I currently have a rather disorderly method for doing this, and there's a significant lag noticeable while employing it that disappears when I disable sounds:
var bounceSoundPlayers = [AVAudioPlayer!]()
func buildBounceSound() {
let path = Bundle.main.path(forResource: "Bounce.mp3", ofType:nil)!
let url = URL(fileURLWithPath: path)
for _ in 0...2 {
do {
let sound = try AVAudioPlayer(contentsOf: url)
sound.prepareToPlay()
bounceSoundPlayers.append(sound)
} catch {
fatalError("couldn't load music file")
}
}
}
This creates three AVAudioPlayers with sound "Bounce.mp3" and runs the prepareToPlay() method on them. Then it stores them in an array called bounceSoundPlayers so I can use them later. Then I have a didBeginContact method that is called whenever anything collides with anything:
func didBegin(_ contact: SKPhysicsContact) {
if contact.collisionImpulse > 0.45 && defaults.bool(forKey: "SFX") {
if ((contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "permeable platform") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "permeable platform") ||
(contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "ring") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "ring"))
&&
ball.collidingWithPermeable {
playBounceSound(contact.collisionImpulse/8<=1 ? contact.collisionImpulse/8 : 1)
}
if (contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "impermeable platform") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "impermeable platform") ||
(contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "wall") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "wall") {
playBounceSound(contact.collisionImpulse/5<=1 ? contact.collisionImpulse/5 : 1)
}
}
}
Basically what this method does is it checks each collision, and if the ball collides with the right kind of platform or wall, it calls the method playBounceSound(volume: CGFloat) with a volume proportional to the force of the collision. So if the ball collides with a platform lightly, the volume is quieter than if it's a really strong collision. Here's the playBounceSound method:
var bouncePlayerIndex = Int(0)
func playBounceSound(_ volume: CGFloat) {
let previousIndex = bouncePlayerIndex - 1 >= 0 ? bouncePlayerIndex - 1 : 2
if bounceSoundPlayer[previousIndex].isPlaying {
if bounceSoundPlayer[previousIndex].currentTime > 0.1 {
bounceSoundPlayer[bouncePlayerIndex].volume = Float(volume)
bounceSoundPlayer[bouncePlayerIndex].play()
}
}
else {
bounceSoundPlayer[bouncePlayerIndex].volume = Float(volume)
bounceSoundPlayer[bouncePlayerIndex].play()
}
bouncePlayerIndex += 1
if bouncePlayerIndex > 2 {bouncePlayerIndex = 0}
}
Basically what this method does is it cycles through the three AVAudioPlayers we created earlier by using an index integer. Every time it's called, it tells the bounceSoundPlayer at bouncePlayerIndex to play() at the given volume. Then it increments bouncePlayerIndex so that next time it's called, it uses a different AVAudioPlayer. If bouncePlayerIndex > 2, it goes back to 0. Using three AVAudioPlayers allows me to play multiple bounces simultaneous — i.e. a bounce sound can begin before the last one ended, such as when the ball lands on a platform and bounces high, then lower and lower and more frequently, until it stops bouncing.
I'm sure there's a better way of doing this than with an array of AVAudioPlayers. Please help.
Swift 3
This is the best way I have found to play sounds. They can overlap and you can play as many as you want at the same time. Fast and simple.
1.) Make sure you import the framework
import AVFoundation
2.) Create the function
// my sound file is named shatter.mp3
func shatterSound() {
if let soundURL = Bundle.main.url(forResource: "shatter", withExtension: "mp3") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}
3.) Call the function where you need it with
shatterSound()

swift error in didBeginContact if running newer iPhone in simulator

this is the Code in the "didBeginContact" method:
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask
{
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000003"{
contact.bodyB.node!.removeFromParent()
}
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000004"{
contact.bodyB.node!.removeFromParent()
}
}
else
{
if contact.bodyB.node!.name == "0" && contact.bodyA.node!.name == "1000003"{
contact.bodyA.node!.removeFromParent()
}
if contact.bodyB.node!.name == "0" && contact.bodyA.node!.name == "1000004"{
contact.bodyA.node!.removeFromParent()
}
}
it works in "iPhone 4s" and "iPhone 5" simulation and fail in a higher version with this error message:
"fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)"
this is the line it marks red:
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000004"{
nice to know:
the object with the name "1000004" is a SKSpriteNode I create in the "GameScene" class: let TestPixel = SKSpriteNode(imageNamed: "6")
the property for this object are in the "touchesBegan" method.
here is the code for that:
TestPixel.name = "1000004"
TestPixel.anchorPoint = CGPointMake(0.5, 0.5)
TestPixel.userInteractionEnabled = false
TestPixel.zPosition = 1000003
TestPixel.size = CGSizeMake(1, 1)
TestPixel.position = CGPointMake(positionInScene.x, positionInScene.y)
TestPixel.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "6"), size: TestPixel.size)
TestPixel.physicsBody?.categoryBitMask = ClickOnCategory
TestPixel.physicsBody?.contactTestBitMask = BuildingsCategory
TestPixel.physicsBody?.affectedByGravity = false
TestPixel.physicsBody?.dynamic = true
TestPixel.physicsBody?.collisionBitMask = 0
addChild(TestPixel)
the error occur if this SpriteNode(name:"1000004") hit another SpriteNode which I create in the "didMoveToView" method, here is the code:
func addBG(){
let background = SKSpriteNode(imageNamed: "0")
background.name = "0"
background.anchorPoint = CGPointMake(0.5, 0.5)
background.zPosition = 1
background.position = CGPointMake(CGRectGetMinX(self.frame)+self.frame.width/4,CGRectGetMaxY(self.frame)-self.frame.height/4)
background.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "0"), size: background.size)
background.physicsBody?.categoryBitMask = BuildingsCategory
background.physicsBody?.affectedByGravity = false
background.physicsBody?.dynamic = true
background.physicsBody?.collisionBitMask = 0
self.addChild(background)
}
addBG()
and in the "didMoveToView" also is:
physicsWorld.contactDelegate = self
the categoryBitMask in the "GameScene" class is set like this:
class GameScene: SKScene, SKPhysicsContactDelegate{
let BuildingsCategory : UInt32 = 0x1 << 1
let ClickOnCategory : UInt32 = 0x1 << 2
yeah ok as I said it works in simulator with "iPhone 4s" && "iPhone 5" but not in higher versions... any ideas why?
thanks
Yes, I can tell you why:
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000003"{
contact.bodyB.node!.removeFromParent() <----PROBLEM
}
if contact.bodyA.node!.name == "0" && contact.bodyB.node!.name == "1000004"{
contact.bodyB.node!.removeFromParent()
}
What is happening, is you are hitting your 1st if statement, which does the remove from parent, then you are trying to access it again after. Well if you remove from parent, what is maintaining the node in memory? Probably nothing, so at that first line, it nils out the node. Your code then continues into another if condition, and you are now trying to unwrap a nil. This is where you would use ELSE IF instead of if.
Something else I strongly recommend doing with sprite kit is to never remove nodes during the contact phase. You could have multiple contacts happening, and you may need to handle all of these contact situations, so it is best to keep the node alive.
Instead, whenever you want to remove nodes, place them into a queue (array or set), then on your didFinishUpdate method, loop through the queue and remove all the nodes.
Another trick I do, is I reserve bit 31 on the categoryBitMask to determine if a sprite is alive or dead, then on the didContact methods, I place a guard against it.
E.G.
func didBeginContact(contact: SKPhysicsContact!)
{
guard let bodies = (a:contact.bodyA,b:contact.bodyB) where a.categoryBitMask < 1 << 31 && b.categoryBitMask < 1 << 31
else
{
return
}
// do other work here
}
Seift 3.0
func didBeginContact(contact: SKPhysicsContact!)
{
guard let bodies = (a:contact.bodyA,b:contact.bodyB, a.categoryBitMask < 1 << 31 && b.categoryBitMask < 1 << 31
else
{
return
}
// do other work here
}