I am creating my first game in SpriteKit. I am trying to pause my GameScene and then run some animations via SpriteKit. The issue was that if I pause the scene using self.view?.isPaused = true everything stops. After searching around I found that the way to do this is to add another node to the scene and make everything else that you want to pause, child nodes of that node. I am trying to do that but haven't been successful. This is the code that I have
var worldNode:SKNode?
override func didMove(to view: SKView) {
worldNode = SKNode()
self.addChild(self.worldNode!)
let spriteNode:SKSpriteNode = SKSpriteNode(imageNamed: "red_ball")
spriteNode.size = CGSize(width: 40, height: 40)
spriteNode.physicsBody = SKPhysicsBody(circleOfRadius: 15)
spriteNode.physicsBody?.isDynamic = true
self.worldNode?.addChild(spriteNode)
spriteNode.physicsBody?.applyImpulse(CGVector(dx: 10, dy: 10))
self.worldNode?.isPaused = true
//self.view?.isPaused = true
}
when I run this I see the ball moving on the screen which should not happen according to my understanding, since I am pausing the worldNode and the spriteNode is child node of that. If I use self.view?.isPaused = true to pause the scene, then the ball is stationary as expected. Am I missing something ? how can I add nodes to the worldNode and then just pause only those nodes by pausing worldNode. Thanks !
Well one way to pause nodes moved by impulses and forces is certainly to pause the scene:
self.isPaused = true
but I would say that you want to have other nodes unpaused (eg. HUD elements) so you added your game elements into world node.
So one way to pause physics along with pausing the node is to pause the complete physics world, like this:
self.physicsWorld.speed = 0
Related
How can I disable a SpriteKit node touch event and then enable it after 25 seconds so that when the node is touched it will take the user to the next scene?
I am setting up a GameScene that plays an audio file for 25 seconds and then after that I want the user to be able to click on a SpriteKit Node that will take the user to the next Scene. The problem is the node cannot be hidden. It needs to be visible and but disable and then visible and enabled for touch after 25 seconds.
if nextButton.contains(location) {
if nextButton.isHidden == true {
nextButton.isUserInteractionEnabled = false
} else {
goToScene(scene: getNextScene()!)
}
I have used this code for when the SPriteKit Node has been able to be hidden, but this time the Node must be visible the whole time.
From the looks of this, your GameScene is what handles the touch events, so when you are playing your audio, you should do something like this:
self.userInteractionEnabled = false
let audio = SKAudioNode()
audio.run{
SKAction.group([SKAction.play,
SKAction.sequence([
SKAction.wait(forDuration:25),
SKAction.run({self.userInteractionEnabled = true})
])
)
}
self.addChild(audio)
If you are using playSound action
self.userInteractionEnabled = false
let audio = SKSpriteNode()
audio.run{
SKAction.sequence([
SKAction.playSoundFileNamed("mySound",true),
SKAction.run({self.userInteractionEnabled = true})
])
}
Basically what this does is sets a delay action to enable touch after the sound finishes.
I'm creating a small game where the ball needs to bounce on the upper, left and right wall, while it should stop as soon as it touches the floor.
I already have a working contact-detection code and delegate that is called in the correct situation and with the correct timing.
Creating a small circle in the collision point found in the func didBegin(_ contact: SKPhysicsContact) creates the node in the correct position.
In the same method I call ball.physicsBody?.velocity = .zero and ball.position = contact.contactPoint and the ball correctly stops. The problem is that sometimes the ball stops a frame too late, after it's already bounced off the floor.
The ball has a restitution of 1 because I would like it to bounce without slowing down, except when touching the floor.
I have already tried to set the position to zero, the velocity to zero, its isDynamic attribute to false. Each of these tries does what it's supposed to do, but sometimes it happens a frame too late.
Both the ball and the floor have the usesPreciseCollisionDetection property set to true.
If I try to add some artificial delay, the contact method is called again (breaking part of the gameplay).
Here is the code of my ball node:
class Ball: SKSpriteNode {
convenience init() {
self.init(imageNamed: "earth")
physicsBody = SKPhysicsBody(circleOfRadius: 12.5)
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.categoryBitMask = Masks.ball
physicsBody?.contactTestBitMask = Masks.floor
physicsBody?.allowsRotation = false
physicsBody?.restitution = 1
physicsBody?.friction = 0
physicsBody?.angularDamping = 0
physicsBody?.linearDamping = 0
}
}
and here is the code for my didBegin method:
func didBegin(_ contact: SKPhysicsContact) {
ball.physicsBody?.velocity = .zero
ball.position = contact.contactPoint
}
I would expect the ball to stop while still touching the floor, and not a frame after it's already bounced off.
I'm using the following to pause my game
self.scene?.paused = true
When the scene is paused, however, all SKActions stop. What can I do to allow some of the actions to continue to work?
Add certain nodes to different SKNode so instead you can only pause the the layer (SKNode) that you wish to pause. It would look something like this:
let gameLayer = SKNode()
let pauseLayer = SKNode()
Now when you want to add a child to the scene, instead add it to a layer:
gameLayer.addChild(gameSceneNode)
pauseLayer.addChild(pauseSceneNode)
Don't forget to add the layers to the scene too
addChild(gameLayer)
addChild(pauseLayer)
To pause a layer write this:
Swift 3
gameLayer.isPaused = true
Swift 2
gameLayer.paused = true
Note that in this example, all the nodes on the gameLayer will be paused, however everything on the pauseLayer will not.
You need to design your node tree in a way where you can pause certain nodes (the gameplay nodes, for example), and not pause others (the pause menu nodes, for example). When you set the paused property on a node, it applies to all of its children as well.
An example node hierarchy:
GameScene
GameplayNode
Character
Enemy
Enemy
PauseMenu
PlayButton
VolumeButton
If you want to animate your PlayButton while the game is paused, you can set the GameplayNode.paused to true, and still have working SKAction's for your pause menu.
I need to pause the balls for my game. I want them to stop in place but they are moving by impulse and if I make them non-dynamic to stop them, the impulse goes away. I'm trying to pause them and un-pause them, and they still keep going in the same direction. I tried
ball.paused = true
but that didn't work. Anyone know?
Instead of freezing the nodes, I froze the scene with
scene?.physicsWorld.speed = 0
because that freezes all of the nodes and not my time or score integers which is exactly what I needed.
Here is how I solved it in my game:
var ballVelocity = CGVector()
func pauseAction(ball: SKSpriteNode){
if ball.dynamic {
//Ball is moving. Save the current velocity of ball.
ballVelocity = ball.velocity
//Stop the ball
ball.physicsBody.dynamic = false
}else{
//Ball is paused.
//Resume ball movement.
ball.physicsBody.dynamic = true
//Add the saved velocity.
ball.velocity = ballVelocity
}
}
You can use ball.physicsBody.velocity = CGVectorMake(0, 0); but keep in mind that if the ball is affected by gravity, it will still drop.
Okay so i am trying to learn how to create a game... I want a node to be the camera, so that i can move it and center the view to my player node. When i subclass (i don't know if it's right to say that i subclass it, maybe not...) the player node to the world node, the application crashes. When player is simply a node, a "subclass" of GameScene (and not of the "world" node), it goes "fine", i mean i can move my player (yeah but the camera doesn't work).
so here is my code (few // lines in italian but not relevant)
import SpriteKit
class GameScene: SKScene {
var world: SKNode? //root node! ogni altro nodo del world dev'essere sottoclasse di questo
var overlay: SKNode? //node per l'HUD e altre cose che devono stare sopra al world
var camera: SKNode? //camera node. muovo questo per cambiare le zone visibili
//world sprites
var player: SKSpriteNode!
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPoint(x: 0, y: 0) //center the scene's anchor point at the center of the screen
//world setup
self.world = SKNode()
self.world!.name = "world"
self.addChild(world!)
//camera setup
self.camera = SKNode()
self.camera!.name = "camera"
self.world!.addChild(self.camera!)
//UI setup
self.overlay = SKNode()
self.overlay?.zPosition = 10
self.overlay?.name = "overlay"
addChild(self.overlay!)
player = world!.childNodeWithName("player") as SKSpriteNode!
}
var directionToMove = CGVectorMake(0, 0)
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
directionToMove = CGVectorMake((directionToMove.dx + (location.x - player!.position.x)), (directionToMove.dy + (location.y - player!.position.y)))
}
}
override func update(currentTime: CFTimeInterval) {
//*these both print the string*
if player == nil {
println("player is nil")
}
if player?.physicsBody == nil {
println("player.physicsBody is nil")
}
//*here it crashes*
player!.physicsBody!.applyForce(directionToMove)
}
override func didFinishUpdate() {
if self.camera != nil {
self.centerOnNode(self.camera!)
}
}
func centerOnNode(node: SKNode) {
let cameraPositionInTheScene: CGPoint = node.scene!.convertPoint(node.position, fromNode: node.parent!)
node.parent!.position = CGPoint(x: node.parent!.position.x - cameraPositionInTheScene.x, y: node.parent!.position.y - cameraPositionInTheScene.y)
}
}
thanks in advance : )
Your player is nil because you are accessing it from the world node, which is empty. By default, the player is a child of scene. You can change this in the scene editor by setting the node's parent property (see Figure 1).
Figure 1. SpriteKit Scene Editor's Property Inspector
I suggest you make the following changes:
Create the world, overlay, and camera nodes in the scene editor and access them with childNodeWithName
Add the player to the scene, not the world, and have it fixed in middle of the scene
Move the camera node not the player. The code in didFinishUpdate will automatically adjust the world to center the camera in the scene. You can add a physics body to the camera and move it by applying a force/impulse or by setting its velocity.
Add the other sprites to the world (instead of to the scene)
If you add the other sprites to the world, they will move appropriately when you adjust the world's position to center the camera. You will then need to move the other sprites relative to the world. The scene is just a window to view a portion of the world at a time.
I'm not sure why you are using optionals everywhere. You only need to use an optional when a variable might be nil. Are your world, overlay, and camera ever going to be nil? I would guess probably not.
To answer your question:
Youre trying to get player from world. I dont see that youve added any player sprite to the world node. It doesnt find it, and youre unwrapping an optional that isnt there. So you get an error.
You're going to have a lot more luck if you only use optionals when you need them. If you use them for everything youre going to add unnecessary complexity to your code.
Optionals should be the exception, not the rule. I could try to fix your code for you, but I'm not sure what you intended the player sprite to be?
Follow simple tutorials and start small. Things will make more and more sense over time.