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)
}
}
}
Related
I'm my game I'm using a custom SKSpriteNode which the player are going to be able to drag and drop at a position of his/hers desire.
My custom node is made of this code in file DraggableNode.swift:
class DraggableNode : SKSpriteNode {
var nodeSelected : SKNode?
init() {
let texture = SKTexture(imageNamed: "draggableNode_1")
super.init(texture: texture, color: UIColor.white, size: texture.size())
self.isUserInteractionEnabled = true
self.zPosition = 1
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let touchedNodes = self.nodes(at: location)
for node in touchedNodes.reversed() {
nodeSelected = node
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let node = self.nodeSelected {
let touchLocation = touch.location(in: self)
node.position = touchLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
nodeSelected = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
nodeSelected = nil
}
}
In my GameScene.swift I add the custom node in this way:
let node = DraggableNode()
node.position = CGPoint(x: randomX, y: randomY)
self.addChild(node)
Now, the problem is that when I start to drag the node it's all jump and is placed a total random place - sometimes even out of the screenarea.
I wonder if the problem consists of the fact that it's a custom node inherited from a SKSpriteNode?
If anyone can point me in the right direction, I'd be a happy man :-)
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)
}
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
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)
}
I created a game involving falling balls. Every time the player taps a ball, they're supposed to get a point. I set up the score label, but it just stays at 0.
Is there something wrong with my code?
I use a GameElements.swift file as an extension. Here is the file:
extension GameScene {
//1st Ball//
func createPlayer() -> SKNode {
let playerNode = SKNode()
playerNode.position = CGPoint(x:self.size.width / 2, y:440)
let sprite = SKSpriteNode(imageNamed: "Ball")
sprite.name = "ballPoints"
playerNode.addChild(sprite)
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: 120)
playerNode.physicsBody?.dynamic = true
playerNode.physicsBody?.allowsRotation = false
playerNode.physicsBody?.restitution = 3
playerNode.physicsBody?.friction = 0
playerNode.physicsBody?.angularDamping = 0
playerNode.physicsBody?.linearDamping = 0
playerNode.physicsBody?.usesPreciseCollisionDetection = true
playerNode.physicsBody?.categoryBitMask = CollisionBitMask.Player
playerNode.physicsBody?.categoryBitMask = 0
return playerNode
}
}
Here is the GameScene.swift file:
class GameScene: SKScene {
var foreground: SKNode!
var hud: SKNode!
var firstBall: SKNode!
var scoreLabel: SKLabelNode!
private var score = 0
override func didMoveToView(view: SKView) {
scoreLabel = SKLabelNode(fontNamed:"Geared-Slab")
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPoint( x: self.frame.midX, y: 3 * self.frame.size.height / 4 )
scoreLabel.fontSize = 100.0
scoreLabel.zPosition = 100
scoreLabel.text = String(score)
self.addChild(scoreLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if(firstBall.containsPoint(location)) {
firstBall.physicsBody?.velocity = CGVectorMake(0, 600)
firstBall.physicsBody?.applyImpulse(CGVectorMake(0, 1100))
}
if
let touch : UITouch! = touches.first,
let tappedSprite = nodeAtPoint(touch!.locationInNode(self)) as? SKSpriteNode,
let scoreLabel = childNodeWithName("scoreLabel") as? SKLabelNode
where tappedSprite.name == "ballPoints" {
score += 1
scoreLabel.text = "Score: \(score)"
}
}
}
override init(size:CGSize) {
super.init(size: size)
foreground = SKNode()
addChild(foreground)
//1st Ball//
firstBall = createPlayer()
foreground.addChild(firstBall)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I know this is a lot of code, but I want experts to be able to test the code themselves. For background, I was following this tutorial: https://www.youtube.com/watch?v=0gOi_2Jwt28 up until a certain point.
Step 1
First of all let's create a class for the Ball
class Ball: SKSpriteNode {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
}
Step 2
Then inside createPlayer() let's replace this
let sprite = SKSpriteNode(imageNamed: "Ball")
with this
let sprite = Ball(imageNamed: "Ball")
sprite.userInteractionEnabled = true
Step 3
Let's remove the touchesBegan from GameScene.
Update
This code is working as expected for me
class GameScene: SKScene {
var scoreLabel: SKLabelNode!
var ball: Ball!
private var score = 0 {
didSet {
scoreLabel.text = "\(score)"
print(score)
}
}
override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(ball)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// this is called when you tap outside of the Ball
// use self.ball to make the ball to jump
}
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}