SpriteKit Main Menu button issue - swift

Wondering if you could help, I created a main menu with a background and button node. When I tap on the PLAY button, the game does not navigate to my GameScene and instead calls my print statement. Here is the code below:
import Foundation
import SpriteKit
import GameplayKit
class MainMenu: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
background()
playButton()
}
func background()
{
let back = SKSpriteNode(imageNamed: "MainMenu")
back.anchorPoint = CGPoint(x: 0, y: 0)
back.zPosition = 1
back.size = CGSize(width: frame.width,height: frame.height)
back.name = "Background"
addChild(back)
}
func playButton()
{
let button = SKSpriteNode(imageNamed: "button")
button.zPosition = 2
button.name = "Play Button"
button.position = CGPoint(x: frame.width/2, y: frame.height/2)
button.setScale(0.3)
addChild(button)
}
func loadGame(){
guard let skView = self.view as SKView? else{
print("Could not get Skview")
return
}
guard let scene = GameScene(fileNamed: "GameScene") else {
print("Error Getting GameScene")
return
}
//let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
skView.showsPhysics = true
scene.scaleMode = .aspectFill
skView.presentScene(scene)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else{
return
}
let touchLoation = touch.location(in: self)
let touchNodes = nodes(at: touchLoation)
let firstTouchedNode = atPoint(touchLoation).name
print(firstTouchedNode)
if firstTouchedNode == "Play Button"{
loadGame()
}
}
}

func loadGame(){
guard let skView = self.view as SKView? else{
print("Could not get Skview")
return
}
var scene:SKScene = GameScene(size: self.size)
}
//let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
skView.showsPhysics = true
scene.scaleMode = .aspectFill
var transition:SKTransition = SKTransition.fadeWithDuration(1)
self.view?.presentScene(scene, transition: transition)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node = self.atPoint(location)
if node.name == "Play Button"{
//when playbutton is pressed execute code here
loadGame()
}
}

Related

whose view is not in the window hierarchy

I am developing a game and I am trying to move from my game scene to my view controller but it gives me the error:
Warning: Attempt to present <Breakout.GameViewController: 0x7f8874d19ed0> on whose view is not in the window hierarchy!.
My game is set up so I do level 1 and finish it then it takes me to the vc where I can do level 1. That works, but then when I finish level 2 and try to go back to the vc, it does not work and gives me the error.
I have looked at other posts but nothing has directly linked to this.
My Game Scene Controller:
class Level1: SKScene,SKPhysicsContactDelegate {
var ball:SKSpriteNode!
var paddle:SKSpriteNode!
var scoreLabel:SKLabelNode!
var score:Int = 0 {
didSet{
scoreLabel.text = "Score:\(score)"
}
}
override func didMove(to view: SKView) {
ball = self.childNode(withName: "Ball") as! SKSpriteNode
paddle = self.childNode(withName: "Paddle") as! SKSpriteNode
scoreLabel = self.childNode(withName: "Score") as! SKLabelNode
ball.physicsBody?.applyImpulse(CGVector(dx: 50, dy: 50))
ball.physicsBody?.mass=0.1
let border = SKPhysicsBody(edgeLoopFrom: (view.scene?.frame)!)
border.friction = 0
self.physicsBody = border
ball.physicsBody?.mass = 0.09
self.physicsWorld.contactDelegate = self
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let touchLocation = touch.location(in: self)
paddle.position.x = touchLocation.x
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let touchLocation = touch.location(in: self)
paddle.position.x = touchLocation.x
}
}
func didBegin(_ contact: SKPhysicsContact) {
let bodyAName = contact.bodyA.node?.name
let bodyBName = contact.bodyB.node?.name
if bodyAName == "Ball" && bodyBName == "Brick" || bodyAName == "Brick" && bodyBName == "Ball"{
if bodyAName == "Brick" {
contact.bodyA.node?.removeFromParent()
score += 1
} else if bodyBName == "Brick" {
contact.bodyB.node?.removeFromParent()
score += 1
}
}
}
override func update(_ currentTime: TimeInterval) {
if (score == 9) {
scoreLabel.text = "You Won!"
self.view?.isPaused = true
sleep(2)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "GameViewController")
self.view?.window?.rootViewController?.present(vc, animated: true, completion: nil)
level1done = true
}
if (ball.position.y < paddle.position.y) {
scoreLabel.text = "You Lost!"
self.view?.isPaused = true
sleep(2)
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "GameViewController")
self.view?.window?.rootViewController?.present(vc, animated: true, completion: nil)
}
}
}

swift/scenekit problems getting touch events from SCNScene and overlaySKScene

Good afternoon, I'm trying to figure out how to get touch notifications from an SCNNode & a SKSpriteNode from an SCNScene overlayed with a SKScene.
import UIKit
import SceneKit
class GameViewController: UIViewController {
var scnView:SCNView!
var scnScene:SCNScene!
var sprite: spritekitHUD!
var cameraNode: SCNNode!
var shape: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setupScene()
}
func setupScene() {
scnView = self.view as! SCNView
scnView.delegate = self
scnView.allowsCameraControl = true
scnScene = SCNScene(named: "art.scnassets/scene.scn")
scnView.scene = scnScene
sprite=spritekitHUD(size: self.view.bounds.size, game: self)
scnView.overlaySKScene=sprite
cameraNode = scnScene.rootNode.childNode(withName: "camera",
recursively: true)!
shape=scnScene.rootNode.childNode(withName: "shape", recursively: true)
shape.name="ThreeDShape"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch = touches.first!
let location = touch.location(in: scnView)
let hitResults = scnView.hitTest(location, options: nil)
if let result = hitResults.first {
handleTouchFor(node: result.node)
}
}
func handleTouchFor(node: SCNNode) {
if node.name == "ThreeDShape" {
print("SCNNode Touched")
}
}
}
This is my Spritekit overlay scene
import Foundation
import SpriteKit
class spritekitHUD: SKScene{
var game:GameViewController!
var shapeNode: SKSpriteNode!
init(size: CGSize, game: GameViewController){
super.init(size: size)
self.backgroundColor = UIColor.white
let spriteSize = size.width/12
self.shapeNode= SKSpriteNode(imageNamed: "shapeNode")
self.shapeNode.size = CGSize(width: spriteSize, height: spriteSize)
self.shapeNode.position = CGPoint(x: spriteSize + 8, y: spriteSize + 8)
self.shapeNode.name="test"
self.game=game
self.addChild(self.pauseNode)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch=touches.first else{
return
}
let location=touch.location(in: self)
if self.atPoint(location).name=="test" {
print("Spritekit node pressed")
}
}
}
so with this I can successfully get notifications that my spritenode has been touched on my overlaySKScene but I cant figure out how to get a notification that my SCNode has been touched. If you cant have 2 touchesbegan functions does anyone have any ideas how I can handle the 3d events with 2d events at the same time?
Thanks for your help!!
If you want to use an SKScene overlay of an SCNView for user controls, (eg you want to implement a button in the SKScene overlay that "consumes" the touch), but also have touches that don't hit the controls to pass through and register in the underlying SCNView, you have to do this: set isUserInteractionEnabled to false on the SKScene overlay itself, but then to true on any individual elements within that overlay that you'd like to act as buttons.
let overlay = SKScene(fileNamed: "overlay")
overlay?.isUserInteractionEnabled = false
let pauseButton = overlay?.childNode(withName: "pauseButton") as? Button
// Button is a subclass of SKSpriteNode that overrides touchesEnded
pauseButton?.isUserInteractionEnabled = true
sceneView.overlaySKScene = overlay
If the user touches a button, the button's touch events (touchesBegan, touchesEnded etc) will fire and consume the touch (underlying gesture recognizers will still fire though). If they touch outside of a button however, the touch will pass through to the underlying SCNView.
This is "lifted" straight out of Xcode's Game template......
Add a gesture recognizer in your viewDidLoad:
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action:
#selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result: AnyObject = hitResults[0]
// result.node is the node that the user tapped on
// perform any actions you want on it
}
}
You can implement this method in spritekitHUD:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
game.touchesBegan(touches, with: event)
}

SpriteKit - SpriteNode never shows

I am attempting to learn SpriteKit. I want to add a rectangle/square to the scene in code; but the item never shows;
All I want to do is add a white square to the screen, but find that it never adds.
In a breakpoint, I notice that didMove() never seems to get called.
What am I doing wrong?
class GameScene: SKScene {
override func didMove(to view: SKView) {
let item = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 200))
item.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(item)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
update
I did not change the view controller generated by xcode
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
There is no GameScene.sks, I deleted this file as I do not want to use sks files
If a breakpoint in didMove(to view:) is never reached, then definitely your ViewController doesn't present the scene for some reason. Can you open your ViewController's file (e.g. GameViewController.swift) and see if you have any of those lines there (or anything to that effect):
let viewSize = UIScreen.main.bounds.size
let scene = GameScene(size: viewSize)
let skView = self.view as! SKView
skView.presentScene(scene)
Turns out view controller was not presenting the scene and changing it to this, following code posted by #Stoyan worked and I saw my sprite showing
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let viewSize = UIScreen.main.bounds.size
let scene = GameScene(size: viewSize)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.presentScene(scene)
}

How to add 1 point every time SKNode is touched

I created a game involving falling balls. Every time the player taps a ball, they're supposed to get a point. I set up the score label, but it just stays at 0.
Is there something wrong with my code?
I use a GameElements.swift file as an extension. Here is the file:
extension GameScene {
//1st Ball//
func createPlayer() -> SKNode {
let playerNode = SKNode()
playerNode.position = CGPoint(x:self.size.width / 2, y:440)
let sprite = SKSpriteNode(imageNamed: "Ball")
sprite.name = "ballPoints"
playerNode.addChild(sprite)
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: 120)
playerNode.physicsBody?.dynamic = true
playerNode.physicsBody?.allowsRotation = false
playerNode.physicsBody?.restitution = 3
playerNode.physicsBody?.friction = 0
playerNode.physicsBody?.angularDamping = 0
playerNode.physicsBody?.linearDamping = 0
playerNode.physicsBody?.usesPreciseCollisionDetection = true
playerNode.physicsBody?.categoryBitMask = CollisionBitMask.Player
playerNode.physicsBody?.categoryBitMask = 0
return playerNode
}
}
Here is the GameScene.swift file:
class GameScene: SKScene {
var foreground: SKNode!
var hud: SKNode!
var firstBall: SKNode!
var scoreLabel: SKLabelNode!
private var score = 0
override func didMoveToView(view: SKView) {
scoreLabel = SKLabelNode(fontNamed:"Geared-Slab")
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPoint( x: self.frame.midX, y: 3 * self.frame.size.height / 4 )
scoreLabel.fontSize = 100.0
scoreLabel.zPosition = 100
scoreLabel.text = String(score)
self.addChild(scoreLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if(firstBall.containsPoint(location)) {
firstBall.physicsBody?.velocity = CGVectorMake(0, 600)
firstBall.physicsBody?.applyImpulse(CGVectorMake(0, 1100))
}
if
let touch : UITouch! = touches.first,
let tappedSprite = nodeAtPoint(touch!.locationInNode(self)) as? SKSpriteNode,
let scoreLabel = childNodeWithName("scoreLabel") as? SKLabelNode
where tappedSprite.name == "ballPoints" {
score += 1
scoreLabel.text = "Score: \(score)"
}
}
}
override init(size:CGSize) {
super.init(size: size)
foreground = SKNode()
addChild(foreground)
//1st Ball//
firstBall = createPlayer()
foreground.addChild(firstBall)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I know this is a lot of code, but I want experts to be able to test the code themselves. For background, I was following this tutorial: https://www.youtube.com/watch?v=0gOi_2Jwt28 up until a certain point.
Step 1
First of all let's create a class for the Ball
class Ball: SKSpriteNode {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
}
Step 2
Then inside createPlayer() let's replace this
let sprite = SKSpriteNode(imageNamed: "Ball")
with this
let sprite = Ball(imageNamed: "Ball")
sprite.userInteractionEnabled = true
Step 3
Let's remove the touchesBegan from GameScene.
Update
This code is working as expected for me
class GameScene: SKScene {
var scoreLabel: SKLabelNode!
var ball: Ball!
private var score = 0 {
didSet {
scoreLabel.text = "\(score)"
print(score)
}
}
override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(ball)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// this is called when you tap outside of the Ball
// use self.ball to make the ball to jump
}
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How can I flash the whole screen?

I wanted to know how can the the screen flash when I touch the screen. I had tried to colorizeWhitColor but it only colorized the background, and I don't know how to return to the same color.
scene?.runAction(SKAction.colorizeWithColor(UIColor.blackColor(), colorBlendFactor: 1.0, duration: 0.5))
class GameViewController: UIViewController {
let viewFlash = UIView.init(frame: UIScreen.mainScreen().bounds)
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* 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)
viewFlash.hidden = true
self.view.addSubview(viewFlash)
}
func flashScreen(color: UIColor, flashTime: NSTimeInterval){
viewFlash.backgroundColor = color
viewFlash.hidden = false
NSTimer.scheduledTimerWithTimeInterval(flashTime, target: self, selector:#selector(ViewController.stopFlash), userInfo: nil, repeats: false) //here it tells me that
}
func stopFlash(){
viewFlash.hidden = true
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if gameStarted == false {
flashScreen(UIColor.whiteColor(), 0.1)
circuloVerde.removeFromParent()
circuloMorado.removeFromParent()
circuloRojo.removeFromParent()
circuloBlanco.removeFromParent()
enemigoTimer = NSTimer.scheduledTimerWithTimeInterval(0.685, target: self, selector: Selector("enemigos"), userInfo: nil, repeats: true)
gameStarted = true
circuloPrincipal.runAction(SKAction.scaleTo(0.44, duration: 0.5))
score = 0
scoreLabel.text = "\(score)"
hits = 0
highscoreLabel.runAction(SKAction.fadeOutWithDuration(0.5))
}
}
In GameViewController.swift:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
let viewFlash = UIView.init(frame: UIScreen.mainScreen().bounds)
override func viewDidLoad() {
super.viewDidLoad()
viewFlash.hidden = true
self.view.addSubview(viewFlash)
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* 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)
}
}
func flashScreen(color: UIColor, flashTime: NSTimeInterval){
viewFlash.backgroundColor = color
viewFlash.hidden = false
NSTimer.scheduledTimerWithTimeInterval(flashTime, target: self, selector:#selector(self.stopFlash), userInfo: nil, repeats: false)
}
func stopFlash(){
viewFlash.hidden = true
}
}
In GameScene.swift:
import SpriteKit
class GameScene: SKScene {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if gameStarted == false {
//This is the new part
var parentVC = view?.window?.rootViewController as! GameViewController
parentVC.flashScreen(UIColor.whiteColor(), flashTime: 0.1)
//This is the new part
circuloVerde.removeFromParent()
circuloMorado.removeFromParent()
circuloRojo.removeFromParent()
circuloBlanco.removeFromParent()
enemigoTimer = NSTimer.scheduledTimerWithTimeInterval(0.685, target: self, selector: Selector("enemigos"), userInfo: nil, repeats: true)
gameStarted = true
circuloPrincipal.runAction(SKAction.scaleTo(0.44, duration: 0.5))
score = 0
scoreLabel.text = "\(score)"
hits = 0
highscoreLabel.runAction(SKAction.fadeOutWithDuration(0.5))
}
}
}