How to spawn images forever? Sprite-Kit - ios

Im trying to create a dodgeball feature in my game and I need to repeatedly spawn a dodgeball and the current code I used (shown below) results in Thread 1: Exception: "Attemped to add a SKNode which already has a parent: name:'(null)' texture:[ 'dodgeball5' (500 x 500)] position:{-140.00019836425781, -55.124687194824219} scale:{1.00, 1.00} size:{30, 30} anchor:{0.5, 0.5} rotation:0.00". What am I doing incorrect?
class ClassicLevelScene: SKScene {
// Right Pointing Cannons
var rightPointingCannon: [SKReferenceNode] = []
// Dodgeball 5 Constants
var dodgeball5 = SKSpriteNode(imageNamed: "dodgeball5")
// Dodgeball SKActions
var dodgeballRepeat = SKAction()
let dodgeballMoveLeft = SKAction.moveBy(x: -400, y: 0, duration: 0.5)
let dodgeballMoveRight = SKAction.moveBy(x: 400, y: 0, duration: 0.5)
let dodgeballMoveDone = SKAction.removeFromParent()
let dodgeballWait = SKAction.wait(forDuration: 1)
var dodgeballLeftSequence = SKAction()
var dodgeballRightSequence = SKAction()
override func didMove(to view: SKView) {
// Cannon Setup
for child in self.children {
if child.name == "rightPointingCannon" {
if let child = child as? SKReferenceNode {
rightPointingCannon.append(child)
dodgeball5.position = child.position
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(spawnDodgeball),
SKAction.wait(forDuration: 1.0)
])
))
}
}
}
// Dodgeball Right Sequence
dodgeballRightSequence = SKAction.sequence([dodgeballMoveRight, dodgeballMoveDone, dodgeballWait])
}
func spawnDodgeball() {
dodgeball5.zPosition = -1
dodgeball5.size = CGSize(width: 30, height: 30)
addChild(dodgeball5)
dodgeball5.run(dodgeballRightSequence)
}
}

Assuming single ball and ball can go off screen:
I would actually recommend against cloning the sprite.
Simply reuse it.
Once it's off the screen, you can reset it's position and speed and display it again. No need to create lots of objects, if you only need one.

One SKNode only can have 1 parent at time.
You need to create a new SKSpriteNode or clone the dodgeball5 before to add again on spawnDodgeball()
You can use
addChild(dodgeball5.copy())
instead of
addChild(dodgeball5)
But be make sure dodgeball5 have no parent before.

Your code seems to be unnecessarily complex.
all you need to do is copy() you ball and add it to your scene
Also all of your SKActions don't need to be declared at a class level
And it's not clear why you are creating an array of cannons when only one is instantiated
class ClassicLevelScene: SKScene {
// Dodgeball 5 Constants
var dodgeball5 = SKSpriteNode(imageNamed: "dodgeball5")
override func didMove(to view: SKView) {
setupCannonAndBall()
let dodgeballMoveRight = SKAction.moveBy(x: 400, y: 0, duration: 0.5)
let dodgeballMoveDone = SKAction.removeFromParent()
let dodgeballWait = SKAction.wait(forDuration: 1)
dodgeballRightSequence = SKAction.sequence([dodgeballMoveRight, dodgeballMoveDone, dodgeballWait])
//setup sequences ^ before spawning ball
let spawn = SKAction.run(spawnDodgeball)
let wait = SKAction.wait(forDuration: 1.0)
run(SKAction.repeatForever(SKAction.sequence([spawn, wait])))
}
func setupCannonAndBall() {
if let cannonRef = SKReferenceNode(fileNamed: "rightPointingCannon") {
dodgeball5.position = cannonRef.position
dodgeball5.isHidden = true
dodgeball5.size = CGSize(width: 30, height: 30)
dodgeball5.zPosition = -1
}
}
func spawnDodgeball() {
let dodgeball = dodgeball.copy()
dodgeball.isHidden = false
addChild(dodgeball)
dodgeball.run(dodgeballRightSequence)
}
}

Related

How to detect when an SKSpriteNode hits another SKSpriteNode?

I'm trying to make a game where you tilt your phone and try to keep the ball inside the boundary. I can't seem to figure out how to make the ball not go through the boundary. I have the tilt to move the ball working, but it just goes through all my boundaries and I can't seem to figure out how to make the ball stop when it comes in contact with a boundary. Here is my code:
override func didMove(to view: SKView) {
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody = border
boundary = (self.childNode(withName: "boundry") as! SKSpriteNode) //the boundary is spelled wrong
airplane = SKSpriteNode(imageNamed: "ball image")
airplane.physicsBody = SKPhysicsBody(circleOfRadius: 10)
airplane.position = CGPoint(x: -211.163, y: 367.3)
airplane.size = CGSize(width: 50, height: 50)
airplane.physicsBody?.isDynamic = true
airplane.physicsBody?.affectedByGravity = false
airplane.physicsBody?.allowsRotation = true
airplane.physicsBody?.pinned = false
self.addChild(airplane)
if motionManager.isAccelerometerAvailable {
// 2
motionManager.accelerometerUpdateInterval = 0.01
motionManager.startAccelerometerUpdates(to: .main) {
(data, error) in
guard let data = data, error == nil else {
return
}
// 3
let currentX = self.airplane.position.x
self.destX = currentX + CGFloat(data.acceleration.x * 500)
let currentY = self.airplane.position.y
self.destY = currentY + CGFloat(data.acceleration.y * 500)
}
}
}
override func update(_ currentTime: TimeInterval) {
let action = SKAction.moveTo(x: destX, duration: 1)
let action2 = SKAction.moveTo(y: destY, duration: 1)
airplane.run(action)
airplane.run(action2)
}
In SpriteKit things don't collide unless you give them a set of matching collisionBitMask. ie border.collisionBitMask & airplane.collisionBitMask need to have at least one non zero bit in common. Try setting both to 1 to begin with.
Another thing is that you may need to add a distance constraint to airplane so that it cannot escape from the border if speed is too high.

Swift: Detect when SKSpriteNode has even touched

I have a game where an object falls from the top of the screen to the bottom randomly. I want to have it to where if the object is tapped, I can tell it to do something, like add points to the score.
Can you help me try to figure this out? I'll be on to answer any questions if you happen to have any. Here is the code I'm using:
class GameSceneTest: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.clearColor()
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
//Change duration
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addObject), SKAction.waitForDuration(1)])
))
//AddMusicHere
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func add Object() {
let Object = SKSpriteNode(imageNamed: "Object\(arc4random_uniform(6) + 1).png")
Object.userInteractionEnabled = true
Object.size = CGSize(width: 50, height: 50)
Object.physicsBody = SKPhysicsBody(rectangleOfSize: Object.size)
Object.physicsBody?.dynamic = true
//Determine where to spawn gem across y axis
let actually = random(min: Object.size.width/2, max: size.width - Object.size.width/2)
//Position Object slightly off screen along right edge
//and along random y axis point
//Object.position = CGPoint(x: size.width + Object.size.width/2 , y: actually)
Object.position = CGPoint(x: actually, y: size.height + Object.size.height/2)
//Add the Object to the scene
addChild(Object)
//Determines speed of Object (edit later to where speed depends on type of Object)
let actualDuration = random(min: CGFloat(4), max: CGFloat(5))
//Create the Actions
let actionMove = SKAction.moveTo(CGPoint(x: actually, y: gem.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let loseAction = SKAction.runBlock() {
let reveal = SKTransition.flipHorizontalWithDuration(0.5) //Change to flipe vertical
let gameOverScene = GameOverScene(size: self.size, won: false)
self.view?.presentScene(gameOverScene, transition: reveal)
}
Object.runAction(SKAction.sequence([actionMove, loseAction, actionMoveDone]))
}
Take a look at "Moving The Cards Around The Board" in the following tutorial: Ray Wenderlicht SpriteKit Tutorial.
Then read a bit more around handling touch events in iOS (e.g. Handling Touches...).
Then try something in code and if you get stuck post a more specific question - because this one is a little too general.

Access Constant from outside Function Swift

I have a game that runs like this: The class file is run and uses the function "addObject()" to an add an object to the screen. The object then falls from the top of the screen to the bottom and the player has to try to catch the object by tapping on it. However, the object is declared (as an SKSpriteNode)in the addObject function. So when I go to add the "touch controls" function to the game, I can't reference the object. What can I do to fix this? Here is some of my code:
class GameSceneTest: SKScene, SKPhysicsContactDelegate {
var ObjectDestroyed = 0
let label = SKLabelNode(fontNamed: "Title 1")
var money: Int = 0
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.clearColor()
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
//Change duration
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addObject), SKAction.waitForDuration(1)])
))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addObject() {
let Object = SKSpriteNode(imageNamed: "Object\(arc4random_uniform(6) + 1).png")
Object.size = CGSize(width: 50, height: 50)
Object.physicsBody = SKPhysicsBody(rectangleOfSize: Object.size)
Object.physicsBody?.dynamic = true
//Determine where to spawn Object across y axis
let actually = random(min: Object.size.width/2, max: size.width - Object.size.width/2)
//Position Object slightly off screen along right edge
//and along random y axis point
//Object.position = CGPoint(x: size.width + Object.size.width/2 , y: actually)
Object.position = CGPoint(x: actually, y: size.height + Object.size.height/2)
//Add the Object to the scene
addChild(Object)
//Determines speed of Object (edit later to where speed depends on type of Object)
let actualDuration = random(min: CGFloat(4), max: CGFloat(5))
//Create the Actions
let actionMove = SKAction.moveTo(CGPoint(x: actually, y: gem.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let loseAction = SKAction.runBlock() {
let reveal = SKTransition.flipHorizontalWithDuration(0.5) //Change to flipe vertical
let gameOverScene = GameOverScene(size: self.size, won: false)
self.view?.presentScene(gameOverScene, transition: reveal)
}
Object.runAction(SKAction.sequence([actionMove, loseAction, actionMoveDone]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
if Object.containsPoint(touch.locationInNode(self)) {
Object.removeFromParent()
print("touched")
}
}
}
There are several different ways that you could correctly implement this. One option would be to make an class level variable that is an array of type SKSpriteNode and append the objects to it when you create them in that function.
// Declare an array variable at the class level
var objectArray = [SKSpriteNode]()
// Then in the function after you create the object and configure it
objectArray.append(object)
Another alternative would be to use a dictionary that you add the new objects to. It just depends on exactly what you need to do with the objects when you reference them again.
Note: This answer assumes that you will have multiple objects that you need reference to at any given time. If you only need reference to one at a time, you can just make a class level variable of type SKSpriteNode and set it in your addObject function
For Example:
// Declaring class level variable
var myNode: SKSpriteNode?
When your addObject method is called, set myNode equal to the object that you create and configure
// When you go to use myNode, just unwrap it to make sure that it has a value
if let unwrappedNode = self.myNode {
// Do something with unwrappedNode
}

Trying to delay spawn nodes using waitForDuration in didMoveToView with SpriteKit Scene (SWIFT) but not working

I'm trying to stop my nodes from falling just for a second or two at the start of my game. So my problem is when I push start the nodes are already halfway down the screen. I also tried changing how high the nodes start but it seems like a costly solution since I want to be careful not to let my FPS get too low. In my code I am trying to do this in the didMoveToView and I am using waitForDuration but it doesn't work.
Example Image of Nodes Falling Down
Any SpriteKit masters know what I should do? I'm using Swift.
Here is my code:
override func didMoveToView(view: SKView) {
let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
self.spawnNumbers()
}
numContainer.runAction(SKAction.sequence([wait, run]))
}
func spawnNumbers() {
let minValue = self.size.width / 8
let maxValue = self.size.width - 36
let spawnPoint = CGFloat(arc4random_uniform(UInt32(maxValue - minValue)))
let action = SKAction.moveToY(-300, duration: 2)
numContainer = SKSpriteNode(imageNamed: "Circle")
numContainer.name = "Circle"
numContainer.size = CGSize(width: 72, height: 72)
numContainer.anchorPoint = CGPointMake(0, 0)
numContainer.position = CGPoint(x: spawnPoint, y: self.size.height)
numContainer.runAction(SKAction.repeatActionForever(action))
numContainer.zPosition = 2
let numberLabel = SKLabelNode(fontNamed: "AvenirNext-Bold")
numberLabel.text = "\(numToTouch)"
numberLabel.name = "Label"
numberLabel.zPosition = -1
numberLabel.position = CGPointMake(CGRectGetMidX(numContainer.centerRect) + 36, CGRectGetMidY(numContainer.centerRect) + 36)
numberLabel.horizontalAlignmentMode = .Center
numberLabel.verticalAlignmentMode = .Center
numberLabel.fontColor = UIColor.whiteColor()
numberLabel.fontSize = 28
addChild(numContainer)
numContainer.addChild(numberLabel)
numContainerArray.append(numContainer)
numToTouch += 1
}
I think you have to cast the 2.5 seconds to an NSTimeInterval:
let wait = SKAction.waitForDuration(NSTimeInterval(2.5))
You could also try putting the actions in the init function for your scene:
override init (size: CGSize) {
super.init(size: size)
//your code from before
}
Also, not sure if it matters, but this is what I normally do and it works,
let wait = SKAction.waitForDuration(NSTimeInterval(2.5))
let action = SKAction.runBlock({() in self.spawnNumbers()})
let actionThenWait = SKAction.sequence([wait, action])
self.runAction(actionThenWait)

How to interact with a SKSpriteNode after another one of the same var name has been created?

I appologise for the confusing question title, but I wasn't quite sure how to phrase my question. My ultimate goal is to remove an SKSpriteNode a certain amount of time after it's creation. However, there is a small complication. Using the following code
func remove() {
laser.removeFromParent()
}
runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.runBlock(remove)
])
)
I'm able to remove the SKSpriteNode I named 'laser'. When I call my function once, fireLasers(), everything runs smoothly and the laser disappears after 5 seconds. The problem is when I call it twice within a period of 5 seconds. When this happens, the first laser will stick around indefinitely and the second disappears earlier than intended. I understand why this happens, but would like to know if there's a way around it. Here's the code for the GameScene which creates the world, background image, player Sprite, and defines the fireLasers function. There's a lot of stuff that goes on in the ViewController that works with the velocities and directions of the sprites, but I hope this is sufficient to find a solution.
import SpriteKit
class GameScene: SKScene {
var player = SKSpriteNode()
var world = SKShapeNode()
var worldTexture = SKSpriteNode()
var laser = SKShapeNode()
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPointMake(0.5, 0.5)
self.size = CGSizeMake(view.bounds.size.width, view.bounds.size.height)
// Add world
world = SKShapeNode(rectOfSize: CGSizeMake(7000, 7000))
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path!)
world.fillColor = SKColor.blackColor()
self.addChild(world)
// Add Background Image
worldTexture = SKSpriteNode(imageNamed: "grid")
worldTexture.size = CGSize(width: 7000, height: 7000)
world.addChild(worldTexture)
// Add player
player = SKSpriteNode(imageNamed: "Spaceship")
player.size = CGSize(width: 50, height: 50)
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 50, height: 50))
player.physicsBody?.affectedByGravity = false
player.physicsBody?.dynamic = true
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x
world.position.y = -player.position.y
}
func fireLasers() {
laser = SKShapeNode(rectOfSize: CGSizeMake(10, 50))
laser.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 10, height: 50))
laser.position.x = player.position.x
laser.position.y = player.position.y + CGFloat(50)
laser.fillColor = SKColor.redColor()
laser.physicsBody?.dynamic = true
laser.physicsBody?.affectedByGravity = false
world.addChild(laser)
func remove() {
laser.removeFromParent()
}
runAction(
SKAction.sequence([
SKAction.waitForDuration(5.0),
SKAction.runBlock(remove)
])
)
}
}
Instead of keeping a reference to the laser (which will, as you noticed, force you to only have one around at a time), why not run an action on the created laser nodes themselves? This will allow you to use the handy SKAction class method removeFromParent():
let newLaser = makeNewLaser()
let laserAction = SKAction.sequence([SKAction.waitForDuration(5), SKAction.removeFromParent()])
newLaser.runAction(laserAction)

Resources