SCNCamera rotation no longer works when changing NSGestureRecognizer - swift

Changing NSClickGestureRecognizer to a NSPanGestureRecognizer in a SceneKit MacOS Game Template app and the camera rotate no longer works.
For example if one generates a new MacOS Game template app and then change the line:
let clickGesture = NSClickGestureRecognizer(target: self, action: #selector(handleClick(_:)))
to:
let clickGesture = NSPanGestureRecognizer(target: self, action: #selector(handleClick(_:)))
Then the rotate camera gesture no longer works.

This is due to the nature of the NSPanGestureRecognizer. In order for the Hit-Test to work for your model, you need to click on the mouse button, then drag it some distance, and then release it. The exact same action is required to pan or orbit the camera when the .allowsCameraControl property is true. Thus, by default, NSPanGestureRecognizer blocks the gestures of the camera controller because the first one has a higher priority in SceneKit. Setting priority is a common practice to avoid conflicts between objects with similar behaviors.
By default, this gesture recognizer blocks the gestures for the camera:
let panGesture = NSPanGestureRecognizer(target: self,
action: #selector(handlePan))
panGesture.buttonMask = 1 // default
To do the opposite, use:
panGesture.buttonMask = 0
Unlike its counterpart, NSClickGestureRecognizer needs you to simply click (not drag) the model.

Related

Stop propagation of touch event to views behind in Swift

I have a scrollview and a subview inside it. If the user does a certain touch in that subview, I want to prevent the scrollview to move, in order to let the user do their stuff in the subview without being bothered by the scrollview movement. If the certain touch is not detected, then the scrollview should move normally .
I intercept these gestures with raw touch events touchesBegan, touchesMoved. I don't use gestureRecognizers because the gestures I want to recognise are very specifics and I feel more confortable using no abstraction layer to recognise them.
I know after seeing many answers on SO, I could just hold a reference of the scrollview behind and stop its movement if I detect the gesture. I'm looking for a more stable solution. I want to stop the propagation of the event (to any view behind), if I detect the gesture, without having to hold the references of any of these views behind.
As I understood, view in iOS are subclasses of UIResponder. When UIKit detects a touch on the screen, it gives the event to the first responder, which is generally the top most subview. My question is : in touchesBegan how to tell UIKit : "Do not send the event to any other following view in the responder chain". If I can see the scrollview moving behind, UIKit must have forwarded the event to it (despite I'm not calling super in touchesBegan)
In Android for example, onTouchEvent function of View class returns a bool. false tells android to continue propagating the event, true tells to stop propagating. I'm looking for the same mechanism in iOS:
#Override
public boolean onTouchEvent(MotionEvent e)
{
return true ; // stops propagation
}
In Javascript (jQuery), there's quite the same mechanism :
$('#myview').bind('click', function(e) {
e.stopPropagation()
})
How to do that in Swift ?
Found the solution. This was tricky to understand so I'll try to make it easy if anyone has the same problem :
As stated in another SO answer, the "raw touch system" ( touchesBegen, touchesMoved ..) and the "gesture recognizer system" are mutually exclusive, both of them are actually at the "raw" view level, and there are independent.
This means that when you have a view and you touch it, you have a chance that your touch is handled by the gestureRecognizer system instead of the raw touch system. overriding next UIResponder property by override var next:UIResponder? { get { return nil }} only force UIKit not to forward the event in the raw touch system, the gesture recognition of views behind are still fired, because it's a system completely apart.
In my case, I tried to override var next:UIResponder? { get { return nil }} : the touchesBegan of the views behind remained quiet as expected, but I could still recognize gesture there.
So, it appears that UIScrollView uses gestureRecognizer to handle user touches. The solution is to shut down the gesture recognizer system from your top most view so the gesture is not forwarded : this can be done using :
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
{
return !yourConditionToShutDownGR
}
I was confused because the Android gesture "detector" system is built on top of the raw touch system. When you catch a touch event in public boolean onTouchEvent(MotionEvent event) you can pass it as a parameter of a gestureDetector, which returns if it found a gesture or not. That's not the same approach for iOS in which the gestureRecognizer system is built aside of the raw touch system.

How to deactivate touch functions for the first few seconds after loading a new scene?

I'm building a simple SpriteKit game and I want to deactivate any touch functions for the first few seconds after a scene is loaded. I have an animated SKSpriteNode that needs to finish the animation before I want any touch functions to move the node. How do I approach setting this up?
The easiest way is to disable user interaction of super view and then run a timer with desired delay for the touch and enable the user interaction once the timer is invalidated.
Do not use timers or the dispatch. These do not correspond to your in game time. If you exit your app or get a phone call, these will fire prematurely.
Instead, use an action on your scene:
let wait = SKAction.wait(forDuration:2)
let run = SKAction.run{self.isUserInteractionEnabled = true}
self.run(SKAction.sequence[wait,run])
self.isUserInteractionEnabled = false

SceneKit and SpriteKit together

I'm building a 3D game scene with SceneKit and I want to build like a "hud" over top, in order to get it kind of working, I built my ViewController by using a subview and overlaySKScene like so:
class ViewController:UIViewController {
var sceneView: SCNView!
var mainScene = MainScene() // <- SCNScene
var spriteScene: OverlayScene! // <- SKScene
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
self.sceneView.scene = mainScene
self.sceneView.backgroundColor = UIColor.clear
self.view.addSubview(self.sceneView)
self.spriteScene = OverlayScene(size: self.view.bounds.size)
self.sceneView.overlaySKScene = self.spriteScene
}
}
I've got a few questions about how this should be done the right way, as I'm running into some things that seem unusual.
First, lets say I need to do a segue, it doesn't seem clear to me how I would actually observe a tap on one of the SpriteKit nodes in the OverlayScene and then change controllers for the main parent ViewController. I haven't found any resources online that describe a similar situation.
Here is a trimmed down version of my OverlayScene:
import UIKit
import SpriteKit
class OverlayScene: SKScene {
var actionBarNode: SKSpriteNode!
override init(size: CGSize) {
super.init(size: size)
self.backgroundColor = UIColor.clear
self.actionBarNode = SKSpriteNode()
self.actionBarNode.size = CGSize(width: size.width, height: 100)
self.actionBarNode.position = CGPoint(x: size.width / 2, y: 50)
self.actionBarNode.color = .white
self.addChild(self.actionBarNode)
}
}
I am not answering your two questions directly, instead I will deal with your larger issue which is that you want a HUD on your game. A user interface. I never made a Scenekit game, but I did make a Spritekit game and the HUD is a clever trick. Let me explain.
Imagine you are standing behind a camera and looking through to see what the camera is seeing through its lens. You see the scene in front of it. Now take a piece of paper, cut out a rectangular box in the middle and draw on the remaining periphery. Tape this piece of paper to the front of the camera and look through. You still see the scene (a little smaller now), but now you also see the paper with the drawings. This is how HUDs work for the game.
I did some searching and both SpriteKit and SceneKit have a camera. In sceneKit you create a camera node and set the point of view of the scene to that camera. This represents the player looking out into the scene.
I am not sure if it works the same way in SceneKit, but in SpriteKit you can add nodes to the camera that will be locked in place. These become your HUD elements (buttons, score, etc). Since these are all child nodes of the camera they move when you move the camera so it feels like they are a static HUD. This is how you achieve a HUD. Read up on the documentation for SceneKit cameras as this will help you understand how to achieve the same effect.
I once also thought about doing it the way you are going now and it proved to be a very painful experience to mix SceneKit and SpriteKit so I would caution against it.
Also, while you can mix Interface Builder and SpriteKit I would caution against it. It basically ruined my first game into a buggy mess. If you do decide to use Interface Builder, try to limit it to menu screens and options screens. But, I found that programmatically creating everything gives you a much smoother, cleaner, and bug free experience.
Hope this helps!
I found a solution for my problem for anyone who may stumble into this same issue (which I think is likely as many examples demonstrate an overlay scene added as a subview in the manner I did.
First the tapHandler I had in my viewController needed to have the property cancelsTouchesInView set to false. This will allow the gesture to be received by the underlying views.
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
tapGesture.cancelsTouchesInView = false
sceneView.addGestureRecognizer(tapGesture)
The reason this doesn't happen to all subviews is because I was relying on a view that was sized to 100% of the frame, covering the underlying ones, rather than just having the view occupy a smaller screen area, like a button or scoreboard as seen in the example docs.

How to fix the share button on SpriteKit Swift when Players plays the game again Share Button shows up on the game scene?

Here is my code for my "Share" button:
ShareButton = UIButton(frame: CGRect(x: 0, y:0, width: view.frame.size.width / 3, height: 60))
ShareButton.center = CGPointMake(CGRectGetMidX(self.frame), 3*CGRectGetHeight(self.frame)/4)
ShareButton.setTitle("Share", forState: UIControlState.Normal)
ShareButton.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
ShareButton.addTarget(self, action: ("pressed:"), forControlEvents: .TouchUpInside)
self.view?.addSubview(ShareButton)
PS: My Share button works but when the user shares his score and wants to play again, the Share button shows up on the Game Scene.
The first rule when making a SpriteKit game is to try to not use UIKit.
All of your UI should be created directly in the SKScenes using SpriteKit APIs (SKLabelNodes, SKSpriteNodes, SKNodes etc).
There are exceptions to this, like maybe using UICollectionViews for massive level select menus, but basic UI should never be done using UIKit.
So you should be making your buttons using SKSpriteNodes and add them directly to the SKScene you want.
There are plenty of tutorials to google on how to do this, a simple one is this
https://nathandemick.com/2014/09/buttons-sprite-kit-using-swift/
For a more complete one check out apples sample game "DemoBots" or have a look at these cool projects on gitHub.
https://github.com/nguyenpham/sgbutton
https://github.com/jozemite/JKButtonNode
In SpriteKit games you only tend to have 1 view Controller (GameViewController) which will present all your SKScenes (GameScene, MenuScene etc). If you use UIKit elements they get added to the GameViewController, and therefore they will be shown in all scenes (like your share button).
self.view?.addSubview(shareButton) // self.view is your GameViewController
If you have a game with more than 1 SKScene and quite a few buttons this will be madness to manage.
On the other hand if you use SpriteKit APIs, and because every SKScene starts in a clean state when you enter it, you dont have to worry about any of this.
If you insist on using UIKit than you will have to either remove or hide the share button before you transition to game scene and unhide it or add it again when you want to.
shareButton.isHidden = true
or
shareButton.removeFromSuperview()
Finally as good practice your properties should start with small letters not capital letters
shareButton = ...
Hope this helps
As stated, you should try to not use UIKit as that's for UIKit applications and not necessarily games.
You can use my JKButtonNode class to create buttons. The class file is right here. And the best part is that they are fully compatible with SpriteKit because they are made of an SKTexture and an SKLabelNode.
First, create the button at the top level of your class.
var shareButton: JKButtonNode?
Then in your didMoveToView or wherever you configured your button. (Except the init method as calling a function of the class itself before being initialized will not work) enter the following.
let shareButtonBackground = SKShapeNode(rect: CGRect(x: 0, y: 0, width: view.frame.size.width / 3, height: 60))
shareButton = JKButtonNode(title: "Share", background: SKView().textureFromNode(shareButtonBackground)!, action: shareButtonAction)
shareButton?.title.fontColor = UIColor.whiteColor()
shareButton?.canChangeState = false
shareButton?.canPlaySounds = false
addChild(shareButton!)
And you will get an error since you didn't declare the button's action but then just add it anywhere in your class.
func shareButtonAction(button: JKButtonNode) {
print("The share button has been pressed.")
}
These buttons work just like UIButtons. They can even change states if you want them to. And of course when you don't want it displayed, then just remove it from the parent. You can also make it more customizable; just look in the first link I gave you. Example screenshot. You can change the title properties too just by calling setTitleProperties.

Difference between GameViewController and SKScenes

I've been developing a game using SpriteKit and Swift but I seem to be having trouble determining what the real differences are between the GameViewController and any one of my SKScenes. I'm trying to understand the differences because I want to implement a GameCenter or local leaderboard into my game but in all the tutorials I find (like this one:Game Center Leaderboards! (Swift 2 in Xcode) ) they have all the logic in GameViewController as they are working with single view apps. I'm having trouble understanding the relation when I read the docs, so any help would be great. Ultimately, I want to be able to display and push data to and from GameCenter in one of my scenes such as GameOverScene. Thanks for any help!
Here is some good info to start with:
Diagram of what happens each frame in SK:
So you see, the SKScene is the class with all of the fun stuff like Nodes and Actions, and is where everything (important to you) happens. You can generate these scenes through the Editor, but then you probably need to make a new .swift file to go with it (as each scene can have its own logic).
The editor is just a 'shortcut' to initializing a bunch of stuff, and honestly, you can make complete games with little code (but you very quickly find out that you want more)
So in this code, where you declare GameScene or PauseScreen (which are basically just class declarations, that inherit from SKScene), you quickly find this line talking about something that ISNT a scene:
override func didMoveToView(view: SKView)
.. it's calling a SKView... what is that, and where did it come from?
(Read about SKView here, and look at its inheritance):
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKView/index.html#//apple_ref/occ/cl/SKView
We find this SKView declaration in the GameViewController file, (which is just a class), notice that it's the same as the regular iOS apps mostly, as it inherits UIViewController:
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
Again, that method is declared in GameViewController.swift, which is basically just this:
class GameViewController: UIViewController
So how does all of this relate to iOS apps and SpriteKit? Well, they are all mashed on top of each other:
IOS app anatomy:
Basically, from right to left, you have the Window, which is (correct me if wrong) the AppDelegate, then the ViewController, then your View, which has all of the cool stuff in it (Storyboards sit inside of the View, just as SKScenes sit inside of the View.... Labels, Nodes, or Buttons, all sit inside of their respective classes ((the view)))
It's all a big sandwich of inheritance.
Check out the Apple websites for more info.
https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/ContentViews.html#//apple_ref/doc/uid/TP40006556-CH13-SW1
https://developer.apple.com/spritekit/
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SpriteKitFramework_Ref/
https://developer.apple.com/library/safari/documentation/UserExperience/Conceptual/MobileHIG/Anatomy.html
Basically, everything is an Class inherited from a class inherited from a class and so on, so on... It can get messy. You can also see these inheritances in Xcode by CMD+clicking on them, which will jump you to the source file.
Goodluck with your studies and adventures in SpriteKit :)
You should only have a single GameViewController in your game. SKScenes are the scenes that the game transitions in between an to.
For example, the home menu screen? That's an SKScene. The main gameplay? That's an SKScene. The gameover screen? That's an SKScene.
The GameViewController initializes the entire view that the game will be maintained in, so the view. The SKScenes are just scenes that are placed on top of the view. You should be looking at a tutorial that uses SKScenes.
Here's how to make game center work as of the latest Swift 2.2.
Add this function anywhere inside the GameViewController class, and then just call it right after super.viewDidLoad().
func authenticateLocalPlayer() {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
Add the following functions in your SKScene class file. Don't forget to import GameKit. Just call showLeader() whenever you want the leaderboard to be displayed.
func showLeader() {
let viewControllerVar = self.view?.window?.rootViewController
let gKGCViewController = GKGameCenterViewController()
gKGCViewController.gameCenterDelegate = self
viewControllerVar?.presentViewController(gKGCViewController, animated: true, completion: nil)
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
And this is a sample I have of how the score is saved to game center.
func saveHighscore(gameScore: Int) {
print("Player has been authenticated.")
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "YOUR_LEADERBOARD_ID")
scoreReporter.value = Int64(gameScore)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("An error has occured: \(error)")
}
})
}
}
It all depends about how you are designing your app, and what technologies you want to use.
If you are looking to build an app in 100% Sprite Kit, then you treat your UIViewController as a shell that holds your Sprite Kit app. The only time you should be touching this is when you need to do things that the SpriteKit scene shouldn't be doing, like creating gesture controls and what not.
However, there are uses to having multiple view controllers with sprite kit elements. Perhaps you are making a business application, and decide to include a little game to go with it.
IMO the best way to think about it in terms of web design is think of your View controller as your HTML page, and think of your Scene as your flash/silverlight/unity/etc game player that you embed in the website. Sometimes you want that player to be full screen, some times you do not, it comes down to the design of the application. When in full screen, we do not need any other components, so the player can do all the work. But what if we attach a how to guide link on the page. We wouldn't want this in game, we want this outside of it. This link will then open up a new page, not associated with the old page, and has no use for the game player components.
Now for your situation with Game Center, it gets more complicated. Game Center was built before Sprite Kit came to existence, so all of its functionality is built on UIKit. But Game Center also allows for customization, so you do not have to use the UIKit features of it. Of course, you will have to do all of the work then in displaying the information inside of your scene with Sprite Kit objects.
To make life easiest for you, you would include all of the built in code needed into your View Controller, then what you do is create a delegate that the scene knows about, and assign your view controller to this delegate. Now Game Scene can access any element of that view controller that you allow, like presenting leader boards or passing up leader boards. Check out this tutorial in its entirety, it will help you learn all you will need to achieve what you want.
https://www.raywenderlich.com/115300/swift-2-tutorial-part-3-tuples-protocols-delegates-and-table-views
In MVC the controller acts as a coordinator, a bit like the conductor in an orchestra. My preference is that scenes just do the one thing they were designed for i.e. implement game play. When a scene is completed, the final task is to notify the controller (using delegate pattern) that the scene is complete. It is then up to the controller to decide what happens next i.e. transition to next scene or game over.