Updating nodes in a SpriteKit scene from an outside UIViewController - ios

How can I update SpriteKit Scene from outside the scene itself. E.g. how do I update a SKLabelNode by pressing a UIButton.
Scene
class GameScene: SKScene {
var myLabel:SKLabelNode = SKLabelNode()
override func didMoveToView(view: SKView) {
myLabel.text = "Initial State"
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(myLabel)
}
func didPressButton() {
myLabel.text = "Pressed! 👻"
}
ViewController
import UIKit
import SpriteKit
class GameViewController: UIViewController {
#IBAction func didPressButton(sender: AnyObject) {
if let scene = GameScene(fileNamed:"GameScene") {
scene.didPressButton()
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
let skView = self.view as! SKView
skView.presentScene(scene)
}
}
…
}
I understand that the usual touchesBegan inside the scene works just fine for updating the SKLabelNode inside the scene. But I am specifically interested in updating the scene from outside events. Any ideas?

In the file where is your button:
protocol PauseBtnSelectorDelegate {
func didPressPauseBtn(pauseBtn:SKSpriteNode)
}
class HUD: SKNode {
var pauseBtnDelegate:PauseBtnSelectorDelegate?
var pauseBtn :SKSpriteNode!
...
func pauseBtnTap(sender:SKSpriteNode) {
print("pauseBtn touched")
pauseBtnDelegate!.didPressPauseBtn(sender)
}
}
In the file where you want to notify the tap event:
class GameScene: SKScene,PauseBtnSelectorDelegate {
var hud: HUD!
...
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
// Adding HUD
self.hud = HUD(currentScene: self,nodeSize: self.frame.size)
self.hud.pauseBtnDelegate = self
self.addChild(hud)
}
func didPressPauseBtn(pauseBtn:SKSpriteNode) {
print("pauseBtn touched")
// Pause the game
}
}

Related

Calling a func of GameViewController from GameScene

I want to call a func of GameViewController from the GameScene.
-> If the game Ends I want to call GameViewController().GameOver()
I tried now a lot of different things like this one: LINK (I tried every answer more than once, still not working)
But doesn't matter what I tried it doesn't even call the func.
Hope anyone can help me with this.
CODE:
GameViewController:
class GameViewController: UIViewController {
#IBOutlet weak var Button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
if UIDevice().userInterfaceIdiom == .phone {
scene.scaleMode = .aspectFill
}else{
scene.scaleMode = .aspectFit
}
// Present the scene
view.presentScene(scene)
skView = view
}
view.ignoresSiblingOrder = true
view.showsFPS = false
view.showsNodeCount = false
}
...
#IBAction func Button(_ sender: Any) {
animation()
if let gameScene = skView.scene as? GameScene { // check to see if the current scene is the game scene
gameScene.start()
}
}
func animation(){
UIButton.animate(withDuration: 1, animations: {
self.Button?.alpha = 0
})
}
func GameOver(){
UIButton.animate(withDuration: 1, animations: {
self.Button?.alpha = 1
})
}
}
GameScene:
class GameScene: SKScene, SKPhysicsContactDelegate {
...
func torpedoDidCollideWithAlien (torpedoNode:SKSpriteNode, alienNode:SKSpriteNode) {
GameViewController().GameOver()
removeAllActions()
removeAllChildren()
}
}
Option 1:
You should pass GameViewController instance when you are calling GameScene from GameViewController at the first time, Then just save this instance in some var gameVC: GameViewController! and initialize it like you do in prepare for segue method.
Then you'll be able to call gameVC.GameOver()
Option 2:
Put this in GameScene when you want to call GameOver():
if let controller = self.view?.window?.rootViewController as? GameViewController {
controller.GameOver()
}

Segue between SKScene and UIViewController

I'm writing simple math game with swift 3 and I have a problem with segues. I have two GameViewController's, oneUIViewController and one NavigationViewController.
I want to make two menus, one with game modes and one with difficulties, I want that the difficulties menu be interactive and have gravity so I decided to make it in SkScene. Everything works fine except segue between SkScene with dark screen and difficulties menu when user lost the game. I write a protocol which works when I'm going from main menu to second menu but when I'm going from SkScene with game it doesn't. I was trying to make a manual segue but protocol doesn't want to execute.
MyGVViewController code:
import UIKit
import SpriteKit
import GameplayKit
enum skad1 {
case game,menu
}
var skad = skad1.gra
class MyGVViewController: UIViewController, GameViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "Menugame") {
let gameScene = scene as! MySKScene
gameScene.gameViewControllerDelegate = self
gameScene.scaleMode = .aspectFill
gameScene.size = view.bounds.size
// Present the scene
view.presentScene(gameScene)
}
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
}
func goback() {
print("goback executed")
let view = self.view as! SKView?
view?.presentScene(nil)
navigationController?.popViewController(animated: true)
}
func goback2() {
print("goback2 executed")
let view = self.view as! SKView?
view?.presentScene(nil)
self.performSegue(withIdentifier: "comeback", sender: nil)
}
}
My SkScene with difficulties menu code:
import GameKit
import SpriteKit
import CoreMotion
protocol GameViewControllerDelegate: class {
func goback()
func goback2()
}
class MySKScene: SKScene {
//Variables
var motionManager: CMMotionManager!
var ball = SKSpriteNode()
var ball1 = SKSpriteNode()
var ball2 = SKSpriteNode()
var ball3 = SKSpriteNode()
var ball4 = SKSpriteNode()
weak var gameViewControllerDelegate: GameViewControllerDelegate?
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if ball.contains(location){
showgame(zakres: 200)
}else if ball1.contains(location){
showgame(zakres: 100)
}else if ball2.contains(location) {
showgame(zakres: 50)
}else if ball3.contains(location) {
showgame(zakres: 150)
}else if ball4.contains(location) {
if skad == .menu{
print("should execut goback")
self.gameViewControllerDelegate?.goback()
}else{
print("should execut goback2")
self.gameViewControllerDelegate?.goback2()
}
}
}
}
func showgame(zakres:Int) {
if let view = self.view {
range = zakres
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
let gameScene = scene as! GameScene
gameScene.scaleMode = .aspectFill
// Present the scene
gameScene.size = view.bounds.size
view.presentScene(gameScene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override func didMove(to view: SKView) {
ball = self.childNode(withName: "ball") as! SKSpriteNode
ball1 = self.childNode(withName: "ball1") as! SKSpriteNode
ball2 = self.childNode(withName: "ball2") as! SKSpriteNode
ball3 = self.childNode(withName: "ball3") as! SKSpriteNode
ball4 = self.childNode(withName: "ball4") as! SKSpriteNode
motionManager = CMMotionManager()
motionManager.startAccelerometerUpdates()
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 0
self.physicsBody = border
}
override func update(_ currentTime: TimeInterval) {
if let accelerometerData = motionManager.accelerometerData {
physicsWorld.gravity = CGVector(dx: accelerometerData.acceleration.x * 20, dy: accelerometerData.acceleration.y * 35)
}
}
}
PS.
I'm reading what I wrote and I have to correct it, in difficulties menu I have a button which execute protocol that hide SKScene and show main menu. It works only when I'm going from main menu but when segue is from game when user lost, protocol isn't executed

Show Interstitial In Other Scenes - Admob, SpriteKit, Swift

How do I show an interstitial ad from admob every x times the user has died or every x times the user does something like presses a button? This is how I showed an interstitial ad on my GameScene and limited ad impressions with a simple if statement.
This will only work if you have the GoogleMobileAds.sdk and have imported the googlemobileads module into your GameViewController, and GameScene, OR GameOverScene.
I'll be showing you cross-scene ad implementation and programmatically limiting ad impressions.
First, in your GameViewController:
import GoogleMobileAds
class GameViewController: UIViewController, GADInterstitialDelegate {
var myAd = GADInterstitial()
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.loadAndShow), name: "loadAndShow", object: nil)
}
Create two functions at the bottom of your GameViewController:
func loadAndShow() {
myAd = GADInterstitial()
let request = GADRequest()
myAd.setAdUnitID("ca-app-pub-3940256099942544/4411468910")
myAd.delegate = self
myAd.loadRequest(request)
}
func interstitialDidReceiveAd(ad: GADInterstitial!) {
if (self.myAd.isReady) {
myAd.presentFromRootViewController(self)
}
}
You are done with GameViewController. Now head to GameOverScene or GameScene, whatever you need.
Create a global int variable:
var playCount = Int()
In your DidMoveToView say:
playCount = 1
This part is sort of confusing, kinda, not really. Go to your touchesBegan and find where you add actions to a button if it's pressed. For example, a resetGame button resets the scene. Add this there and increment the playButton Int like so:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let location = touch.locationInNode(self)
if resetGame.containsPoint(location) {
restartScene()
playCount += 1
}
Last step. Add these two functions to the bottom of the scene you want to show interstitial ads in:
func displayAd() {
NSNotificationCenter.defaultCenter().postNotificationName("loadAndShow", object: nil)
}
func checkAd() {
if playCount % 4 == 0 {
displayAd()
}
}
}
Now every fourth time that the user presses the reset game button or dies, an interstitial ad should show up. I hope this helps.
EDIT: I forgot to tell you to call the checkAd() function. Call this function wherever your players dies. So if you have a Bool variable called died or gameover call it in the same spot. For example..
if died == true {
checkAd()
}
import UIKit
import SpriteKit
import GoogleMobileAds
var playCount = Int()
class GameViewController: UIViewController, GADBannerViewDelegate {
#IBOutlet var banner: GADBannerView!
var myAd = GADInterstitial()
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(GameViewController.loadAndShow), name: "loadAndShow", object: nil)
let scene = MainScene(size: CGSize(width: 1536, height: 2048))
// 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)
banner.hidden = true
banner.delegate = self
banner.adUnitID = "ca-app-pub-8889875503423788/7902691359"
banner.rootViewController = self
banner.loadRequest(GADRequest())
}
func loadAndShow() {
myAd = GADInterstitial()
let request = GADRequest()
myAd.setAdUnitID("ca-app-pub-8889875503423788/7902691359")
myAd.delegate = self
myAd.loadRequest(request)
}
func interstitialDidReceiveAd(ad: GADInterstitial!) {
if (self.myAd.isReady) {
myAd.presentFromRootViewController(self)
}
}
func adViewDidReceiveAd(bannerView: GADBannerView!) {
banner.hidden = false
}
func adView(bannerView: GADBannerView!, didFailToReceiveAdWithError error: GADRequestError!) {
banner.hidden = true
}
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
}
}

Seque from a storyboard image tap into a SpriteKitScene

I'm creating a Swift project for a high school programming class. I can't seem to figure out this problem, and everyone else in my class doesn't seem to have any ideas.
To start, I created a new Swift project, and chose a game format.
I then used some basic code to make the first level for my game, a maze game where the maze moves around instead of the ball based on how the user tilts the device.
This is my GameScene.swift:
import SpriteKit
import CoreMotion
var accelupdateinterval = 0.1
var accelmultiplier = 15.0
class GameScene: SKScene {
let manager = CMMotionManager()
override func didMoveToView(view: SKView) {
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = accelupdateinterval
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * CGFloat(accelmultiplier), CGFloat((data?.acceleration.y)!) * CGFloat(accelmultiplier))
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
I want to have a main menu that the app opens into, which is mainMenu.storyboard:
I successfully have the app launching into the mainMenu.storyboard (and the level physics work well when I've tested the level1.sks), but I'm having trouble figuring out how to segue.
GOAL: I want people to be segued into the level1.sks (and the levels that I add later), when they tap the corresponding image in mainMenu.storyboard.
I can't use the method of adding a Storyboard Reference to segue it, as the Storyboard Reference won't let me choose level1.sks.
I'd also love to find out how to send users back to the main menu when the player icon touches the goal (the blue thing up near the top in this screenshot):
So to do this I think the best approach is to create another ViewController subclass, maybe named LauncherViewController, which will present your SKScene. Then in your storyboard add this viewController and have your menu segue to it on an image press.
Here is a start for the LauncherViewController
class LauncherViewController: UIViewController {
var gameScene: GameScene!
override func viewDidLoad() {
}
override func viewWillAppear() {
presentGameScene()
}
func presentGameScene(){
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
gameScene.size = skView.bounds.size
gameScene.scaleMode = .AspectFill
skView.presentScene(gameScene)
}
}
Where in your menu controller you have a prepareForSegue like this:
override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
if segue.identifier == "yourSegue" {
let destinationViewController = segue.destinationViewController as! LauncherViewController
destinationViewController.gameScene = GameScene()
}
to have your LauncherViewController dismiss the gameScene when the user finishes the maze, use a delegate pattern. So in GameScene add a protocol above your class
protocol GameDelegate {
func gameFinished()
}
have your LauncherViewController conform to this delegate and set the gameScene's delegate variable to self (see below)
class LauncherViewController: UIViewController, GameDelegate {
var gameScene: GameScene!
override func viewDidLoad() {
gameScene.delegate = self
}
override func viewWillAppear() {
presentGameScene()
}
func presentGameScene(){
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
gameScene.size = skView.bounds.size
gameScene.scaleMode = .AspectFill
skView.presentScene(gameScene)
}
func gameFinished(){
// this forces LauncherViewController to dismiss itself
dismissViewControllerAnimated(true, completion: nil)
}
}
add a variable in GameScene to hold the delegate (LauncherViewController) and add a function that calls the delegate function. You will also need to add the logic to know when the game is over as I haven't done that.
class GameScene: SKScene {
let manager = CMMotionManager()
var delegate: GameDelegate!
override func didMoveToView(view: SKView) {
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = accelupdateinterval
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * CGFloat(accelmultiplier), CGFloat((data?.acceleration.y)!) * CGFloat(accelmultiplier))
}
}
func gameOver(){
delegate.gameFinished()
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
// it's probably easiest to add the logic for a gameOver here
if gameIsOver {
gameOver()
}
}
}
There will probably be some mistakes in here as I wrote this on my phone so just comment below for anything you are unsure about or doesn't work.

Need help instantiating a controller and storyboard scene from controller SWIFT?

I have a controller that is using a Singleton to change SKScenes in a single storyboard scene.
class GameViewController: UIViewController, GKGameCenterControllerDelegate, GADBannerViewDelegate, GADInterstitialDelegate, ADBannerViewDelegate, FBLoginViewDelegate, CLLocationManagerDelegate, MapViewControllerDelegate {
var sharedInstance = Singleton.sharedInstance
var skView:SKView!
var scene:GameScene! = GameScene.unarchiveFromFile("GameScene") as? GameScene
var searchedTypes = ["bakery", "bar", "cafe", "grocery_or_supermarket", "restaurant"]
let locationManager = CLLocationManager()
var adHelper: AdHelper = AdHelper()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
// Configure the view.
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, transition: SKTransition.crossFadeWithDuration(kSceneTransitionSpeed))
}
// LOAD MAP SCENE
func showMap(){
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("MapController") as UIViewController
self.presentViewController(viewController, animated: false, completion: nil)
}
}
In my game scene I have a button that I am calling to this controller to change to the other scene in the storyboard because I don't have a layout button to use an IBAction
import SpriteKit
class GameScene: SKScene {
var sharedInstance = Singleton.sharedInstance
let playButton = Button(imageNamed:"playButton");
let highscoresButton = Button(imageNamed:"highscoresButton");
let creditsButton = Button(imageNamed:"creditsButton");
let moreGamesButton = Button(imageNamed: "moreGamesButton")
override func didMoveToView(view: SKView) {
// background
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPointMake(self.size.width/2, self.size.height/2)
self.addChild(background)
// main image
let main = SKSpriteNode(imageNamed: "main")
main.position = CGPointMake(self.size.width/2, self.size.height/2)
self.addChild(main)
// buttons
moreGamesButton.position = CGPointMake(self.size.width/2 + playButton.size.width, main.position.y + main.size.height/2 + playButton.size.height)
self.sharedInstance.addChildFadeIn(moreGamesButton, target: self)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self);
if self.nodeAtPoint(location) == self.moreGamesButton {
var gameController: GameViewController=GameViewController()
gameController.showMap()
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
When I click this I don't get a defined error just hits a break and it the thread I get "Swift dynamic cast failed".
Can I not call the controller like that within my scene or is is it a problem in instantiating the other controller?

Resources