I have set play/pause button to gameLayer - here: How to prevent run SKAction on paused scene (after unpaused), texture of node not change after pause/unpause scene
Everything works fine, but I'm helpless with app on background. When I pause gameLayer and app goes to background or when I lock device, after app going to foreground, game layer is automatically unpause. How to prevent this?
Thanks!
So this is what worked for me:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var gameLayer = SKNode()
override func didMove(to view: SKView) {
NotificationCenter.default.addObserver(self,
selector: #selector(GameScene.pause),
name: .UIApplicationDidBecomeActive,
object: nil)
let moveUp = SKAction.moveBy(x: 0, y: 100, duration: 2)
let sequence = SKAction.sequence([moveUp, moveUp.reversed()])
let loop = SKAction.repeatForever(sequence)
let sprite = SKSpriteNode(color: .purple, size: CGSize(width: 100, height: 100))
addChild(gameLayer)
gameLayer.addChild(sprite)
sprite.run(loop)
}
func pause(){
gameLayer.speed = 0
}
override func willMove(from view: SKView) {
super.willMove(from: view)
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidBecomeActive,
object: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
gameLayer.speed = gameLayer.speed == 0 ? 1 : 0
}
}
I haven't had time to test more, but I was expecting that something like this:
func pause(){
gameLayer.isPaused = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
gameLayer.isPaused = !gameLayer.isPaused
}
would work, but surprisingly it isn't (the node continued with moving after the pause() method was executed). I don't really know if this is a bug or a feature :) but IMHO, I would say it is a bug. Anyways, you can achieve what you want using the speed property. Just copy and paste this code to see how it works.
I've made a maybe not that beautiful, but a very easy workaround.
Just added var gamePaused = false to the Scene class, which is set to true in pause(), and then in
override func update(_ currentTime: TimeInterval) {
guard gamePaused == false else {
if playLayer.isPaused == false {playLayer.isPaused = true}
return
}
.....
}
So each time the app start from the background, the playLayer.isPaused is set to true just with the first rendered frame.
Related
I am trying to implement a pause button in my game. However every time I tap the pause button (SKSpriteNode) on my iOS Device nothing happens. I have tried making the button do other actions and tried making other sprites do the action. None have worked, although I am able to touch any location on the screen and the action is performed. I am using Swift 4 with the latest version of Xcode (9.4.1). The app is a iOS Game and I am using the GameScene.swift that is created along with the app.
Here is the part of the code for the button (irrelevant parts of the code are left out):
import SpriteKit
import CoreMotion
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var pauseButton:SKSpriteNode!
override func didMove(to view: SKView) {
pauseButton = SKSpriteNode(imageNamed: "pauseButton")
pauseButton.size = CGSize(width: 50, height: 50)
pauseButton.position = CGPoint(x: self.size.width -
pauseButton.size.width, y: self.size.height -
pauseButton.size.height)
pauseButton.zPosition = 5
self.addChild(pauseButton)
}
override func touchesEnded(_ touches: Set<UITouch>, with: UIEvent?) {
fireBullet() //This function does not relate to the pause button.
}
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self) {
let nodesArray = self.nodes(at: location)
if nodesArray.first?.name == "pauseButton" {
self.view?.isPaused = true
}
}
}
}
Thanks in advance for taking your time to reply it really helps me!
Thomas
Easy fix, in your touchesBegan method you are searching for touches on a node named "pauseButton" but there are no nodes named "pauseButton" so your search returns a false value.
Just add pauseButton.name = "pauseButton" to your code where you set up the pause button and it should work.
I have to buttons leftbutton, rightbutton both of them being SKSpriteNode().
When the user touches one of the buttons, there is a little ship that moves left and right as long as the user holds the touch.
Now I need a function or anything else that gives me the ship.position.x the whole time. I am stuck to try to make it print the position constantly. I can make it print everytime the button is touched but it only prints it once.
In my didMove I only created the buttons and the ship. So it should be rather irrelevant.
func moveShip (moveBy: CGFloat, forTheKey: String) {
let moveAction = SKAction.moveBy(x: moveBy, y: 0, duration: 0.09)
let repeatForEver = SKAction.repeatForever(moveAction)
let movingSequence = SKAction.sequence([moveAction, repeatForEver])
ship.run(movingSequence, withKey: forTheKey)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("\(ship.position.x)")
for touch: AnyObject in touches {
let pointTouched = touch.location(in: self)
if leftButton.contains(pointTouched) {
// !! I MAKE IT PRINT THE POSITION HERE !!
moveShip(moveBy: -30, forTheKey: "leftButton")
}
else if rightButton.contains(pointTouched) {
moveShip(moveBy: 30, forTheKey: "rightButton")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == aButton {
} else {
ship.removeAction(forKey: "leftButton")
ship.removeAction(forKey: "rightButton")
}
}
}
In my code, the position is only printed once at the beginning of the touch and not printed until you release the touch and touch it again. Is there a possible way to do this with my code?
The touchesMoved function won't help you with your particular problem. You can check your frame constantly by creating a var timer = Timer() as instance variable.
You then have to set up the timer and the function that is called when a specific amount of time is over.
Do as following in your didMove function:
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self,
selector: #selector(detectShipPosition), userInfo: nil, repeats: true)
As this will repeat itself every 0.01 seconds it will call the function detectShipPosition which you will implement OUTSIDE didMove.
func detectShipPosition(){
print("\(ship.position.x)")
}
Hi you can make use of the touchesMoved delegate method(It tells the responder when one or more touches associated with an event changed.) as follows,
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("\(ship.position.x)")
}
Solution for the comment you posted on Bharath answer.
You can change below with your own value:
longGesture.minimumPressDuration
You can use UILongPressGestureRecognizer :
class ViewController: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet weak var leftButton: UIButton!
#IBOutlet weak var rightButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longGesture.minimumPressDuration = 0.5
longGesture.delegate = self
leftButton.addGestureRecognizer(longGesture)
rightButton.addGestureRecognizer(longGesture)
}
#objc func longPress(_ gestureReconizer: UILongPressGestureRecognizer) {
print("\(ship.position.x)")
}
}
If you implement the update() function in your scene, it will be called for every frame and you can check your object's position (and existence) in there to print the value when it changes:
override func update(_ currentTime: TimeInterval)
{
if let ship = theShipSprite,
ship.position != lastPosition
{
print(ship.position) // or whatever it is you need to do
lastPosition = shipPosition
}
}
I am trying to implement moving to another scene when a wheel stops rotating. The code I have is shown below. I cannot figure out how to detect when the velocity has reached 0.0?
import SpriteKit
import GameplayKit
class GameplayScene: SKScene {
var player: Player?;
override func didMove(to view: SKView) {
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
So from here, I would like to execute the following when the wheel has stopped spinning:
let play_scene = Questions(fileNamed: "QuestionsScene")
play_scene?.scaleMode = .aspectFill
self.view?.presentScene(play_scene!, transition: SKTransition.doorsOpenVertical(withDuration: 1))
I have now edited the class and it looks as follows:
import SpriteKit
import GameplayKit
class GameplayScene: SKScene, SKSceneDelegate {
var player: Player?
override func didMove(to view: SKView) {
self.delegate = self
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.pinned = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func didSimulatePhysics() {
if ((player?.physicsBody?.angularVelocity)! <= CGFloat(0.01))
{
print("Got it")
}
}
}
Problem is I still receive an error on the if statement within didSimulatePhysics function. The error I receive is "Thread 1: EXC_BAD_INSTRUCTION...."
Your wheel's SKPhysicsBody has a built-in property, angularVelocity, that tells you how fast it's spinning. You're already using it when you set it to r to start the wheel spinning.
To watch angularVelocity you can use didSimulatePhysics(). It gets called once every frame, right after the physics calculations are done. That will look something like this:
func didSimulatePhysics() {
if wheelIsSpinning && angularVelocity != nil && angularVelocity! <= CGFloat(0.001) {
wheelIsSpinning = false
// wheel has stopped
// add your code here
}
}
Due to the vagaries of physics modeling, the angularVelocity might never be exactly zero. So instead we watch for it to be less than some arbitrary threshold, in this case 0.001.
You don't want it to execute every frame when the wheel isn't moving, so I added a property wheelIsSpinning to keep track. You'll need to add it as an instance property to GameplayScene, and set it to true in spin().
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 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 :)