Attemped to add a SKNode which already has a parent swift - swift

I am creating a game and i keep getting this error for my bullet spawn method linked with a joystick. I want to repetitively spawn nodes when the joystick is active Here is how i am creating a firing method
override func didMoveToView(view: SKView) {
if fireWeapon == true {
NSTimer.scheduledTimerWithTimeInterval(0.25, target: self,
selector: Selector ("spawnBullet1"), userInfo: nil, repeats: true)
}
}
func spawnBullet1(){
self.addChild(bullet1)
bullet1.position = CGPoint (x: hero.position.x , y:hero.position.y)
bullet1.xScale = 0.5
bullet1.yScale = 0.5
bullet1.physicsBody = SKPhysicsBody(rectangleOfSize: bullet1.size)
bullet1.physicsBody?.categoryBitMask = PhysicsCategory.bullet1
bullet1.physicsBody?.contactTestBitMask = PhysicsCategory.enemy1
bullet1.physicsBody?.affectedByGravity = false
bullet1.physicsBody?.dynamic = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (CGRectContainsPoint(joystick.frame, location)) {
stickActive = true
if stickActive == true {
fireWeapon = true
}
the first bullet launches as planned and works great however, every time the second bullet launches the app crashes and i get the error. can someone tell me an alternative way to create a fire rate

Instead of self.addChild(bullet1) you need something like self.addChild(SKSpriteNode(imageNamed: "bullet.png"))
This because you need to create a new bullet every time you fire, you do not want to move the same bullet every single time.

Related

Cannot disable, then reenable touch, after an SKAction animation

I am working on an interactive, animated scene. I want all touches on the scene to be disabled on entry. Then, once the objects (which are subclassed nodes) in the scene finish rotating/moving, I want to re-enable all touches on the screen to allow interaction. I have disabled user interaction using this code:
override func didMove(to view: SKView) {
setupNodes()
view?.isUserInteractionEnabled = false
spinLocations()
}
This is the code, within the scene file, for spinLocations:
func spinLocations() {
var allLocationArrays = [[String : CGPoint]]()
var previousArray = hiddenLocationPositions
for _ in 0...SearchConstant.numSpins {
let freshArray = generateNewLocationArray(previous: previousArray)
allLocationArrays.append(freshArray)
previousArray = freshArray
}
for (item, _) in hiddenLocationPositions {
let node = fgNode.childNode(withName: item) as! LocationNode
node.spin(position: allLocationArrays) // this is function below
}
hiddenLocationPositions = previousArray
}
This is the code for the animations in the node class:
func spin(position: [[String : CGPoint]]) {
var allActions = [SKAction]()
for array in position {
let action = SKAction.move(to: array[self.name!]!, duration: 2.0)
allActions.append(action)
}
let allActionsSeq = SKAction.sequence(allActions)
self.run(SKAction.sequence([SKAction.wait(forDuration: 5.0), allActionsSeq, SKAction.run {
self.position = position[position.count - 1][self.name!]!
},]))
}
This is the code for passing back the touches to the main scene from this class:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let parent = self.parent else { return }
}
As you can see, touch is not disabled here.
I do not want to add a "waitForDuration" SKAction to the runBlock to change the view status after the previous action; I want the program to determine when the animations are finished executing and then re-enable touches.
In order to do this, I theorised using a completion handler might work, but it only re-enables touches immediately (e.g. handling a handler to spin causes the touches to be detected again). Previously, I also tried to disable the view in the runBlock, but of course, that is run instantaneously. How do I ensure that the touches are re-detected following the animation without using "waitForDuration."?
So, this is a simple example that shows how you can:
1) Disable touches completely
2) Spin a node
3) When node is done with spinning, to enable touches
Here is the code (you can copy/paste it to try how it works):
class Object:SKSpriteNode{
func spin(times:Int,completion:#escaping ()->()) {
let duration = 3.0
let angle = CGFloat(M_PI) * 2.0
let oneRevolution = SKAction.rotate(byAngle: angle , duration: duration)
let spin = SKAction.repeat(oneRevolution, count: times)
let sequence = SKAction.sequence([spin,SKAction.run(completion)])
run(sequence, withKey:"spinning")
}
}
class WelcomeScene: SKScene {
override func didMove(to view: SKView) {
view.isUserInteractionEnabled = false
print("Touches Disabled")
let object = Object(texture: nil, color: .purple, size: CGSize(width: 200, height: 200))
addChild(object)
object.spin(times: 3, completion: {[weak self] in
self?.view?.isUserInteractionEnabled = true
print("Touches Enabled")
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch detected")
}
deinit {
print("Welcome scene deinited")
}
}
Here, you disable touches when scene is loaded, start spinning the object, and you pass a completion block to it... That block of code is used here:
let sequence = SKAction.sequence([spin,SKAction.run(completion)])
So after spinning, that block will be executed. Now, there are different ways to do this...Personally, I would use delegation, but I thought this can be less confusing... I can write an example for delegation too if needed, but basically, what you would do, is to set a scene as a delegate of your custom node, and notify it about spinning is done, so the scene can tell the view to re-enable the touches.

how to appoint a UItapGesture recognizer to a CGpoint for a button

I am creating a game ad i am having a hard time creating a jump button. I have created the jump up and fall down SKaction sequence which works perfect here is how it works.
func JumpArrow () {
self.addChild(jumpArrow)
jumpArrow.position = CGPointMake(60, 145)
jumpArrow.xScale = 1
jumpArrow.yScale = 1
}
func heroJumpMovement () {
let heroJumpAction = SKAction.moveToY(hero.position.y + 85,
duration: 0.5)
let heroFallAction = SKAction.moveToY(hero.position.y , duration:
0.5)
let jumpWait:SKAction = SKAction.waitForDuration(CFTimeInterval(1))
let heroMovementSequence:SKAction =
SKAction.sequence([heroJumpAction, heroFallAction ,jumpWait])
hero.runAction(heroMovementSequence)
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == jumpArrow {
heroJumpMovement()
}
however, I have a problem. if you quickly tap the button the player will fly off the screen. I hope that i can create a UItapGestureRecognizer and create a delay for the tap so you can't tap the button 2-4 times per second you will only be able to tap it once. If this is the wrong way to go about this please tell me
Adding a delay would be the wrong way to go about it.
Instead, in your touchesBegan function, before you call heroJumpMovement() you should check to see if your player is on the ground or not.
Another alternative would be to check if the last jump SKActionSequence has completed or not.
To do the above, you would have something like this (code not checked):
var canJump = true; // Variable will be true if we can jump
func JumpArrow () {
self.addChild(jumpArrow)
jumpArrow.position = CGPointMake(60, 145)
jumpArrow.xScale = 1
jumpArrow.yScale = 1
}
func heroJumpMovement () {
let heroJumpAction = SKAction.moveToY(hero.position.y + 85,
duration: 0.5)
let heroFallAction = SKAction.moveToY(hero.position.y , duration:
0.5)
let jumpWait:SKAction = SKAction.waitForDuration(CFTimeInterval(1))
let heroMovementSequence:SKAction =
SKAction.sequence([heroJumpAction, heroFallAction ,jumpWait])
canJump = false; // We are about to jump so set this to false
hero.runAction(heroMovementSequence, completion: {canJump = true;}) // Set the canJump variable back to true after we have landed
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == jumpArrow {
if (canJump) { // Make sure we are allowed to jump
heroJumpMovement()
}
}
Notice the canJump variable.

how to repetitively spawn a SKnode while a button is active

I'm creating a game and I am trying to create a shooting method. i have the bullets spawning at a gun and moving in the direction f a joystick. here is how i am spawning bullets at a consistent rate (a fire rate) with the gun and moving in the direction toward the joystick.
override func didMoveToView(view: SKView) {
if fireWeapon == true {
NSTimer.scheduledTimerWithTimeInterval(0.25, target: self,
selector: Selector ("spawnBullet1"), userInfo: nil, repeats: true)
}
}
func spawnBullet1(){
self.addChild(bullet1)
bullet1.position = CGPoint (x: hero.position.x , y:hero.position.y)
bullet1.xScale = 0.5
bullet1.yScale = 0.5
bullet1.physicsBody = SKPhysicsBody(rectangleOfSize: bullet1.size)
bullet1.physicsBody?.categoryBitMask = PhysicsCategory.bullet1
bullet1.physicsBody?.contactTestBitMask = PhysicsCategory.enemy1
bullet1.physicsBody?.affectedByGravity = false
bullet1.physicsBody?.dynamic = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (CGRectContainsPoint(joystick.frame, location)) {
stickActive = true
if stickActive == true {
fireWeapon = true
}
This method only launches the first bullet then right before it launches the second one the app crashes. everything with the bullet movement is perfect just spawning a stream of bullets for firing. I can't think of an alternative way to create a fire speed.
You are always trying to add the same child when you call self.addChild(bullet1)
You need to create a new instance every time in the spawnBullet1 function and add that object as a child.

How to implement a SpriteKit timer?

I am currently trying to implement a timer for my sprite kit game, but I don't get it working. The initial value of the timer always remains the same.
I am assuming I need to update the label somehow/somewhere, but I don't know HOW and WHERE :? I don't get the point. Any ideas?
Here is my code within my GameScene Class
let levelTimerLabel = SKLabelNode(fontNamed: "Chalkduster")
var levelTimerValue: Int = 500
var levelTimer = NSTimer()
func startLevelTimer() {
levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
addChild(levelTimerLabel)
levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("levelCountdown"), userInfo: nil, repeats: true)
levelTimerLabel.text = String(levelTimerValue)
}
func levelCountdown(){
levelTimerValue--
}
I would stick to SKActions for these kind of tasks in SpriteKit due to fact that NSTimer is not affected by scene's, or view's paused state, so it might lead you into troubles. Or at least, it will require from you to implement a pause feature in order to pause your timers in certain situations, like when user pause the scene, or receive a phone call etc. Read more here about SKAction vs NSTimer vs GCD for time related actions in SpriteKit.
import SpriteKit
class GameScene: SKScene {
var levelTimerLabel = SKLabelNode(fontNamed: "ArialMT")
//Immediately after leveTimerValue variable is set, update label's text
var levelTimerValue: Int = 500 {
didSet {
levelTimerLabel.text = "Time left: \(levelTimerValue)"
}
}
override func didMoveToView(view: SKView) {
levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
levelTimerLabel.text = "Time left: \(levelTimerValue)"
addChild(levelTimerLabel)
let wait = SKAction.waitForDuration(0.5) //change countdown speed here
let block = SKAction.runBlock({
[unowned self] in
if self.levelTimerValue > 0{
self.levelTimerValue--
}else{
self.removeActionForKey("countdown")
}
})
let sequence = SKAction.sequence([wait,block])
runAction(SKAction.repeatActionForever(sequence), withKey: "countdown")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Stop the countdown action
if actionForKey("countdown") != nil {removeActionForKey("countdown")}
}
}
Sounds like you need to update the label everytime levelTimerValue is changed. The easiest way would be something like this.
func levelCountdown(){
levelTimerValue--
levelTimerLabel.text = String(levelTimerValue)
}

How to make SKSpriteNode with fixed position (after adding physics)?

I have several programmatically made SKSpriteNode's. Some of them I want to move around, some I want to be static (have a fixed position). When adding physics to the nodes (need that to be able to do collision detection, right?) and set physicsBodyXXXX.dynamic = false they stay in the same position when moving other object over them. That's fine!
But, I'm still able to grab the node I want to be statically positioned, and move them around. How can I mask out the node I don't want to move in touches function? Or is there another solution?
Tried to find a property like static which made the node's position fixed, but can't find it...
Here's my code for auto generating nodes (in override func didMoveToView(view: SKView):
for Character in englishWord{
// Make letters:
let letterToMove = SKSpriteNode(imageNamed: "\(Character)")
//then setting size and position
var physicsBodyLetterToMove = SKPhysicsBody(rectangleOfSize: letterToMove.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterToMove.allowsRotation = false
physicsBodyLetterToMove.dynamic = false
letterToMove.physicsBody = physicsBodyLetterToMove
self.addChild(letterToMove)
// Make empty boxes for the letters:
let letterRecBox = SKSpriteNode(imageNamed: "EmptyBox")
//then setting size and position
var physicsBodyLetterRecBox = SKPhysicsBody(rectangleOfSize: letterRecBox.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterRecBox.dynamic = false
letterRecBox.physicsBody = physicsBodyLetterRecBox
self.addChild(letterRecBox)
}
So the touches func's:
var selected: [UITouch: SKNode] = [:]
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
selected = [:]
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
selected[touch as UITouch] = nodeAtPoint(location)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
for (touch, node) in selected{
if !contains([self], node){
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
}
Any idea?
Setting dynamic to false will make the node unaffected by physics. SKActions and touch events are not considered physics so they will still affect your nodes that are not dynamic.
You could do something like:
YourSpriteNode.name = #"staticNode"; //Right after you create it
Then alter your touch method:
for (touch, node) in selected{
if !contains([self], node){
if(![node.name isEqualToString:#"staticNode"])
{
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
The newly introduced if statement will prevent any node named "staticNode" from getting moved due to your SKActions. Other nodes will move as expected.
Thanx, that worked.
Only, I had to write it like this:
letterRecBox.name = "staticNode"
...
if !(node.name == "staticNode"){
...then do something...
}