Access Constant from outside Function Swift - ios

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
}

Related

How to spawn images forever? Sprite-Kit

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)
}
}

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.

Embed icon beside a SKLabelNode

I was wondering if there's any way to set an icon besides a SKLabelNode (since I need to use SKAction to move this label up) like this:
All I found about it was using UILabel (here) or a GitHub project (here), where I can't move or bounce (with SpriteKit-Spring) my label.
I was thinking in create a sprite node with the icon image and set it's position besides the coinsLabel, but since this label is used as a coin counter, it would get larger when increased; and the icon would be overlaid.
I made this example project below to make it easier to visualize (it doesn't have the icon, of course. It's only incrementing and moving coinsLabel by buttons).
If you want, you can download it here.
import SpriteKit
class GameScene: SKScene {
//Declaration
var icon = SKSpriteNode()
var coins = Int()
var coinsLabel = SKLabelNode()
var incrementButton = SKSpriteNode()
//Setup
func setupIcon(){
//icon
icon = SKSpriteNode(imageNamed: "icon")
icon.position = CGPoint(x: self.frame.width / 1.45, y: self.frame.height / 1.075)
icon.setScale(0.1)
}
func setupCoinsLabel(){
//coinsLabel
coinsLabel.position = CGPoint(x: self.frame.width / 150 - 300, y: 0)
coinsLabel.setScale(12.5)
coinsLabel.text = "0"
}
func setupIncrementButton(){
//incrementButton
incrementButton = SKSpriteNode(imageNamed: "incrementButton")
incrementButton.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 3.15)
incrementButton.setScale(2.0)
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
setupIcon()
addChild(icon)
setupCoinsLabel()
icon.addChild(coinsLabel)
setupIncrementButton()
addChild(incrementButton)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
//When touch buttons/screen
for touch in touches{
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
//Increment
if node == incrementButton{
coins += 1
coinsLabel.text = NSString(format: "%i", coins) as String
coinsLabel.position = CGPoint(x: self.frame.width / 150 - coinsLabel.frame.width, y: 0)
}
}
}
}
Just make a SKSpriteNode and add it as a child to the SKLabelNode, you can always set the SKSpriteNode's position to be to the right of the SKLabel regardless of how many digits are in your label, so overlapping would never happen
//Increment
if node == incrementButton{
coins += 1
coinsLabel.text = NSString(format: "%i", coins) as String
icon.position = CGPoint(x: coinsLabel.frame.width / 2, y: 0)
}

(SpriteKit + Swift 2) - Remove sprite when user tapped on it

I am trying to make a simple bubble tapped game. I have a created a SKSpriteKid node in my code. The problem is I want to sprite to disappear when user tap on it. I can't seem to removeparent() the node. How do I tackle this?
I have created a sprite with bubble1 as the name.
func bubblePressed(bubble1: SKSpriteNode) {
print("Tapped")
bubble1.removeFromParent()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
if (touchedNode.name == "bubble1") {
touchedNode.removeFromParent()
print("hit")
}
}
}
//the node's creation.
func bubblesinView() {
//create the sprite
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
//spawn in the X axis min is size, max is the total height minus size bubble
let width_bubble_1 = random(min: bubble1.size.width/2, max: size.width - bubble1.size.height/2)
//y: size.height * X, X is 0 at bottom page to max
//spawn position, let x be random
bubble1.position = CGPoint(x: width_bubble_1, y: size.height)
// Add the monster to the scene
addChild(bubble1)
// Determine speed of the monster from start to end
let bubblespeed = random(min: CGFloat(1.1), max: CGFloat(4.0))
//this tell the bubble to move to the given position, changeble x must be a random value
let moveAction = SKAction.moveTo(CGPoint(x: width_bubble_1, y: size.height * 0.1), duration: NSTimeInterval(bubblespeed))
let actionMoveDone = SKAction.removeFromParent()
bubble1.runAction(SKAction.sequence([moveAction, actionMoveDone]))
}
I am really trying to make this work. Thanks in advance!
If you want to use the name property of the node, you should set it to something. The runtime cannot figure out the variable name as the name. So in bubblesInView function after
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
you should do
bubble1.name = "bubble1"
You just forgot to name the node :
//create the sprite
let bubble1 = SKSpriteNode(imageNamed: "bubble_green.png")
bubble1.name = "bubble1"

SKPhysicsJoint makes rotation not work properly

Using Swift and Sprite-Kit, I am trying to create a rope with realistic physics between the location of a static SKSpriteNode called "pin", and wherever the user touches the screen. I am doing this by adding individual SKSpriteNodes called ropeNodes, and linking them up with a series of SKPhysicsJointPins. The physics works just fine, however when I try to rotate each individual piece so that they are oriented properly, the ropeNodes no longer form a straight line, nor do they rotate to the correct angle. When I remove the SKPhysicsJoints however, the rotation works as intended for each separate Node. Moving around the anchorPoint for each individual ropeNode only seemed to jumble things up worse. Why does this happen, and how could I go about fixing it? Thanks in advance (:
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)
dx = pin.position.x - location.x
dy = pin.position.y - location.y
let length = sqrt(pow(dx!, 2) + pow(dy!, 2))
let distanceBetweenRopeNodes = 40
let numberOfPieces = Int(length)/distanceBetweenRopeNodes
var ropeNodes = [SKSpriteNode]()
//adds the pieces to the array and the scene at respective locations
for var index = 0; index < numberOfPieces; ++index{
let point = CGPoint(x: pin.position.x + CGFloat((index) * distanceBetweenRopeNodes) * sin(atan2(dy!, -dx!) + 1.5707), y: pin.position.y + CGFloat((index) * distanceBetweenRopeNodes) * cos(atan2(dy!, -dx!) + 1.5707))
let piece = createRopeNode(point)
piece.runAction(SKAction.rotateByAngle(atan2(-dx!, dy!), duration: 0))
ropeNodes.append(piece)
self.addChild(ropeNodes[index])
}
//Adds an SKPhysicsJointPin between each pair of ropeNodes
self.physicsWorld.addJoint(SKPhysicsJointPin.jointWithBodyA(ropeNodes[0].physicsBody, bodyB: pin.physicsBody, anchor:
CGPoint(x: (ropeNodes[0].position.x + pin.position.x)/2, y: (ropeNodes[0].position.y + pin.position.y)/2)))
for var i = 1; i < ropeNodes.count; ++i{
let nodeA = ropeNodes[i - 1]
let nodeB = ropeNodes[i]
let middlePoint = CGPoint(x: (nodeA.position.x + nodeB.position.x)/2, y: (nodeA.position.y + nodeB.position.y)/2)
let joint = SKPhysicsJointPin.jointWithBodyA(nodeA.physicsBody, bodyB: nodeB.physicsBody, anchor: middlePoint)
self.physicsWorld.addJoint(joint)
}
}
}
func createRopeNode(location: CGPoint) -> SKSpriteNode{
let ropeNode = SKSpriteNode(imageNamed: "RopeTexture")
ropeNode.physicsBody = SKPhysicsBody(rectangleOfSize: ropeNode.size)
ropeNode.physicsBody?.affectedByGravity = false
ropeNode.physicsBody?.collisionBitMask = 0
ropeNode.position = location
ropeNode.name = "RopePiece"
return ropeNode
}
This is an image of what happens when I try to rotate each individual ropeNode
After some thought, I think adding the presentation node as child is a better idea, since you don't need to keep track of the extra nodes in a separate array. To set the rotation of the child, simply unwind the parent's rotation and then add the new rotation angle:
node.zRotation = -node.parent!.zRotation + newRotation
If the rope segments are in a container SKNode, you can iterate over them by
for rope in ropes.children {
if let node = rope.children.first {
let newRotation = ...
node.zRotation = -rope.zRotation + newRotation
}
}

Resources