swift spritekit how to run an action with a specified duration? - ios

I'm trying to make my player change image once it detects collision with one of my game items and changes back after 0.3 seconds however, I dont know how to set the 0.3 duration for my image change? Currently the image changes in a flash
Here's my code:
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if firstBody.node?.name == "Player" && secondBody.node?.name == "Poop" {
let sound = SKAction.playSoundFileNamed("coinsound", waitForCompletion: false)
run(sound)
score += 1
// Changes's player image to Poop once it collides with "Poop"
let poopImage = SKTexture(imageNamed: "Poop")
let action = SKAction.setTexture(poopImage)
firstBody.node?.run(action)
scoreLabel?.text = String(score)
secondBody.node?.removeFromParent()
}
}
// Changes player's image back to what it was once it starts moving
private func managePlayer(){
if canMove{
player?.move(left: moveLeft)
player?.texture = SKTexture(imageNamed:"Player")
}
}
Thank you for your help in advance

Inside didBegin(_ contact) put your actions in a sequence like this
let sequence = SKAction.sequence([firstTextureChangeAction, SKAction.wait(forDuration:0.3), secondTextureChangeAction])
firstBody.node?.run(sequence)

Related

Sprite Kit Contact Detection

Basically I have the ground, the player (raymond), and coins.
When the player touches the ground nothing should happen, game continues as normal. When the player comes in contact with the coin, I want it to print to console "coin contact with player".
enum ColliderType: UInt32 {
case Raymond = 1
case Object = 2
case Coin = 3
}
Raymonds physics
raymond.physicsBody = SKPhysicsBody(circleOfRadius: raymondTexture.size().height/2)
raymond.physicsBody!.dynamic = true
raymond.physicsBody!.categoryBitMask = ColliderType.Raymond.rawValue
raymond.physicsBody?.contactTestBitMask = ColliderType.Object.rawValue
raymond.physicsBody?.collisionBitMask = ColliderType.Object.rawValue
Coins Physics
coin.physicsBody = SKPhysicsBody(circleOfRadius: raymondTexture.size().height/2)
coin.physicsBody!.dynamic = true
coin.physicsBody!.categoryBitMask = ColliderType.Coin.rawValue
coin.physicsBody?.contactTestBitMask = ColliderType.Raymond.rawValue
coin.physicsBody?.collisionBitMask = ColliderType.Object.rawValue
Ground Physics if you need
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, 1))
ground.physicsBody!.dynamic = false
ground.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
ground.physicsBody?.contactTestBitMask = ColliderType.Object.rawValue
ground.physicsBody?.collisionBitMask = ColliderType.Object.rawValue
Heres the contact function I have, I know its wrong and I need help with how to detect the coin and raymond touching.
func didBeginContact(contact: SKPhysicsContact) {
print("coin contact with player")
}
Thanks in advance.
There really is several ways of checking this, Here are 2 of the most basics ways to get you started. The first checks name for contact, and the second checks the CategoryBitMask. It is worth noting that if your PhysicsBodies bitmasks are not set properly contact may never be reported between 2 objects.
Edit Make sure the Scene conforms to SKPhysicsContactDelegate
class GameScene: SKScene, SKPhysicsContactDelegate
...
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "raymond") || (contactBName == "raymond") {
if (contactAName == "coin") || (contactBName == "coin") {
print("coin contact with player")
return
}
}
//or
if contact.bodyA.categoryBitMask == ColliderType.Coin || contact.bodyB.categoryBitMask == ColliderType.Coin {
if contact.bodyA.categoryBitMask == ColliderType.Raymond || contact.bodyB.categoryBitMask == ColliderType.Raymond {
print("coin contact with player")
return
}
}
}

iOS Swift SpriteKit didEndContact not working properly

in my app(game) i need to detect contact with water and run action(withKey) while my Node is under water,im doing that with didBeginContact and Update method. But when i move my Node(with touches) from water im using didEndContact to detect whether Node is outof water and stop previous action(withKey) and it could be all fine but sometimes didEndContact not stoping action and do not detect (using print("EndContact")) that contact is ended.
Cheking contact between to nodes:
func didBeginContact(contact: SKPhysicsContact)
{
if let node1 = contact.bodyA.node
{
let entityNode = node1 as! Entity
entityNode.collideWith(contact.bodyB, contact: contact)
}
if let node2 = contact.bodyB.node
{
let entityNode = node2 as! Entity
entityNode.collideWith(contact.bodyA, contact: contact)
}
}
func didEndContact(contact: SKPhysicsContact)
{
if let node1 = contact.bodyA.node
{
let entityNode = node1 as! Entity
entityNode.collideEndedWith(contact.bodyB, contact: contact)
}
if let node2 = contact.bodyB.node
{
let entityNode = node2 as! Entity
entityNode.collideEndedWith(contact.bodyA, contact: contact)
}
}
Entity is subclass of SKSpriteNode, where i declare physics bitmask and so-on. I got Player class and Enemy class based on Entity. In my Player class i got functions that run some code when didBeginContact and didEndContact triggers
override func collideWith(body: SKPhysicsBody, contact: SKPhysicsContact)
{
if let enemy = body.node as? Enemy
{
contactingWater = true
self.runAction(SKAction.scaleBy(1.1, duration: 0.2), withKey: "action")
}
}
override func collideEndedWith(body: SKPhysicsBody, contact: SKPhysicsContact)
{
if let enemy = body.node as? Enemy
{
contactingWater = false
self.removeActionWithKey("action")
}
}
override func update(delta: CFTimeInterval){
}
Also i update my 'Player' on 'MainScene' in Update method. It seems to work fine but as i said sometimes didEndContact do not trigger
override func update(currentTime: NSTimeInterval)
{
if lastUpdateTime > 0
{
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
player?.update(dt)
}
I noticed that this"sometimes" occurs
when i move Node into the water and when move back remove my finger ith stil thinks that im in contact with water. Ill be glad to any sugestion, cause im very new in programing) thx

Collisions between sprites in SpriteKit

I'm making a game in XCode using SpriteKit. The game has a player and different types of projectiles that he has to avoid. When the player collides with the projectiles, the score changes and the projectile disappears. However, when two projectiles collide, they kind of bounce away.
I want to make that every time two projectiles collide, they act like nothing happened and they keep going in their original path. What should I do?
*Note: This is not the whole code, it's just what matters.
import SpriteKit
struct Physics {
static let player : UInt32 = 1
static let missileOne : UInt32 = 2
static let missileTwo : UInt32 = 3
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode(imageNamed: "p1.png")
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
player.position = CGPointMake(self.size.width/2, self.size.height/5)
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.dynamic = false
player.physicsBody?.categoryBitMask = Physics.player
player.physicsBody?.collisionBitMask = Physics.missileOne
player.physicsBody?.collisionBitMask = Physics.missileTwo
var missileOneTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("SpawnMissileOne"), userInfo: nil, repeats: true)
var missileTwoTimer = NSTimer.scheduledTimerWithTimeInterval(1.2, target: self, selector: Selector("SpawnMissileTwo"), userInfo: nil, repeats: true)
self.addChild(player)
}
//When contact happens
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == Physics.player) && (secondBody.categoryBitMask == Physics.missileOne)) {
CollisionWithMissileOne(firstBody.node as SKSpriteNode, missileOne: secondBody.node as SKSpriteNode)
} else if ((firstBody.categoryBitMask == Physics.player) && (secondBody.categoryBitMask == Physics.missileTwo)){
CollisionWithMissileTwo(firstBody.node as SKSpriteNode, missileTwo: secondBody.node as SKSpriteNode)
} else if ((firstBody.categoryBitMask == Physics.missileOne)&&(secondBody.categoryBitMask == Physics.missileTwo)) {
CollisionBetweenMissiles(firstBody.node as SKSpriteNode, missileTwo: secondBody.node as SKSpriteNode)
}
}
//For Player and MissileOne
func CollisionWithMissileOne(player: SKSpriteNode, missileOne: SKSpriteNode) {
missileOne.removeFromParent()
}
//For Player and MissileTwo
func CollisionWithMissileOne(player: SKSpriteNode, missileTwo: SKSpriteNode) {
missileTwo.removeFromParent()
}
//For MissileOne and MissileTwo
func CollisionBetweenMissiles(missileOne: SKSpriteNode, missileTwo: SKSpriteNode) {
???WHAT SHOULD I CODE HERE???
}
}
The confusion is that the collisionBitMask is used to define which physicsBodies that interacts in the physicsModel. What you really want is contactTestBitmask.
Also your Physics doesn't return proper values to use for a Bit Mask. As pure Ints they should be 1,2,4,8 etc.
Here is your code changed to something that (hopefully) works, I've commented changes wherever I've made them.
struct Physics {
static let player : UInt32 = 1
static let missileOne : UInt32 = 2
static let missileTwo : UInt32 = 4 // to work properly as bit masks
}
This change is necessary if you want to check for contact with more than one type of physicsBody.categoryBitMask. Check out the player.physicsBody?.contactTestBitMask = ... in didMoveToView:
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
// All your existing player-stuff is fine until...
// contactTest, not collision but contact, also: use bitwise OR
player.physicsBody?.contactTestBitMask = Physics.missileOne | Physics.missileTwo
self.addChild(player)
// It is not recommended to use NSTimer for SpriteKit, use SKActions instead
let missileOneWait = SKAction.waitForDuration(1)
let callSpawnOneAction = SKAction.runBlock({ self.spawnMissileOne() })
let missileOneRepeat = SKAction.repeatActionForever(SKAction.sequence([missileOneWait, callSpawnOneAction]))
runAction(missileOneRepeat)
let missileTwoWait = SKAction.waitForDuration(1.2)
let callSpawnTwoAction = SKAction.runBlock({ self.spawnMissileTwo() })
let missileTwoRepeat = SKAction.repeatActionForever(SKAction.sequence([missileTwoWait, callSpawnTwoAction]))
runAction(missileTwoRepeat)
}
Pretty much rewritten didBeginContact to something I believe reads and scales a lot better:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody = contact.bodyA
var secondBody = contact.bodyB
// Rewritten with dynamic variables
var playerNode : SKSpriteNode? {
if firstBody.categoryBitMask == Physics.player {
return firstBody.node as? SKSpriteNode
} else if secondBody.categoryBitMask == Physics.player {
return secondBody.node as? SKSpriteNode
}
return nil
}
// If you want to handle contact between missiles you need to split this
// into two different variables
var missileNode : SKSpriteNode? {
let bitmask1 = firstBody.categoryBitMask
let bitmask2 = secondBody.categoryBitMask
if bitmask1 == Physics.missileOne || bitmask1 == Physics.missileTwo {
return firstBody.node as? SKSpriteNode
} else if bitmask2 == Physics.missileOne || bitmask2 == Physics.missileTwo {
return secondBody.node as? SKSpriteNode
}
return nil
}
if playerNode != nil {
collisionBetweenPlayer(playerNode, missile: missileNode)
}
}
Then you'll only need one function for contact between missile and player:
func collisionBetweenPlayer(player: SKSpriteNode?, missile: SKSpriteNode?) {
missile?.removeFromParent()
}

When projectile hits two "monsters" the didBeginContact method crashes. I know why but i don't know how to avoid it

So I have this code from the tutorial:
func didBeginContact(contact: SKPhysicsContact) {
// 1
var firstBody: SKPhysicsBody?
var secondBody: SKPhysicsBody?
var body: SKPhysicsBody
//contact.bodyB.node!.physicsBody!.allContactedBodies().count
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
println("1 = A, 2 = B")
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
println("2 = A, 1 = B")
}
// 2
if ((firstBody!.categoryBitMask & PhysicsCategory.Monster != 0) &&
(secondBody!.categoryBitMask & PhysicsCategory.Projectile != 0)) {
for var c = 1; c <= contact.bodyB.node!.physicsBody!.allContactedBodies().count; c++ {
projectileDidCollideWithMonster(firstBody!.node as! SKSpriteNode, monster: secondBody!.node as! SKSpriteNode)
}
secondBody!.node?.removeFromParent()
}
}
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
println("Hit")
changeScore(1)
changeAmo(true)
projectile.removeFromParent()
monster.removeFromParent()
}
Then what is happening is that a projectile sometimes hit TWO monsters at once.
When this happens - didBeginContact method crashes saying that secondBody is nil.
After a thorough research I found out the reason:
when projectile collides with two other nodes at once - this method runs two times. After the first run - if gets bodyA as projectile, bodyB as a monster - passes them on to projectileDidCollideWithMonster and it removes them both. then it runs immediately again but at that moment projectile doesn't exist anymore and it crashes not able to assign it's node.
I have no idea how to overcome this:( any suggestions, please?
SOLUTION: Thanks to ideas below i did some changes.
added and array and a trigger at the top of the class:
var bodiesToBeRemoved = [SKSpriteNode]()
var shouldRemoveBodies = false
and did these modifications:
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
/* Called when collisions is detected and collided nodes are passed to it */
//score and stuff
println("Hit")
changeScore(1)
changeAmo(true)
//collect the nodes to be removed
bodiesToBeRemoved.append(projectile)
bodiesToBeRemoved.append(monster)
//change the trigger to remove collected nodes later on
shouldRemoveBodies=true
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
//check the trigger
if shouldRemoveBodies == true {
println("\(bodiesToBeRemoved.count)")
//remove collected nodes
for var i = 0; i<bodiesToBeRemoved.count; i++ {
bodiesToBeRemoved[i].removeFromParent()
}
//reset array
bodiesToBeRemoved.removeAll(keepCapacity: false)
//reset the trigger
shouldRemoveBodies = false
} else {
//do nothing:)
}
}
Instead of removing your projectile immediately, just mark it for removal (e.g. by setting a boolean flag, or adding it to some collection if it's not already in the collection). Then later, before the next physics check (e.g. at the end of this frame), go through and remove all projectiles marked for removal.
Just check it's nil or not better than check every nodes's mark.
func removeNodeFromPhysicsBody(ps: SKPhysicsBody){
if (ps.node != nil){
ps.node?.removeFromParent()
}
}
func wallDidCollideWithMeteor(wall:SKPhysicsBody, meteor:SKPhysicsBody) {
removeNodeFromPhysicsBody(wall)
removeNodeFromPhysicsBody(meteor)
}

Sprite Kit Collision error - Swift

I am new to IOS dev and am currently having some issues with sprite kit collisions in the didBeginContact method.
How do I break out of, or stop didBeginContact from running if one of the colliding physics bodies are removed.
eg:
1 bullet collides with 2 overlapping enemies. Because the bullet hits enemy one and is destroyed, the collision check running on the second enemy throws an exception because the bullet no longer exists.
I have tried checking for nil and NSNULL values with no luck.
The error code that I receive is "Thread 1: EXC_BAD_INSTRUCTION(code=EXC_I386_INVOIP,subcode=0x0)" and occurs when trying to check the category bit mask (Because the torpedo no longer exists).
CODE:
var bodyA:SKPhysicsBody = contact.bodyA
var bodyB:SKPhysicsBody = contact.bodyB
if(contact.bodyA.categoryBitMask == alienCategory && contact.bodyB.categoryBitMask == alienCategory){
return
}
if((bodyA.categoryBitMask == alienCategory) && (bodyB.categoryBitMask == photonTorpedoCategory)){
torpedoDidCollideWithAlien(bodyA.node as SKSpriteNode)
}
var currTorpedo:SKSpriteNode = contact.bodyB.node as SKSpriteNode
var currAlien:SKSpriteNode = contact.bodyA.node as SKSpriteNode
currTorpedo.removeFromParent()
currAlien.removeFromParent()
}
func torpedoDidCollideWithAlien(alien:SKSpriteNode){
aliensDestroyed++
label.text = ("Current Score: " + String(aliensDestroyed))
}
Perhaps you could delay removing the torpedo from the scene until the next scene 'update'. You could flag the torpedo as disabled/inactive so it doesn't impact your 2nd overlapping enemy. Something like this:
class YourScene: SkScene {
// Array of disabled torpedos
var disabledTorpedos = SKSpriteNode()[]
func didBeginContact(contact: SKPhysicsContact) {
var bodyA:SKPhysicsBody = contact.bodyA
var bodyB:SKPhysicsBody = contact.bodyB
if(contact.bodyA.categoryBitMask == alienCategory && contact.bodyB.categoryBitMask == alienCategory){
return
}
if((bodyA.categoryBitMask == alienCategory) && (bodyB.categoryBitMask == photonTorpedoCategory)){
torpedoDidCollideWithAlien(bodyA.node as SKSpriteNode)
}
var currTorpedo:SKSpriteNode = contact.bodyB.node as SKSpriteNode
var currAlien:SKSpriteNode = contact.bodyA.node as SKSpriteNode
// Created a new photonTorpedoDisabledCategory so the torpedo cannot hit a 2nd alien
currTorpedo.physicsBody.categoryBitMask = photonTorpedoDisabledCategory
disabledTorpedoes.append(currTorpedo)
// Hide torpedo from scene
currTorpedo.hidden = true
currAlien.removeFromParent()
}
override func update(currentTime: NSTimeInterval) {
// loop through disabledTorpedos array and removeFromParent
}
}

Resources