Emitter Collision not working? - swift

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

Related

Is there a way to apply sound effects to collisions?

It's my first project with Swift and I'm trying to learn, but I'm stuck and can't find a solution online. Basically, it's a clone of pong, and everything is going good, until now.
My problem it's that I can't understand how to make a sound effect play, once my ball hit the paddles.
Everything else with the collisions is ok, and other sound like the goal one is playing fine. Just can't figure out how i can make a sound play when the two sprites collide.
Here's what I've written:
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.node?.name == "main" && contact.bodyB.node?.name == "ball" || contact.bodyB.node?.name == "main" && contact.bodyA.node?.name == "ball" || contact.bodyA.node?.name == "enemy" && contact.bodyB.node?.name == "ball" || contact.bodyB.node?.name == "enemy" && contact.bodyA.node?.name == "ball" {
ball.run(blipPaddleSound)
}
}
It's all wrong?
I’ve already tried to break the if statement, and already tried with make one of my node run the sound effect (ball.run(‘nameofthesound’). I’ve thought maybe it could be a problem with the file, but other effect don’t play as well in that part of the code. I don’t think there is a problem with the collision, because the ball bounce on the paddle and on the wall without problem
This is the part of the code where I have put all the collision and contact mask:
ball.physicsBody?.contactTestBitMask = 1
ball.physicsBody?.collisionBitMask = 3
ball.physicsBody?.categoryBitMask = 1
main.physicsBody?.contactTestBitMask = 3
main.physicsBody?.collisionBitMask = 1
main.physicsBody?.categoryBitMask = 2
enemy.physicsBody?.contactTestBitMask = 3
enemy.physicsBody?.collisionBitMask = 1
enemy.physicsBody?.categoryBitMask = 2
Most likely you are not setting the SKPhysicsContactDelegate to the Scene
You'll also find that a single collision can fire multiple triggers within the game cycle so you may want to put a variable controlling the sound play so that it doesn't play multiple times per collision
class GameScene: SKScene, SKPhysicsContactDelegate {
private var didCollide = false
func didBegin(_ contact: SKPhysicsContact) {
let object1 = contact.bodyA.node?.name
let object2 = contact.bodyB.node?.name
if object1 == "ball" || object2 == "ball" {
if object1 == "main" || object2 == "main" || object1 == "enemy"|| object2 == "enemy" {
//check if we have just collided within the last 0.3 seconds
if !didCollide {
//set the marker to true so that the sound doesn't repeatedly fire
didCollide = true
//play the sound
ball.run(blipPaddleSound)
//wait 0.3 seconds (give the ball a chance to move away from the object)
self.run(.wait(forDuration: 0.3) {
self.didCollide = false
}
}
}
}
}
}

Collide Once then don't do it again

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

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()

Unexpected repeated collisions in SpriteKit

I'm making my first game with Swift and SpriteKit and while I've had collisions working properly for some time now, I recently noticed a huge bug. My collisions can either be the user with and enemy (spaceship against alien) user shooting an enemy (laser against alien). The latter works fine, but recently the collision detection when the ship touches an alien hasn't been working-- normally the alien should be removed from the scene when it touches the so that only a single life (1 of 3 total) is removed. However, the ship looses 3-6 lives upon touch now. Here's the code and where I'd assume the problem is:
This is one of a few contact functions that is then called in the general contact method.
func alien_ship_contact(contact:SKPhysicsContact){
var alien:SKNode? = nil
if contact.bodyA.categoryBitMask == PhysicsCategory.Alien && contact.bodyB.categoryBitMask == PhysicsCategory.Ship{
alien = contact.bodyA.node
}
else if contact.bodyB.categoryBitMask == PhysicsCategory.Alien && contact.bodyA.categoryBitMask == PhysicsCategory.Ship{
alien = contact.bodyB.node
}
else{
return
}
killOffAlien((alien)!)
aliensKilled = aliensKilled + 1
shipLives = shipLives-1
aShip.lives = aShip.lives - 1
print("ship/alien contact")
}
Here is the killOffAlien function:
func killOffAlien(alien:SKNode){
print("Kill off")
//alien.removeFromParent()
func stopMotion(){
alien.physicsBody?.categoryBitMask = 0
alien.physicsBody?.collisionBitMask = 0
alien.physicsBody?.contactTestBitMask = 0
alien.physicsBody?.dynamic = false
alien.physicsBody?.velocity = CGVector(dx:0, dy:0)
alien.removeActionForKey("facialMotion")
}
func removeAlien(){
alien.removeFromParent()
}
let stopMoving = SKAction.runBlock(stopMotion)
let fadeOut = SKAction.fadeOutWithDuration(1)
let removeFromParent = SKAction.runBlock(removeAlien)
let die = SKAction.sequence([stopMoving, fadeOut, removeFromParent])
alien.runAction(die)
}
And here is the general contact method:
func didBeginContact(contact:SKPhysicsContact){
alien_laser_contact(contact)
alien_ship_contact(contact)
....
Any help would be awesome, I would think that upon initial contact the alien has it's bitmask set off from Alien so that in itself would prevent future collisions as every alien should only be able to remove at max one life from the ship.

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
}