Swift : Updated Variable in a different scene - swift

I've got a variable in my GameScene, it's score variable, and it's being displayed on screen with SKLabelNode.
Everytime time there is a collision, 1 is being added to score, which is being transfered as a string to the SKLabelNode, and then updated on screen.
Problem is, when I call it from my GameOverScene (scene where the final score is being displayed along with "game over"), I get first value of score, which is 0. Like GameOverScene is reading variable, but not the updated one.
How to get the updated variable? Can anyone help?
Code:
class GameScene: SKScene, SKPhysicsContactDelegate {
// S C O R E !
var score = 0
var scoreText: String = ""
var scoreOnScreen = SKLabelNode()
...
override func didMoveToView(view: SKView) {
// S C O R E
scoreOnScreen.position = CGPoint(x: size.width / 2, y: size.height * 0.8)
scoreText = String(score)
scoreOnScreen.text = scoreText
when collision takes place, score get's +1
func bulletDidCollideWithEnemy(enemy: SKSpriteNode, bullet: SKSpriteNode) {
score++
scoreText = String(score)
scoreOnScreen.text = scoreText
and then, the GameOverScene:
import SpriteKit
import UIKit
class GameOverScene: SKScene {
let GameSceneInstance = GameScene()
let bgImage = SKSpriteNode(imageNamed: "background")
let afraidLogo = SKSpriteNode(imageNamed: "gameoverlogo")
var gameOverLabel = SKLabelNode()
override func didMoveToView(view: SKView) {
GameSceneInstance.scoreOnScreen.text = String(GameSceneInstance.score)
bgImage.position = CGPoint(x: size.width / 2, y: size.height / 2)
bgImage.setScale(0.75)
addChild(bgImage)
afraidLogo.position = CGPoint(x: size.width / 2, y: size.height / 2)
afraidLogo.setScale(0.50)
addChild(afraidLogo)
gameOverLabel.fontSize = 40
gameOverLabel.fontColor = SKColor.whiteColor()
gameOverLabel.text = "score: \(GameSceneInstance.scoreOnScreen.text)"
gameOverLabel.position = CGPointMake(self.size.width/2, 2.0 / 3.0 * self.size.height);
addChild(gameOverLabel)
}
I bet it's just a simple issue with the code. Appreciate any help.

I figured it out with some help from a reddit guy with a nickname Jasamer. Guy rocks.
Here's what I've changed:
when the scene is being changed
let scene = GameOverScene(size: skView.bounds.size)
I added:
scene.gameScene = self
scene.score = score
and then, the GameOverScene:
class GameOverScene: SKScene {
var gameScene = GameScene()
override func didMoveToView(view: SKView) {
gameOverLabel.text = "score: \(gameScene.score)"
and it works.
The main problem I think was not setting scene.GameScene = self.
I hope this helps someone someday.

Related

how to carry across values across scenes

I am trying to make a 'money' value show on 3 different scenes, and it is showing on the GameScene but not on the GameOverScene or the ShopScene.
this is the relevant code of the GameScene that is working:
func adjustMoney(by points: Int){
var money = UserDefaults().integer(forKey: "moneyscore")
money += points
MoneyLabel.text = "Money = " + UserDefaults().integer(forKey: "moneyscore").description
UserDefaults().set(money, forKey: "moneyscore")
}
func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
print("Hit")
projectile.removeFromParent()
monster.removeFromParent()
monstersDestroyed += 1
adjustScore(by: 1)
adjustMoney(by: 2)
and this is the total code in the other scenes (there is just the ShopScene as it is the same in the other):
import Foundation
import SpriteKit
var welcomeLabel: SKLabelNode!
class ShopScene: SKScene {
var background = SKSpriteNode(imageNamed: "background")
override func didMove(to view: SKView) {
background.zPosition = -1
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background) welcomeLabel = SKLabelNode(fontNamed: "Chalkduster")
welcomeLabel.text = "Welcome to the shop!"
welcomeLabel.fontSize = 40
welcomeLabel.fontColor = SKColor.black
welcomeLabel.position = CGPoint(x: size.width/2, y: size.height/1.2)
addChild(welcomeLabel)
MoneyLabel = SKLabelNode(fontNamed: "Chalkduster")
MoneyLabel.text = "Money = \(money)"
MoneyLabel.fontSize = 20
MoneyLabel.fontColor = SKColor.black
MoneyLabel.position = CGPoint(x: size.width/6.2, y: size.height/1.35)
addChild(MoneyLabel)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let gameScene = GameScene(size: self.size)
gameScene.scaleMode = .aspectFill
self.view?.presentScene(gameScene, transition: SKTransition.doorsCloseHorizontal(withDuration: 1.0))
}
}
However it only says money = 0
---- I'm not sure if it is of any relevance but before my 'projectile' and 'monster' first collide each game all my values of high score and score and money are 0 then they go back to their saved values, apart from score obviously that goes to 0. Score is the only value that isn't saved and that does move across to the GameOverScene.
You need to access UserDefaults().integer(forKey: "moneyscore") to use this in both the GameOverScene or the ShopScene class as you are updating userdefaults value(money).
This is actually for your specific case only. On the other hand, if you are interested to pass data in different classes(you can mention them as screens if they have views), you can follow different methodologies. You can follow this link for references.

Move a sprite randomly using Swift 3

I was trying to make a game where the dragon moves around randomly and the hero has to avoid it. I can make the dragon appear at a random location and move once or twice but to make it continuously move from point to point and then move some more has given me trouble. I think it might be because I'm not waiting for the action to complete before generating all of the random numbers. I tried the following code including labels to prove to myself that the random numbers are generating, at least up to 20...
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private let greenDragonNode = GreenDragonSprite.newInstance()
private var lastUpdateTime : TimeInterval = 0
private var i = 0
override func sceneDidLoad() {
self.lastUpdateTime = 0
let xOrigin = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yOrigin = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonOrigin = CGPoint(x: xOrigin, y: yOrigin)
greenDragonNode.position = dragonOrigin
greenDragonNode.setScale(0.1)
addChild(greenDragonNode)
}
override func didMove(to view: SKView) {
for i in 1...20 {
let xDestination = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yDestination = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonDestination = CGPoint(x: xDestination, y: yDestination)
let xDestination2 = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yDestination2 = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonDestination2 = CGPoint(x: xDestination2, y: yDestination2)
let dragonNodeTravel = SKAction.move(to:dragonDestination, duration: 3.0)
let dragonNodeReturn = SKAction.move(to: dragonDestination2, duration: 3.0)
greenDragonNode.run(SKAction.sequence([dragonNodeTravel, dragonNodeReturn]))
// i += 1
let label = SKLabelNode(text: "\(i)")
label.position = dragonDestination2
addChild(label)
}
}
}
That is happening because only the last action from the for loop is executed. You have to make a queue of actions. So you should append them to an array, and run them in a sequence, like this:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private let greenDragonNode = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
func getRandomPoint(withinRect rect:CGRect)->CGPoint{
let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: rect.size.width)
let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: rect.size.height)
return CGPoint(x: x, y: y)
}
override func didMove(to view: SKView) {
greenDragonNode.position = self.getRandomPoint(withinRect: frame)
addChild(greenDragonNode)
var actions:[SKAction] = []
for i in 1...20 {
let destination = getRandomPoint(withinRect: frame)
let move = SKAction.move(to:destination, duration: 1.0)
actions.append(move)
let label = SKLabelNode(text: "\(i)")
label.position = destination
addChild(label)
}
let sequence = SKAction.sequence(actions)
greenDragonNode.run(sequence)
}
}

How can I add a physics body to an SKAtlasTexture or create an animation through Images.xcassets?

I wanted to create a small animation of a car driving down the road, so I made an atlas of 9 different pictures. The car simply looks like its wheels are rotating and the car is bouncing a bit as it drives along. I already made an SKSpriteNode with an image and added a physics body on it so that it can jump and be affected by gravity.
So I was wondering how to add either a physics body to an SKAtlasTexture or create an animation through my image.xcassets folder. I tried to just change the SKSpriteNode to SKAtlasTexture, but that obviously didn't work as there are no physics bodies in SKAtlasTexture. So that's where I'm at. Any suggestions or solutions would be greatly appreciated.
Here some parts of my code:
class PlayScene: SKScene, SKPhysicsContactDelegate {
let road = SKSpriteNode(imageNamed: "road")
var origRoadPositionX = CGFloat(0)
var maxRoad = CGFloat(0)
var groundSpeed = 3
var carBaseLine = CGFloat(0)
let car = SKSpriteNode(imageNamed: "car")
enum ColliderType:UInt32{
case car = 1
case tower = 2
}
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor(hex: 0x80E8FF)
self.physicsWorld.contactDelegate = self
//Car
self.car.position = CGPointMake(CGRectGetMinX(self.frame)-20 + self.car.size.width, self.carBaseLine)
self.car.physicsBody = SKPhysicsBody (rectangleOfSize: self.car.size)
self.car.physicsBody?.allowsRotation = false
self.car.physicsBody?.affectedByGravity = false
self.car.physicsBody?.categoryBitMask = ColliderType.car.rawValue
self.car.physicsBody?.contactTestBitMask = ColliderType.tower.rawValue
self.car.physicsBody?.collisionBitMask = ColliderType.tower.rawValue
self.addChild(car)
If more code is needed in order to find a solution, let me know and i can supply more of it.
You can use atlas folder for performing animation with images.
Consider below example:
import SpriteKit
class GameScene: SKScene {
var bombFrames : [SKTexture]!
var bomb : SKSpriteNode!
let NoneCategory : UInt32 = 0x1 << 0
let ProjectileCategory : UInt32 = 0x1 << 2
let bombCategory : UInt32 = 0x1 << 7
override func didMoveToView(view: SKView) {
/* Setup your scene here */
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addBomb), SKAction.waitForDuration(5.0)])))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addBomb() {
let Name = "Bomb"
let AnimatedAtlas = SKTextureAtlas(named: Name)
var Framese = [SKTexture]()
let numImages = AnimatedAtlas.textureNames.count
for var i=1; i<=numImages; i++ {
let TextureName = "\(i)"
Framese.append(AnimatedAtlas.textureNamed(TextureName))
}
bombFrames = Framese
let firstFrame = bombFrames[0]
bomb = SKSpriteNode(texture: firstFrame)
let actualY = random(min: bomb.size.height/2, max: size.height - bomb.size.height/2)
bomb.position = CGPoint(x: size.width + bomb.size.width/2, y: actualY)
bomb.physicsBody = SKPhysicsBody(texture: bomb.texture, size: bomb.texture!.size())
bomb.physicsBody?.dynamic = true
bomb.physicsBody?.categoryBitMask = bombCategory
bomb.physicsBody?.contactTestBitMask = ProjectileCategory
bomb.physicsBody?.collisionBitMask = NoneCategory
bomb.physicsBody?.usesPreciseCollisionDetection = true
addChild(bomb)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.moveTo(CGPoint(x: -bomb.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
bomb.runAction(SKAction.sequence([actionMove, actionMoveDone]))
playBombAnimation()
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func playBombAnimation() {
bomb.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(bombFrames, timePerFrame: 0.1, resize: false, restore: true)), withKey:"bombAnimation")
}
}
And don't forget to add atlas folder into your project navigator like this:
As you can see in code you can add physics body to your sprite. and if you want you can try this way.
Hope this will help.

Swift SpriteKit get visible frame size

I have been trying to create a simple SpriteKit app using Swift. The purpose is to have a red ball re-locate itself on the screen when clicked on. But the variables self.frame.width and self.frame.height do not return the boundaries of the visible screen. Instead they return boundaries of the whole screen. Because I am choosing the location of the ball at random I need the visible boundaries. Couldn't find an answer after hours of research. How can I achieve this?
var dot = SKSpriteNode()
let dotScreenHeightPercantage = 10.0
let frameMarginSize = 30.0
override func didMoveToView(view: SKView) {
var dotTexture = SKTexture(imageNamed: "img/RedDot.png")
dot = SKSpriteNode(texture: dotTexture)
dot.size.height = CGFloat( Double(self.frame.height) / dotScreenHeightPercantage )
dot.size.width = dot.size.height
dot.name = "dot"
reCreateDot()
}
func reCreateDot() {
dot.removeFromParent()
let dotRadius = Double(dot.size.height / 2)
let minX = Int(frameMarginSize + dotRadius)
let maxX = Int(Double(self.frame.width) - frameMarginSize - dotRadius)
let minY = Int(frameMarginSize + dotRadius)
let maxY = Int(Double(self.frame.height) - frameMarginSize - dotRadius)
let corX = randomInt(minX, max: maxX)
let corY = randomInt(minY, max: maxY)
println("result: \(corX) \(corY)")
dot.position = CGPoint(x: corX, y: corY)
self.addChild(dot)
}
func randomInt(min: Int, max:Int) -> Int {
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node.name == "dot" {
println("Dot tapped.")
reCreateDot()
}
}
}
Adding this bit of code to the GameViewController seems to have fixed the issue.
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
Thank you #ABakerSmith for pointing me in the right direction.
I tried with your code and setting the size of the scene fixed the issue. If you're unarchiving the scene, its size will be the size set in the sks file. Therefore you need to set the size of the scene to the size of your SKView.
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
scene.size = skView.frame.size
skView.presentScene(scene)
}
or in didMoveToView of your SKScene subclass:
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
self.size = view.frame.size
}

Collision Detection In Sprite Kit using Swift

The code below is from a project I am currently working on, I have been trying to teach myself Swift language and Sprite Kit for the past few days and this is my first attempt at a game, it is a Flappy Bird type game. I ran into a problem today when I was trying to write the code for the collision detection. When the bird touches one of the pipes the game is supposed to pause. However when I run the code and the bird touches the pipe, nothing happens, the bird just bounces off of it. I have read many tutorials and watched many videos on this subject to try and resolve my problem and haven't had any luck. I have written all of the collision detection code that I learned off of the last video I watched in the code below. Could anyone please tell me what I am doing wrong. Any advice would be greatly appreciated, thank you.
//
// GameScene.swift
// Bird Flappy Game
//
// Created by Brandon Ballard on 1/4/15.
// Copyright (c) 2015 Brandon Ballard. All rights reserved.
//
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var bird = SKSpriteNode()
var pipeUpTexture = SKTexture()
var pipeDownTexture = SKTexture()
var pipesMoveAndRemove = SKAction()
let pipeGap = 150.0
enum ColliderType:UInt32 {
case BIRD = 1
case PIPE = 2
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
backgroundColor = SKColor.cyanColor()
//physics
self.physicsWorld.gravity = CGVectorMake(0.0, -15.0);
self.physicsWorld.contactDelegate = self
func didBeginContact(contact: SKPhysicsContactDelegate) {
scene?.view?.paused = true
bird.setScale(12.0)
}
//Bird
var birdTexture = SKTexture(imageNamed:"Bird")
birdTexture.filteringMode = SKTextureFilteringMode.Nearest
bird = SKSpriteNode(texture: birdTexture)
bird.setScale(0.6)
bird.position = CGPoint(x: 375, y: self.frame.size.height * 0.6)
bird.physicsBody = SKPhysicsBody(circleOfRadius: bird.size.height / 2.0)
bird.physicsBody?.dynamic = true
bird.physicsBody?.allowsRotation = true
bird.physicsBody?.affectedByGravity = true
bird.physicsBody!.categoryBitMask = ColliderType.BIRD.rawValue
bird.physicsBody!.contactTestBitMask = ColliderType.PIPE.rawValue
bird.physicsBody!.collisionBitMask = ColliderType.PIPE.rawValue
self.addChild(bird)
//Ground
var groundTexture = SKTexture(imageNamed: "Ground")
var sprite = SKSpriteNode(texture: groundTexture)
sprite.setScale(2.0)
sprite.position = CGPointMake(self.size.width / 2, sprite.size.height / 2.0)
self.addChild(sprite)
var ground = SKNode()
ground.position = CGPointMake(0, groundTexture.size().height + 0)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, groundTexture.size().height * 2.0))
ground.physicsBody?.dynamic = false
self.addChild(ground)
//Pipes
//Create the Pipes
pipeUpTexture = SKTexture(imageNamed: "PipeUp")
pipeDownTexture = SKTexture(imageNamed: "PipeDown")
//Movement of Pipes
let distanceToMove = CGFloat(self.frame.size.width + 2.0 * pipeUpTexture.size().width)
let movePipes = SKAction.moveByX(-distanceToMove, y: 0.0, duration: NSTimeInterval(0.01 * distanceToMove))
let removePipes = SKAction.removeFromParent()
pipesMoveAndRemove = SKAction.sequence([movePipes,removePipes])
//Spawn Pipes
let spawn = SKAction.runBlock({() in self.spawnPipes()})
let delay = SKAction.waitForDuration(NSTimeInterval(2.0))
let spawnThenDelay = SKAction.sequence([spawn,delay])
let spawnThenDelayForever = SKAction.repeatActionForever(spawnThenDelay)
self.runAction(spawnThenDelayForever)
}
func spawnPipes() {
let pipePair = SKNode()
pipePair.position = CGPointMake(self.frame.size.width + pipeUpTexture.size().width * 2, 0)
pipePair.zPosition = -10
let height = UInt32(self.frame.size.height / 4)
let y = arc4random() % height + height
var pipeDown = SKSpriteNode(texture: pipeDownTexture)
pipeDown.setScale(2.0)////////
pipeDown.position = CGPointMake(3.0, CGFloat(y) + pipeDown.size.height + CGFloat(pipeGap) )
pipeDown.physicsBody = SKPhysicsBody(rectangleOfSize: pipeDown.size)
pipeDown.physicsBody?.dynamic = false
pipeDown.physicsBody!.affectedByGravity = false
pipeDown.physicsBody!.categoryBitMask = ColliderType.PIPE.rawValue
pipeDown.physicsBody!.contactTestBitMask = ColliderType.BIRD.rawValue
pipeDown.physicsBody!.collisionBitMask = ColliderType.BIRD.rawValue
pipePair.addChild(pipeDown)
var pipeUp = SKSpriteNode(texture: pipeUpTexture)
pipeUp.setScale(2.0)
pipeUp.position = CGPointMake(0.0, CGFloat(y))
pipeUp.physicsBody = SKPhysicsBody(rectangleOfSize: pipeUp.size )
pipeUp.physicsBody?.dynamic = false
pipeUp.physicsBody!.affectedByGravity = false
pipeUp.physicsBody!.categoryBitMask = ColliderType.PIPE.rawValue
pipeUp.physicsBody!.contactTestBitMask = ColliderType.BIRD.rawValue
pipeUp.physicsBody!.collisionBitMask = ColliderType.BIRD.rawValue
pipePair.addChild(pipeUp)
pipePair.runAction(pipesMoveAndRemove)
self.addChild(pipePair)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
bird.physicsBody?.velocity = CGVectorMake( 0, 0 )
bird.physicsBody?.applyImpulse(CGVectorMake(0,25))
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
From what I can see, you only need to DETECT collisions, not actually simulate them. For this, you need to set only the contactTestBitMask of the physicsBodies. You can set the collisionBitMask as 0.
bird.physicsBody!.collisionBitMask = 0
pipe.physicsBody!.collisionBitMask = 0
Also, as hamobi has already said, the didBeginContact method needs to be outside the didMoveToView method with the override keyword. (This question has the exact same problem as yours)
class GameScene: SKScene, SKPhysicsContactDelegate {
// ...
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
// set as delegate:
self.physicsWorld.contactDelegate = self
// ..
}
// should be called now
func didBeginContact(contact: SKPhysicsContact){
scene?.view?.paused = true
bird.setScale(12.0)
}
}
You put your didBeginContact INSIDE of didMoveToView. It's not callable from there. Put it in the body of your class