SKSpriteNode not able to have multiple children - xcode

I'm trying to have multiple sprite nodes of the same type/parent, but when I try to spawn another node I get an error. 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent'
Here's my code:
import SpriteKit
class GameScene: SKScene {
//global declarations
let player = SKSpriteNode(imageNamed: "mage")
let fireball = SKSpriteNode(imageNamed: "fireball")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
createScene()
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
spawnFireball(location)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
self.enumerateChildNodesWithName("fireball", usingBlock: ({
(node,error) in
if (self.fireball.position.x < -self.fireball.size.width/2.0 || self.fireball.position.x > self.size.width+self.fireball.size.width/2.0
|| self.fireball.position.y < -self.fireball.size.height/2.0 || self.fireball.position.y > self.size.height+self.fireball.size.height/2.0) {
self.fireball.removeFromParent()
self.fireball.removeAllChildren()
self.fireball.removeAllActions()
}
}))
}
func createScene() {
//player
player.size = CGSizeMake(100, 100)
player.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2 + 50)
player.zPosition = 2.0
self.addChild(player)
}
func spawnFireball(point: CGPoint) {
//setup
fireball.name = "fireball"
fireball.size = CGSizeMake(100, 50)
let fireballCenter = CGPointMake(fireball.size.width / 4 * 3, fireball.size.height / 2)
fireball.position = player.position
fireball.physicsBody = SKPhysicsBody(circleOfRadius: fireball.size.height/2, center: fireballCenter)
fireball.physicsBody?.affectedByGravity = false
//action
var dx = CGFloat(point.x - player.position.x)
var dy = CGFloat(point.y - player.position.y)
let magnitude = sqrt(dx * dx + dy * dy)
dx /= magnitude
dy /= magnitude
let vector = CGVector(dx: 32.0 * dx, dy: 32.0 * dy)
var rad = atan2(dy,dx)
fireball.runAction(SKAction.rotateToAngle(rad, duration: 0.0))
self.addChild(fireball)
fireball.physicsBody?.applyImpulse(vector)
}
}

You need to instantiate another SKSpriteNode. In your existing code, you create a single fireball and add it to the scene; your program crashes when you try to add the same fireball again.
First, remove your let fireball = SKSpriteNode... line. Move it to inside the spawnFireball() method, like so:
func spawnFireball(point: CGPoint) {
let fireball = SKSpriteNode(imageNamed: "fireball")
//Insert all customization here (your existing code should mostly work)
self.addChild(fireball)
}
Because the fireball variable is a local variable, you can now instantiate a new one every time you call the function. Now, just change your update() method to properly use enumerateChildrenWithName() by getting changing every self.fireball to just node.
This way, the code will loop through every existing fireball that is currently on the scene, rather than your current code, which only allows you to create one fireball.

Related

error removing child node from parent node in spriteKit Swift4

I made a parallax background, in GameScene.sks I added an empty node, added two sprites to an empty node. I have a reset button, when I click on it I need the background to be removed and added to its position (restarted), but when I add in scrollBg.removeAllChildren restart function I error occurs, how do I properly add and remove children from the scene ?
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var player: SKSpriteNode!
var scrollBg: SKNode!
var spawnTimer: CFTimeInterval = 0
let fixedDelta: CFTimeInterval = 1.0/60.0 /* 60 FPS */
let scrollSpeed: CGFloat = 700
var sinceTouch: CFTimeInterval = 0
func resetGameScene() {
scrollBG.removeAllChildren()
player.removeAllChildren()
player.position = CGPoint(x: 590 , y: 690)
pauseButton()
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
player = childNode(withName: "player") as? SKSpriteNode
scrollBg = childNode(withName: "scrollBG")!
resetGameScene()
}
func scrollWorld() {
scrollBg.position.y -= scrollSpeed * CGFloat(fixedDelta)
for ground in scrollBg.children as! [SKSpriteNode] {
let groundPosition = scrollBg.convert(ground.position, to:
self)
if groundPosition.y <= -ground.size.width {
let newPosition = CGPoint(x: groundPosition.x, y:
(self.size.width ) + ground.size.width * 2)
ground.position = self.convert(newPosition, to:
scrollBg)
}
}
}
override func update(_ currentTime: TimeInterval) {
sinceTouch+=fixedDelta
spawnTimer+=fixedDelta
scrollWorld()
}
}

SKScene and Swift Files are not linking

In my xcode project I am trying to transition from one SKScene to another. What triggers the transition is the touching of a SKLabelNode. All of that is working correctly. But after the scene changes none of my code from my class that controls the "StartScence.sks" works. It seems as if my "StartScene.swift" and my "StartScene.sks" are not linked.
This is the code in my GameScene,
import SpriteKit
class GameScene: SKScene {
var isTouched: Bool = false
var booleanTouched: Bool!
let ns = SKScene(fileNamed: "StartScene")
let crosswf = SKTransition.crossFadeWithDuration(2)
override func didMoveToView(view: SKView) {
let backgroundimage = SKSpriteNode(imageNamed: "ipbg")
backgroundimage.size = CGSize(width: self.frame.size.width, height: self.frame.size.height)
backgroundimage.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2)
addChild(backgroundimage)
let playButton = SKLabelNode(fontNamed: "")
playButton.name = "play"
playButton.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2 + 100)
playButton.text = "Play"
let wait = SKAction.waitForDuration(2)
let run = SKAction.runBlock({
let randomNumber = Int(arc4random_uniform(UInt32(4)))
switch(randomNumber){
case (0):
playButton.fontColor = UIColor.blueColor()
case 1:
playButton.fontColor = UIColor.yellowColor()
case 2:
playButton.fontColor = UIColor.purpleColor()
case 3:
playButton.fontColor = UIColor.orangeColor()
default: print("default")
}
})
addChild(playButton)
var repeatActionForever = SKAction.repeatActionForever(SKAction.sequence([wait, run]))
runAction(repeatActionForever)
backgroundimage.zPosition = 1
playButton.zPosition = 2
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if (touchedNode.name == "play"){
scene!.view?.presentScene(ns!, transition: crosswf)
}else{
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
And this is the code that is my "StartScene.swift" that isnt controlling the "StartScene.sks" properly.
import SpriteKit
class StartScene: SKScene {
override func didMoveToView(view: SKView) {
print("Scene Loaded")
}
}
There's two things to be aware of.
In your code you are currently loading your .SKS file like this
let ns = SKScene(fileNamed: "StartScene")
Your new scene will load, as all SKS Files are of the class SKScene.
But, it will only use code from that class.
If you want it to load with the code in your class StartScene, a subclass of SKScene. Change the line to this
let ns = StartScene(fileNamed: "StartScene")
We can also make the SKS File have a custom class instead of it's default SKScene. So when it's loaded it uses a custom class.
Open the SKS File in Xcode so you can give the scene a Custom Class. In the scene editor and with nothing selected. Click in the utilities area, switch to the Custom Class Inspector, which is the last tab on the right.
Give it a Custom Class of StartScene.
It should work.

Moving a sprite with a joystick in swift Xcode

hello :D I have this code below.
import SpriteKit
class GameScene: SKScene {
let base = SKSpriteNode(imageNamed: "yellowArt/Base")
let ball = SKSpriteNode(imageNamed: "yellowArt/Ball")
let ship = SKSpriteNode(imageNamed: "yellowArt/Ship")
var stickActive:Bool = false
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.blackColor()
self.anchorPoint = CGPointMake(0.5, 0.5)
self.addChild(base)
base.position = CGPointMake(0, -200)
self.addChild(ball)
ball.position = base.position
self.addChild(ship)
ship.position = CGPointMake(0, 200)
ball.alpha = 0.4
base.alpha = 0.4
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in (touches ) {
let location = touch.locationInNode(self)
if (CGRectContainsPoint(ball.frame, location)) {
stickActive = true
} else {
stickActive = false
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in (touches ) {
let location = touch.locationInNode(self)
if (stickActive == true) {
let v = CGVector(dx: location.x - base.position.x, dy: location.y - base.position.y)
let angle = atan2(v.dy, v.dx)
let deg = angle * CGFloat( 180 / M_PI)
print( deg + 180 )
let length: CGFloat = base.frame.size.height / 2
let xDist: CGFloat = sin(angle - 1.57079633) * length
let yDist: CGFloat = cos(angle - 1.57079633) * length
if (CGRectContainsPoint(base.frame, location)) {
ball.position = location
} else {
ball.position = CGPointMake(base.position.x - xDist, base.position.y + yDist)
ship.zRotation = angle - 1.57079633
ship.position = CGPointMake(angle, angle)
}
} // ends stick active test
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if (stickActive == true) {
let move: SKAction = SKAction.moveTo(base.position, duration: 0.2)
move.timingMode = .EaseOut
ball.runAction(move)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
The code above creates a joystick and a ship. By moving the joystick I am able to rotate the "ship" with the joystick. However I want to move the ship in the direction that the joystick is holding. How do I tackle this problem? Thanks.
Your ship isn't going to go anywhere since you have ship.position = CGPointMake(angle, angle). As long as you're moving the joystick, the ship is going to go to that point - remove that line.
I would use the update method to move the ship around. First you need to find how much you want to move it in the x and y direction.
Create class variables for the joystick movements below your var stickActive:Bool = false statement:
var xJoystickDelta = CGFloat()
var yJoystickDelta = CGFloat()
Put the following code in your touchesMoved method:
xJoystickDelta = location.x - base.position.x
yJoystickDelta = location.y - base.position.y
Put the following code in your update method:
let xScale = 1.0 //adjust to your preference
let yScale = 1.0 //adjust to your preference
let xAdd = xScale * self.xJoytickDelta
let yAdd = yScale * self.yJoystickDelta
self.ship.position.x += xAdd
self.ship.position.y += yAdd
I hope this helps.

SKSpriteNode Won't Move

I have created a rectangular image in the form of an SKSpriteNode in Swift with the following code:
var screenImage = SKSpriteNode(texture: SKTexture(imageNamed: "\(imageChoices[randomImageChoice].0)"))
screenImage.position = CGPointMake(screen1.position.x, screen1.position.y)
screenImage.size = CGSizeMake(self.frame.size.width * 0.6, self.frame.size.height)
self.addChild(screenImage)
I proceed to move the image with the following code:
func swipedTrue(sender: UISwipeGestureRecognizer) {
if gameOver == false && tutorial == false {
//if you swipe, it checks if you were right, then moves on or GameOver()
if (wordChoices[randomWordChoice]).1 == true {
//reset time
timePerQuestion = 1.0
//randomize word
randomWordChoice = Int(arc4random() % 3)
newImage = SKSpriteNode(texture: SKTexture(imageNamed: "\(wordChoices[randomWordChoice].0)"))
//randomize color of screens, mid-swipe
newScreen.fillColor = UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
//replace timeBar
decreaseTimeBlock.fillColor = newScreen.fillColor
decreaseTimeBlock.position = CGPointMake(self.frame.size.width * 1.5, self.frame.size.height * 0.985)
timeBarRedValue = 0.0; timeBarGreenValue = 1.0
newTimeBar.fillColor = UIColor(red: CGFloat(timeBarRedValue), green: CGFloat(timeBarGreenValue), blue: 0.0, alpha: 1.0)
//actions caused by swipe: it's "bringNewScreen" because if you swipeFalse, the newScreen comes from bottom. If you swipeTrue, it comes from the top.
var swipeTrueCurrentScreen = SKAction.moveToX(self.frame.size.width * 2, duration: 0.5)
var bringNewScreen = SKAction.moveToY(self.frame.size.height * 0.5, duration: 0.5)
var bringNewTimeBar = SKAction.moveToY(self.frame.size.height * 0.985, duration: 0.5)
//reset the newScreen and word to the top of the screen, to be dropped again
newScreen.position = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 1)
newImage.position = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 1)
newTimeBar.position = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 1.58)
//swipe word and screen
currentImage.runAction(swipeTrueCurrentScreen)
currentTimeBar.runAction(swipeTrueCurrentScreen)
currentScreen.runAction(swipeTrueCurrentScreen)
//make swiping noise
runAction(SKAction.playSoundFileNamed("Swoosh 3-SoundBible.com-1573211927.mp3", waitForCompletion: false))
//bring in the newScreen
newScreen.runAction(bringNewScreen)
newImage.runAction(bringNewScreen)
newTimeBar.runAction(bringNewTimeBar)
//increase score
++score
scoreLabel.text = "\(score)"
//here, switch the currentScreen with the newScreen so that the process can be repeated
if newScreen == screen1 {
newScreen = screen2
newImage = screenImage2
newTimeBar = timeBar2
currentScreen = screen1
currentImage = screenImage1
currentTimeBar = timeBar1
} else {
newScreen = screen1
newImage = screenImage1
newTimeBar = timeBar1
currentScreen = screen2
currentImage = screenImage2
currentTimeBar = timeBar2
}
} else {
GameOver()
}
}
}
However, for some reason, the image will not move, and when I try to move it in other situations at well, it refuses. How can I fix this?
Except one missing parenthesis here (but I guess it's not the case in your code) the code have no particular reason to not work. The problem is most likely on how you use it.
My guess is that you are doing something like so :
import SpriteKit
class GameScene: SKScene {
var sprite : SKSpriteNode = SKSpriteNode() // A
override func didMoveToView(view: SKView) {
// You are creating another `sprite` variable
// and not using the (A) sprite you declare above
var sprite = SKSpriteNode(imageNamed:"Spaceship") // B
// Here you set the (B) sprite you just created
sprite.size = CGSizeMake(self.frame.size.width * 0.6, self.frame.size.height)
sprite.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
// Here it's still the (B) sprite you just created
// that you add to the scene
self.addChild(sprite)
// You are calling your action from somewhere else
self.applyAction()
}
func applyAction() {
// You create an action, OK
let action = SKAction.moveToX(self.frame.size.width * 2, duration: 0.5)
// You apply the action to the (A) sprite property you have in your class
// Same as : self.sprite.runAction(action)
sprite.runAction(action)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
In that case, you just have to not create another sprite variable. By removing the var keyword on the // B line.
Let met know if it helped.
If it's not the case, please give more details (code, ...).

Do I have to delete a sprite node once I end up using it?

I'm creating a series of moving pipes in my scene. But it always crashes after ~30 pipes are generated. Is it because of too many nodes in the scene and no memories for new ones? The code is like this:
import SpriteKit
class GameScene: SKScene {
var mainPipe: SKSpriteNode = SKSpriteNode()
var space:Float = 1000
var pipeCount:Int = 0
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.blackColor()
self.size.width = 640
self.size.height = 1136
}
func randomOffset() -> Float{
var rNum:Float = Float(arc4random()%181) // 0-180
return rNum
}
var durations: CFloat = 5.0
var colorPipes:UIColor = UIColor.grayColor()
func spawnPipeRow(offs:Float){
self.pipeCount = self.pipeCount + 1
println("\(self.pipeCount)")
//offs is the random number
//let offset = offs + (space/2) - 105
let offset = offs + Float(self.size.height/100) - 180
// mainPipe = SKSpriteNode(color:colorPipes, size:CGSize(width: view.bounds.size.width/3, height:700))
mainPipe = SKSpriteNode(color:colorPipes, size:CGSize(width: self.size.width/5, height:self.size.height/1.5))
let pipeBottom = (mainPipe as SKSpriteNode).copy() as SKSpriteNode
let pipeTop = (mainPipe as SKSpriteNode).copy() as SKSpriteNode
let xx = self.size.width * 2.0
self.setPositionRelativeBot(pipeBottom, x:Float(xx), y: offset )
self.setPositionRelativeTop(pipeTop, x:Float(xx), y: offset + space)
pipeBottom.physicsBody = SKPhysicsBody(rectangleOfSize: pipeBottom.size)
pipeTop.physicsBody = SKPhysicsBody(rectangleOfSize: pipeTop.size)
pipeBottom.physicsBody?.dynamic = false
pipeTop.physicsBody?.dynamic = false
//pipeTop.physicsBody?.contactTestBitMask = birdCategory
//pipeBottom.physicsBody?.contactTestBitMask = birdCategory
self.addChild(pipeBottom)
self.addChild(pipeTop)
var actionArray1:NSMutableArray = NSMutableArray()
actionArray1.addObject(SKAction.moveTo(CGPointMake(-1000, pipeBottom.size.height - 200), duration: NSTimeInterval(durations)))
var actionArray2:NSMutableArray = NSMutableArray()
actionArray2.addObject(SKAction.moveTo(CGPointMake(-1000, pipeTop.size.height - 200), duration: NSTimeInterval(durations)))
actionArray1.addObject(SKAction.removeFromParent())
actionArray2.addObject(SKAction.removeFromParent())
pipeBottom.runAction(SKAction.sequence(actionArray1))
pipeTop.runAction(SKAction.sequence(actionArray2))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
}
}
override func update(currentTime: CFTimeInterval) {
var timeSinceLastUpdate = currentTime - lastUpdateTimerInterval
lastUpdateTimerInterval = currentTime
if(timeSinceLastUpdate > 1){
timeSinceLastUpdate = 1/60
lastUpdateTimerInterval=currentTime
}
updateWithTimeSinceLastUpdate(timeSinceLastUpdate)
/* Called before each frame is rendered */
}
func setPositionRelativeBot(node:SKSpriteNode, x: Float, y: Float){
let xx = (Float(node.size.width)/2) + x
let yy = (Float(self.size.height)/2) - (Float(node.size.height)/2) + y
node.position.x = CGFloat(xx)
node.position.y = CGFloat(yy)
}
func setPositionRelativeTop(node:SKSpriteNode, x:Float, y:Float){
let xx = (Float(node.size.width)/2) + x
let yy = (Float(self.size.height)/2) + (Float(node.size.height)/2) + y
node.position.x = CGFloat(xx)
node.position.y = CGFloat(yy)
}
var lastUpdateTimerInterval:NSTimeInterval = NSTimeInterval()
var lastYieldTimeInterval:NSTimeInterval = NSTimeInterval()
var speedOfBird: CDouble = 1.8
func updateWithTimeSinceLastUpdate(timeSinceLastUpdate:CFTimeInterval){
lastYieldTimeInterval += timeSinceLastUpdate
if(lastYieldTimeInterval > speedOfBird ){
lastYieldTimeInterval=0
self.spawnPipeRow(self.randomOffset())
if speedOfBird > 0.8{
speedOfBird -= 0.1}
}
}
}
You should remove your sprites from the scene when you no longer need them. However your problem is probably not related to the memory occupied by your textures:
SpriteKit Programming Guide
An SKTexture object is created and attached to the sprite. This
texture object automatically loads the texture data whenever the
sprite node is in the scene, is visible, and is necessary for
rendering the scene. Later, if the sprite is removed from the scene or
is no longer visible, Sprite Kit can delete the texture data if it
needs that memory for other purposes. This automatic memory management
simplifies but does not eliminate the work you need to do to manage
art assets in your game.
The texture object itself is just a placeholder for the actual texture
data. The texture data is more resource intensive, so Sprite Kit loads
it into memory only when needed.
If you already have an SKTexture object, you can create new textures
that reference a portion of it. This approach is efficient because the
new texture objects reference the same texture data in memory.
Try to delete them with this code :
override func update(currentTime: CFTimeInterval) {
self.enumerateChildNodesWithName("nodeName") {
node, stop in
if (node is SKSpriteNode) {
let sprite = node as SKSpriteNode
// Check if the node is not in the scene
if (sprite.position.x < -sprite.size.width/2.0 || sprite.position.x > self.size.width+sprite.size.width/2.0
|| sprite.position.y < -sprite.size.height/2.0 || sprite.position.y > self.size.height+sprite.size.height/2.0) {
sprite.removeFromParent()
println("outside")
}
}
}
}
Don't forget to named your node :
node.name = "nodeName"
Hope your crash will stop

Resources