I have some nodes that I would like to run their action in the start menu. How would I do this? I have these clouds that move from left to right and I want to un pause them. I tried a lot of different things but cant seem to get it to work. Thanks here is the code but its not working for me. Im in spritekit swift.
override func didMoveToView(view: SKView) {
view.scene?.paused = true
}
override func update(currentTime: CFTimeInterval) {
//movement of clouds
cloud.paused = false
cloud2.paused = false
cloud3.paused = false
}
}
You can use the .speed property of your SKAction to pause and resume the action:
//Stops your action
yourAction.speed = 0
//Resumes your action
yourAction.speed = 1
Also don't put code in the update method which doesn't need to be called every frame. Put it inside a didMoveToView method or something similar.
Related
Let me preface this question by saying I know it has been asked a few times, but the most upvoted post here was from back in 2014 and the top answer doesn't seem to work anymore.
Issue
In my game, I have all my 'moving' nodes as children of pauseNode. When the pauses button is pressed or when the player leaves the app, pauseScene() is called:
func pauseScene() {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
}
This pauses all sprite nodes and emitter nodes. This keeps the sprite nodes paused if the app transitions from background to foreground state, but the particle emitters seem to resume animating.
Current solution
I solved this issue by triggering pauseScene() after a small delay when the app became active.
override func didMove(to view: SKView) {
NotificationCenter.default.addObserver(self, selector: #selector(GameScene.applicationDidBecomeActive(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: app)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
NSDelay(0.01) {
pauseScene()
}
}
func NSDelay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
Goal
While this solution pauses the particles, it is not perfect because they are visible moving for the split second it takes to pause them again (this is the shortest delay that actually re-pauses them).
So, after reading the post I linked above, I tried to override applicationDidBecomeActive() in an SKView subclass as was suggested, but I couldn't get the method to be called. I want to prevent these particles from being unpaused in the first place, if possible. Thanks
So, it's been a little over a day and I've found a better solution. A comment in this post suggested having a variable that recorded the status of the scene, and updating it along with the pause/play functions:
var sceneIsPaused = false
func pauseScene() {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
sceneIsPaused = true
}
func playScene() {
self.node.speed = 1
self.physicsWorld.speed = 1
self.node.isPaused = false
sceneIsPaused = false
}
Then, override SKView's update method and check the state of that variable. If the scene should be paused, pause it. This will mean that if the scene is automatically unpaused it will re-pause the next frame. This is much faster and cleaner than setting a delay:
override func update(_ currentTime: TimeInterval) {
if (sceneIsPaused == true) {
self.node.speed = 0
self.physicsWorld.speed = 0
self.node.isPaused = true
}
}
Desired behavior is: when an action is removed from a node (with removeAction(forKey:) for instance) it stops to animate and all the changes caused by action are discarded, so the node returns back to pervious state. In other words, I want to achieve behavior similar to CAAnimation.
But when a SKAction is removed, the node remains changed. It's not good, because to restore it's state I need to know exactly what action was removed. And if I then change the action, I also will need to update the node state restoration.
Update:
The particular purpose is to show possible move in a match-3 game. When I show a move, pieces start pulsating (scale action, repeating forever). And when the user moves I want to stop showing the move, so I remove the action. As the result, pieces may remain downscaled. Later I would like to add more fancy and complicated animations, so I want to be able to edit it easily.
Thanks to the helpful comment and answer I came to my own solution. I think the state machine would be bit too heavy here. Instead I created a wrapper node, which main purpose is run the animation. It also has a state: isAimating property. But, first of all, it allows to keep startAnimating() and stopAnimating() methods close to each other, incapsulated, so it's more difficult to mess up.
class ShowMoveAnimNode: SKNode {
let animKey = "showMove"
var isAnimating: Bool = false {
didSet {
guard oldValue != isAnimating else { return }
if isAnimating {
startAnimating()
} else {
stopAnimating()
}
}
}
private func startAnimating() {
let shortPeriod = 0.2
let scaleDown = SKAction.scale(by: 0.75, duration: shortPeriod)
let seq = SKAction.sequence([scaleDown,
scaleDown.reversed(),
scaleDown,
scaleDown.reversed(),
SKAction.wait(forDuration: shortPeriod * 6)])
let repeated = SKAction.repeatForever(seq)
run(repeated, withKey: animKey)
}
private func stopAnimating() {
removeAction(forKey: animKey)
xScale = 1
yScale = 1
}
}
Usage: just add everything that should be animated to this node. Works well with simple animations, like: fade, scale and move.
As #Knight0fDragon suggested, you would be better off using the GKStateMachine functionality, I will give you an example.
First declare the states of your player/character in your scene
lazy var playerState: GKStateMachine = GKStateMachine(states: [
Idle(scene: self),
Run(scene: self)
])
Then you need to create a class for each of these states, in this example I will show you only the Idle class
import SpriteKit
import GameplayKit
class Idle: GKState {
weak var scene: GameScene?
init(scene: SKScene) {
self.scene = scene as? GameScene
super.init()
}
override func didEnter(from previousState: GKState?) {
//Here you can make changes to your character when it enters this state, for example, change his texture.
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is Run.Type //This is pretty obvious by the method name, which states can the character go to from this state.
}
override func update(deltaTime seconds: TimeInterval) {
//Here is the update method for this state, lets say you have a button which controls your character velocity, then you can check if the player go over a certain velocity you make it go to the Run state.
if playerVelocity > 500 { //playerVelocity is just an example of a variable to check the player velocity.
scene?.playerState.enter(Run.self)
}
}
}
Now of course in your scene you need to do two things, first is initialize the character to a certain state or else it will remain stateless, so you can to this in the didMove method.
override func didMove(to view: SKView) {
playerState.enter(Idle.self)
}
And last but no least is make sure the scene update method calls the state update method.
override func update(_ currentTime: TimeInterval) {
playerState.update(deltaTime: currentTime)
}
I'm making a card game with Swift 4 and SpriteKit. I made a custom subclass of SKScene, and inside this class is a SKNode which I'm using as a layer (gameLayer), to which I attach all Nodes, which shall be stopped.
I also have made a button, which calls a function (pauseGame) that toggles isPaused of the gameLayer, which works perfectly.
The problem is: I want that when the game moves to the scene, it starts already stopped, the user shall begin the game by pressing a button. But when I call the pauseGame function inside the didMove function, it doesn't work, the gameLayer remains active.
I made some observations putting ' print (gameLayer.isPaused) ' and frame count inside the update function. It showed that actually the scene starts with gameLayer.isPaused set to true inside the didMove as it should be, but after the 2nd frame it gets set to false. I have really no idea where it can happen, as I don't appeal to gameLayer.isPaused anywhere else in code.
Of course a solution would be to call pauseGame after the 2nd frame, but I think its not a clean way.
class BMScene: SKScene {
let gameLayer = SKNode()
func pauseGame(){
if gameLayer.isPaused {
gameLayer.isPaused = false
} else {
gameLayer.isPaused = true
}
}
override func didMove(to view: SKView) {
self.pauseGame()
}
isPaused is a bugged up mess in the world of SpriteKit. I have literally argued with developers at apple about the broken nature of it, and they claim that is how it is intended to work, and we must deal with it. The latest atrocity I know about, is that isPaused is defaulted to true for new scenes, then a message is sent out to unpause it. On top of that, when you unpause a parent node, it also unpauses all children nodes.
So in your code here, override the isPaused of your scene to be able to capture your pausing event to block it from going to its children
class BMScene: SKScene {
let gameLayer = SKNode()
private var _paused : Boolean = false
override var isPaused : Boolean
{
get
{
return _paused
}
set
{
_paused = newValue
}
}
func pauseGame(_ state: Boolean? = null){
gameLayer.isPaused = state ?? !gameLayer.isPaused
}
override func didMove(to view: SKView) {
self.pauseGame(true)
}
}
This will stop your node from changing its childrens pause state. After this moment, you should have complete control of your gameLayer pause state, and it should flow how you think it is flowing.
I have also modified your function to allow you to force the pause state, as well as toggle when you want to swap the state.
E.g.
Pause:
pauseGame(true)
'gameLayer.isPaused is now true
Unpause:
pauseGame(true)
'gameLayer.isPaused is now false
Toggle:
pauseGame()
'gameLayer goes from false to true, or true to false
?? Means COALESCE, so if state is null, it will move along the chain to the next value, which is !gameLayer.isPaused
I've solved similar problem by setting speed to zero:
gameLayer.speed = 0
// This will, obviously, unpause
gameLayer.speed = 1
So I added two label nodes to my code, and want one to be hidden while the other is shown, and then vice versa once the game begins.
class GameScene: SKScene {
// Declarations
let startLabel:SKLabelNode = SKLabelNode()
let scoreLabel:SKLabelNode = SKLabelNode()
var score = 0
override func didMoveToView(view: SKView) {
// Properties
startLabel.fontSize = 20
startLabel.fontColor = UIColor.blackColor()
startLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.5)
startLabel.text = "Touch Paddle To Begin"
startLabel.hidden = false
self.addChild(startLabel)
scoreLabel.fontSize = 20
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.75)
scoreLabel.text = "\(score)"
scoreLabel.hidden = true
self.addChild(scoreLabel)
Then, when the Paddle is touched, a moveBall() function is initiated and the game begins. This is where I swapped the labels.
func moveBall() {
// Setting Labels
self.startLabel.hidden = true
self.scoreLabel.hidden = false
// Starting game
self.ball.physicsBody?.dynamic = true
However, it only works sometimes. It will work perfectly, and then without changing anything I'll run it again and only one label will show up. Then again and the other label will show up, or the startLabel will show for only a frame then disappear.
Disclaimer: I do not really know how to code, just got an idea for a game and am trying to make it a reality. Apologies if solution is something simple. Also any advice for my code would be appreciated. Thanks!
From the information given it should work correctly. However you should call the moveBall() in the touchesBegan method. Not the update method.
Also if you want it to be called if the user touches the labelNode use the location of touches began. (But I would rather use a sprite instead of a label coz labels are a bit unresponsive coz sometimes finger doesn't touch the label.
So call the moveBall() in the correct method. Give some more info to give a more accurate answer.
I'm making my first game, it will be similar to Mario. (using swift)
Have started to make some enemies they will need to move left and right and respond to collisions, I can do this for my player node as I call player.update() function from the SKScene update function,
override func update(currentTime: NSTimeInterval)
{
player.update()
}
My question is:
For enemyX , how can I get the node to update since SKSpriteNode does not have a default update function that can be overridden and from my game scene
I do not want really write some thing like:
override func update(currentTime: NSTimeInterval)
{
player.update()
enemyX.update()
enemyY.update()
enemy001.update()
...
enemy100.update
}
Do you see what I mean, how do I get around this, does SKSpriteNode need to be a different class?
Maybe actions?
let moveLeft = SKAction.moveByX( 200, y:0, duration: 5)
self.runAction(moveLeft, completion: {
print("----> move left Complete")
})
Thanks.
you subclass SKSpriteNode and and an update method inside that class. then in your update method in your scene you do exactly what you said
player.update()
enemy.update()
etc.
Don't recommend using SKAction unless the behavior of your enemies is extremely simple.