Swift- SKEffectNode takes a while to appear - swift

So I have a pause button in my game that when you press it, the scene gets paused, and everything but one SKNode (the pause menu) gets blurred out. I'm doing this by creating a SKEffectNode that has a filter, and adding everything but the pause menu to it. It works, but it takes a solid 2 seconds for the blur to appear in the background. The scene pauses as soon as you press the button, but the blur and the pause menu only appear a few seconds later. Any ideas?
Here's the code:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if (self.nodeAtPoint(location).name == "PauseButton"){
if(!scene!.paused) {
blurScene()
scene!.paused = true
self.addChild(pauseMenu!)
}else {
removeBlur()
scene!.paused = false
pauseMenu!.removeFromParent()
}
}
}
}
func blurScene() {
blurNode = SKEffectNode() //Created in the beginning of the class
let blur = CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius": 15.0])
blurNode!.filter = blur
self.shouldEnableEffects = true
for node in self.children {
node.removeFromParent()
blurNode!.addChild(node as! SKNode)
}
self.addChild(blurNode!)
}
func removeBlur() {
var blurredNodes = [SKNode]()
for node in blurNode!.children {
blurredNodes.append(node as! SKNode)
node.removeFromParent()
}
for node in blurredNodes {
self.addChild(node as SKNode)
}
self.shouldEnableEffects = false
blurNode!.removeFromParent()
}

Try adding the SKEffectNode as root view and add the child nodes to it. Then you can set the blur filter already but
self.shouldEnableEffects = false
when you want to blur simply
self.shouldEnableEffects = true

Related

How to draw circles around a button if it is selected?

My scene looks like this:
I'm using SpriteKit and I created this scene with four buttons.
If the user taps on one of them there should appear a black circle around the selected button (and stay there). And if the user selects another button the previous circle should disappear and the new button gets a circle.
Does anyone know how to do this?
Here is my code:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locationUser = touch.location(in: self)
if atPoint(locationUser) == blueButton {
}
if atPoint(locationUser) == purpleButton {
}
if atPoint(locationUser) == redButton {
}
if atPoint(locationUser) == orangeButton {
}
}
}
there are several ways that you could achieve this. What I would do (and you should consider doing this any time you have 4 of the same object) is to subclass your buttons. That way you could add a property to your subclass that indicates whether or not the button isSelected and change it accordingly.
class Button: SKSpriteNode {
var isSelected = false {
didSet {
if isSelected {
background.isHidden = false
}
else {
background.isHidden = true
}
}
}
//you can design the init to better suit however you want to differentiate between your buttons
init(color: String) {
//create an image of a black circle that you want to use as your border that is slightly larger than your other images
let texture = SKTexture(imageNamed: "blackCircle")
super.init(texture: nil, color: .clear, size: texture.size())
let background = SKSpriteNode(texture: texture)
background.zPosition = 0
addChild(background)
//add your color image here based on passed in parameters
//but make sure that the zPosition is higher than 0
let buttonTexture = SKTexture(imageNamed: color)
let button = SKSpriteNode(texture: buttonTexture)
button.zPosition = 1
addChild(button)
}
}
in your gameScene I would store the buttons in an array
private var blueButton: Button!
private var purpleButton: Button!
private var redButton: Button!
private var orangeButton: Button!
private var buttons = [Button]()
//example of creating your new button
blueButton = Button(color: "blue")
blueButton.position = CGPoint(x: blah, y: blah)
blueButton.zPosition = 1
addChild(blueButton)
buttons.append(blueButton)
//previously selected Button so that you know which one to unselect
private var prevSelectedButton: Button!
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let locationUser = touch.location(in: self)
//unhighlight the last selected button
prevSelectedButton.isSelected = false
if atPoint(locationUser) == blueButton {
blueButton.isSelected = true
prevSelectedButton = blueButton
}
else if atPoint(locationUser) == purpleButton {
purpleButton.isSelected = true
prevSelectedButton = purpleButton
}
else if atPoint(locationUser) == redButton {
redButton.isSelected = true
prevSelectedButton = redButton
}
else if atPoint(locationUser) == orangeButton {
orangeButton.isSelected = true
prevSelectedButton = orangeButton
}
}
}
Worth noting that I would handle the touches events inside of the Button class to truly encapsulate the functionality, but that is outside of the scope of this question
Also if you are using shapeNodes instead of SpriteNodes you can still use this method just add a black circle behind color circle

Cannot disable, then reenable touch, after an SKAction animation

I am working on an interactive, animated scene. I want all touches on the scene to be disabled on entry. Then, once the objects (which are subclassed nodes) in the scene finish rotating/moving, I want to re-enable all touches on the screen to allow interaction. I have disabled user interaction using this code:
override func didMove(to view: SKView) {
setupNodes()
view?.isUserInteractionEnabled = false
spinLocations()
}
This is the code, within the scene file, for spinLocations:
func spinLocations() {
var allLocationArrays = [[String : CGPoint]]()
var previousArray = hiddenLocationPositions
for _ in 0...SearchConstant.numSpins {
let freshArray = generateNewLocationArray(previous: previousArray)
allLocationArrays.append(freshArray)
previousArray = freshArray
}
for (item, _) in hiddenLocationPositions {
let node = fgNode.childNode(withName: item) as! LocationNode
node.spin(position: allLocationArrays) // this is function below
}
hiddenLocationPositions = previousArray
}
This is the code for the animations in the node class:
func spin(position: [[String : CGPoint]]) {
var allActions = [SKAction]()
for array in position {
let action = SKAction.move(to: array[self.name!]!, duration: 2.0)
allActions.append(action)
}
let allActionsSeq = SKAction.sequence(allActions)
self.run(SKAction.sequence([SKAction.wait(forDuration: 5.0), allActionsSeq, SKAction.run {
self.position = position[position.count - 1][self.name!]!
},]))
}
This is the code for passing back the touches to the main scene from this class:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let parent = self.parent else { return }
}
As you can see, touch is not disabled here.
I do not want to add a "waitForDuration" SKAction to the runBlock to change the view status after the previous action; I want the program to determine when the animations are finished executing and then re-enable touches.
In order to do this, I theorised using a completion handler might work, but it only re-enables touches immediately (e.g. handling a handler to spin causes the touches to be detected again). Previously, I also tried to disable the view in the runBlock, but of course, that is run instantaneously. How do I ensure that the touches are re-detected following the animation without using "waitForDuration."?
So, this is a simple example that shows how you can:
1) Disable touches completely
2) Spin a node
3) When node is done with spinning, to enable touches
Here is the code (you can copy/paste it to try how it works):
class Object:SKSpriteNode{
func spin(times:Int,completion:#escaping ()->()) {
let duration = 3.0
let angle = CGFloat(M_PI) * 2.0
let oneRevolution = SKAction.rotate(byAngle: angle , duration: duration)
let spin = SKAction.repeat(oneRevolution, count: times)
let sequence = SKAction.sequence([spin,SKAction.run(completion)])
run(sequence, withKey:"spinning")
}
}
class WelcomeScene: SKScene {
override func didMove(to view: SKView) {
view.isUserInteractionEnabled = false
print("Touches Disabled")
let object = Object(texture: nil, color: .purple, size: CGSize(width: 200, height: 200))
addChild(object)
object.spin(times: 3, completion: {[weak self] in
self?.view?.isUserInteractionEnabled = true
print("Touches Enabled")
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch detected")
}
deinit {
print("Welcome scene deinited")
}
}
Here, you disable touches when scene is loaded, start spinning the object, and you pass a completion block to it... That block of code is used here:
let sequence = SKAction.sequence([spin,SKAction.run(completion)])
So after spinning, that block will be executed. Now, there are different ways to do this...Personally, I would use delegation, but I thought this can be less confusing... I can write an example for delegation too if needed, but basically, what you would do, is to set a scene as a delegate of your custom node, and notify it about spinning is done, so the scene can tell the view to re-enable the touches.

How to make button work in SpriteKit?

I am creating a game, where I have created a Simple UI, and I want the play button to be an action whenever the player hit the play button.
Here is an image:
Here is my code as well.
//I JUST DID THIS.
let bgImage = SKSpriteNode(imageNamed: "bg.png")
bgImage.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bgImage.size = self.frame.size
bgImage.name = "button1"
self.addChild(bgImage)
By default isUserInteractionEnabled is false so the touch on a scene child like your bgImage is, by default, a simple touch handled to the main (or parent) class (the object is here, exist but if you don't implement any action, you simply touch it)
If you set the userInteractionEnabled property to true on a SKSpriteNode subclass then the touch delegates will called inside this specific class. So, you can handle the touch for the sprite within its class. But you don't need it, this is not your case, you don't have subclassed your bgImage.
You should simply made in your scene:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "button1" {
print("Tapped")
}
}
}
When I look to your image I suspected that your sprite bg.png was composed by background and also the button image: this is very uncomfortable, you should use only an image about your button and , if you want , make another sprite to show your background otherwise you touch ALL (background and button obviusly, not only the button as you needed..).
So, you should separate the image, for example your button could be this:
Did you try to add: bgImage.isUserInteractionEnabled = true ?
let bgImage = SKSpriteNode(imageNamed: "bg.png")
bgImage.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bgImage.size = self.frame.size
bgImage.name = "button1"
bgImage.isUserInteractionEnabled = true
self.addChild(bgImage)
Then:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "button1" {
print("Tapped")
}
}
}

Trying to display a resume button after pausing the game scene on SpriteKit

I’m working with Xcode 8 and Swift 3, trying to display a “Resume Game” button on my scene after pausing the game but since everything stops, I have not found a way to properly do so. I followed Mike's advice on this page “Tap To Resume” Pause text SpriteKit, but that didn't help.
When my pause button is tapped, it runs the pause function with the following code:
func pauseGame() {
self.isPaused = true
self.physicsWorld.speed = 0
self.speed = 0.0
self.scene?.view?.isPaused = true
}
It may seem a little overkill but that works. If I remove
self.scene?.view?.isPaused = true
from my pause function, I'm able to display the tap to Resume Button but I can still interact with some of the SpriteKit nodes in the scene. I'm working on a space shooter game so the user can still move the spceship and tap to fire although the bullets don't travel until I resume the scene.
I thought about adding a boolean "true" to the pause function and adding an IF statement to the firing and the moving above but that seems to me like complicating things a bit.
Any suggestions on how I can display the Resume button when pausing the scene?
You can't interact with any nodes on your scene because you've paused it entirely, which pauses all its children, which is everything on the scene. To avoid this, pause only certain SKNodes (layers).
Add certain nodes to different SKNodes so instead of pausing the entire scene, you can only pause the the layer (SKNode) that you wish to pause (gameLayer). It would look something like this:
Initialize the nodes
let gameLayer = SKNode()
let pauseLayer = SKNode()
Now when you want to add a child to the scene, instead add it to the layer that you want it to be a part of:
Add child nodes to the main layers
gameLayer.addChild(gameSceneNode)
pauseLayer.addChild(resumeButton)
Don't forget to add the layers to the scene too
Add the layers to the scene
addChild(gameLayer)
addChild(pauseLayer)
To pause a layer write this:
gameLayer.isPaused = true
Note that in this example, all the nodes on the gameLayer will be paused, however everything on the pauseLayer will not.
Your complete example might look something like this:
func pauseGame() {
gameLayer.isPaused = true
pauseLayer.isHidden = false
gameLayer.physicsWorld.speed = 0
gameLayer.speed = 0.0
}
func unpauseGame() {
gameLayer.isPaused = false
pauseLayer.isHidden = true
// Whatever else you need to undo
}
For my next game I'm sure I'll give Nik's suggestion a try. As for my issue above, it worked by using the code shown below. The additional code is to dismiss the pause button as well. Now that this works I can maybe add a fadeIn and fadeOut actions to both buttons.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "PauseButton" {
if (self.isPaused == false) {
pauseGame()
}
}
if nodeUserTapped.name == "ResumeButton" {
if (self.isPaused == true) {
resumeGame()
}
}
}
}
// MARK: - pauseGame
func pauseGame() {
self.isPaused = true
currentGameState = gameState.pauseGame
self.physicsWorld.speed = 0
self.speed = 0.0
if (backgroundMusicIsOn == true) {
backingAudio.stop()
}
if resumeButton.isHidden == true {
resumeButton.isHidden = false
}
if pauseButton.isHidden == false {
pauseButton.isHidden = true
}
}
// MARK: - resumeGame
func resumeGame() {
self.isPaused = false
currentGameState = gameState.inGame
self.physicsWorld.speed = 1
self.speed = 1.0
if (backgroundMusicIsOn == true) {
backingAudio.play()
}
if resumeButton.isHidden == false {
resumeButton.isHidden = true
}
if pauseButton.isHidden == true {
pauseButton.isHidden = false
}
}

How can I put a mute button for background music in my Sprite Kit Game with Swift 2(Xcode 7)?

I am making a game with Sprite kit, I recently added background music to the game and it works, but i want to put a mute button that can allow the player to stop and play the background music while the game is running in case he don't like it. Thanks.
import AVFoundation
class GameScene: SKScene, SKPhysicsContactDelegate {
var backgroundMusic = SKAudioNode()
func restartScene(){
self.removeAllChildren()
self.removeAllActions()
died = false
gameStarted = false
score = 0
createScene()
}
func createScene(){
backgroundMusic = SKAudioNode(fileNamed: "Musicnamefile.mp3")
addChild(backgroundMusic)
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
createScene()
}
To create a button add this in didMoveToView:
// pause button
let pauseButton = SKLabelNode()
let pauseContainer = SKSpriteNode()
pauseContainer.position = CGPointMake(hud.size.width/1.5, 1)
pauseContainer.size = CGSizeMake(hud.size.height*3, hud.size.height*2)
pauseContainer.name = "PauseButtonContainer" // pause button
let pauseButton = SKLabelNode()
let pauseContainer = SKSpriteNode()
pauseContainer.position = CGPointMake(hud.size.width/1.5, 1)
pauseContainer.size = CGSizeMake(hud.size.height*3, hud.size.height*2)
pauseContainer.name = "PauseButtonContainer"
hud.addChild(pauseContainer)
pauseButton.position = CGPointMake(hud.size.width/2, 1)
pauseButton.text="I I"
pauseButton.fontSize=hud.size.height
//pauseButton.fontColor = UIColor.blackColor()
pauseButton.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center
pauseButton.name="PauseButton"
hud.addChild(pauseButton)
I'm using the SKLabel to show the pause symbol and a container to increase the touch area. HUD is an rectangle of type SKNode at the top of my game. You have to add the nodes to an element in your game and change the size and position.
To react on the touch:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if (node.name == "PauseButton" || node.name == "PauseButtonContainer") {
Insert your code here ....
}
}
}