Another SKSpriteNode is removed when current one is touched - swift

I am in developing an application using Xcode and currently I am facing a issue regarding SKSpriteNodes. When there are more than a single SKSpriteNode and when the node is touched, the touched node is not removed but the other not touched is removed. I have also noticed that when there are multiple nodes on the screen only the latest node coming from the top of the screen is removed whilst others are still moving down, although they are being touched. Can someone help identify why this has occurred and the methods of preventing such mistakes please?
For reference, I am have included the class in which the bug is in.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var gameCounter: Int = 0
private var currentLevel: Int = 0
private var debug: SKLabelNode?
private var increasedTouchArea:SKSpriteNode?
private var generatedNode:SKSpriteNode?
private var moveAndRemove:SKAction?
private var moveNode:SKAction?
// Here we set initial values of counter and level. Debug label is created here as well.
override func didMove(to view: SKView) {
print(action(forKey: "counting") == nil)
gameCounter = 0
currentLevel = 1
//backgroundColor = SKColor.gray
debug = SKLabelNode(fontNamed: "ArialMT")
debug?.fontColor = SKColor.red
debug?.fontSize = 30.0
debug?.position = CGPoint(x: frame.midX, y: frame.midY)
debug?.text = "Counter : [ \(gameCounter) ], Level [ \(currentLevel) ]"
if let aDebug = debug {
addChild(aDebug)
}
}
//Method to start a timer. SKAction is used here to track a time passed and to maintain current level
func startTimer() {
print("Timer Started...")
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
weakSelf?.gameCounter = (weakSelf?.gameCounter ?? 0) + 1
//Maintaining level
if (weakSelf?.gameCounter ?? 0) < 5 {
//level 1
weakSelf?.currentLevel = 1
} else if (weakSelf?.gameCounter ?? 0) >= 5 && (weakSelf?.gameCounter ?? 0) < 10 {
//level 2
weakSelf?.currentLevel = 2
} else {
//level 3
weakSelf?.currentLevel = 3
}
weakSelf?.debug?.text = "Counter : [ \(Int(weakSelf?.gameCounter ?? 0)) ], Level [ \(Int(weakSelf?.currentLevel ?? 0)) ]"
})
run(SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration: 1), block])), withKey: "counting")
}
//Method for stopping timer and reseting everything to default state.
func stopTimer() {
print("Timer Stopped...")
if action(forKey: "counting") != nil {
removeAction(forKey: "counting")
}
gameCounter = Int(0.0)
currentLevel = 1
debug?.text = "Counter : [ \(gameCounter) ], Level [ \(currentLevel) ]"
}
//Get current speed based on time passed (based on counter variable)
func getCurrentSpeed() -> CGFloat {
if gameCounter < 30 {
//level 1
return 1.0
} else if gameCounter >= 31 && gameCounter < 49 {
//level 2
return 2.0
} else {
//level 3
return 3.0
}
}
//Method which stop generating stones, called in touchesBegan
func stopGeneratingStones() {
print("STOPPED GENERATING STONES...")
if action(forKey: "spawning") != nil {
removeAction(forKey: "spawning")
}
}
func randomFloatBetween(_ smallNumber: CGFloat, and bigNumber: CGFloat) -> CGFloat {
let diff: CGFloat = bigNumber - smallNumber
return CGFloat(arc4random() % (UInt32(RAND_MAX) + 1)) / CGFloat(RAND_MAX) * diff + smallNumber
}
//Method for generating stones, you run this method when you want to start spawning nodes (eg. didMoveToView or when some button is clicked)
func generateStones() {
print("Generating Stones...")
let delay = SKAction.wait(forDuration: 1, withRange: 0.5) //Change forDuration: delay decreases as game progresses.
//randomizing delay time
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
let stone: SKSpriteNode? = weakSelf?.spawnNodes(withSpeed: weakSelf?.getCurrentSpeed() ?? 0.0)
stone?.zPosition = 20
if let aStone = stone {
weakSelf?.addChild(aStone)
}
})
run(SKAction.repeatForever(SKAction.sequence([delay, block])), withKey: "spawning")
}
func spawnNodes(withSpeed stoneSpeed: CGFloat) -> SKSpriteNode? {
let nodeSize = CGSize(width: 60, height: 60) //size of shape.
let initalNodePosition = CGPoint(x: randomFloatBetween(0.0, and: (self.view?.bounds.size.width)!) - 110, y: frame.maxY)
generatedNode = SKSpriteNode(color: SKColor.green, size: nodeSize)
generatedNode?.position = initalNodePosition
moveNode = SKAction.moveBy(x: 0, y: self.view!.scene!.frame.minY + self.view!.scene!.frame.minY , duration: 5.25)
generatedNode?.isUserInteractionEnabled = false //Allows users to touch shape.
increasedTouchArea = SKSpriteNode(color: UIColor.clear, size: CGSize(width: nodeSize.width * 1.35, height: nodeSize.height * 1.35))
increasedTouchArea?.name = "generatedNode"
increasedTouchArea?.isUserInteractionEnabled = false
generatedNode?.addChild(increasedTouchArea!)
moveNode?.speed = stoneSpeed
moveAndRemove = SKAction.sequence([moveNode!, SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
generatedNode?.name = "generatedNode"
return generatedNode
}
func deleteNode(){
moveAndRemove = SKAction.sequence([SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent? {
for touch in touches {
let locationTocuhed = touch.location(in: self)
let touchedNode : SKNode = self.atPoint(locationTocuhed)
if touchedNode.name == generatedNode?.name {
print("Node touched")
deleteNode()
}
}
if action(forKey: "counting") == nil {
print("Game started...")
startTimer()
generateStones()
} else {
print("Game paused...")
}
}
}

Your method deleteNode() runs the deletion animation on the node pointed to by generatedNode, not the node last touched:
func deleteNode(){
moveAndRemove = SKAction.sequence([SKAction.removeFromParent()])
generatedNode?.run(moveAndRemove!, withKey: "moving")
}
If you want to delete the node last touched, pass a reference to this node to the method deleteNode and then run your deletion animation in that, not generatedNode.

Related

How to detect a contact in SpriteKit

I'm trying to have a contact to be detected by SpriteKit in Swift 4. I set a flipper to a certain category and a ball to another one. Then I set their collision and contact mask to each other, I add a contactDelegate in didMove to, and print "contact" to inform that there is a contact in a didEnterFonction. However I cannot detect any contact at all. Even after following all the tutorials, and scouring through similar questions. I do not understand what I am doing wrong. My end goal is to have the ball stop from falling when it hits the flipper, but I am trying to detect any contact at all in the first place which do not work.
import SpriteKit
import GameplayKit
struct PhysicsCategory {
static let none : UInt32 = 0
static let all : UInt32 = UInt32.max
static let flip : UInt32 = 0b1
static let ball: UInt32 = 0b10 // 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
private var flip1: SKSpriteNode?
private var flip2: SKSpriteNode?
private var spinnyNode : SKShapeNode?
private var balle:SKSpriteNode?
override func sceneDidLoad() {
self.lastUpdateTime = 0
self.flip1 = self.childNode(withName:"//flip1") as? SKSpriteNode
self.flip2 = self.childNode(withName: "//flip2") as? SKSpriteNode
self.balle = self.childNode(withName: "//ball") as?SKSpriteNode
self.flip1?.physicsBody = SKPhysicsBody()
self.flip1?.physicsBody?.affectedByGravity = false
self.flip1?.physicsBody?.allowsRotation = true
self.balle?.physicsBody = SKPhysicsBody();
//self.balle?.physicsBody?.affectedByGravity = true
self.flip2?.physicsBody = SKPhysicsBody()
self.flip2?.physicsBody?.affectedByGravity = false
self.flip2?.physicsBody?.allowsRotation = true
self.balle?.physicsBody?.allContactedBodies()
self.flip1?.physicsBody?.usesPreciseCollisionDetection
= true
self.flip1?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip1?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip1?.physicsBody?.collisionBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip2?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.collisionBitMask = PhysicsCategory.ball
// Get label node from scene and store it for use later
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
// Create shape node to use during mouse interaction
let w = (self.size.width + self.size.height) * 0.05
self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: w, height: w), cornerRadius: w * 0.3)
if let spinnyNode = self.spinnyNode {
spinnyNode.lineWidth = 2.5
spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
SKAction.fadeOut(withDuration: 0.5),
SKAction.removeFromParent()]))
}
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case 0x31:
if let label = self.label {
label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
}
case 123:
print("left")
self.flip1?.run(SKAction.rotate(byAngle: -1.5, duration: 0.2))
// self.flip1?.physicsBody?.applyAngularImpulse(600)
self.flip1?.run(SKAction.rotate(byAngle: 1.5, duration: 0.1))
case 124:
self.flip2?.run(SKAction.rotate(byAngle: 1.5, duration: 0.2))
self.flip2?.run(SKAction.rotate(byAngle: -1.5, duration: 0.1))
case 15:
self.balle?.run(SKAction.move(to: CGPoint(x: 50,y: 50), duration: 1))
default:
print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
func didEnter(_ contact: SKPhysicsContact) {
print("contact")
}
}
You haven't given the physic bodies a shape or size. Take a look here and read the 'Creating a Body from a ___' sections.

Swift 3: class doesn't function correctly

So I've got a class named FlashyPaddleEffect. I also mention it in another class, GameScene, but it doesn't apply any effect that it should. The paddle should be flashing blue and white colors, but it simply stays white. The paddle also should lose its physics body when it is blue.
If you need any other information, don't hesitate to ask.
NOTE: There might be problems with the code I gave (because of indents, it's quite tricky to make code by indenting 4 spaces every line, sorry).
import SpriteKit
import GameplayKit
class FlashyPaddleEffect {
var node = SKSpriteNode()
var ballNode = SKSpriteNode()
var updateTimer: Timer? = nil
var timer: Timer? = nil
#objc func changeNodeColor() {
switch node.color {
case SKColor.blue: node.color = SKColor.white
case SKColor.white: node.color = SKColor.blue
default: _ = 1 + 2
}
}
#objc func update() //I used the objc prefix to silence the warning the selectors of the timers produced. {
let previousPhysicsBody = node.physicsBody
if node.color == SKColor.blue {
node.physicsBody = nil
}
node.physicsBody = previousPhysicsBody
}
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
node = applyEffectTo
ballNode = ball
timer = Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(changeNodeColor), userInfo: nil, repeats: true)
updateTimer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(update), userInfo: nil, repeats: true)
_ = node
_ = timer
}
}
class GameScene: SKScene {
var ball = SKSpriteNode()
var player = SKSpriteNode()
var enemy = SKSpriteNode()
var scores = [0, 0]
var initScores = [0, 0]
var scoreLabels: [SKLabelNode]? = nil
let playLabel = SKLabelNode()
let timeLabel = SKLabelNode()
let timeUpLabel = SKLabelNode()
var secondsLeft: Int = 180
var initialTime: Int? = nil
var timer = Timer()
var amountOfPauseMenuCloses = 0
var resultsLabel = SKLabelNode()
let flashEffect = FlashyPaddleEffect() //I define a variable to mention the class easier later on.
override func didMove(to view: SKView) {
//Initialize nodes of the scene editor.
ball = self.childNode(withName: "Ball") as! SKSpriteNode
player = self.childNode(withName: "PPaddle") as! SKSpriteNode
enemy = self.childNode(withName: "EPaddle") as! SKSpriteNode
//Set styles for the Play Notification Label (referred to as PNL later)
playLabel.position = CGPoint(x: 0, y: 0)
playLabel.text = "Tap anywhere to play."
playLabel.name = "playLabel"
//Set styles for the Timer Label (referred to as Timer later)
timeLabel.position = CGPoint(x: 90, y: 0)
timeLabel.text = String(secondsLeft)
//Doing manipulations connected with scores here.
scores = [0, 0]
initScores = scores
scoreLabels = [self.childNode(withName: "PScoreLabel") as! SKLabelNode, self.childNode(withName: "EScoreLabel") as! SKLabelNode]
//Create a border for our ball to bounce off.
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
border.linearDamping = 0
border.angularDamping = 0
self.physicsBody = border
//To avoid the ball's damping.
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
showPause() //Show the (pause) menu at the beginning
//Set a variable to refer to as a time standard later.
initialTime = secondsLeft
//The game timer.
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(reduceSecondFromTimer), userInfo: nil, repeats: true)
flashEffect.make(applyEffectTo: player, ball: ball) //This is where I reference the class in another class.
}
//A function to show the (pause) menu.
func showPause(hideNodes: Bool = true) {
self.addChild(playLabel)
if hideNodes == true {
ball.removeFromParent()
player.removeFromParent()
enemy.removeFromParent()
scoreLabels?[0].removeFromParent()
scoreLabels?[1].removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
var alreadyChangedTextLabel = false
for i in scoreLabels! {
if i.name == "PScoreLabel" {i.text = String(scores[0])}
if i.name == "EScoreLabel" {i.text = String(scores[1])}
}
if ball.position.y <= player.position.y {scores[1] += 1}
if ball.position.y >= enemy.position.y {scores[0] += 1}
if secondsLeft == 0 {
showPause(hideNodes: false)
if alreadyChangedTextLabel == false {
timeUpLabel.text = "TIME UP! \(whoIsWinning(scores: scores)) won!"
alreadyChangedTextLabel = true
}
timeUpLabel.name = "timeUpLabel"
timeUpLabel.position = CGPoint(x: 0, y: 180)
if !(self.children.contains(timeUpLabel)) {
self.addChild(timeUpLabel)
}
timeLabel.removeFromParent()
}
if self.children.contains(playLabel) {
secondsLeft = initialTime!
}
timeLabel.text = String(secondsLeft)
let alignWithBall = SKAction.move(to: CGPoint(x: ball.position.x, y: enemy.position.y), duration: 0.8)
enemy.run(alignWithBall)
}
func initGame() {
//Initializing process for every game.
scores = initScores
ball.position = CGPoint(x: 0, y: 0)
if amountOfPauseMenuCloses == 1 {ball.physicsBody?.applyImpulse(CGVector(dx: 15, dy: 15))}
secondsLeft = initialTime!
timeLabel.text = String(secondsLeft)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for childNode in self.children {
if childNode.name == "playLabel" {
if self.children.contains(playLabel) {
playLabel.removeFromParent()
}
if !(self.children.contains(player)) {
self.addChild(player)
}
if !(self.children.contains(enemy)) {
self.addChild(enemy)
}
if !(self.children.contains(ball)) {
self.addChild(ball)
}
if !(self.children.contains((scoreLabels?[0])!)) {
self.addChild((scoreLabels?[0])!)
}
if !(self.children.contains((scoreLabels?[1])!)) {
self.addChild((scoreLabels?[1])!)
}
if !(self.children.contains(timeLabel)) {
self.addChild(timeLabel)
}
if self.children.contains(timeUpLabel) {
timeUpLabel.removeFromParent()
}
amountOfPauseMenuCloses += 1
initGame()
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
if self.nodes(at: t.location(in: self)).contains(player) {player.position.x = t.location(in: self).x}
}
}
func reduceSecondFromTimer() {
secondsLeft -= 1
}
func whoIsWinning(scores: Array<Int>) -> String {
var r: String? = nil
if scores[0] >= scores[1] {
r = "You"
}
if scores[1] >= scores[0] {
r = "Enemy"
}
return r!
}
}
Thanks a lot for answers.
P.S It's my first question ever so don't judge me strictly.
1) Do not use NSTimer use SKAction. I can see the way you are doing it you stack timer after timer, this is bad.
2) Do not have your temp variable global (node in this case), it makes code hard to read
3) Do not remove your physics body, simply remove the category.
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
ballNode = ball
let blue = SKAction.colorize(with SKColor.blue, colorBlendFactor: 1.0, duration sec: 0)
let white = SKAction.colorize(with SKColor.white, colorBlendFactor: 1.0, duration sec: 0)
let wait = SKAction.wait(for:0.6)
let turnOnPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = #######})
let turnOffPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = 0})
let seq = [blue, turnOffPhysics,wait,white,turnOnPhysics,wait]
let repeat = SKAction.repeatForever(seq)
applyEffectTo.run(repeat, withKey:"flashing")
}
Note: I have no idea what your categoryBitmask is, you need to fill it in

SKSpriteNode not working with Random Number Array

I have a SKSpriteNode that is spawn repeatedly. I have created a random number generator and have tried to associate the returns with a different Sprite Image. However, it does not work. The numbers are being generated randomly but even if "2" is returned the SKSpriteNode is drawn as "Fire Barrel 1".
class GameScene: SKScene {
var firstObstacle = SKSpriteNode()
var player = SKSpriteNode()
var randomValue = Int()
override func didMove(to view: SKView) {
createPlayer()
let delay = SKAction.wait(forDuration: 3)
let repeatingAction = SKAction.run(repeatingSequence)
let sequence = SKAction.sequence([ delay, repeatingAction ] )
run(SKAction.repeatForever(sequence))
}
func randomSelector() {
let array = [0, 1, 2, 3]
let randomValue = Int(arc4random_uniform(UInt32(array.count)))
print(array[randomValue])
}
func createPlayer() {
player = SKSpriteNode(imageNamed: "Player")
player.setScale(0.4)
player.position = CGPoint(x: -player.size.width, y: 0)
player.zPosition = 1
addChild(player)
}
func createObstacle() {
if randomValue == 2 {
firstObstacle = SKSpriteNode(imageNamed: "Fire Barrel 2")
}
else {
firstObstacle = SKSpriteNode(imageNamed: "Fire Barrel 1")
}
firstObstacle.position = CGPoint(x: self.frame.width / 2, y: -self.frame.height / 2 + firstObstacle.size.height / 2)
firstObstacle.zPosition = 1
addChild(firstObstacle)
}
func repeatingSequence() {
randomSelector()
createObstacle()
let moveObstacle = SKAction.moveBy(x: -self.frame.width - firstObstacle.size.width, y: 0, duration: 5)
firstObstacle.run(moveObstacle)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
}
}
Your issue is
let randomValue = Int(arc4random_uniform(UInt32(array.count)))
You are doing what is called "variable shadowing". randomValue also exists at the class instance level, but here because you use let you've declared a local scoped version.
The actual value of self.value is Int() which is 0. Hence why it is "First Barrel l".
The generalized design you're using is not really good. Unless you have some real reason where you need to hold randomValue, you should not make it a class instance variable. Instead pass it by argument.
This would look something like:
func randomSelector() -> Int {
let array = [0, 1, 2, 3]
return Int(arc4random_uniform(UInt32(array.count)))
}
func createObstacle(type:Int) {
if (type == 2) {
// Type 2 stuff
} else {
// Other stuff
}
// Rest of your stuff
}
createObstacle(type: randomSelector())
Although given the nature of how this works, you don't even need the argument, you could just do:
func createObstacle() {
if (randomSelector() == 2) {
// Type 2 stuff
} else {
// Other stuff
}
// Rest of your stuff
}

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.

Swift: Attemped to add a SKNode which already has a parent:

I know why I'm getting that error, but I can't figure out a way around it. I'm trying to have objects come appear and then be removed and the player should try to tap them before they're removed, but everytime the next node is about to appear it crashes. If i declare it inside its func then it all comes out but I can't tap on it...
Code:
let step = SKSpriteNode()
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
backgroundColor = UIColor.feelFreeToColor()
self.color = self.randomNumbersInt(3)
self.showBars()
self.showScore()
let spawn = SKAction.runBlock {
//self.color = self.randomNumbersInt(3)
self.showSteps()
}
let delay = SKAction.waitForDuration(1.5)
let spawnDelay = SKAction.sequence([spawn , delay])
let spawnDelayForever = SKAction.repeatActionForever(spawnDelay)
self.runAction(spawnDelayForever)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
var location = touch.locationInNode(self)
if self.nodeAtPoint(location) == step {
self.score += 1
}
}
}
func showSteps() {
let createSteps = SKAction.moveByX(0, y: -self.frame.height - 30 , duration: 10)
let removeSteps = SKAction.removeFromParent()
step.color = colors[randomNumbersInt(3)]!
step.size = CGSize(width: 275, height: 30)
step.position = CGPoint(x: self.frame.width * 0.5, y: self.frame.height * 0.75)
step.physicsBody = SKPhysicsBody(rectangleOfSize: step.size)
step.physicsBody?.categoryBitMask = Config.PhysicBodyType.Steps.rawValue
step.physicsBody?.affectedByGravity = false
step.physicsBody?.dynamic = false
step.zPosition = 1
step.runAction(SKAction.repeatActionForever(SKAction.sequence([createSteps, removeSteps])))
addChild(step)
}
In your showSteps function, declare the step node inside it, not at the top of your code, and also give it a name:
func showSteps() {
let step = SKSpriteNode()
...
step.name = "step"
step.color = colors[randomNumbersInt(3)]!
// etc
}
In your touchesBegan method, you have this if statement:
if self.nodeAtPoint(location) == step {
self.score += 1
}
You want to remove that node that you have hit, but now you can just check the name property like so:
if self.nodeAtPoint(location)?.name == "step" {
self.nodeAtPoint(location)?.removeFromParent()
self.score += 1
}
Please note that I am not super fluent in Swift, but I think you will probably need the ? in your if statement, as it might not exist (such as if you didn't tap on a specific node). Somebody more familiar with Swift is free to correct me.