I am trying to use a variable from my GameScene.swift file on my GameViewController.swift file to time my interstitial ads appropriately. It's a boolean value that determines if my player is dead or not.
var died = Bool()
That's all I did to create the variable in my GameScene.
When died == true in my GameScene, I want to send that to my GameViewController and then show an interstitial ad. I really just need to know how to pass a boolean between scenes. Thanks in advanced for your help.
You can follow these steps.
Do this in your GameScene:
protocol PlayerDeadDelegate {
func didPlayerDeath(player:SKSpriteNode)
}
class GameScene: SKScene {
var playerDeadDelegate:PlayerDeadDelegate?
...
// during your game flow the player dead and you do:
playerDeadDelegate.didPlayerDeath(player)
...
}
In the GameViewController you do:
class GameViewController: UIViewController,PlayerDeadDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
...
scene.playerDeadDelegate = self
}
}
func didPlayerDeath(player:SKSpriteNode) {
print("GameViewController: the player is dead now!!!")
// do whatever you want with the property player..
}
}
Your GameScene should have a reference object as delegate (e.g conforms to GameSceneDelegate protocol) which is actually pointing to GameViewController object. Then when died becomes true, inform your delegate object (GameViewController object) about this event via a delegate-method and implement that method by conforming to the above protocol in your GameViewController class.
Related
I've read up on the GameplayKit State Machine and the documentation explicitly mention Game UI as an example of usage. Im fully understanding the concept but I am unsure how to implement it while using SpriteKit.
Lets say I want 3 states. Menu, Gameplay and Game Over - All which display content to the SKScene.
GameScene class extends SKScene. So it's impossible to turn GameScene into a state machine since we only can extend one class.
Should GameScene have a variable for a state machine with a reference to the SKScene, or is there a better solution? Earlier I've used protocols for state machines which makes it easy, but I'd like to try the functionality of GameplayKit.
You're on the right track. Add the state machine variable in GameScene and initialise its starting state.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var stateMachine: GKStateMachine!
override func didMove(to view: SKView) {
self.stateMachine = GKStateMachine(states: [
StartState(scene: self),
PlayingState(scene: self),
PausedState(scene: self),
GameOverState(scene: self)
])
self.stateMachine.enter(StartState.self)
}
override func update(_ currentTime: TimeInterval) {
self.stateMachine.update(deltaTime: currentTime)
}
}
Then initialise the states so they accept GameScene as input parameter.
import SpriteKit
import GameplayKit
class StartState: GKState {
var scene: GameScene!
init(scene: GameScene){
super.init()
self.scene = scene
}
override func isValidNextState(_ stateClass: Swift.AnyClass) -> Bool {
return stateClass == PlayingState.self
}
override func updateWithDeltaTime(seconds: TimeInterval) {
self.stateMachine?.enterState(PlayingState.self)
}
}
Since I'm working in SpriteKit, my buttons are SKSpriteNodes...however I find myself in a situation where I need to set focus in my viewController via overriding preferredFocusedView. Is there a way to downcast an SKSpriteNode to a UIView? If so I haven't been able to figure out yet...any alternative?
let playButton = SKSpriteNode(imageNamed: "PlayButton")
playButton.position = CGPoint(x: scene.size.width * 0.25, y: scene.size.height * 0.25)
playButton.zPosition = Layer.UI.rawValue
scene.worldNode.addChild(playButton)
override var preferredFocusedView: UIView? {
get {
return //playButton how?
}
}
Focus navigation is only now supported with tvOS 10 and SpriteKit, prior to that you had to do it manually using your own focus system. For that reason preferred focus view is deprecated because it only supports UIViews. You should now use preferred focus environments instead.
First thing you do is in your GameViewController set the preferred focus environment to the currently presented SKScene. This essentially means that your SKScenes will handle the preferred focus instead of GameViewController. In a SpriteKit game the SKScenes should handle the UI such as buttons using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes etc. Therefore you need to pass the preferred focus to the SKScene.
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// default code to present your 1st SKScene.
}
}
#if os(tvOS)
extension GameViewController {
/// Tell GameViewController that the currently presented SKScene should always be the preferred focus environment
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if let scene = (view as? SKView)?.scene {
return [scene]
}
return []
}
}
#endif
Your playButton should be a subclass of SKSpriteNode that you will use for all your buttons in your game. Use enums and give them different names/ identifiers to distinguish between them when they are pressed (checkout Apples sample game DemoBots).
class Button: SKSpriteNode {
var isFocusable = true // easy way to later turn off focus for your buttons e.g. when overlaying menus etc.
/// Can become focused
override var canBecomeFocused: Bool {
return isFocusable
}
/// Did update focus
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.previouslyFocusedItem === self {
// SKAction to reset focus animation for unfocused button
}
if context.nextFocusedItem === self {
// SKAction to run focus animation for focused button
}
}
}
Than in your SKScenes you can set the focus environment to your playButton or other UI.
e.g Start Scene
class StartScene: SKScene {
....
}
#if os(tvOS)
extension StartScene {
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [playButton]
}
}
#endif
e.g GameScene (e.g transfer focus to game menu when needed)
class GameScene: SKScene {
....
}
#if os(tvOS)
extension GameScene {
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if isGameMenuShowing { // add some check like this
return [gameMenuNode]
}
return []
}
}
#endif
You will also have to tell your GameViewController to update its focus environment when you transition between SKScenes (e.g StartScene -> GameScene). This is especially important if you use SKTransitions, it took me a while to figure this out. If you use SKTransitions than the old and new scene are active during the transition, therefore the GameViewController will use the old scenes preferred focus environments instead of the new one which means the new scene will not focus correctly.
I do it like this every time I transition between scenes. You will have to use a slight delay or it will not work correctly.
...
view?.presentScene(newScene, transition: ...)
#if os(tvOS)
newScene.run(SKAction.wait(forDuration: 0.1)) { // wont work without delay
newScene.view?.window?.rootViewController?.setNeedsFocusUpdate()
newScene.view?.window?.rootViewController?.updateFocusIfNeeded()
}
#endif
You should read this article
https://medium.com/folded-plane/tvos-10-getting-started-with-spritekit-and-focus-engine-53d8ef3b34f3#.x5zty39pc
and watch the 2016 apple keynote called "Whats New in SpriteKit" where they talk about it half way through.
Hope this helps
So I got in my game 3 different scenes: MenuScene, GameScene and GameOverScene.
I would like to know how I can get the score variable located in GameScene from GameOverScene so I can show it up there after the player loses.
So in GameScene, I created this simple getter:
func getScore() -> Int {
return self.score
}
But if I try to do GameScene.getScore() from a different Scene I get an error.
Tried using "class" before func
class func getScore() -> Int {
return self.score
}
But that also gives me an error from GameScene saying:
"Instance member 'score' cannot be used on type GameScene"
So how's the best way to do it? I don't want to create a global variable for this, it would be ugly.
This is quite easy actually. When you segue to the new scene, you can also pass in a variable with it... Here is an example.
GameOverScene.swift
var score:Int = 0
GameScene.swift
var score:Int = THE USERS SCORE
func segue(){
let gameScene = GameOverScene(size: self.size)
gameScene.score = score
let transition = SKTransition.doorsCloseHorizontalWithDuration(0.5)
gameScene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
You need to keep an instance of GameScene in your MenuScene class (recommended). Alternatively, you can store the score in a global storage medium.
To enter the game scene, you need to create it first, right?
let scene = SKScene(fileNamed: "blah blah blah")
view.presentScene(scene)
Now instead of creating the scene and assigning it to a local variable, you assign it to a class level variable.
var gameScene: GameScene?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if (...) {
gameScene = SKScene(fileNamed: "blah blah blah")
view.presentScene(gameScene!)
}
}
Then, when appropriate, you can access the score like this:
gameScene.getScore()
Another way is to use NSUserDefaults, but you don't seem to like it.
The final way that I can think of is to use static variables. You put all the stuff that you need to pass between the two scenes in a class as static variables. The disadvantage of this is that unless you declare the class private and put both scenes and the class in the same file, others classes can access and change the variables. This reduces maintainability.
Explicit getters are not needed in Swift, btw.
There are many ways to achieve what do you want to do. You could use a manager class , a shared instance to maintain your game variables.
Why this choice?
Because you will want to save your values or your profile or you will want to reset all the game progress, because you will want to select the old 2nd level when you have unlocked the 18th level, or repeat a failed level without accumulating values. It's fast to do it and you can handle separately your common game variables (it seems that I'm selling pots in an advertisement..)
class Settings: NSObject { // NSCoding in case you want to save your data
var score: Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(score, forKey: "score")
}
init(coder aDecoder: NSCoder!) {
score = aDecoder. decodeIntegerForKey("score")
}
override init() {
super.init()
// do whatever you want to initializing your settings
}
}
class GameManager: NSObject {
static let sharedInstance = GameManager()
var settings : Settings! = Settings()
}
class MenuScene: SKScene {
let gameManager = GameManager.sharedInstance
override func didMoveToView(view: SKView) {
print("the current score is \(self.gameManager.settings.score)")
}
}
class GameScene: SKScene {
let gameManager = GameManager.sharedInstance
override func didMoveToView(view: SKView) {
print("the current score is \(self.gameManager.settings.score)")
}
}
class GameOverScene: SKScene {
let gameManager = GameManager.sharedInstance
override func didMoveToView(view: SKView) {
print("the current score is \(self.gameManager.settings.score)")
}
}
In my GameScene.swift file, I have the following member variable "chosenBall" that I want to assign in another file called ChooseBall.swift.
class GameScene: SKScene, SKPhysicsContactDelegate {
internal var chosenBall: BallType!
}
My ChooseBall.swift file looks like this.
class ChooseBall: UIViewController {
#IBAction func chooseBeachBall(sender: UIButton) {
chosenBall = BallType.BeachBall
}
}
I am getting a compiler error saying:
Use of unresolved identifier 'chosenBall'
How can I fix it ?
I haven't used SpriteKit so I do not know the basics on where and when to instantiate Scenes.
However, the error you are getting is because you have not created an instance of GameScene and made that accessible from your view controller.
A possible solution could be the following code, but again, I'm not sure when or where the scene should be instantiated.
class ChooseBall: UIViewController {
var scene = GameScene()
#IBAction func chooseBeachBall(sender: UIButton) {
scene.chosenBall = BallType.BeachBall
}
}
Im am trying to pause or stop an SKAction that is being repeated forever which should happen when the user presses the pause button. I have found a way to stop the music but i cant call the function it is in because of this error. It says exactly: Missing argument for parameter 'coder' in call.
Class GameViewController: UIViewController, SwiftrisDelegate, UIGestureRecognizerDelegate {
#IBAction func didPause(sender: UIButton) {
if self.scene.paused == false{
self.scene.stopTicking()
self.scene.paused = true
GameScene().stopGameMusic() //error on this line
}
}
}
class GameScene: SKScene {
runAction(SKAction.playSoundFileNamed("theme.mp3", waitForCompletion: true), withKey:("themeSong"))
func stopGameMusic() {
removeActionForKey("themeSong")
}
}
There is no initializer for GameScene that takes no arguments - you haven't defined one nor is one inherited from SKScene. If you intend to create a GameScene each time 'pause' is pushed, which is a questionable approach in itself, then you'll need to call an existing initializer or to create an initializer w/o any arguments.
It looks like the designated initializer for SKScene is init(size: CGSize). So instead of simply calling GameScene() call GameScene(size: ...) or, in the class GameScene define
class GameScene : SKScene {
// ...
init () {
super.init (size: ...)
}
}