I have the following code to pause game scene:
class GameProcessScene: SKScene {
...
var onPause: Bool = false {
willSet {
self.paused = newValue
self.view?.paused = newValue
}
}
...
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches {
let location = touch.locationInNode(self)
let possiblePauseNode = self.nodeAtPoint(location)
if (possiblePauseNode.name == "pauseButton") {
if (self.paused) {
onPause = false
} else {
onPause = true
}
return
}
...
}
}
It works to pause by pressing button. But i also want to pause game when it appears from background. I am trying to do this from ViewController class:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "pauseGameScene", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func pauseGameScene() {
let view = self.view as SKView
let scene = view.scene
switch scene {
case is MainMenuScene:
println("MainMenu")
case let game as GameProcessScene:
game.onPause = true
default:
println("default")
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
But it doesnt work when game appears from background.
Can anybody help? Thanks!
UPD1: Also tried UIApplicationWillResignActiveNotification, UIApplicationDidEnterBackgroundNotification but game.onPause = true still doesnt work. It changes "SKScene.paused" property to true, but the actions are still executing when game enters foreground, even game scene was paused before it goes background.
UPD2: I moved observers and selectors from ViewController class to GameProcessScene: SKScene class - same effect, "paused" property changes, but in fact gameplay is continuing.
I solved my problem by creating a property called isPaused and overriding the paused property to always follow the isPaused property. Note : This disables the SKSCene from automatically pausing when going to background mode. You have to manually set the isPaused property. So only use it in scenes where you actually need the pause mode on resume.
class GameScene: SKScene,SKPhysicsContactDelegate {
// Initializers
var isPaused : Bool = false {
didSet {
self.paused = isPaused
}
}
override var paused : Bool {
get {
return isPaused
}
set {
super.paused = isPaused
}
}
}
In ViewController
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "pauseGameScene", name: UIApplicationWillResignActiveNotification, object: nil)
}
func pauseGameScene() {
println("will become inactive")
let view = self.view as SKView
let scene = view.scene
switch scene {
case let game as GameScene:
game.isPaused = true
default:
println("default")
}
}
Did you try put your code into
override func viewWillAppear(animated: Bool) {
//Put your pauseGameScene(); code here
}
This method called before your view appears every times. The viewDidLoad() run only once after your instance is initialized.
Related
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
}
}
I know SpriteKit already handles pausing the game when the app enters the inactive state but what I'm trying to do is add a SKLabelNode "tap to resume" when the app re-enters the active state. Right now it's calling my functions correctly and pausing the game, but the text is not showing.
AppDelegate.swift
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
println("applicationWillResignActive")
NSNotificationCenter.defaultCenter().postNotificationName("PauseGameScene", object: self)
NSNotificationCenter.defaultCenter().postNotificationName("ShowPauseText", object: self)
...
}
GameScene.swift
class GameScene: SKScene, SKPhysicsContactDelegate {
...
let tapToResume = SKLabelNode(fontNamed: "Noteworthy")
...
override func didMoveToView(view: SKView) {
...
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("pauseGameScene"), name: "PauseGameScene", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showPauseText"), name: "ShowPauseText", object: nil)
tapToResume.text = "tap to resume"
tapToResume.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
tapToResume.fontSize = 55
tapToResume.hidden = true
self.addChild(tapToResume)
...
}
func pauseGameScene() {
println("pause game")
self.view?.paused = true
}
func showPauseText() {
if self.view?.paused == true {
tapToResume.hidden = false
println("show text")
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
...
if self.paused {
self.view?.paused = false
if tapToResume.hidden == false {
tapToResume.hidden = true
}
}
}
...
}
EDIT:
Below is a screenshot of my terminal output with my latest edits to my above code:
So I "hacked" my solution here. Thanks to ABakerSmith with the suggestion of setting self.speed = 0.0, the actions were paused and the my label will appear but the physicsWorld was still active. So my solution was to set self.speed = 0.0 AND self.physicsWorld.speed = 0.0. When the app returns from the inactive state, I just reset self.speed = 1.0 and self.physicsWorld.speed = 1.0. I'm sure there are other solutions to this dilemma but since SpriteKit already handles interruptions, all I really needed to do was pause the actions and the physics.
GameScene.swift
class GameScene: SKScene, SKPhysicsContactDelegate {
let tapToResume = SKLabelNode(fontNamed: "Noteworthy")
...
override func didMoveToView(view: SKView) {
...
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("pauseGameScene"), name: "PauseGameScene", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showPauseText"), name: "ShowPauseText", object: nil)
}
func pauseGameScene() {
self.physicsWorld.speed = 0.0
self.speed = 0.0
}
func showPauseText() {
if self.physicsWorld.speed == 0.0 {
tapToResume.hidden = false
}
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
...
if self.physicsWorld.speed == 0.0 {
self.physicsWorld.speed = 1.0
self.speed = 1.0
if tapToResume.hidden == false {
tapToResume.hidden = true
}
}
}
...
}
AppDelegate.swift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
NSNotificationCenter.defaultCenter().postNotificationName("PauseGameScene", object: self)
NSNotificationCenter.defaultCenter().postNotificationName("ShowPauseText", object: self)
}
...
}
I believe your problem was setting self.view?.paused = true, as was pointed out by #Steve in a comment and #Linus G. in his answer. Therefore, when you tried to unhide the label, nothing happened because the view was paused.
I tried using self.paused to pause the SKScene instead. This solved the problems of showing the label, however it didn't actually pause the scene. This could be due to: since iOS8, SpriteKit automatically pauses your game when it enters the background and un-pauses the game when it enters the foreground. Therefore, trying to set self.paused = true, using applicationWillResignActive, had no effect because it was un-paused when entering the foreground.
To solve this you can observe UIApplicationWillResignActiveNotification. Then when the application is going to resign being active, you set self.speed = 0, which has the same effect as pausing the SKScene. This displays the label and pauses the scene as required.
For example:
class GameScene: SKScene {
let tapToResume = SKLabelNode(fontNamed: "Noteworthy")
override func didMoveToView(view: SKView) {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("pauseScene"),
name: UIApplicationWillResignActiveNotification,
object: nil)
tapToResume.text = "tap to resume"
tapToResume.position = CGPoint(x: frame.midX, y: frame.midY)
tapToResume.fontSize = 55
tapToResume.hidden = true
self.addChild(tapToResume)
}
func pauseScene() {
self.speed = 0.0
tapToResume.hidden = false
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// Check the label was pressed here.
if labelWasPressed {
self.speed = 1.0
tapToResume.hidden = true
}
}
I think I got the problem. You should call pauseGameScene() first. Then view?.paused is true. Then you can call showPauseText().
Hope that helps :)
If you set AVPlayerViewController.showsPlaybackControls to false, the controls will not show at all. Even if you tap the screen.
I want the controls to start out hidden, but still be able to summon them by tapping. If I set the mentioned property to true, they start out visible. (Yes they fade after a few seconds.) Is there a way to start hidden, but still be accessible?
UPDATE: I ended up making my own controls for better customization. It's more difficult but worth the time. Please read Apple's sample code for reference. It's about implementing PiP but also about making custom controls: https://developer.apple.com/library/prerelease/ios/samplecode/AVFoundationPiPPlayer/Introduction/Intro.html
UPDATE: When tapped, AVPlayerViewController only fires touchesBegan event, and not touchesEnded event. But it's enough to show the controls.
First you need to hide the control. Put this code right before you present AVPlayerViewController
YourAVPlayerViewController.showsPlaybackControls = false
Then subclass AVPlayerViewController and add this function:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.showsPlaybackControls = true
super.touchesBegan(touches, withEvent: event)
}
OLD SOLLUTION:
I've just solved this. The main idea is to put a UIView on top of the AVPlayerViewController to reveive tap gesture, and hide that UIView when it is no longer needed.
Here's the code:
import AVKit
import UIKit
// Create a custom AVPlayerViewController
#available(iOS 8.0, *)
final class CustomAVPlayerViewController: AVPlayerViewController {
// Create a UIView to put on top of all
lazy var topView = UIView(frame: CGRectMake(0, 0, width, height))
override func viewDidLoad() {
super.viewDidLoad()
// For sure, set it to clearcolor
// (DON'T set alpha = 0 because it will stop receiving user interaction)
topView.backgroundColor = UIColor.clearColor()
// Add it to the view of AVPlayerViewController
self.view.addSubview(topView)
// Bring it to front
self.view.bringSubviewToFront(topView)
// Add a tap gesture recognizer
topView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap"))
}
// Handle the tap
func handleTap() {
// Show the control
self.showsPlaybackControls = true
// Hide the topView. You can unhide it when needed later.
self.topView.hidden = true
}
}
And when you need to hide the controls, do this:
var AVViewController = CustomAVPlayerViewController()
...
// Hide controls
AVViewController.showsPlaybackControls = false
// Show topView
AVViewController.topView.hidden = false
I think I've solved this using dynamic gesture recognizer relationships. The solution avoids custom controls (for consistency), uses only public API and does not subclass AVPlayerViewController (which is explicitly disallowed, as noted in other answers).
Here's how:
Make a container view controller that embeds AVPlayerViewController. (This is useful regardless of the controls, because you need to put the playback logic somewhere.)
Set showsPlaybackControls to false initially.
Add a UITapGestureRecognizer to recognize the initial tap.
In the action method for the gesture recognizer, set showsPlaybackControls to true.
So far, it would work, but the controls would disappear immediately on that initial tap. To fix that, set yourself as a delegate for the gesture recognizer, implement gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer: and return true for any other single-tap gesture recognizer.
Here's the actual implementation in Swift; check andreyvit/ModalMoviePlayerViewController repo for the latest code:
import UIKit
import AVKit
import AVFoundation
public class ModalMoviePlayerViewController: UIViewController {
private let fileName: String
private let loop: Bool
private var item: AVPlayerItem!
private var player: AVPlayer!
internal private(set) var playerVC: AVPlayerViewController!
private var waitingToAutostart = true
public init(fileName: String, loop: Bool = true) {
self.fileName = fileName
self.loop = loop
super.init(nibName: nil, bundle: nil)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
let url = NSBundle.mainBundle().URLForResource(fileName, withExtension: nil)!
item = AVPlayerItem(URL: url)
player = AVPlayer(playerItem: item)
player.actionAtItemEnd = .None
player.addObserver(self, forKeyPath: "status", options: [], context: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ModalMoviePlayerViewController.didPlayToEndTime), name: AVPlayerItemDidPlayToEndTimeNotification, object: item)
playerVC = AVPlayerViewController()
playerVC.player = player
playerVC.videoGravity = AVLayerVideoGravityResizeAspectFill
playerVC.showsPlaybackControls = false
let playerView = playerVC.view
addChildViewController(playerVC)
view.addSubview(playerView)
playerView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
playerView.frame = view.bounds
playerVC.didMoveToParentViewController(self)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ModalMoviePlayerViewController.handleTap))
tapGesture.delegate = self
view.addGestureRecognizer(tapGesture)
}
deinit {
player.pause()
player.removeObserver(self, forKeyPath: "status")
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func togglePlayPause() {
if isPlaying {
pause()
} else {
play()
}
}
func restart() {
seekToStart()
play()
}
func play() {
if player.status == .ReadyToPlay {
player.play()
} else {
waitingToAutostart = true
}
}
func pause() {
player.pause()
waitingToAutostart = false
}
var isPlaying: Bool {
return (player.rate > 1 - 1e-6) || waitingToAutostart
}
private func performStateTransitions() {
if waitingToAutostart && player.status == .ReadyToPlay {
player.play()
}
}
public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
performStateTransitions()
}
#objc func didPlayToEndTime() {
if isPlaying && loop {
seekToStart()
}
}
private func seekToStart() {
player.seekToTime(CMTimeMake(0, 10))
}
public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if !playerVC.showsPlaybackControls {
playerVC.showsPlaybackControls = true
}
super.touchesBegan(touches, withEvent: event)
}
}
extension ModalMoviePlayerViewController: UIGestureRecognizerDelegate {
#IBAction func handleTap(sender: UIGestureRecognizer) {
if !playerVC.showsPlaybackControls {
playerVC.showsPlaybackControls = true
}
}
/// Prevents delivery of touch gestures to AVPlayerViewController's gesture recognizer,
/// which would cause controls to hide immediately after being shown.
///
/// `-[AVPlayerViewController _handleSingleTapGesture] goes like this:
///
/// if self._showsPlaybackControlsView() {
/// _hidePlaybackControlsViewIfPossibleUntilFurtherUserInteraction()
/// } else {
/// _showPlaybackControlsViewIfNeededAndHideIfPossibleAfterDelayIfPlaying()
/// }
public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if !playerVC.showsPlaybackControls {
// print("\nshouldBeRequiredToFailByGestureRecognizer? \(otherGestureRecognizer)")
if let tapGesture = otherGestureRecognizer as? UITapGestureRecognizer {
if tapGesture.numberOfTouchesRequired == 1 {
return true
}
}
}
return false
}
}
thegathering's answer is good. I would override touchesCancelled instead so that the controls do not show and then hide again.
override public func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
// toggle the player controls on if they were set to off
if !self.showsPlaybackControls {
self.showsPlaybackControls = true
}
}
A simple way to do it in Swift 3 is to set myController.showsPlaybackControls = false, and to overlay the whole player view with a button or gesture recognizer. I embed it into another view in another controller on a storyboard to make this simple and to not override the player controller. The trick then is to hide the button after being clicked once, because the player controller will thereafter track taps to show/hide the controls.
#IBAction func enableControls(button:UIButton)
{
controller?.showsPlaybackControls = true
button.isHidden = true //The button is only needed once, then the player takes over.
}
I have button in GameViewController that lives on my main game screen:
var gameScene: GameScene!
override func viewDidAppear(animated: Bool) {
gameScene = GameScene()
}
#IBAction func butPressed(sender: UIButton) {
gameScene.buttonPressed = true
println("Pressed from GameViewController")
}
but when I do this from GameScene() update method:
override func update(currentTime: CFTimeInterval) {
if (buttonPressed == true) {
println("Pressed from GameScene")
buttonPressed = false
}
}
I never can change the variable to true. If I println("buttonPressed") from update(), it always stays false. However if I print buttonPressed from within the IBAction, it will show as true. The variable change in GameViewController() isn't changing the boolean value in update() in GameScene(). Why?
Please help me
Try to make new class with this code
class Data {
struct gameScene {
static var ButtonPressed:Bool = false
}
}
Then make some changes in your code
var gameScene: GameScene!
override func viewDidAppear(animated: Bool) {
gameScene = GameScene()
}
#IBAction func butPressed(sender: UIButton) {
Data.gameScene.buttonPressed = true
println("Pressed from GameViewController")
}
override func update(currentTime: CFTimeInterval) {
if (Data.gameScene.buttonPressed == true) {
println("Pressed from GameScene")
Data.gameScene.buttonPressed = false
}
}
You can call to this variable anywhere in your project with this code
Data.gameScene.buttonPressed
I've read everything I could find on this topic and still cant figure out my issue. I have tried pausing my game in every area of appdelegate
func applicationWillResignActive(application: UIApplication!) {
NSNotificationCenter.defaultCenter().postNotificationName("pauseGameScene", object: self)
}
func applicationDidEnterBackground(application: UIApplication!) {
NSNotificationCenter.defaultCenter().postNotificationName("pauseGameScene", object: self)
}
func applicationWillEnterForeground(application: UIApplication!) {
NSNotificationCenter.defaultCenter().postNotificationName("pauseGameScene", object: self)
}
func applicationDidBecomeActive(application: UIApplication!) {
NSNotificationCenter.defaultCenter().postNotificationName("pauseGameScene", object: self)
}
In my controller:
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "pauseGame:", name: "pauseGameScene", object: nil)
}
func pauseGame(){
self.skView.paused = true
self.skView.scene!.paused = true
}
i know pauseGame works because if i toggle it with a button in my scene, it will stop the game. Even if I pause my skview and scene directly after they are loaded in the controller.. the game will not be paused on launch. It's easy to pause the game when I'm in-game. but for some reason whenever i exit and resume the app, the game will un-pause itself.
i notice if i get hacky and use some kind of delay.. i can get it to work. but obviously this is very stupid.. i just need to know where the game is unpausing itself!
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
func pauseGame(sender: UIButton!){
delay(2) {
println("blah")
self.skView.paused = true
self.skView.scene!.paused = true
}
}
Here's a way to keep the view paused after returning from background mode.
Xcode 7 (see below for Xcode 8 instructions)
In the storyboard,
1) Change the class of the view to MyView
In the View Controller,
2) Define an SKView subclass with a boolean named stayPaused
class MyView: SKView {
var stayPaused = false
override var paused: Bool {
get {
return super.paused
}
set {
if (!stayPaused) {
super.paused = newValue
}
stayPaused = false
}
}
func setStayPaused() {
if (super.paused) {
self.stayPaused = true
}
}
}
3) Define the view as MyView
4) Add a notifier to set the stayPaused flag
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as MyView
NSNotificationCenter.defaultCenter().addObserver(skView, selector:Selector("setStayPaused"), name: "stayPausedNotification", object: nil)
In the App Delegate,
5) Post a notification to set the stay paused flag when the app becomes active
func applicationDidBecomeActive(application: UIApplication) {
NSNotificationCenter.defaultCenter().postNotificationName("stayPausedNotification", object:nil)
}
Xcode 8
In the storyboard,
1) Change the class of the view from SKView to MyView
In the View Controller,
2) Define an SKView subclass with a boolean named stayPaused
class MyView: SKView {
var stayPaused = false
override var isPaused: Bool {
get {
return super.isPaused
}
set {
if (!stayPaused) {
super.isPaused = newValue
}
stayPaused = false
}
}
func setStayPaused() {
if (super.isPaused) {
self.stayPaused = true
}
}
}
3) Define the view as MyView
4) Add a notifier to set the stayPaused flag
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! MyView? {
NotificationCenter.default.addObserver(view, selector:#selector(MyView.setStayPaused), name: NSNotification.Name(rawValue: "stayPausedNotification"), object: nil)
In the App Delegate,
5) Post a notification to set the stay paused flag when the app becomes active
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "stayPausedNotification"), object: nil)
}
Here is an Obj-C example completely based on answer given by 0x141E, without subclassing a view. In my case, because I have internal game states, like finished, paused and started, I had to make an additional check in overridden setPaused: method. But this worked flawlessly for me and definitely it is the one possible way to go.
AppDelegate.m
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:#"stayPausedNotification" object:nil];
}
GameScene.m
#interface GameScene()
#property (nonatomic, assign) BOOL stayPaused;
#end
#implementation GameScene
//Use initWithCoder: if you load a scene from .sks file, because initWithSize is not called in that case.
-(instancetype)initWithSize:(CGSize)size{
if(self = [super initWithSize:size]){
_stayPaused = NO;
//register to listen for event
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(setStayPaused)
name:#"stayPausedNotification"
object:nil ];
}
return self;
}
-(void)setStayPaused{
self.stayPaused = YES;
}
-(void)setPaused:(BOOL)paused{
if (!self.stayPaused) {
[super setPaused:paused];
}
self.stayPaused = NO;
}
-(void)willMoveFromView:(SKView *)view{
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"stayPausedNotification" object:nil];
}
#end
I recently encountered the same issue. The stayPaused solution works fine for me. Thanks guys:)
However, I think the mechanism you use to set stayPaused is NOT robust. Just by calling setStayPaused once on App becoming active is not enough, engineers in apple may change the code of Sprite Kit and call setPaused(false) MORE THAN ONCE on activating (In fact, I think they just did!). The second setPaused(false) will resume the game as you set stayPaused back to false on the first call.
My suggestion is to directly set the stayPaused property yourself in your pauseGame/resumeGame functions, and remove the "self.stayPaused = NO;" from setPaused function. In this way, the default behavior of SpriteKit can do whatever it likes, but the game will stay paused as long as we want.