I want to calculate the delta between a swipe gesture or dragging gesture. what I want to do is to get this delta and then to use it as velocity (by adding time var). I really get confused about how should I do this - via touchesMoved or via UIPanGestureRecognizer. also, I don't really understand the difference between them. right now I set and get the first touch on the screen but I don't know how to get the last one so I can calculate the vector. can anyone help me with that?
right now I'm doing that via touchesBegan and touchesEnded, I'm not sure if its the right and the better way, here is my code so far:
class GameScene: SKScene {
var start: CGPoint?
var end: CGPoint?
override func didMove(to view: SKView) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.start = touch.location(in: self)
print("start point: ", start!)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.end = touch.location(in: self)
print("end point: ", end!)
let deltax:CGFloat = ((self.start?.x)! - (self.end?.x)!)
let deltay:CGFloat = ((self.start?.y)! - (self.end?.y)!)
print(UInt(deltax))
print(UInt(deltay))
}
You can detect swipe gestures using the built-in touch handlers of SpriteKit or you can implement a UISwipeGestureRecognizer. The following is an example of how to detect swipe gestures using SpriteKit's touch handlers:
First, define the variables and constants...
Define the starting point and time of the initial touch.
var touchStart: CGPoint?
var startTime : TimeInterval?
Define constants that specify the characteristics of the swipe gesture. By changing these constants, you can detect the difference between a swipe, drag, or flick.
let minSpeed:CGFloat = 1000
let maxSpeed:CGFloat = 5000
let minDistance:CGFloat = 25
let minDuration:TimeInterval = 0.1
In touchesBegan, store the starting point and time of the initial touch
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touchStart = touches.first?.location(in: self)
startTime = touches.first?.timestamp
}
In touchesEnded, calculate the gesture's distance, duration, and speed. Compare these value against the constants to determine if the gesture was a swipe.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchStart = self.touchStart else {
return
}
guard let startTime = self.startTime else {
return
}
guard let location = touches.first?.location(in: self) else {
return
}
guard let time = touches.first?.timestamp else {
return
}
var dx = location.x - touchStart.x
var dy = location.y - touchStart.y
// Distance of the gesture
let distance = sqrt(dx*dx+dy*dy)
if distance >= minDistance {
// Duration of the gesture
let deltaTime = time - startTime
if deltaTime > minDuration {
// Speed of the gesture
let speed = distance / CGFloat(deltaTime)
if speed >= minSpeed && speed <= maxSpeed {
// Normalize by distance to obtain unit vector
dx /= distance
dy /= distance
// Swipe detected
print("Swipe detected with speed = \(speed) and direction (\(dx), \(dy)")
}
}
}
// Reset variables
touchStart = nil
startTime = nil
}
Related
I am trying to add a different object to a scene with a hit test, and I am currently only able to add one object. I want to access the second, third and fourth indices in my hit test results in order to to add these objects with a touch.
I have tried accessing through .indices but it is not compatible with UITouch.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchLocation = touch.location(in: sceneView)
let results = sceneView.hitTest(touchLocation, types: .featurePoint)
if let hitResult = results.first {
let cubeScene = SCNScene(named: "art.scnassets/cube.scn")!
if let cubeNode = cubeScene.rootNode.childNode(withName: "cube", recursively: true) {
sceneView.scene.rootNode.addChildNode(cubeNode)
}
}
}
}
How should I go about this? I have thought about creating one SCNScene, pass it as an array, and then would load all the models from the scene.
As Samual suggested... example
var hitTestOptions = [SCNHitTestOption: Any]()
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
let location: CGPoint = recognizer.location(in: gameScene)
let hitResults: [SCNHitTestResult] = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
gameControl.selectPanel(vPanel: vHit.node.name!)
return
}
}
}
my problem is that touchesBegan() is not called as I know it from other projects. Therefore there must be a difference somewhere to my other projects. Now I got the idea, that the following code prevents calling touchesBegan():
In didMove()
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
The function:
#objc func tappedView(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Single tap")
print(point)
}
touchesBegan()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let locationUser = touch.location(in: self)
if self.atPoint(locationUser) == background {
print("background touch")
}
}
}
I come to the idea that the above function tappedView() is to blame for not executing touchesBegan(), since this function is called when the screen is touched instead of touchesBegan().
Is there a way to use both touchesBegan() and tappedView() without them blocking each other?
Thank you in advance.
In the code below if I press the removeButton the button would disappear but it's still working. Maybe I should make the if statement false but I don't know how.
class GameScene : SKScene {
var button : SKSpriteNode!
var removeButton : SKSpriteNode!
var image : SKSpriteNode!
override func didMove(to view: SKView) {
createButton()
createRemoveButton()
}
func createButton() {
button = SKSpriteNode(imageNamed: "button")
button.position = CGPoint(x: -300,y: 0)
self.addChild(button)
}
func createRemoveButton() {
removeButton = SKSpriteNode(imageNamed: "removeButton")
removeButton.position = CGPoint(x: 300,y: 0)
self.addChild(removeButton)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if button.contains(touchLocation) {
image = SKSpriteNode(imageNamed: "image")
image.position = CGPoint(x: 0,y: 300)
self.addChild(image)
}
if removeButton.contains(touchLocation) {
button.removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
}
}
You need to understand ARC (Automatic Reference Counting) and what it takes to retain an instance.
In your case you are retaining button, which means it does not delete from memory.
Now when you remove the button from the parent, the button's frame is still exactly the same, the only thing different is button.parent is now nil
When you call button.contains(touchLocation), this is going to pass because you are not concerned with whether or not button is on the scene, all you are doing is checking if the touchLocation is within the frame of the button.
The quickest fix is to check the parent:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if button.parent == self && button.contains(touchLocation) {
image = SKSpriteNode(imageNamed: "image")
image.position = CGPoint(x: 0,y: 300)
self.addChild(image)
}
if removeButton.contains(touchLocation) {
button.removeFromParent()
}
}
In reality though, you need to learn how to better manage your resources. I would recommend trying to find tutorials about how ARC works.
It is normal because you dont use the good button. You use button instead of removeButton.
You should do:
if removeButton.contains(touchLocation) {
removeButton.removeFromParent()
}
Now it should work correctly
I'm using SKTileMaps and a CameraNode as well as GameplayKit and SpriteKit
The code I want to work is incorrectly finding a location in the "view"
func handleTapFrom(_ sender: UITapGestureRecognizer)
{
let location = sender.location(in: self.view)
if (map.contains(location))
{
let tRow = map.tileRowIndex(fromPosition: location)
let tColumn = map.tileColumnIndex(fromPosition: location)
let movePosition = map.centerOfTile(atColumn: tColumn, row: tRow)
let moveAction = SKAction.move(to: movePosition, duration:1.0)
cam.run(moveAction)
}
}
and the code that is working correctly is finding locations in self (rather than self.view) and this is correct
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let firstTouch = touches.first?.location(in: self)
if (map.contains(firstTouch!))
{
let tRow = map.tileRowIndex(fromPosition: firstTouch!)
let tColumn = map.tileColumnIndex(fromPosition: firstTouch!)
let movePosition = map.centerOfTile(atColumn: tColumn, row: tRow)
let moveAction = SKAction.move(to: movePosition, duration:1.0)
cam.run(moveAction)
}
}
The columns and rows are correct based on the location, the problem I am having is that the locations (xfloat, yfloat) are different between the two functions even when the touch is at the same location
These two bits of code are all in the class GameScene, but I cannot figure out why they have different locations? And how to fix the tap gesture so that the locations it finds are the same as those found in touchesBegan.
Sorry if this is confusing or unintelligible. I welcome an opportunity to clarify.
In your handleTapFrom function the location should come from convertPoint.
func handleTapFrom(_ sender: UITapGestureRecognizer) {
if sender.state != .ended {
return
}
let location = sender.location(in: sender.view!)
let targetLocation = self.convertPoint(fromView: location)
if map.contains(targetLocation) {
// Your code here
}
}
I'm trying to make a button that's an SKLabelNode. When it gets pressed, it's supposed to change scenes, but there is something wrong with the line where location is declared.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let location = touches.locationInNode(self)
let touchedNode = self.nodeAtPoint(location)
if touchedNode.name == "startGameButton" {
let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1.0)
let scene = GameScene(size: self.scene.size)
scene.scaleMode = SKSceneScaleMode.AspectFill
self.scene.view.presentScene(scene, transition: transition)
}
}
The error is here on the line.
let location = touches.locationInNode(self)
It reads
'Set< NSObject>' does not have a member named 'locationInNode'
I'm not sure how to fix it. I've looked at a lot of working button templates, but mine always has an error.
The problem is exactly what the error states - Set<NSObject> doesn't have a method named locationInNode. What you need to do is retrieve an object out of the Set; check it's a UITouch object; if it is, you can use it to get a touch location. Try:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let location = (touches.first as? UITouch)?.locationInNode(self) {
// ...
}
}
Or
if let touch = touches.first as? UITouch {
let location = touch.locationInNode(self)
// ...
}
To fix it, this is the default fix, it just enumerates through all the touches.
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
node = self.nodeAtPoint(location)
//do something
}