What I want to know:
I want to know that how to make buttons/labels appear and disappear. When my character collides with an object the buttons/labels will show up over the view and the game-view wont be working any more, only the buttons/labels that appeared can be interacted with.
What I have tried:
I have tried .hidden = false and .hidden = true but it didn't work but maybe I was not using it correctly.
CODE: I have delete unnecessary code!
import Foundation
import AVFoundation
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var movingGround: PPMovingGround!
var square1: PPSquare1!
var square2: PPSquare2!
var wallGen: PPWallGen!
var isStarted = false
var isGameOver = false
override func didMoveToView(view: SKView) {
addMovingGround()
addSquare1()
addWallGen()
start()
}
func addSquare1() {
square1 = PPSquare1()
square1.position = CGPointMake(70, movingGround.position.y + movingGround.frame.size.height/2 + square1.frame.size.height/2)
square1.zPosition = 1
playerNode.addChild(square1)
}
func addWallGen() {
wallGen = PPWallGen(color: UIColor.clearColor(), size: view!.frame.size)
wallGen.position = view!.center
addChild(wallGen)
}
func start() {
isStarted = true
//square2.stop()
square1.stop()
movingGround.start()
wallGen.startGenWallsEvery(1)
}
// MARK - Game Lifecycle
func gameOver() {
isGameOver = true
// everything stops
//square2.fall()
square1.fall()
wallGen.stopWalls()
diamondGen.stopDiamonds()
movingGround.stop()
square1.stop()
//square2.stop()
// create game over label
let gameOverLabel = SKLabelNode(text: "Game Over!")
gameOverLabel.fontColor = UIColor.whiteColor()
gameOverLabel.fontName = "Helvetica"
gameOverLabel.position.x = view!.center.x
gameOverLabel.position.y = view!.center.y + 80
gameOverLabel.fontSize = 22.0
addChild(gameOverLabel)
func restart() {
let newScence = GameScene(size: view!.bounds.size)
newScence.scaleMode = .AspectFill
view!.presentScene(newScence)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if isGameOver {
restart()
} else {
square1.flip()
}
}
override func update(currentTime: CFTimeInterval) {
// MARK: - SKPhysicsContactDelegate
func didBeginContact(contact: SKPhysicsContact) {
if !isGameOver {
gameOver()
} else {
println("error, not game over!"
}
Without seeing your code, this is a little hard to determine, but I would suggest the following:
Be sure you have connected the buttons to an Outlet variable. This is critical. Without connecting them, you can use the hidden boolean, but it would not have an effect on an actual button.
Be sure you are not somehow undoing your own changes. For example, further down in the code, you might have something which is setting hidden to false even after you set it to true, and so on.
In some cases, you might want to set your outlet variable as strong instead of weak. This may retain changes that are being lost with a view switch.
You can also use "alpha" such as:
myButton.alpha = 0
as an alternate way of controlling visibility. 0 would set the alpha to none (which would make the button invisible) and 1 would set the alpha to full (which would make the button visible again.)
Right after you set hidden (or alpha) put in:
println("i hid the button!")
just to be sure the code you think you are executing really is being executed. Sometimes code we think is not working is actually not even being called.
Please provide more info and I will gladly work to get this solved for you.
Related
So for a school project, I have been tasked with making a 2D game. The game is fine but I'm struggling with how to make a back button (In the middle of the page) so was wondering if there was specific code to make this work. I am using spriteKit so I'm trying to go back to the previous scene after clicking on a colour sprite.
I apologise if this is a stupid question but I am slightly new to Swift.
Kind Regards,
James
Here is an example of how you can create a button using a colored sprite. It shows how you can set up a button to receive touch events and how you can use those touch events to navigate between scenes.
In this example you can navigate forward to new scenes and backwards to previous scenes.
import SpriteKit
class Button: SKSpriteNode {
var tapped: (() -> Void)?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tapped?()
}
}
class GameScene: SKScene {
var parentScene: SKScene?
var sceneCount = 1
override func didMove(to view: SKView) {
if parentScene != nil {
let backButton = addButton(color: .red, position: CGPoint(x: -200, y: 0))
backButton.tapped = {
if let previousScene = self.parentScene {
view.presentScene(previousScene)
}
}
}
let nextButton = addButton(color: .blue, position: CGPoint(x: 200, y: 0))
nextButton.tapped = {
if let nextScene = SKScene(fileNamed: "GameScene") as? GameScene {
nextScene.scaleMode = self.scaleMode
nextScene.parentScene = self
nextScene.sceneCount = self.sceneCount + 1
view.presentScene(nextScene)
}
}
let label = SKLabelNode(text: "Scene \(sceneCount)")
addChild(label)
}
func addButton(color: SKColor = .white, position: CGPoint = .zero) -> Button {
let button = Button(color: color, size: CGSize(width: 200, height: 200))
button.position = position
button.isUserInteractionEnabled = true
addChild(button)
return button
}
}
Too add a button the simplest way is to detect touches on your sprite(s) in the relevant SKScene.
enum NodeName: String {
case coloredSprite1
case coloredSprite2
}
class GameScene: SKScene {
let coloredSprite = SKSpriteNode(imageNamed: "YourImageName")
/// Scene setup
override func didMove(to view: SKView) {
// set up your colored sprite if necessary
// Give your sprites unique names to identify them
coloredSprite.name = NodeName.coloredSprite1.rawValue // always use enums for things like string identifiers so you avoid typos
}
/// Touches
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
// Way 1 by node (probably less preferable)
switch touchedNode {
case coloredSprite:
// do something (e.g call loadScene method)
// see below
default:
break
}
// Way 2 by node name (probably more preferable)
// name is an optional so we have to unwrap it when using it in the switch statement.
// The easiest way is by providing an alternative string, by using the nil coalescing operator (?? "NoNodeNameFound")
switch touchedNode.name ?? "NoNodeNameFound" {
case NodeName.coloredSprite1.rawValue:
// do something (e.g call loadScene method)
// see below
default:
break
}
}
}
// Also call touchesEnded, touchesMoved and touchesCancelled and do necessary stuff
}
For a more reusable solution you ideally want to create a button subclass. There is quite a few tutorials to google on how to do this.
To than transition between SKScenes you can create a loadScene method in each scene and than call them when necessary.
// Start Scene
class StartScene: SKScene {
...
func loadGameScene() {
// If you do everything in code
let gameScene = GameScene(size: self.size)
view?.presentScene(gameScene, transition: ...)
// If you use SpriteKit scene editor
guard let gameScene = SKScene(fileNamed: "GameScene") else { return } // fileNamed is the name you gave the .sks file
view?.presentScene(gameScene, transition: ...)
}
}
// Game scene
class GameScene: SKScene {
....
func loadStartScene() {
// If you do everything in code
let startScene = StartScene(size: self.size)
view?.presentScene(startScene, transition: ...)
// If you use SpriteKit scene editor
guard let startScene = SKScene(fileNamed: "StartScene") else { return } // fileNamed is the name you gave the .sks file
view?.presentScene(startScene, transition: ...)
}
}
Hope this helps
I'm trying to fade in an object into my game when I first touch the screen, but I think that because it was hidden before (when game was launched), it won't fade in, but only show without any animation.
Do you have any suggestions?
This is an example code:
import SpriteKit
class GameScene: SKScene {
var myLabel = SKLabelNode()
var gameStarted = Bool()
func setupMyLabel(){
myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "Hello, World!"
myLabel.fontSize = 35
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
setupMyLabel()
self.addChild(myLabel)
myLabel.hidden = true
gameStarted = false
}
func startGame(){
myLabel.hidden = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if gameStarted == false{
gameStarted = true
startGame()
self.myLabel.runAction(SKAction.fadeInWithDuration(2.0))
}
else{
//do nothing
}
}
}
According to Apple's documentation on fadeInWithDuration it states that:
When the action executes, the node’s alpha property animates from its
current value to 1.0.
So you're right in thinking it's because your node is hidden when it starts. =)
One possible solution would be to instead of setting the node's hidden property to true, instead set it's alpha value to 0. Or you could even create your own method to perform that includes the runAction method that would set the alpha to 0, un-hide the node, and then call SKAction.fadeInWithDuration similar to something below (please forgive any syntax errors, this is free-hand pseudo code)...
startGame()
self.fadeIn(self.myLabel, duration: 2.0)
...
func fadeIn() {
self.myLabel.alpha = 0.0
self.myLabel.hidden = false
self.myLabel.runAction(SKAction.fadeInWithDuration(2.0))
}
I'm creating my first Game and it crashes after come back from GameOverScene. This are the part that gives me the error:
// GameScene.swift
// Test_Crash_1
import SpriteKit
struct global {
static var wheelRotPlat2 = SKShapeNode(circleOfRadius: 400)
static var button : SKShapeNode = SKShapeNode()
static var actualPlayer : String = String()
static let btnNextPlayer = SKShapeNode(circleOfRadius: 70)
}
class GameScene: SKScene {
deinit {
print("The GameScene has been removed from memory")
}
override func didMoveToView(view: SKView) {
let buttonDice = SKShapeNode(rectOfSize: (CGSizeMake(40,40)), cornerRadius: 5)
global.button = buttonDice
global.button.position = CGPoint(x: (CGRectGetMidX(self.frame)-400), y:(CGRectGetMidY(self.frame)-350))
global.button.fillColor = UIColor(red: 0.2, green: 1.0, blue: 0.2, alpha: 0.8)
global.button.name = "button"
global.button.zPosition = 1
addChild(global.button)
global.wheelRotPlat2.addChild(global.btnNextPlayer)
global.btnNextPlayer.name = "btnNextPlayer"
} // end didMoveToView
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches //as! Set<UITouch>
let location = touch.first!.locationInNode(self)
let node = self.nodeAtPoint(location)
if (node.name == "button") {
self.removeAllActions()
self.removeAllChildren()
let reveal = SKTransition.flipHorizontalWithDuration(0.5)
let gameOverScene = GameOverScene(size: self.size, player: global.actualPlayer)
self.view?.presentScene(gameOverScene, transition: reveal)
}
} // End func touchesBegan
} // end GameScene
And the GameOverScene file:
// GameOverScene.swift
// Test_Crash_1
import Foundation
import SpriteKit
class GameOverScene: SKScene {
init(size: CGSize, player: String) {
super.init(size: size)
backgroundColor = SKColor.whiteColor()
runAction(SKAction.sequence([
SKAction.waitForDuration(3.0),
SKAction.runBlock() {
let reveal = SKTransition.flipHorizontalWithDuration(0.5)
let scene = GameScene(size: size)
self.view?.presentScene(scene, transition:reveal)
}
]))
} // end init(size
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
} // end GameOverScene
The error is well the known:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent: SKShapeNode name:'btnNextPlayer'
This example has just a green button that takes me to the GameOverScene. When it returns the game crashes.
I don't understand why it happens in this case.
Can anyone tell me why this happens and how to fix it?
The error tells you what is happening, you are adding a node to another node, but the node already has a parent. This is why using globals is a bad idea and should almost never be used. You have a button that is global. It is attached to GameScene, lets call GameScene1. You then call Gameover. Now you call a new GameScene, called GameScene2. Since you are not managing your memory correctly because of things like global variables, GameScene1 still exists. Well button is still attached to GameScene1, so when you try to add it to GameScene2, XCode says "No, this node has a parent, go fix yo stuff". Try to not use global data, and you should see less problems like this.
Edit:
As #Whirlwind pointed out, the issue is in the global item global.wheelRotPlat2 that is causing the problem.
global.wheelRotPlat2.addChild(global.btnNextPlayer) is being called every time a GameScene is moved to a view, and since you never remove the btnNextPlayer from its parent when you kill your 1st Scene, when you create your 2nd Scene, the global.btnNextPlayer will try to reattach to the same exact parent again (global.wheelRotPlat2), which is causing your problem.
I have an SKSpriteNode that functions properly. But when I add a UIImage, the SKSpriteNode becomes hidden behind the UIImage. I have been trying to figure out why, but I am having a little bit of trouble and can't seem to figure out what I am missing to allow the SKSSpriteNode to appear on top of the UI background Image, instead of behind it where it can't be seen. Any help would be greatly appreciated!
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
import SpriteKit
import SceneKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var blueBall:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0.0, -5.0)
self.physicsWorld.contactDelegate = self
blueBall = SKSpriteNode( imageNamed: "ball")
blueBall.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
blueBall.physicsBody = SKPhysicsBody(circleOfRadius: blueBall.size.width / 1.5 )
blueBall.physicsBody!.dynamic = true
blueBall.physicsBody!.allowsRotation = false
self.addChild(blueBall)
}
override func touchesBegan(touches: Set<UITouch> , withEvent event: UIEvent?) {
self.blueBall.physicsBody?.velocity = CGVectorMake(35, 0)
self.blueBall.physicsBody?.applyImpulse(CGVectorMake(4, 10))
}
}
For draw order use the zPosition property:
In your case you will need to give the Sprite you want to be displayed in front a higher .zPosition value than the one to be displayed further back.
Example: ball.zPosition = 10
zPosition definition:
The height of the node relative to its parent.
Tipps:
The default value is 0.0. The positive z axis is projected toward the viewer so that nodes with larger z values are closer to the viewer.
I am making a game that, when the game loads in, doesn't require the user to tap the screen to start, but starts straight away.
My code with the "tap to start":
import Foundation
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var isStarted = false
override func didMoveToView(view: SKView) {
//backgroundColor = UIColor.greenColor()
addTapToStartLabel()
func addTapToStartLabel() {
let tapToStartLabel = SKLabelNode(text: "Tap to start!")
tapToStartLabel.name = "tapToStartLabel"
tapToStartLabel.position.x = view!.center.x
tapToStartLabel.position.y = view!.center.y + 40
tapToStartLabel.fontColor = UIColor.whiteColor()
tapToStartLabel.fontName = "Helvetica"
tapToStartLabel.fontSize = 22.0
addChild(tapToStartLabel)
}
func start() {
isStarted = true
let tapToStartLabel = childNodeWithName("tapToStartLabel")
tapToStartLabel?.removeFromParent()
square1.stop()
movingGround.start()
wallGen.startGenWallsEvery(1)
diamondGen.startGenDiamondsEvery(1)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if isGameOver {
restart()
} else if !isStarted {
start()
} else {
square1.flip()
}
}
I have tried a couple of things and I can't seem to figure it out.
First, your didMoveToView is missing }
Your game have started, but you should override update() function that is called at the beginning of every frame and do some actions there.