How to make button work in SpriteKit? - swift

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")
}
}
}

Related

How to add normal Buttons in a SpriteKit Game?

So I have made 2 games so far and I always used the touchesBegan function and SpriteNodes to create a Menu, like that:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if (atPoint(location).name == "playButton"){
startGame()
}
}
}
But this way is pretty ugly because as soon as you touch the button it will call the according action to it and you can't cancel it. But with a normal UIButton it will only call the action after you take the finger off the button, and you can even cancle a button click by just moving your finger away from the button. And the problem is, I don't know how I can add a UIButton to my MenuScene.swift file and I also don't use the Storyboard because it's just confusing to me and I don't understand how the .sks, .swift and .Storyboard files are linked together, it just makes no sense to me. I mean the .sks and .storyboard files are both for buildung a GUI but in .sks you cant add a UIButton... but why??? Is there any convenient way to add a button?
I wouldn't use UIButton in my Spritekit code, you are better off to create your own Button class in Spritekit and use it. The sks file is not linked to a storyboard it is linked to any class that you designate (GameScene, MenuScene) it can even be linked to smaller objects that have their own class (ScoreHUD, Castle, etc).
Honestly you are just over thinking the button thing. Think about the touch events. touchesBegan fires when the object is clicked if you want it to fire on finger up call touchesEnded
Here is a very simple button class I wrote for this example, there are a lot of things more you could do with this, but this covers the basics. It decides if the button will click on down or up and will not click if you move your finger off the button. It uses protocols to pass the click event back to the parent (probably you scene)
you can also add this to an sks file by dragging a colorSprite/ or image onto the scene editor and making it a custom class of "Button" in the Custom Class Inspector.
the Button Class...
protocol ButtonDelegate: class {
func buttonClicked(button: Button)
}
class Button: SKSpriteNode {
enum TouchType: Int {
class down, up
}
weak var buttonDelegate: ButtonDelegate!
private var type: TouchType = .down
init(texture: SKTexture, type: TouchType) {
var size = texture.size()
super.init(texture: texture ,color: .clear, size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isPressed = true
if type == .down {
self.buttonDelegate.buttonClicked(button: self)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if !frame.contains(touchLocation) {
isPressed = false
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard isPressed else { return }
if type == .up {
self.buttonDelegate.buttonClicked(button: self)
}
isPressed = false
}
}
In your GameScene file
class GameScene: SKScene, Button {
private var someButton: Button!
private var anotherButton: Button!
func createButtons() {
someButton = Button(texture: SKTexture(imageNamed: "blueButton"), type: .down)
someButton.position = CGPoint(x: 100, y: 100)
someButton.Position = 1
someButton.name = "blueButton"
addChild(someButton)
anotherButton = Button(texture: SKTexture(imageNamed: "redButton"), type: .up)
anotherButton = CGPoint(x: 300, y: 100)
anotherButton = 1
anotherButton = "redButton"
addChild(anotherButton)
}
func buttonClicked(button: Button) {
print("button clicked named \(button.name!)")
}
}

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

Sprite subclass detecting touches despite actually touching empty space

I'm subclassing nodes to use for touch detection. I have a box parent which has a line child directly next to it, with the gray space being just empty space:
The problem is when I click the gray space, it registers as a touch on the box, which is quite far away.
Here's the code where I show the problem, and my crappy workaround... I make two sets of boxes, the first being the one showing the problem, and the second being the one with the workaround:
import SpriteKit
class GameScene: SKScene {
enum sizez {
static let
box = CGSize(width: 50, height: 35),
line = CGSize(width: 200, height: 10)
}
class Box: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched box")
}
}
class Line: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched line")
}
}
override func didMove(to view: SKView) {
// The problem sprites that register touch despite empty space:
let box = Box(color: .red, size: sizez.box)
box.isUserInteractionEnabled = true
addChild(box)
let line = Line(color: .purple, size: sizez.line)
line.isUserInteractionEnabled = true
line.anchorPoint = CGPoint.zero
line.position = CGPoint(x: box.frame.maxX + 10, y: box.frame.minY)
///////////////////
box.addChild(line)
///////////////////
// These sprites detect touches properly (no detection in empty space)
let box2 = Box(color: .red, size: sizez.box)
box2.isUserInteractionEnabled = true
box2.position.y -= 100
addChild(box2)
let line2 = Line(color: .purple, size: sizez.line)
line2.isUserInteractionEnabled = true
line2.anchorPoint = CGPoint.zero
line2.position = CGPoint(x: box2.frame.maxX + 10, y: box2.frame.minY)
////////////////
addChild(line2)
////////////////
}
}
When you click directly above (or even farther out from) the top line, you get this:
When you do the same for the bottom line, you get this:
It will be a huge hassle to forgo SK's built in parent / child system, and then for me to keep track of them on my own manually... as well as it being a big performance hit for my app.
Any reasonable workaround or solution that lets me click in the gray space while using code similar to the first box would be greatly appreciated.
UPDATE:
By making an invisible background node and setting its zPositon 1 less, I can now click in the gray space and it registers as the background node, not the box.
let bkgSize = CGSize(width: 1000, height: 1000)
let bkg = Bkg(color: .gray, size: bkgSize)
bkg.isUserInteractionEnabled = true
bkg.zPosition -= 1
addChild(bkg)
But still, why is this empty space touch being registered as a box touch in the absence of a background node?
You're absolutely correct with a background node it works as expected, without a background sprite it gives the results as described by #Confused. I was able to get it to work as expected without a background by just fine tuning the TouchesBegan function like so...
class Box: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if frame.contains(touchLocation) {
print("touched box")
}
}
}
}
class Line: SKSpriteNode {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first as UITouch! {
let touchLocation = touch.location(in: parent!)
if frame.contains(touchLocation) {
print("touched line")
}
}
}
}
From my understanding of how bounds work for parented objects, I think this is what's going on...
Addendum
This is slightly related, and may help in understanding why this is happening, and why Apple considers this to be how a parent and its children should use an accumulated (combined) rectangle/quad for touch response:

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 ....
}
}
}

show scratch-card effect with the help of using scratch and win example

I am new in ios developement.i need to show scratch-card effect for an iPhone app after scratches if coupon number is visible i need to show alert message How can i do this?.i have download sample code from iPhone - Scratch and Win Example and also i have done showing below screen and scratches also works fine
if UILabel text is visible i want to show alert message How can i do this?
i have attached screen shot for your refference
i Found very nice Github example please take a look this and impliment as par you need hope this helps to you my Frnd.
CGScratch This is same thing that you want to apply.
review the code and check the visible area of Number else check che scration overImage is tatally removed of not if remove then show Alert.
A simple UIImageView subclass that allows your UIImageView become a scratch card.
In your storyboard or xib set custom class of your UIImageView that represents your scratch image to ScratchCardImageView. Change lineType or lineWidth to change the appearance of the scratch lines.
Download example
Swift 3:
import UIKit
class ScratchCardImageView: UIImageView {
private var lastPoint: CGPoint?
var lineType: CGLineCap = .square
var lineWidth: CGFloat = 20.0
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
lastPoint = touch.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, let point = lastPoint else {
return
}
let currentLocation = touch.location(in: self)
eraseBetween(fromPoint: point, currentPoint: currentLocation)
lastPoint = currentLocation
}
func eraseBetween(fromPoint: CGPoint, currentPoint: CGPoint) {
UIGraphicsBeginImageContext(self.frame.size)
image?.draw(in: self.bounds)
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: currentPoint)
let context = UIGraphicsGetCurrentContext()!
context.setShouldAntialias(true)
context.setLineCap(lineType)
context.setLineWidth(lineWidth)
context.setBlendMode(.clear)
context.addPath(path)
context.strokePath()
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
Updated
With this solution touch events will be tracked only inside the UIImageView bounds. If you need touch events to start already outside your scratchcard, see ScratchCardTouchContainer example
You can also check this github tutorial for scratch:
https://github.com/SebastienThiebaud/STScratchView
https://github.com/moqod/iOS-Scratch-n-See