Stop repeatActionForever in Sprite Kit swift - swift

I created an SKAction that repeatsForever (it's the planet spinning). But I want to stop it once the lander lands on the landing pad. So far my code is not working. I tried both removeAllActions() and removeActionForKey as you'll see. The contact detection works just fine, and there's a bunch of code not shown which includes the collision delegate, etc.
func createPlanet() {
var planet = SKSpriteNode()
planet.zPosition = 1
planet.name = "mars"
redPlanet = SKSpriteNode(imageNamed: "redPlanet")
redPlanet.name = "red"
redPlanet.zPosition = 2
redPlanet.physicsBody = SKPhysicsBody(texture: redPlanet.texture!, size: size)
redPlanet.physicsBody!.dynamic = false
redPlanet.physicsBody!.categoryBitMask = planetMask
redPlanet.physicsBody!.contactTestBitMask = 0
planet.addChild(redPlanet)
landingPad = SKSpriteNode(imageNamed: "landingPad")
landingPad.name = "pad"
landingPad.zPosition = 3
landingPad.position = CGPoint(x: 0, y: redPlanet.size.height / 2 - 60)
landingPad.physicsBody = SKPhysicsBody(rectangleOfSize: landingPad.size)
landingPad.physicsBody!.dynamic = false
landingPad.physicsBody!.categoryBitMask = landingPadMask
landingPad.physicsBody!.collisionBitMask = landerMask
landingPad.physicsBody!.contactTestBitMask = landerMask
planet.addChild(landingPad)
planet.position = CGPoint(x: frame.size.width / 2, y: -redPlanet.size.height / 6)
let spinner = SKAction.rotateByAngle(CGFloat(M_PI), duration: 3)
planet.runAction(SKAction.repeatActionForever(spinner), withKey: "planetSpin")
addChild(planet)
}
And this...
func didBeginContact(contact: SKPhysicsContact) {
if !hasLanded {
if contact.bodyA.node!.name == "lander" {
hasLanded = true
print("bodyA contact")
physicsWorld.speed = 0
removeAllActions()
removeActionForKey("planetSpin")
} else if contact.bodyB.node!.name == "lander" {
print("bodyB contact")
hasLanded = true
physicsWorld.speed = 0
removeAllActions()
removeActionForKey("planetSpin")
}
}
}

You're not removing actions from the planet node, you're removing them from the scene or parent node or whatever your didBeginContact is a member of.
Make planet a class variable and in didBeginContact, call planet.removeAllActions()

Related

Swift / SpriteKit Collision producing varying results

I have a small test project (my first using Swift / XCode) which is designed to move me away from HTML5 and Canvas for game production.
The code compiles and runs fine. I use my iPhone as the test device rather than the built in simulator.
The symptoms of the problem are
that the lasers being repeatedly fired from the player's ship appear to occasionally bend around the aliens
the names being pulled out from the nodes are being shown as their default names not the names I assigned to them at creation
In some cases the collision works fine and the alien explosion is generated and the alien sprite node is removed from the scene.
I have named the alien nodes "alien" and the laser nodes "laser".
Both have their contactTestBitMask set to the same value.
Here is my GameScene.swift code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var lastUpdateTime: TimeInterval = 0
var delta: TimeInterval = 0
var sp_player: SKSpriteNode!
var stars: SKSpriteNode!
var deeperstars: SKSpriteNode!
var laser: SKSpriteNode!
var alien: SKSpriteNode!
var explosionSplat1: SKSpriteNode!
var playerScore: UInt32!
struct PhysicsCategory {
static let base:UInt32 = 0x1 << 0
static let alien:UInt32 = 0x1 << 1
static let laser:UInt32 = 0x1 << 2
static let player:UInt32 = 0x1 << 3
}
override func didMove(to view: SKView) { // called when the scene is presented into view (happens only once)
playerScore = 0
physicsWorld.contactDelegate = self
physicsWorld.gravity = .zero
// BACKGROUND
backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 48/255, alpha: 1.0)
print("Background color is set")
// WRAP THE STARFIELDS
// Front most layer of stars
let starsTexture = SKTexture(imageNamed: "stars.png")
let bgAnimation = SKAction.move(by: CGVector(dx: 0, dy: -starsTexture.size().height), duration: 5)
let bgReset = SKAction.move(by: CGVector(dx: 0, dy: starsTexture.size().height), duration: 0)
let bgConstantMotion = SKAction.repeatForever(SKAction.sequence([bgAnimation,bgReset]))
// Back layer of slower stars
let deeperStarsTexture = SKTexture(imageNamed: "stars-deeper.png")
let deeperStarsbgAnimation = SKAction.move(by: CGVector(dx: 0, dy: -deeperStarsTexture.size().height), duration: 8)
let deeperStarsbgReset = SKAction.move(by: CGVector(dx: 0, dy: deeperStarsTexture.size().height), duration: 0)
let deeperStarsbgConstantMotion = SKAction.repeatForever(SKAction.sequence([deeperStarsbgAnimation,deeperStarsbgReset]))
var i: CGFloat = 0
while i < 3
{
stars = SKSpriteNode(texture: starsTexture)
stars.position = CGPoint(x: frame.midX, y: starsTexture.size().height * i)
stars.size.height = frame.height
stars.run(bgConstantMotion)
stars.zPosition = -1
addChild(stars)
deeperstars = SKSpriteNode(texture: deeperStarsTexture)
deeperstars.position = CGPoint(x: frame.midX, y: deeperStarsTexture.size().height * i)
deeperstars.size.height = frame.height
deeperstars.run(deeperStarsbgConstantMotion)
deeperstars.zPosition = -1
addChild(deeperstars)
i += 1
}
// PLAYER
let playerTexture1 = SKTexture(imageNamed: "player-1.png")
let playerTexture2 = SKTexture(imageNamed: "player-2.png")
let playerAnimation = SKAction.animate(with: [playerTexture1, playerTexture2], timePerFrame: 0.2)
let constantAnimation = SKAction.repeatForever(playerAnimation)
sp_player = SKSpriteNode(texture: playerTexture1)
sp_player.position = CGPoint(x: frame.midX, y: (sp_player.size.height * 2))
sp_player.physicsBody = SKPhysicsBody(rectangleOf: sp_player.size)
sp_player.physicsBody!.isDynamic = false
sp_player.name = "player"
sp_player.run(constantAnimation)
addChild(sp_player)
// PLACE ALIENS
let alienTexture1 = SKTexture(imageNamed: "alien-1a.png")
let alienTexture2 = SKTexture(imageNamed: "alien-1b.png")
let alienAnimation = SKAction.animate(with: [alienTexture1, alienTexture2], timePerFrame: 0.4)
let constantAlienAnimation = SKAction.repeatForever(alienAnimation)
var x: CGFloat = 0, y: CGFloat = 0
while y < 6
{
while x < 6
{
alien = SKSpriteNode(texture: alienTexture1)
alien.position = CGPoint(x: 32 + (x * alien.size.width), y: (frame.size.height - (alien.size.height * 1.5) - (alien.size.height * y)))
print("Setting y to \(frame.size.height - (alien.size.height * y))")
alien.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: alien.size.width, height: alien.size.height))
alien.physicsBody!.isDynamic = false
alien.name = "alien"
alien.physicsBody!.contactTestBitMask = PhysicsCategory.laser
alien.run(constantAlienAnimation)
addChild(alien)
x += 1
}
y += 1
x = 0
}
print("Sprites added to scene")
spawnLasers()
}
func spawnLasers()
{
let delay1 = SKAction.wait(forDuration: 0.5)
let spawn = SKAction.run {
let laserTexture = SKTexture(imageNamed: "laser-1.png")
self.laser = SKSpriteNode(texture: laserTexture)
self.laser.position = CGPoint(x: self.sp_player.position.x, y: self.sp_player.position.y + self.sp_player.size.height)
self.laser.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.laser.size.width, height: self.laser.size.height))
self.laser.physicsBody!.isDynamic = true
self.laser.physicsBody!.linearDamping = 0
self.laser.physicsBody!.allowsRotation = false
self.laser.physicsBody!.contactTestBitMask = PhysicsCategory.laser
self.laser.name = "laser"
self.addChild(self.laser)
let shoot = SKAction.moveTo(y: self.frame.size.height, duration: 1)
let killLaser = SKAction.removeFromParent()
let handleLaser = SKAction.sequence([shoot,killLaser])
self.laser.run(handleLaser)
}
let action = SKAction.sequence([delay1,spawn])
let constantLasers = SKAction.repeatForever(action)
self.run(constantLasers)
}
func didBegin(_ contact: SKPhysicsContact) {
var check: UInt32 = 0
if contact.bodyA.node != nil
{
check += 1
}
if contact.bodyB.node != nil
{
check += 1
}
if check == 2
{
if contact.bodyA.node!.name == "alien" && contact.bodyB.node!.name == "laser"
{
// EXPLOSION
let explosionSplatTexture1 = SKTexture(imageNamed: "explosion-1a.png")
let explosionSplatTexture2 = SKTexture(imageNamed: "explosion-1b.png")
let explosionSplatTexture3 = SKTexture(imageNamed: "explosion-1c.png")
let explosionSplatTexture4 = SKTexture(imageNamed: "explosion-1d.png")
let explosionSplatAnimation = SKAction.animate(with: [explosionSplatTexture1, explosionSplatTexture2, explosionSplatTexture3, explosionSplatTexture4], timePerFrame: 0.1)
let killExplosion = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSplatAnimation,killExplosion])
explosionSplat1 = SKSpriteNode(texture: explosionSplatTexture1)
explosionSplat1.name = "explosion"
explosionSplat1.position = CGPoint(x: contact.bodyA.node!.position.x, y: contact.bodyA.node!.position.y)
addChild(explosionSplat1)
explosionSplat1.run(explosionSequence)
self.playerScore += 1
print("Score: \(self.playerScore!)")
contact.bodyA.node?.removeFromParent()
print("Alien named \(contact.bodyA.node?.name ?? "defaultAlienName") from scene")
contact.bodyB.node?.removeFromParent()
print("Laser named \(contact.bodyB.node?.name ?? "defaultLaserName") from scene")
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
//print("Contact ended between \(contact.bodyA) and \(contact.bodyB)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
// if let touch = touches.first {
// let position = touch.location(in: view)
// storedTouch = position
// }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first {
let position = touch.location(in: view)
var playerpos: CGPoint!
playerpos = sp_player.position
let pl_move = SKAction.move(to: CGPoint(x: position.x, y: playerpos.y), duration: 0.1)
sp_player.run(pl_move)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
/*
Need to figure out how to use storedTouch properly
to move player relative to the screen touch co-ordinates
*/
// if let touch = touches.first {
// let position = touch.location(in: view)
// }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (lastUpdateTime > 0)
{
delta = currentTime - lastUpdateTime
} else {
delta = 0
}
lastUpdateTime = currentTime
}
}
When the game runs it produces this screen:
You can see the unexpected laser behavior here:
In my diags I get the following output from the collision function:
Score: 1
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 2
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 3
Alien named defaultAlienName removed from scene
Laser named laser removed from scene
Score: 4
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
Score: 5
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
Score: 6
Alien named defaultAlienName removed from scene
Laser named defaultLaserName removed from scene
This is most likely my complete lack of understanding for optionals and how collision actually works. I'd be super grateful for any insights.
In your alien loop, as well as spawnLasers(), you are not giving the sprite nodes an actual PhysicsBody category. For sprites to be able to detect contact between one another, they need a category name and a contact name.
So in your while loop (building the aliens), you need to have this:
alien.physicsBody!.categoryBitMask = PhysicsCategory.alien
alien.physicsBody!.contactTestBitMask = PhysicsCategory.laser
And in spawnLasers(), you want this added:
self.laser.physicsBody!.categoryBitMask = PhysicsCategory.laser
and change the contactTestBitMask to alien, not laser:
self.laser.physicsBody!.contactTestBitMask = PhysicsCategory.alien
Hopefully you can see that the alien category wants to know when lasers touch, and the laser category wants to know when the aliens touch.
To visually help you, turn on the show physics option, this way you can see the actual physics bodies you are dealing with.
To do this, in your GameViewController (or similar), find:
showsFPS = true
showsNodeCount = true
You want to add the following:
showsPhysics = true
This will help with seeing the actual physics bodies on screen.
In:
func didBegin(_ contact: SKPhysicsContact)
you are only testing for BodyA being alien and BodyB being laser.
This is the contact test:
if contact.bodyA.node!.name == "alien" && contact.bodyB.node!.name == "laser"
I believe BodyA could be laser and BodyB be alien. Basically the physics engine contact events could be "A hitting B", or "B hitting A".
Therefore, a quick and dirty solution is to create another if statement below the current one, but changing the body names, so:
if contact.bodyA.node!.name == "laser" && contact.bodyB.node!.name == "alien" {
and duplicate the code from your existing if statement, and changing the two print statements.
This isn't the ideal way to do it, but hopefully when you tidy it up you'll get an understanding of what the physics contact is doing.
I am hoping once you have implmented the above, you will be in a much better shape.

Node prematurely removed from scene (Swift game)

I have the following code in Swift for a simple platform game:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var me : SKSpriteNode?
var ceiling : SKSpriteNode?
var studentTimer : Timer?
var cloudTimer : Timer?
let meCategory : UInt32 = 0x1 << 1
let studentCategory : UInt32 = 0x1 << 2
let cloudCategory : UInt32 = 0x1 << 3
let groundAndCeilingCategory : UInt32 = 0x1 << 4
var numberofStudents = 0
var education = ["edu1", "edu2", "edu3", "edu4"]
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
me = childNode(withName: "me") as? SKSpriteNode
me?.physicsBody?.categoryBitMask = meCategory
me?.physicsBody?.contactTestBitMask = studentCategory
me?.physicsBody?.collisionBitMask = groundAndCeilingCategory
// make me run
var meRun : [SKTexture] = []
for number in 1...6 {
meRun.append(SKTexture(imageNamed: "Armature_Run_\(number)"))
}
me?.run(SKAction.repeatForever(SKAction.animate(with: meRun, timePerFrame: 0.1)))
ceiling = childNode(withName: "ceiling") as? SKSpriteNode
ceiling?.physicsBody?.categoryBitMask = groundAndCeilingCategory
ceiling?.physicsBody?.collisionBitMask = meCategory
startTimers()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
me?.physicsBody?.applyForce(CGVector(dx: 0, dy: 20000))
var meJump : [SKTexture] = []
for number in 0...9 {
meJump.append(SKTexture(imageNamed: "Armature_Jump_0\(number)"))
}
me?.run(SKAction.animate(with: meJump, timePerFrame: 0.1))
}
func createStudent() {
let student = SKSpriteNode(imageNamed: "student")
student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
student.physicsBody?.affectedByGravity = false
student.physicsBody?.categoryBitMask = studentCategory
student.physicsBody?.contactTestBitMask = meCategory
student.physicsBody?.collisionBitMask = 0
addChild(student)
student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height)*2))
let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
func startTimers() {
studentTimer = Timer.scheduledTimer(withTimeInterval: 4, repeats: true, block: { (timer) in
if self.numberofStudents < 4 {
self.createStudent()
}
})
cloudTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: { (timer) in
self.createCloud()
})
}
func createCloud() {
let cloud = SKSpriteNode(imageNamed: "cloud")
cloud.zPosition = -1
cloud.physicsBody = SKPhysicsBody(rectangleOf: cloud.size)
cloud.physicsBody?.affectedByGravity = false
cloud.physicsBody?.collisionBitMask = 0
cloud.physicsBody?.categoryBitMask = cloudCategory
addChild(cloud)
let maxY = size.height / 2 - cloud.size.height / 2
let minY = CGFloat(0)
let range = maxY - minY
let cloudY = maxY - CGFloat(arc4random_uniform(UInt32(range)))
cloud.position = CGPoint(x: size.width / 2 + cloud.size.width / 2, y: cloudY)
let moveLeft = SKAction.moveBy(x: -size.width - cloud.size.width, y: 0, duration: 4)
cloud.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == studentCategory {
contact.bodyA.node?.removeFromParent()
numberofStudents += 1
print(numberofStudents)
print(education[(numberofStudents)-1])
}
if contact.bodyB.categoryBitMask == studentCategory {
contact.bodyB.node?.removeFromParent()
numberofStudents += 1
print(numberofStudents)
print(education[(numberofStudents)-1])
}
}
}
I'm working on what happens when the main character ("me") collides with the "student".
Prior to adding 'physicsWorld.contactDelegate = self' the "student" showed up perfectly, moving across the screen. However, when I add this line of code and run the app, the student is invisible. I believe it is still there somewhere as the print common is still running when the character collides, but it is not visibly colliding with anything.
I removed the contact.bodyB.node?.removeFromParent() and ran it, and indeed the "student" does show again, but obviously does not disappear on contact.
I've looked over this for ages and am sure I'm missing something obvious but cannot work out what it is.
you are not supplying us with all of the information.
As it is your code works as expected.
Here is a vid of your code running (I had to use my own images)
Here is my analysis of what might be wrong.
I removed all the animation sequence items because they have no bearing on this.
I notice you are not putting zPositions...tsk tsk tsk. All items should have a zPosition.
you are not showing us how you define the physicsBody on your "me" sprite. Assumably in the editor? You should include that info next time.
I had to create the "me" character and position it in code since you don't show us where that comes from either. I also created the physicsBody in code to get it to work.
Here is the code I used
me = SKSpriteNode(imageNamed: "orange")
me.position = CGPoint(x: -400, y: (-size.height / 2) + ((me.size.height) * 2))
me.zPosition = 10
addChild(me)
me.physicsBody = SKPhysicsBody(rectangleOf: me.size)
me.physicsBody?.categoryBitMask = meCategory
me.physicsBody?.contactTestBitMask = studentCategory
me.physicsBody?.collisionBitMask = groundAndCeilingCategory
me.physicsBody?.affectedByGravity = false
func createStudent() {
let student = SKSpriteNode(imageNamed: "apple")
student.setScale(0.5)
student.position = CGPoint(x: size.width / 2 + student.size.width, y: (-size.height / 2) + ((student.size.height) * 2))
student.zPosition = 10
addChild(student)
student.physicsBody = SKPhysicsBody(rectangleOf: student.size)
student.physicsBody?.affectedByGravity = false
student.physicsBody?.categoryBitMask = studentCategory
student.physicsBody?.contactTestBitMask = meCategory
student.physicsBody?.collisionBitMask = 0
let moveLeft = SKAction.moveBy(x: -size.width - student.size.width, y: 0, duration: 4)
student.run(SKAction.sequence([moveLeft, SKAction.removeFromParent()]))
}
On a side note, it is not necessary to use timers. SpriteKit has a built in feature for this the "update" func.

Collision tests not working in SpriteKit Swift 2

I have been trying to figure out why my didBeginContact method isn't working. I have a triangle falling from the top of the screen to the bottom and I have a line at the bottom which if it collides with it. My score should increment.
Here is the code responsible for this.
func createScene(){
physicsWorld.contactDelegate = self
slider = SKSpriteNode(imageNamed: "Slider")
slider.setScale(0.20)
slider.position = CGPoint(x: self.frame.width / 2, y: 0 + slider.frame.height / 2)
slider.physicsBody = SKPhysicsBody(rectangleOfSize: slider.size)
slider.physicsBody?.categoryBitMask = PhysicsCatergory.slider
slider.physicsBody?.collisionBitMask = PhysicsCatergory.coin | PhysicsCatergory.greenTriangle | PhysicsCatergory.orangeHexagon | PhysicsCatergory.purpleOctagon | PhysicsCatergory.redSquare
slider.physicsBody?.contactTestBitMask = PhysicsCatergory.coin | PhysicsCatergory.greenTriangle | PhysicsCatergory.orangeHexagon | PhysicsCatergory.purpleOctagon | PhysicsCatergory.redSquare
slider.physicsBody?.affectedByGravity = false
slider.physicsBody?.dynamic = false
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("SpawnShapes"), userInfo: nil, repeats: true)
self.addChild(slider)
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == PhysicsCatergory.greenTriangle && secondBody.categoryBitMask == PhysicsCatergory.Score{
score++
print(score)
}
else if firstBody.categoryBitMask == PhysicsCatergory.Score && secondBody.categoryBitMask == PhysicsCatergory.greenTriangle{
score++
print(score)
}
}
override func didMoveToView(view: SKView) {
createScene()
invisibleBounderies()
}
func invisibleBounderies(){
let scoreNode = SKSpriteNode()
scoreNode.size = CGSize(width: 5, height: 600)
scoreNode.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 10)
scoreNode.physicsBody = SKPhysicsBody(rectangleOfSize: scoreNode.size)
scoreNode.physicsBody?.affectedByGravity = false
scoreNode.physicsBody?.dynamic = false
scoreNode.physicsBody?.categoryBitMask = PhysicsCatergory.Score
scoreNode.physicsBody?.collisionBitMask = PhysicsCatergory.greenTriangle
scoreNode.physicsBody?.contactTestBitMask = PhysicsCatergory.greenTriangle
scoreNode.zRotation = CGFloat(M_PI/2.0)
scoreNode.color = SKColor.blueColor()
self.addChild(scoreNode)
}
func SpawnShapes(){
greenTriangle = SKSpriteNode(imageNamed:"greenTriangle")
purpleOctagon = SKSpriteNode(imageNamed: "purpleOctagon")
redSquare = SKSpriteNode(imageNamed: "redSquare")
coin = SKSpriteNode(imageNamed: "coin")
greenTriangle.physicsBody = SKPhysicsBody(rectangleOfSize: greenTriangle.size)
greenTriangle.physicsBody?.categoryBitMask = 0
greenTriangle.physicsBody?.collisionBitMask = PhysicsCatergory.Score
greenTriangle.physicsBody?.contactTestBitMask = PhysicsCatergory.Score
greenTriangle.physicsBody?.affectedByGravity = false
greenTriangle.physicsBody?.dynamic = false
var MinValue = self.size.width / 8
var MaxValue = self.size.width - 150
var SpawnPoint = UInt32(MaxValue - MinValue)
let action = SKAction.moveToY(-30, duration: 2.0)
slider.zPosition = 1
coin.zPosition = 2
greenTriangle.zPosition = 3
orangeHexagon.zPosition = 4
purpleOctagon.zPosition = 5
redSquare.zPosition = 6
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
self.addChild(greenTriangle)
greenTriangle.runAction(SKAction.repeatActionForever(action))
}
You need to set only greenTriangle dynamic property to true.
You set greenTriangle.physicsBody?.categoryBitMask = 0, that's why you are not detecting contact. greenTriangle.physicsBody?.categoryBitMask = PhysicsCatergory.greenTriangle might do the trick.
If you are still not detecting contact after these changes, check if your physicsCatergory is alright. It would be good to implement an enum of type UInt32 where the values are all single bits, or like this, if you prefer:
enum PhysicsCatergory : UInt32{
case slider = 1
case coin = 2
case greenTriangle = 4
case purpleOctagon = 8
case redSquare = 16
case orangeHexagon = 32
case Score = 64
}
Doing like this will require to add .rawValue in every reference to one of these enums. Example: greenTriangle.physicsBody?.categoryBitMask = PhysicsCatergory.greenTriangle.rawValue
I know it is not related to the question, but I would recommend you not to use SKAction for movement in this case, because it's going to ignore physics collision, since you are "forcing" its position. Use the physicsBody.velocity property instead to let the physics work. You will notice that when using the later the block is going to collide with the score line, since you also set its collisionBitMask. This way you can properly control with that it collides. Set physicsBody.linearDamping to zero if don't want the block to lose speed while it travels (air friction).
A physicsBody with a dynamic property of false cannot be involved in collisions; set dynamic to true.

touchesBegin not being called on Sprit Node

I have SpritNodes that are in my scene and I want a method to be called when I touch it. I have isUserInteractionEnabled set to true for my node, but touchesBegan still does not get called when I touch the nodes. (Note: I am using Swift 3.0)
Code:
import SpriteKit
class MainScene: SKScene, SKPhysicsContactDelegate {
var didStart = false
var background = SKSpriteNode(imageNamed: "background")
var backDrop = SKShapeNode()
var emailNodes: [SKSpriteNode] = []
let emailCatagory: UInt32 = 0x1 << 0
let dropCatagory: UInt32 = 0x1 << 1
override func sceneDidLoad() {
startCountDown()
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
background.position = CGPoint(x: frame.midX, y:frame.midY)
}
public func startCountDown(){
var times = 4
let countdownTimer = SKLabelNode()
countdownTimer.text = "3"
countdownTimer.position = CGPoint(x: frame.midX, y: frame.midY)
countdownTimer.fontSize = 120.0
countdownTimer.fontName = "Lao MN"
countdownTimer.fontColor = UIColor.black()
backDrop = SKShapeNode()
backDrop = SKShapeNode(rectOf: CGSize(width: frame.width, height: 100))
backDrop.position = CGPoint(x: frame.midX, y: 10)
backDrop.physicsBody = SKPhysicsBody(rectangleOf: backDrop.frame.size)
//backDrop.size = CGSize(width: 1000, height: 2)
backDrop.physicsBody?.affectedByGravity = false
backDrop.physicsBody?.usesPreciseCollisionDetection = true
backDrop.name = "backDrop"
backDrop.physicsBody?.collisionBitMask = 0
backDrop.physicsBody?.categoryBitMask = dropCatagory
addChild(countdownTimer)
addChild(backDrop)
//addChild(background)
Timer.every(1.2.seconds) { (timer: Timer) in
if(times<=0){
timer.invalidate()
countdownTimer.removeFromParent()
self.didStart = true
self.startDropping()
}else{
print("\(times)")
times = times - 1
countdownTimer.text = "\(times)"
}
}
}
func startDropping(){
Timer.every(1.2.seconds) { (timer: Timer) in
let which = Int(arc4random_uniform(2) + 1)
let ee = self.getEmailNode(type: which)
self.addChild(ee)
ee.physicsBody?.applyImpulse(CGVector(dx: 0.0, dy: -5.0))
}
}
func getEmailNode(type: Int) -> SKSpriteNode{
var email = SKSpriteNode()
if(type == 1){
email = SKSpriteNode(imageNamed: "normal_email")
email.name = "normal_email"
}
if(type == 2){
email = SKSpriteNode(imageNamed: "classified_email")
email.name = "classified_email"
}
email.setScale(3)
email.position = CGPoint(x: getRandomColumn(), y: frame.height)
email.physicsBody = SKPhysicsBody(rectangleOf: email.frame.size)
email.physicsBody?.usesPreciseCollisionDetection = true
email.physicsBody?.categoryBitMask = emailCatagory
email.isUserInteractionEnabled = true
email.physicsBody?.affectedByGravity = false
email.physicsBody?.collisionBitMask = 0
email.physicsBody?.contactTestBitMask = emailCatagory | dropCatagory
emailNodes.append(email)
return email
}
func getRandomColumn() -> CGFloat{
let which = Int(arc4random_uniform(3) + 1)
let gg = frame.size.width/3
switch(which){
case 1:
return gg / 2
case 2:
return frame.midX
case 3:
return (gg * 3) - gg / 2
default:
return (gg * 3) + gg / 2
}
}
func didBegin(_ contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == dropCatagory) &&
(contact.bodyB.categoryBitMask == emailCatagory) {
let node = contact.bodyB.node as! SKSpriteNode
node.removeFromParent()
while emailNodes.contains(node) {
if let itemToRemoveIndex = emailNodes.index(of: node) {
emailNodes.remove(at: itemToRemoveIndex)
}
}
}
}
func doesContainNode(sk: SKSpriteNode) -> Bool {
for it in emailNodes{
if(it == sk){
return true
}
}
return false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
enumerateChildNodes(withName: "//*", using:
{ (node, stop) -> Void in
print("\(node.name)")
print("\(node)")
if((node.name?.contains("email")) != nil){
print("Touched!")
}
})
}
}
You should try the following in order to be able to get the user's position and know when the sprite has been touched. Add the following inside the touchesEnabled function.
for touch in touches {
let userTouch = touch.locationInNode(self)
}
And then check if the sprite was touched by using:
node.containsPoint(userTouch)
See if that works. The way you have your code setup, you might need to nest the above function right after checking if it's nil. As for the userInteractionEnabled, I don't use it at all when using the above code.
You do not want userInteractionEnabled set on any of your nodes, you want that only on the scene. Use userInteractionEnabled only when you are subclassing your node, this way you can use touchesBegan inside your subclassed file. What is happening is your touch is going into your node and being absorbed, which does nothing, and is being ignored by the scene since the node absorbed it.
Edit: Sorry #MarkBrownsword I did not see your comment, if you post it as an answer, I will upvote and delete my answer.
I found a solution. I removed isUserInteractionEnabled and touchesBegan was still not being called. So I went through each of the properties of the "email" node and for some reason the following properties made it where touchesBegan would not be called.
email.physicsBody?.affectedByGravity = false
email.physicsBody?.collisionBitMask = 0
So I removed those and now touchesBegan is being properly called.

Nodes spawning behind background and I can't bring them to the front

I'm using a repeat action. When the app first loads the sprites do spawn in front of the background and so you can see them. But then when you restart the game from the score scene, the nodes spawn behind the background and I can't get them to come to the front. Does anyone know how I can fix this?
override init(size: CGSize) {
super.init(size: size)
//Background
for var index = 0; index < 2; ++index {
let bg = SKSpriteNode(imageNamed: "background")
bg.position = CGPoint(x: -100, y: index * Int(bg.size.height))
bg.anchorPoint = CGPointZero
bg.name = "background"
self.addChild(bg)
}
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(callEnemy), SKAction.waitForDuration(1.0)])))
The runAction is the code to repeat an action that doesn't seem to be working
//Player functions
foreground = SKNode()
addChild(foreground)
player = createPlayer()
foreground.addChild(player)
//Game hud
gameHud = SKNode()
addChild(gameHud)
}
func callEnemy() {
if player.physicsBody?.dynamic == true {
spawnEnemy()
}
}
func spawnEnemy() -> SKNode{
let enemy = SKSpriteNode(imageNamed: "Enemy1")
enemy.position = CGPoint(x: frame.size.width * random(min: 0, max: 1), y: 690 )
addChild(enemy)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.width / 2)
enemy.physicsBody?.dynamic = true
enemy.physicsBody?.allowsRotation = false
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.velocity = CGVector(dx: enemy.physicsBody!.velocity.dx, dy: -200.0)
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.angularDamping = 0.0
enemy.physicsBody?.linearDamping = 0.0
if enemy.position.y <= CGFloat(0) {
enemy.removeFromParent()
}
return enemy
}
In order to have the nodes be on top of the background you need to change their zPosition. If you specify:
bg.zPosition = 0
and
foreground.zPosition = 1
the foreground will now be on top of the background. The zPosition specifies the location of each node on the z-axis.