Getting a vector from a touch location in swift 3 - uikit

As the title says, how can I accomplish this? I can get the first touch but I don't know how to get the last touch if it is a swiping gesture. With the following code I get the error: value of type 'Set' has no member 'last'. Not really sure how to get the position when the touch is released.
override func touchesMoved(_ touches: Set<UITouch>, with: UIEvent?){
let firstTouch:UITouch = touches.first! as UITouch
let lastTouch:UITouch = touches.last! as UITouch
let firstTouchLocation = firstTouch.location(in: self)
let lastTouchLocation = lastTouch.location(in: self)
}
I want to get the location of the first point and the location of the second point. Then I would like to use the duration of the swipe, the size of the line, and the angle of the line to create an impulse vector to use on an SKNode. Is there any way to extract this information using the UITouch? Any help is greatly appreciated. Thanks for your time!
I also have basic touches on the screen that run functions
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch:UITouch = touches.first! as UITouch
let touchLocation = touch.location(in: self)
if playButton.contains(touchLocation) && proceed == true{
playSound1()
Ball.physicsBody?.affectedByGravity = true
proceed = false}
else if infoButton.contains(touchLocation) && proceed == true{
playSound1()
loadScene2()}
else {}
}

You have to store the values in touches began and compare with the values in touches ended and then do the calculations there. Note that a touch can be canceled by something like a phone call so you need to anticipate that as well.
class v: UIViewController {
var startPoint: CGPoint?
var touchTime = NSDate()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard touches.count == 1 else {
return
}
if let touch = touches.first {
startPoint = touch.location(in: view)
touchTime = NSDate()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
defer {
startPoint = nil
}
guard touches.count == 1, let startPoint = startPoint else {
return
}
if let touch = touches.first {
let endPoint = touch.location(in: view)
//Calculate your vector from the delta and what not here
let direction = CGVector(dx: endPoint.x - startPoint.x, dy: endPoint.y - startPoint.y)
let elapsedTime = touchTime.timeIntervalSinceNow
let angle = atan2f(Float(direction.dy), Float(direction.dx))
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
startPoint = nil
}
}

Related

SpriteKit Controls / Gesture

Essentially I am trying to incorporate X2 gamescene buttons that do the following functions:
1) Tap to fly (this I have working)
2) Tap to shoot a projectile from Player position (I do not have working).
My problem is I currently have the fly func set when touched anywhere on the screen. I have tried the following :
This is in reference to my GameScene : I thought in order to split this out I would need a node on the screen to reference this function. This does not error in the console but does not appear in the GameScene.
// Button to trigger shooting :
let btnTest = SKSpriteNode(imageNamed: "Crater")
btnTest.setScale(0.2)
btnTest.name = "Button"
btnTest.zPosition = 10
btnTest.position = CGPoint(x: 100, y: 200)
self.addChild(btnTest)
Next in the Player class I have the following broken down:
var shooting = false
var shootAnimation = SKAction()
var noshootAnimation = SKAction()
Init func:
self.run(noshootAnimation, withKey: "noshootAnimation")
let projectile = SKSpriteNode(imageNamed: "Crater")
projectile.position = CGPoint (x: 100, y: 50)
projectile.zPosition = 20
projectile.name = "projectile"
// Assigning categories to Game objects:
self.physicsBody?.categoryBitMask =
PhysicsCategory.plane.rawValue
self.physicsBody?.contactTestBitMask =
PhysicsCategory.ground.rawValue
self.physicsBody?.collisionBitMask =
PhysicsCategory.ground.rawValue
self.physicsBody?.applyImpulse(CGVector(dx: 300, dy: 0))
self.addChild(projectile)
// Start the shoot animation, set shooting to true:
func startShooting() {
self.removeAction(forKey: "noshootAnimation")
self.shooting = true
}
// Stop the shoot animation, set shooting to false:
func stopShooting() {
self.removeAction(forKey: "shootAnimation")
self.shooting = false
}
The node appears in the GameScene which looks promising, finally I move to the last bit of code in the GameScene as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) for touch in (touches) {
let location = touch.location(in: self)
let nodeTouched = atPoint(location)
if let gameSprite = nodeTouched as? GameSprite {
gameSprite.onTap()
}
// Check the HUD buttons which I have appearing when game is over…
if nodeTouched.name == "restartGame" {
// Transition to a new version of the GameScene
// To restart the Game
self.view?.presentScene(GameScene(size: self.size), transition: .crossFade(withDuration: 0.6))
}
else if nodeTouched.name == "returnToMenu"{
// Transition to the main menu scene
self.view?.presentScene(MenuScene(size: self.size), transition: . crossFade(withDuration: 0.6))
}
}
Player.startFly()
player.startShooting()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
Player.stopFly()
player.stopShooting()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
Player.stopFly()
player.stopShooting()
}
override func update(_ currentTime: TimeInterval) {
player.update()
}
}
Unfortunately nothing happens in the GameScene and the node doesn’t fire when the screen is pressed, with the code above is there anyway I can amend this to allow for both ‘tap to fly’ + ‘tap to shoot’ functions. I still can’t figure out how to get the button I had declared early on in the GameScene to appear in which my gesture / touch position can be localised to this node on the screen as oppose to the whole screen in which I have currently..
I can say this sounded more simple in my head to begin with than actually coding together.
Firstly, the above code does not have any calls to run the animation with the key "shootAnimation" and is missing the call to re-run the not-shooting animation when stopShooting() and the call to re-run the shooting animation in startShooting(). The methods should include these as shown below
func startShooting() {
self.removeAction(forKey: "noshootAnimation")
// Add this call
self.run(shootAnimation)
self.shooting = true
}
func stopShooting() {
self.removeAction(forKey: "shootAnimation")
// Add this call
self.run(noshootAnimation)
self.shooting = true
}
Secondly, the noshootAnimation and shootAnimation animations are empty actions: they will not do anything if initialized as SKAction(). Depending on what you are trying to do, there are a number of class methods of SKAction that will create actions for you here.
Thirdly, any SKNode (recall that a scene is an SKNode subclass) will not receive touch calls if the node's isUserInteractionEnabled property is set to false (to which it is defaulted); instead, it will be treated as if its parent received the call. However, should a node's isUserInteractionEnabled be true, it will be the one that receives the UIResponder calls, not its parent node. Make sure that this property is set to true for the scene and for any nodes that need to receive touches (you can do this in didMove(to:) in the scene or elsewhere).
I will now propose an improvement. I frequently use buttons in Spritekit, but they are all subclasses of a custom button class (itself a subclass of SKSpriteNode) that uses protocol delegation to alert members of touch events. The scheme looks like this:
class Button: SKSpriteNode {
weak var responder: ButtonResponder?
// Custom code
// MARK: UIResponder
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Respond here
responder?.respond(to: self)
}
}
protocol ButtonResponder: AnyObject {
func respond(to button: Button)
}
class MyScene: SKScene, ButtonResponder {
func respond(to button: Button) {
// Do something on touch, but typically check the name
switch button.name {
case "myButton":
// Do something specific
default:
break
}
}
override func didMove(to view: SKView) {
// Set the scene as a responder
let buttonInScene = childNode(withName: "myButton") as! Button
buttonInScene.responder = self
}
}
For more on delegation, look here. Hopefully this helped a little bit.
EDIT: Convert touch location
You can convert the touch to the node on the screen by passing a reference to the location(in:) method of UITouch instead of the scene as you did in your code
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let yourNode = childNode(withName: "yourNode")!
for touch in touches {
let touchLocationToYourNode = touch.location(in: yourNode)
// Do something
}
}
Alternatively, use SKNode's convert(_:from:) and convert(_:to:) methods
let nodeA = SKNode()
let nodeB = SKNode()
nodeA.xScale = 1.5
nodeA.yScale = 1.2
nodeA.position = .init(x: 100, y: 100)
nodeA.zRotation = .pi
let pointInNodeACoordinateSystem = CGPoint(x: 100, y: 100)
let thatSamePointInNodeBCoordinateSystem = nodeB.convert(pointInNodeACoordinateSystem, from: nodeA)

How do I get my node to rotate even if my finger is outside the node?

Ill try to explain this the best way I can. I have this circle that I can rotate when my finger is moving on the circle. The problem Im having is that when Im rotating the circle and I move my finger outside the circle the rotation stops. I would like it to still have the touch even if my finger is outside the node. It works when Im in a single view controller but in spritekit I cant figure it out.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "circle" {
let dy = circle.position.y - location.y
let dx = circle.position.x - location.x
let angle2 = atan2(dy, dx)
circle.zRotation = angle2
}
}
}
Declare an optional SKNode to hold the current rotating node like var rotatingNode:SKNode?. You can override touchesBegan.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?){
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "circle" || node.name == "rightCircle" {
self.rotatingNode = node
}
}
}
From your comment it seems like you have other code in touchesMoved than what is posted in your question. But if you want to rotate either circle or rightCircle then say something like:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let dy = self.rotatingNode?.position.y - location.y
let dx = self.rotatingNode?.position.x - location.x
let angle2 = atan2(dy, dx)
self.rotatingNode?.zRotation = angle2
}
}
Then in touchesEnded:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.rotatingNode = nil
}

How can I make having different gravity object when every touch on object?

My game has a ball and this ball has a gravity. I want to change ball's gravity when every touch on that. How can I do that? Here is my touchesBegan code :
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch:AnyObject in touches{
let location = (touch as! UITouch).locationInNode(self)
let nodeTouched = nodeAtPoint(location)
let ball = childNodeWithName(BallCategoryName) as! SKSpriteNode
//Detect if the ball is touched
if(nodeTouched == ball){
physicsWorld.gravity = CGVectorMake(-30, 30)
}
}

Why won't my Interstitial Ads work in my GameScene but work in the GameViewController in Swift?

I have these facebook interstitial ads and when I call them in my GameScene they dont show up. They work perfectly when I call them in the GameViewController but not in my GameScene. Can someone tell me what Im doing wrong with my code?
//GameViewController.swift
class GameViewController: UIViewController, FBInterstitialAdDelegate {
let interstitialFBAD: FBInterstitialAd = FBInterstitialAd(placementID: "65543456456456_454345345454534")
override func viewDidLoad() {
super.viewDidLoad()
}
//CALL THIS IN MY GAMESCENE.
func loadFBInterstitialAd() {
interstitialFBAD.delegate = self
interstitialFBAD.loadAd()
}
func interstitialAdDidLoad(interstitialAd: FBInterstitialAd!) {
interstitialFBAD.showAdFromRootViewController(self)
}
//GameScene.swift
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "retry" {
GameViewController().loadFBInterstitialAd()
}
}
}
In viewDidLoad of GameViewController register for a notification, and later from the GameScene post a notification when you want the ad to appear.
so your viewDidLoad will have the following extra line
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadFBInterstitialAd", name: "loadAd", object: nil)
then write a method in Gamescene
func showAdNow() {
NSNotificationCenter.defaultCenter().postNotificationName("loadAd", object: nil)
}
now in Gamescene.swift replace the line
GameViewController().loadFBInterstitialAd()
with
showAdNow()
You try:
//GameViewController.swift
class GameViewController: UIViewController, FBInterstitialAdDelegate {
let interstitialFBAD: FBInterstitialAd = FBInterstitialAd(placementID: "65543456456456_454345345454534")
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
let skView = view as! SKView
skView.multipleTouchEnabled = false
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .AspectFill
scene.view?.userInteractionEnabled = true
skView.presentScene(scene)
scene.gvcDelegate = self
}
//GameScene.swift
class GameScene: SKScene {
gvcDelegate: GameViewController?
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "retry" {
gvcDelegate.loadFBInterstitialAd()
}
}
}
Goodluck!

SKSpriteNode touch detected swift

I am having trouble with detecting a specific node being touched. Here is what i have to far.
let playagain = SKSpriteNode(imageNamed: "PlayAgain.png")
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
}
then when the player dies these two nodes come up.
playagain.position = CGPoint(x:frame.size.width * 0.5, y: frame.size.height * 0.5)
addChild(playagain)
gameover.position = CGPoint(x:frame.size.width * 0.5, y: frame.size.height * 0.75)
addChild(gameover)
everything above works. the node comes on the screen where i asked i just can't seem to get it to show i clicked it.
as you can see the node is called playagain, when the playagain node is clicked i want to be able to refresh the game. what i have so far is below.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
let play = self.nodeAtPoint(location)
if play.name == "playagain" {
println("touched")
}
}
}
thanks!
Are you not using the latest Xcode? Your touches began code should not run with swift 2 and Xcode 7.
Try this
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if playagain.containsPoint(location) {
/// playagain was pressed, do something
}
}
}
or this
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(location)
if touchedNode == playagain {
/// playagain was pressed, do something
}
}
}

Resources