How to rotate to another face of 3d cube when swiped right - ios

I created a 3d cube using SceneKit and added a gesture to recognize swiping right. But, I do not know how to rotate the cube to another face when swiped right. I do not want the cube to rotate continuously when swiped, only moving to another face that is on the right side. Sorry for any confusions.
Here is my code where I created cube:
import UIKit
import SceneKit
class ViewController: UIViewController {
// UI
#IBOutlet weak var geometryLabel: UILabel!
#IBOutlet weak var sceneView: SCNView!
// Geometry
var geometryNode: SCNNode = SCNNode()
// Gestures
var currentAngle: Float = 0.0
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// MARK: Scene
sceneSetup()
}
func sceneSetup() {
let scene = SCNScene()
sceneView.scene = scene
sceneView.allowsCameraControl = false
sceneView.autoenablesDefaultLighting = true
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 25)
scene.rootNode.addChildNode(cameraNode)
var geometries = [
SCNBox(width: 8.0, height: 8.0, length: 8.0, chamferRadius: 1.0)
]
var materials = [SCNMaterial]()
for i in 1...6 {
let material = SCNMaterial()
if i == 1 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 2 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 3 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 4 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 5 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 6 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
materials.append(material)
}
for i in 0..<geometries.count {
let geometry = geometries[i]
let node = SCNNode(geometry: geometry)
node.geometry?.materials = materials
scene.rootNode.addChildNode(node)
}
let swipeRight: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture(gesture:)))
swipeRight.direction = .right
sceneView.addGestureRecognizer(swipeRight)
}
Function for swipe right gesture when recognize:
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped Right")
default:
break
}
}
}

You need to use a SCNAction.rotate getting the current w value of the node rotation and adding the amount of angle you want, using this 2 extensions for converting to Radians and from Radians, replace my self.shipNode by your cube node, and change the axis if you need to, taking in account that the first 0 in the SCNVector4Make sentence is X axis
This is an example
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
#objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
//here we rotate the ship node 30 grades in the Y axis each time
self.shipNode?.runAction(SCNAction.rotate(toAxisAngle: SCNVector4Make(0, 1, 0, (self.shipNode?.rotation.w)! + Float(30.degreesToRadians)), duration: 1))
}
To rotate down you only have to change the axis of the rotation
you can use this
- (void) handleTap:(UIGestureRecognizer*)gestureRecognize
{
[self.ship runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(1, 0, 0, [self getRadiansFromAngle:[self getAngleFromRadian:self.ship.rotation.w] + 30]) duration:1]];
}
UPDATED
Using your code
You need to use SCNAction.rotate(by: #Float, around: #SCNVector3, duration: #duration) method instead, below is the full code for your requeriments
#objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped Right")
geometryNode.runAction(SCNAction.rotate(by: CGFloat(90.degreesToRadians), around: SCNVector3Make(0, 1, 0), duration: 0.2))
case UISwipeGestureRecognizerDirection.left:
print("Swiped Left")
geometryNode.runAction(SCNAction.rotate(by: CGFloat(-90.degreesToRadians), around: SCNVector3Make(0, 1, 0), duration: 0.2))
case UISwipeGestureRecognizerDirection.down:
print("Swiped Down")
geometryNode.runAction(SCNAction.rotate(by: CGFloat(90.degreesToRadians), around: SCNVector3Make(1, 0, 0), duration: 0.2))
default:
break
}
}
}

Related

How to attach multiple UIDynamicItems to each other

I am trying to implement circles attached to each other like in Apple's Music App via UIDynamicAnimator. I need to attach circles to each other and to view center. I was trying to implement this via UIAttachmentBehavior, but seems to it's not supporting multiple attachments. In result, circles overlaps on each other :)
let attachment = UIAttachmentBehavior(item: circle, attachedToAnchor: CGPoint(x: view.center.x, y: view.center.y))
attachment.length = 10
animator?.addBehavior(attachment)
let push = UIPushBehavior(items: [circle], mode: .continuous)
collision.addItem(circle)
animator?.addBehavior(push)
What I am doing wrong?
I don't think the apple music genre picker thing uses UIAttachmentBehavior which is closer to attaching two views with a pole or a rope. But, it seems like the problem you're experiencing might be that all of the views are added at the same location which has the effect of placing them on top of each other and with the collision behavior causes them to be essentially be stuck together. One thing to do is to turn on UIDynamicAnimator debugging by calling animator.setValue(true, forKey: "debugEnabled").
For recreating the above circle picker design, I would look into using UIFieldBehavior.springField().
For example:
class ViewController: UIViewController {
lazy var animator: UIDynamicAnimator = {
let animator = UIDynamicAnimator(referenceView: view)
return animator
}()
lazy var collision: UICollisionBehavior = {
let collision = UICollisionBehavior()
collision.collisionMode = .items
return collision
}()
lazy var behavior: UIDynamicItemBehavior = {
let behavior = UIDynamicItemBehavior()
behavior.allowsRotation = false
behavior.elasticity = 0.5
behavior.resistance = 5.0
behavior.density = 0.01
return behavior
}()
lazy var gravity: UIFieldBehavior = {
let gravity = UIFieldBehavior.springField()
gravity.strength = 0.008
return gravity
}()
lazy var panGesture: UIPanGestureRecognizer = {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didPan(_:)))
return panGesture
}()
var snaps = [UISnapBehavior]()
var circles = [CircleView]()
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(panGesture)
animator.setValue(true, forKey: "debugEnabled")
addCircles()
addBehaviors()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gravity.position = view.center
snaps.forEach {
$0.snapPoint = view.center
}
}
func addCircles() {
(1...30).forEach { index in
let xIndex = index % 2
let yIndex: Int = index / 3
let circle = CircleView(frame: CGRect(origin: CGPoint(x: xIndex == 0 ? CGFloat.random(in: (-300.0 ... -100)) : CGFloat.random(in: (500 ... 800)), y: CGFloat(yIndex) * 200.0), size: CGSize(width: 100, height: 100)))
circle.backgroundColor = .red
circle.text = "\(index)"
circle.textAlignment = .center
view.addSubview(circle)
gravity.addItem(circle)
collision.addItem(circle)
behavior.addItem(circle)
circles.append(circle)
}
}
func addBehaviors() {
animator.addBehavior(collision)
animator.addBehavior(behavior)
animator.addBehavior(gravity)
}
#objc
private func didPan(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: sender.view)
switch sender.state {
case .began:
animator.removeAllBehaviors()
fallthrough
case .changed:
circles.forEach { $0.center = CGPoint(x: $0.center.x + translation.x, y: $0.center.y + translation.y)}
case .possible, .cancelled, .failed:
break
case .ended:
circles.forEach { $0.center = CGPoint(x: $0.center.x + translation.x, y: $0.center.y + translation.y)}
addBehaviors()
#unknown default:
break
}
sender.setTranslation(.zero, in: sender.view)
}
}
final class CircleView: UILabel {
override var collisionBoundsType: UIDynamicItemCollisionBoundsType {
return .ellipse
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height * 0.5
layer.masksToBounds = true
}
}
For more information I would watch What's New in UIKit Dynamics and Visual Effects from WWDC 2015

Move spritekit node only within a specific bounds

I'm new to spritekit so this looks like a silly question but I can't figure out. The player (shown in blue circle) can only go above lines and inside the square. I added a joystick, user can go up or down above left line. I want player to be limited to only line so when It comes the left edge, user should move joystick to right. How can I achieve it?
I tried to update player position in override func update(_ currentTime: TimeInterval) function like below to update enum position and check it everytime in move logic;
override func update(_ currentTime: TimeInterval) {
if((player?.position.x)!.rounded() <= self.barra.frame.minX.rounded()){
player?.playerPosition == .left
}
print(player?.position)
}
How I declare square;
let barra = SKShapeNode(rectOf: CGSize(width: 600, height: 300)) //Line
override func sceneDidLoad() {
player = self.childNode(withName: "player") as? Player
player?.physicsBody?.categoryBitMask = playerCategory
player?.physicsBody?.collisionBitMask = noCategory
player?.physicsBody?.contactTestBitMask = enemyCategory | itemCategory
player?.playerPosition = .left
barra.name = "bar"
barra.fillColor = SKColor.clear
barra.lineWidth = 3.0
barra.position = CGPoint(x: 0, y: 0)
self.addChild(barra)
player?.position = CGPoint(x: barra.frame.minX , y: barra.frame.minY)
}
How I move the player;
override func didMove(to view: SKView) {
/* Setup your scene here */
backgroundColor = UIColor.black
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
moveAnalogStick.position = CGPoint(x: moveAnalogStick.radius + 15, y: moveAnalogStick.radius + 15)
addChild(moveAnalogStick)
moveAnalogStick.stick.color = UIColor.white
//MARK: Handlers begin
moveAnalogStick.beginHandler = { [unowned self] in
guard let aN = self.player else {
return
}
//aN.run(SKAction.sequence([SKAction.scale(to: 0.5, duration: 0.5), SKAction.scale(to: 1, duration: 0.5)]))
}
moveAnalogStick.trackingHandler = { [unowned self] data in
guard let aN = self.player else {
return
}
if(self.player?.playerPosition == .left){
aN.position = CGPoint(x: aN.position.x, y: aN.position.y + (data.velocity.y * 0.12))
}
}
moveAnalogStick.stopHandler = { [unowned self] in
guard let aN = self.player else {
return
}
// aN.run(SKAction.sequence([SKAction.scale(to: 1.5, duration: 0.5), SKAction.scale(to: 1, duration: 0.5)]))
}
//MARK: Handlers end
let selfHeight = frame.height
let btnsOffset: CGFloat = 10
let btnsOffsetHalf = btnsOffset / 2
view.isMultipleTouchEnabled = true
}
Player class:
enum Position{
case left
case right
case up
case down
case inside
}
enum CanMove{
case upDown
case leftRight
case all
}
class Player: SKSpriteNode {
var playerSpeed: CGFloat = 0.0
var playerPosition: Position = .left //Default one
var canMove: CanMove = .upDown
func move(){
}
}

Using Multiple AR Image Anchors to display dynamic 3D Overlay

I'm working on a project wherein we have to detect a certain number of custom QR codes (as ARImageAnchors) and then using the position of these anchors to dynamically display a 3D overlay. To be exact, we are planning to dynamically display a 3D model of human anatomy over the anchors which will be placed on a mannequin. For example, the mannequin we are placing the QR codes on is smaller or bigger than the default size of the 3D model, we would like it to adapt based on the distances between the images. Below is the sample code I'm thinking of working off from (source: https://www.appcoda.com/arkit-image-recognition/).
import UIKit
import ARKit
class ViewController: UIViewController {
#IBOutlet weak var sceneView: ARSCNView!
#IBOutlet weak var label: UILabel!
let fadeDuration: TimeInterval = 0.3
let rotateDuration: TimeInterval = 3
let waitDuration: TimeInterval = 0.5
lazy var fadeAndSpinAction: SCNAction = {
return .sequence([
.fadeIn(duration: fadeDuration),
.rotateBy(x: 0, y: 0, z: CGFloat.pi * 360 / 180, duration: rotateDuration),
.wait(duration: waitDuration),
.fadeOut(duration: fadeDuration)
])
}()
lazy var fadeAction: SCNAction = {
return .sequence([
.fadeOpacity(by: 0.8, duration: fadeDuration),
.wait(duration: waitDuration),
.fadeOut(duration: fadeDuration)
])
}()
lazy var treeNode: SCNNode = {
guard let scene = SCNScene(named: "tree.scn"),
let node = scene.rootNode.childNode(withName: "tree", recursively: false) else { return SCNNode() }
let scaleFactor = 0.005
node.scale = SCNVector3(scaleFactor, scaleFactor, scaleFactor)
node.eulerAngles.x = -.pi / 2
return node
}()
lazy var bookNode: SCNNode = {
guard let scene = SCNScene(named: "book.scn"),
let node = scene.rootNode.childNode(withName: "book", recursively: false) else { return SCNNode() }
let scaleFactor = 0.1
node.scale = SCNVector3(scaleFactor, scaleFactor, scaleFactor)
return node
}()
lazy var mountainNode: SCNNode = {
guard let scene = SCNScene(named: "mountain.scn"),
let node = scene.rootNode.childNode(withName: "mountain", recursively: false) else { return SCNNode() }
let scaleFactor = 0.25
node.scale = SCNVector3(scaleFactor, scaleFactor, scaleFactor)
node.eulerAngles.x += -.pi / 2
return node
}()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
configureLighting()
}
func configureLighting() {
sceneView.autoenablesDefaultLighting = true
sceneView.automaticallyUpdatesLighting = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
resetTrackingConfiguration()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
#IBAction func resetButtonDidTouch(_ sender: UIBarButtonItem) {
resetTrackingConfiguration()
}
func resetTrackingConfiguration() {
guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else { return }
let configuration = ARWorldTrackingConfiguration()
configuration.detectionImages = referenceImages
let options: ARSession.RunOptions = [.resetTracking, .removeExistingAnchors]
sceneView.session.run(configuration, options: options)
label.text = "Move camera around to detect images"
}
}
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
DispatchQueue.main.async {
guard let imageAnchor = anchor as? ARImageAnchor,
let imageName = imageAnchor.referenceImage.name else { return }
// TODO: Comment out code
// let planeNode = self.getPlaneNode(withReferenceImage: imageAnchor.referenceImage)
// planeNode.opacity = 0.0
// planeNode.eulerAngles.x = -.pi / 2
// planeNode.runAction(self.fadeAction)
// node.addChildNode(planeNode)
// TODO: Overlay 3D Object
let overlayNode = self.getNode(withImageName: imageName)
overlayNode.opacity = 0
overlayNode.position.y = 0.2
overlayNode.runAction(self.fadeAndSpinAction)
node.addChildNode(overlayNode)
self.label.text = "Image detected: \"\(imageName)\""
}
}
func getPlaneNode(withReferenceImage image: ARReferenceImage) -> SCNNode {
let plane = SCNPlane(width: image.physicalSize.width,
height: image.physicalSize.height)
let node = SCNNode(geometry: plane)
return node
}
func getNode(withImageName name: String) -> SCNNode {
var node = SCNNode()
switch name {
case "Book":
node = bookNode
case "Snow Mountain":
node = mountainNode
case "Trees In the Dark":
node = treeNode
default:
break
}
return node
}
}
I know that the 3D overlay is displayed in the renderer function above, but it is only displaying on top of a single detected image anchor. Now my question is, is it possible to reference multiple ARImage anchors to dynamically display a single 3D model?
Being a novice in ARKit and Swift in general, I'm not sure how to go about this problem yet. I'm hoping someone might have an idea of how to work around this and point me to the right direction. Any help will be greatly appreciated!
Thanks in advance!

iOS 11 ARKit : Drag Object in 3D View

I have a node object in 3d view and i need to drag that object,
So far i have tried from here : Placing, Dragging and Removing SCNNodes in ARKit
and converted in swift
#objc func handleDragGesture(_ gestureRecognizer: UIGestureRecognizer) {
let tapPoint = gestureRecognizer.location(in: self.sceneView)
switch gestureRecognizer.state {
case .began:
print("Object began to move")
let hitResults = self.sceneView.hitTest(tapPoint, options: nil)
if hitResults.isEmpty { return }
let hitResult = hitResults.first
if let node = hitResult?.node.parent?.parent?.parent {
self.photoNode = node
}
case .changed:
print("Moving object position changed")
if let _ = self.photoNode {
let hitResults = self.sceneView.hitTest(tapPoint, types: .featurePoint)
let hitResult = hitResults.last
if let transform = hitResult?.worldTransform {
let matrix = SCNMatrix4FromMat4(transform)
let vector = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
self.photoNode?.position = vector
}
}
case .ended:
print("Done moving object")
default:
break
}
}
but it is not working properly. what is the correct way to do?
You can do this using panGestureRecongniser... see basic swift Playground code for handling a SCNNode.
import UIKit
import ARKit
import SceneKit
import PlaygroundSupport
public var textNode : SCNNode?
// Main ARKIT ViewController
class ViewController : UIViewController, ARSCNViewDelegate, ARSessionDelegate {
var textNode: SCNNode!
var counter = 0
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// set the views delegate
sceneView.delegate = self as! ARSCNViewDelegate
// show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
sceneView.scene.rootNode
// Add ligthing
sceneView.autoenablesDefaultLighting = true
let text = SCNText(string: "Drag Me with Pan Gesture!", extrusionDepth: 1)
// create material
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
text.materials = [material]
//Create Node object
textNode = SCNNode()
textNode.name = "textNode"
textNode.scale = SCNVector3(x:0.004,y:0.004,z:0.004)
textNode.geometry = text
textNode.position = SCNVector3(x: 0, y:0.02, z: -1)
// add new node to root node
self.sceneView.scene.rootNode.addChildNode(textNode)
// Add pan gesture for dragging the textNode about
sceneView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))
}
override func loadView() {
sceneView = ARSCNView(frame:CGRect(x: 0.0, y: 0.0, width: 500.0, height: 600.0))
// Set the view's delegate
sceneView.delegate = self
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
// Now we'll get messages when planes were detected...
sceneView.session.delegate = self
self.view = sceneView
sceneView.session.run(config)
}
#objc func panGesture(_ gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 1
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
textNode.position = position
}
}
PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
EDIT:
The above drag function only worked if you had 1 object in the view, so it was not really necessary to hit the node to start dragging. It will just drag from where ever you tapped on the screen. If you have multiple objects in the view, and you want to drag nodes independently. You could change the panGesture function to the following, detect each node tapped first:
// drags nodes independently
#objc func panGesture(_ gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 1
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
let hits = self.sceneView.hitTest(gesture.location(in: gesture.view), options: nil)
if let tappedNode = hits.first?.node {
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
tappedNode.position = position
}
}
REF: https://stackoverflow.com/a/48220751/5589073
This code works for me
private func drag(sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
let location = sender.location(in: self.sceneView)
guard let hitNodeResult = self.sceneView.hitTest(location,
options: nil).first else { return }
self.PCoordx = hitNodeResult.worldCoordinates.x
self.PCoordy = hitNodeResult.worldCoordinates.y
self.PCoordz = hitNodeResult.worldCoordinates.z
case .changed:
// when you start to pan in screen with your finger
// hittest gives new coordinates of touched location in sceneView
// coord-pcoord gives distance to move or distance paned in sceneview
let hitNode = sceneView.hitTest(sender.location(in: sceneView), options: nil)
if let coordx = hitNode.first?.worldCoordinates.x,
let coordy = hitNode.first?.worldCoordinates.y,
let coordz = hitNode.first?.worldCoordinates.z {
let action = SCNAction.moveBy(x: CGFloat(coordx - self.PCoordx),
y: CGFloat(coordy - self.PCoordy),
z: CGFloat(coordz - self.PCoordz),
duration: 0.0)
self.photoNode.runAction(action)
self.PCoordx = coordx
self.PCoordy = coordy
self.PCoordz = coordz
}
sender.setTranslation(CGPoint.zero, in: self.sceneView)
case .ended:
self.PCoordx = 0.0
self.PCoordy = 0.0
self.PCoordz = 0.0
default:
break
}
}

How can I set UIGestureRecognizer's Speed as the velocity for my Physicsbody in SpriteKit?

I calculated the speed of UIGestureRecognizer and I want to use it as velocity of Physicsbody in SpriteKit.
How can I do that since Speed is calculated in touches began and touches ended method, while my Physicsbody is wrapped in swipeUp & swipeDown etc functions, which does not have access to the Speed variable?
Here is my code:
class GameScene: SKScene, SKPhysicsContactDelegate, SKViewDelegate, UIGestureRecognizerDelegate, UIViewControllerTransitioningDelegate {
var Kite = SKSpriteNode(imageNamed: "ASC_8025_large.jpg")
#objc let minusButton = SKSpriteNode(imageNamed: "minus.jpg")
#objc let plusButton = SKSpriteNode(imageNamed: "plus.png")
//override init(Kite: SKSpriteNode) {
//For long Long press gesture to work
let longPressGestureRecPlus = UILongPressGestureRecognizer()
let longPressGestureRecMinus = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(press:)))
//First we declare all of our Gestures...
//swipes
let swipeRightRec = UISwipeGestureRecognizer()
let swipeLeftRec = UISwipeGestureRecognizer()
let swipeUpRec = UISwipeGestureRecognizer()
let swipeDownRec = UISwipeGestureRecognizer()
//rotate
let rotateRec = UIRotationGestureRecognizer()
//taps
let tapRec = UITapGestureRecognizer()
let tapRec2 = UITapGestureRecognizer()
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
Kite.size = CGSize(width: 40.0, height: 40.0)
Kite.position = CGPoint(x: frame.midX, y: frame.midY)
plusButton.size = CGSize(width: 30.0, height: 30.0)
plusButton.position = CGPoint(x: frame.midX, y: frame.minY + 20.0)
plusButton.name = "plusButton"
//plusButton.isUserInteractionEnabled = true
addChild(Kite)
addChild(plusButton)
swipeRightRec.addTarget(self, action: #selector(GameScene.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(GameScene.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
swipeUpRec.addTarget(self, action: #selector(GameScene.swipedUp) )
swipeUpRec.direction = .up
self.view!.addGestureRecognizer(swipeUpRec)
swipeDownRec.addTarget(self, action: #selector(GameScene.swipedDown) )
swipeDownRec.direction = .down
self.view!.addGestureRecognizer(swipeDownRec)
//notice the function this calls has (_:) after it because we are passing in info about the gesture itself (the sender)
rotateRec.addTarget(self, action: #selector (GameScene.rotatedView (_:) ))
self.view!.addGestureRecognizer(rotateRec)
// again notice (_:), we'll need this to find out where the tap occurred.
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
tapRec2.addTarget(self, action:#selector(GameScene.tappedView2(_:) ))
tapRec2.numberOfTouchesRequired = 1
tapRec2.numberOfTapsRequired = 2 //2 taps instead of 1 this time
self.view!.addGestureRecognizer(tapRec2)
longPressGestureRecPlus.addTarget(self, action: #selector(longPressed(press:)))
longPressGestureRecPlus.minimumPressDuration = 2.0
self.view?.addGestureRecognizer(longPressGestureRecPlus)
}
//the functions that get called when swiping...
#objc func swipedRight() {
print("Right")
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Tilts the Kite towards Right
let tiltRight = SKAction.rotate(toAngle: -1.00, duration: 0.1)
Kite.run(tiltRight)
}
#objc func swipedLeft() {
Kite.physicsBody?.velocity = CGVector(dx: -60, dy: 60)
//Tilts the Kite towards Right
let tiltLeft = SKAction.rotate(toAngle: 1.00, duration: 0.1)
Kite.run(tiltLeft)
print("Left")
}
#objc func swipedUp() {
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Straightens the Kite
let straightens = SKAction.rotate(toAngle: 0.00, duration: 0.1)
Kite.run(straightens)
print("Up")
}
#objc func swipedDown() {
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: -60)
print("Down")
}
// what gets called when there's a single tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Single tap")
print(point)
}
// what gets called when there's a double tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView2(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Double tap")
print(point)
}
//what gets called when there's a rotation gesture
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func rotatedView(_ sender:UIRotationGestureRecognizer) {
if (sender.state == .began) {
print("rotation began")
}
if (sender.state == .changed) {
print("rotation changed")
//you could easily make any sprite's rotation equal this amount like so...
//thePlayer.zRotation = -sender.rotation
//convert rotation to degrees...
let rotateAmount = Measurement(value: Double(sender.rotation), unit: UnitAngle.radians).converted(to: .degrees).value
print("\(rotateAmount) degreess" )
}
if (sender.state == .ended) {
print("rotation ended")
}
}
func removeAllGestures(){
//if you need to remove all gesture recognizers with Swift you can do this....
for gesture in (self.view?.gestureRecognizers)! {
self.view?.removeGestureRecognizer(gesture)
}
//this is good to do before a SKScene transitions to another SKScene.
}
func removeAGesture()
{
//To remove a single gesture you can use...
self.view?.removeGestureRecognizer(swipeUpRec)
}
//Fix the gesture recognizing the plusButton
#objc func longPressed(press: UILongPressGestureRecognizer) {
if press.state == .began {
isUserInteractionEnabled = true
let positionInScene = press.location(in: self.view)
let touchedNode = self.atPoint(positionInScene)
plusButton.name = "plusButton"
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 60.0*3)
print("Pressed on the screen")
if let name = touchedNode.name {
if name == "plusButton" {
print("LONG TAPPED")
}
}
}
}
var start: CGPoint?
var startTime: TimeInterval?
var taps = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
Kite.physicsBody = SKPhysicsBody(rectangleOf: Kite.size)
Kite.physicsBody?.affectedByGravity = false
//Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
Kite.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 5))
plusButton.name = "plusButton"
plusButton.isUserInteractionEnabled = false
//We figure how to interact with BUTTON in Spritekit
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "plusButton" {
taps += 1
Kite.size = CGSize(width: 200, height: 200)
print("tapped")
print("Taps", taps)
}
}
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
start = location
startTime = touch.timestamp
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
//Calculating Speed of the Gestures
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
var dx:CGFloat = location.x - start!.x;
var dy:CGFloat = location.y - start!.y;
var magnitude:CGFloat = sqrt(dx*dx+dy*dy)
//Calculate Time
var dt:CGFloat = CGFloat(touch.timestamp - startTime!)
//Speed = Distance / Time
var speed:CGFloat = magnitude / dt
var speedX:CGFloat = dx/dt
var speedY:CGFloat = dy/dt
print("SpeedY", speedX)
print("SpeedY", speedY)
}
}
so calculating the speed from a vector like dx and dy is
let speed = sqrtf(Float(pow(dx!, 2) + pow(dy!, 2)))
So if you want apply the speed as velocity to an object you need at least one of the directions.
let newDx = 0 // means in no x direction
// next you can change the formula to your new dy to match the given speed
let newDy = sqrtf(pow(speed, 2) - Float(pow(newDx, 2)))
yourNode.physicsBody?.velocity = CGVector(dx: newDx, dy: CGFloat(newDy))

Resources