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)
}
Related
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()
}
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.
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)
}
}
}
I am attempting to learn SpriteKit. I want to add a rectangle/square to the scene in code; but the item never shows;
All I want to do is add a white square to the screen, but find that it never adds.
In a breakpoint, I notice that didMove() never seems to get called.
What am I doing wrong?
class GameScene: SKScene {
override func didMove(to view: SKView) {
let item = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 200))
item.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(item)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
update
I did not change the view controller generated by xcode
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
There is no GameScene.sks, I deleted this file as I do not want to use sks files
If a breakpoint in didMove(to view:) is never reached, then definitely your ViewController doesn't present the scene for some reason. Can you open your ViewController's file (e.g. GameViewController.swift) and see if you have any of those lines there (or anything to that effect):
let viewSize = UIScreen.main.bounds.size
let scene = GameScene(size: viewSize)
let skView = self.view as! SKView
skView.presentScene(scene)
Turns out view controller was not presenting the scene and changing it to this, following code posted by #Stoyan worked and I saw my sprite showing
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let viewSize = UIScreen.main.bounds.size
let scene = GameScene(size: viewSize)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.presentScene(scene)
}
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