Adding button to GameScene.swift programatically and transitioning scenes when pressed - swift

I have been working forever trying to implement a way to add a button and when pressed the scene transitions to my second view. I have read other posts but have had ZERO luck for something in Swift 3. I'm really confused and have tried but failed many times. Hope you can help! Thanks!
Swift 3 in SpriteKit.

What do you mean transition to a new view?. When making SpriteKit games we tend to transition between SKScenes and not views. So do not try to create different views or viewControllers for each scene, just transition between SKScenes directly.
In regards to buttons, there is plenty tutorials available. You basically create a SKSpriteNode as a button and look for it in touchesBegan and do something when its pressed.
This is a simple example here
https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/
Essential you can create a button subclass similar to this.
class Button: SKNode {
let defaultButton: SKSpriteNode
let activeButton: SKSpriteNode
var action: () -> ()
init(defaultButtonImage: String, activeButtonImage: String, buttonAction: #escaping () -> ()) {
defaultButton = SKSpriteNode(imageNamed: defaultButtonImage)
activeButton = SKSpriteNode(imageNamed: activeButtonImage)
activeButton.isHidden = true
action = buttonAction
super.init()
isUserInteractionEnabled = true
addChild(defaultButton)
addChild(activeButton)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
activeButton.isHidden = false
defaultButton.isHidden = true
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if defaultButton.contains(location) {
activeButton.isHidden = false
defaultButton.isHidden = true
} else {
activeButton.isHidden = true
defaultButton.isHidden = false
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if defaultButton.contains(location) {
action()
}
activeButton.isHidden = true
defaultButton.isHidden = false
}
}
}
and than in your relevant SKScene add your buttons
class StartScene: SKScene {
override func didMove(to view: SKView) {
let playButton = Button(defaultButtonImage: "button", activeButtonImage: "button_active", buttonAction: loadGameScene)
playButton.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(playButton)
}
func loadGameScene() {
let scene = GameScene(...)
let transition = SKTransition....
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
}
You can also check out Apples sample game DemoBots for another button class example using protocols.
Hope this helps

Related

How to prevent back button from dissapearing?

I am creating a doodle page with an "X" as the close/dismiss button.
I also have a "clear" button to remove the doodles that have been made.
My issue is that when I press clear, even the X button disappears, how do I prevent this from happening?
This is my current app.
This is my Doodle class which allows me to draw on the view.
import UIKit
class DoodleView: UIView {
var lineColor:UIColor!
var lineWidth:CGFloat!
var path:UIBezierPath!
var touchPoint:CGPoint!
var startingPoint:CGPoint!
override func layoutSubviews() {
self.clipsToBounds = true
self.isMultipleTouchEnabled = false
lineColor = UIColor.white
lineWidth = 10
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startingPoint = touch?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
touchPoint = touch?.location(in: self)
path = UIBezierPath()
path.move(to: startingPoint)
path.addLine(to: touchPoint)
startingPoint = touchPoint
drawShapeLayer()
}
func drawShapeLayer() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(shapeLayer)
self.setNeedsDisplay()
}
func clearCanvas() {
path.removeAllPoints()
self.layer.sublayers = nil
self.setNeedsDisplay()
}
}
This is the class controlling the view controller.
import UIKit
class DoodlePageViewController: UIViewController {
#IBOutlet weak var doodleView: DoodleView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func clearDoodle(_ sender: Any) {
doodleView.clearCanvas()
}
#IBAction func CloseDoodlePage(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Without really seeing how you declared the buttons, I can only guess that when you call the function clearCanvas(), the X button is part of the sublayer in doodle view since you are setting self.layer.sublayers = nil hence making it also dissapear.
Make sure that the X button is on top of the doodle view when you create it.
You are clearing all the subviews out of your view when you call self.layer.sublayers = nil
you either need to re-add the 'x' view back into the hierarchy. I am assuming using your function drawShapeLayer() is the 'x' ??
so it would be
func clearCanvas() {
path.removeAllPoints()
self.layer.sublayers = nil
drawShapeLayer()
self.setNeedsDisplay()
}

Move SKSpriteNode on Touch to create Button effect

I am currently creating the main menu of a mobile game, and it works fine however To make it look nicer I would like to add an effect when I touch one of my SKSpriteNodes which is an image of a button. I would like it to slightly move down then back up before transitioning to the game, im having some difficulty. Currently when the button is pressed it changed scenes to the game scene.
class MainMenuScene: SKScene{
let startGame = SKSpriteNode(imageNamed: "StartButton")
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "Menu")
background.size = self.size
background.position = CGPoint(x: self.size.width/2, y:self.size.height/2)
background.zPosition = 0
self.addChild(background)
startGame.position = CGPoint(x: self.size.width/2, y: self.size.height*0.6)
startGame.zPosition = 1
startGame.name = "startButton"
self.addChild(startGame)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
if nodeITapped.name == "startButton" {
let sceneToMoveTo = GameScene(size: self.size)
sceneToMoveTo.scaleMode = self.scaleMode
let myTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: myTransition)
}
}
}
}
You could make your logic in the function touchesEnded and then you call a SKAction in your button, passing the moveToScene logic as completion.
Something like:
var shouldMoveToNewScene: Bool = False
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
shouldMoveToNewScene = nodeITapped.name == "startButton" ? True : False
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard shouldMoveToNewScene, touces.count == 1 else { return }
startButton.run(myAnimation, completion: {
moveToScene()
})
}
private func moveToScene() {
//do your transition scene logic here
{
I haven't tried it yet, but hopefully you could get the idea.
If you're not familiar with SKAction you should consider follow this guide.

spritekit make childnode not touchable

I have a menu button (skSpriteNode) and a small play icon inside this sprite
mainButton.addChild (playIcon)
mainButton.name = "xyz"
addchild (mainButton)
ok, I gave the button the name to check if a sprite with this name is touched.
when I touch the icon child inside the button, the touchedNode.name is nil. I set isUserInteractionEnabled = false for the icon child. I want to pass the touch to the parent. how can I do this.
for touch in touches {
let location = touch.location(in: self)
let touchedNode = self.atPoint(location)
if (touchedNode.name != nil) {
print ("node \(touchedNode.name!)")
} else {
continue
}
}
You have to implement touchesBegan in a subclass of SKSpriteNode and to set its isUserInteractionEnabled = true, in order to accept and process the touches for that particular node (button).
import SpriteKit
protocol ButtonDelegate:class {
func printButtonsName(name:String?)
}
class Button : SKSpriteNode{
weak var delegate:ButtonDelegate?
init(name:String){
super.init(texture: nil, color: .purple, size: CGSize(width: 250, height: 250))
self.name = name
self.isUserInteractionEnabled = true
let icon = SKSpriteNode(color: .white, size: CGSize(width:100, height:100))
addChild(icon)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
delegate?.printButtonsName(name: self.name)
}
}
class GameScene: SKScene, ButtonDelegate {
override func didMove(to view: SKView) {
let button = Button(name:"xyz")
button.delegate = self
button.position = CGPoint(x: frame.midX - 50.0, y: frame.midY)
addChild(button)
}
func printButtonsName(name: String?) {
if let buttonName = name {
print("Pressed button : \(buttonName) ")
}
}
}
Now, this button will swallow the touches and the icon sprite will be ignored.
I just modified the code from one of my answers to make an example specific to your needs, but you can read, in that link, about whole thing more in detail if interested.
I implemented the button Touchbegan and tried to implement a subclass for a slider with a background and the child thumb inside the slider. I added the sliderDelegate Protocoll in my GameScene. the down touch is ok, but I didn't get the moved in
import Foundation
import SpriteKit
protocol SliderDelegate:class {
func touchesBeganSKSpriteNodeSlider(name:String?, location: CGPoint?)
func touchesMovedSKSpriteNodeSlider(name:String?, location: CGPoint?)
func touchesEndedSKSpriteNodeSlider(name:String?, location: CGPoint?)
}
class SKSpriteNodeSlider : SKSpriteNode{
weak var delegate:SliderDelegate?
init(imageNamed: String){
// super.init(imageNamed: imageNamed)
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: .white, size: texture.size())
// self.name = name
self.isUserInteractionEnabled = true
// let icon = SKSpriteNode(color: .white, size: CGSize(width:100, height:100))
// addChild(icon)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// for touch in touches {
if let touch = touches.first {
let loc = touch.location(in: self)
delegate?.touchesBeganSKSpriteNodeSlider(name: self.name, location: loc)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print ("touchesMoved")
// for touch in touches {
if let touch = touches.first {
let loc = touch.location(in: self)
delegate?.touchesMovedSKSpriteNodeSlider(name: self.name, location: loc)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let loc = touch.location(in: self)
delegate?.touchesEndedSKSpriteNodeSlider(name: self.name, location: loc)
}
}
}

swift/scenekit problems getting touch events from SCNScene and overlaySKScene

Good afternoon, I'm trying to figure out how to get touch notifications from an SCNNode & a SKSpriteNode from an SCNScene overlayed with a SKScene.
import UIKit
import SceneKit
class GameViewController: UIViewController {
var scnView:SCNView!
var scnScene:SCNScene!
var sprite: spritekitHUD!
var cameraNode: SCNNode!
var shape: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setupScene()
}
func setupScene() {
scnView = self.view as! SCNView
scnView.delegate = self
scnView.allowsCameraControl = true
scnScene = SCNScene(named: "art.scnassets/scene.scn")
scnView.scene = scnScene
sprite=spritekitHUD(size: self.view.bounds.size, game: self)
scnView.overlaySKScene=sprite
cameraNode = scnScene.rootNode.childNode(withName: "camera",
recursively: true)!
shape=scnScene.rootNode.childNode(withName: "shape", recursively: true)
shape.name="ThreeDShape"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch = touches.first!
let location = touch.location(in: scnView)
let hitResults = scnView.hitTest(location, options: nil)
if let result = hitResults.first {
handleTouchFor(node: result.node)
}
}
func handleTouchFor(node: SCNNode) {
if node.name == "ThreeDShape" {
print("SCNNode Touched")
}
}
}
This is my Spritekit overlay scene
import Foundation
import SpriteKit
class spritekitHUD: SKScene{
var game:GameViewController!
var shapeNode: SKSpriteNode!
init(size: CGSize, game: GameViewController){
super.init(size: size)
self.backgroundColor = UIColor.white
let spriteSize = size.width/12
self.shapeNode= SKSpriteNode(imageNamed: "shapeNode")
self.shapeNode.size = CGSize(width: spriteSize, height: spriteSize)
self.shapeNode.position = CGPoint(x: spriteSize + 8, y: spriteSize + 8)
self.shapeNode.name="test"
self.game=game
self.addChild(self.pauseNode)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch=touches.first else{
return
}
let location=touch.location(in: self)
if self.atPoint(location).name=="test" {
print("Spritekit node pressed")
}
}
}
so with this I can successfully get notifications that my spritenode has been touched on my overlaySKScene but I cant figure out how to get a notification that my SCNode has been touched. If you cant have 2 touchesbegan functions does anyone have any ideas how I can handle the 3d events with 2d events at the same time?
Thanks for your help!!
If you want to use an SKScene overlay of an SCNView for user controls, (eg you want to implement a button in the SKScene overlay that "consumes" the touch), but also have touches that don't hit the controls to pass through and register in the underlying SCNView, you have to do this: set isUserInteractionEnabled to false on the SKScene overlay itself, but then to true on any individual elements within that overlay that you'd like to act as buttons.
let overlay = SKScene(fileNamed: "overlay")
overlay?.isUserInteractionEnabled = false
let pauseButton = overlay?.childNode(withName: "pauseButton") as? Button
// Button is a subclass of SKSpriteNode that overrides touchesEnded
pauseButton?.isUserInteractionEnabled = true
sceneView.overlaySKScene = overlay
If the user touches a button, the button's touch events (touchesBegan, touchesEnded etc) will fire and consume the touch (underlying gesture recognizers will still fire though). If they touch outside of a button however, the touch will pass through to the underlying SCNView.
This is "lifted" straight out of Xcode's Game template......
Add a gesture recognizer in your viewDidLoad:
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action:
#selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result: AnyObject = hitResults[0]
// result.node is the node that the user tapped on
// perform any actions you want on it
}
}
You can implement this method in spritekitHUD:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
game.touchesBegan(touches, with: event)
}

subclassed SKLabelNode to present scene, how?

I have a subclass of SKLabelNode that's instanced three or four times, each with a name, and a destination:
//
import SpriteKit
class MenuButton: SKLabelNode {
var goesTo = SKScene()
override init() {
super.init()
print("test... did this get called? WTF? HOW/WHY/WHERE?")
}
convenience init(text: String, color: SKColor, destination: SKScene){
self.init()
self.text = text
self.color = color
goesTo = destination
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let nextScene = goesTo
// .....?????????????????
// HOW DO I GET THIS TO FIND ITS SCENE...
// THEN ITS VIEW, THEN PRESENT this nextScene????
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I'm at a loss as to how to get at the view I need to call the scene change on.
Every SKNode has a scene property which returns the scene where the node lives.
And every SKScene as a view property, so
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let nextScene = goesTo
self.scene?.view?.presentScene(nextScene)
}