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.
Related
I am attempting to change from a home scene to a game scene in Spritekit. When I run it in the simulator, there is no transition to the GameScene. I also added a print command to see if the touches on the play button were even being registered, and they were not. Here's the code. Thanks!
import SpriteKit
class HomeScene: SKScene {
var playButton: SKSpriteNode?
var gameScene: SKScene!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "startButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == playButton {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
print("touch")
}
}
}
}
The touches parameter inside of your touchesBegan(_: with:) method is of type Set<UITouch>, i.e. a set of touches. As stated in the Apple Documentation for Sets, a set is used when the order of the items in the collection are not a concern. Therefore, you cannot assume that the first item in touches is the first touch in the set. Knowing this, I recommend refactoring your code inside of HomeScene.swift to the following:
import SpriteKit
class HomeScene: SKScene {
private var playButton: SKSpriteNode?
override func sceneDidLoad() {
self.playButton = self.childNode(withName: "//starButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}
func touchDown(atPoint pos: CGPoint) {
let nodes = self.nodes(at: pos)
if let button = playButton {
if nodes.contains(button) {
let gameScene = SKScene(fileNamed: "GameScene")
self.view?.presentScene(gameScene)
}
}
}
The touchDown(atPoint:) method does all the heavy lifting and touchesBegan(_: with:) is freed up to listen for touch events. Another thing to note when calling childNode(withName:):
Adding / before the name of the file starts searching for nodes beginning at the root of your file hierarchy.
Adding // before the name of the file starts searching for nodes beginning at the root of your file hierarchy, then recursively down the hierarchy.
Adding * before the name of the file acts as a character wildcard and allows you to search for file names that are wholly or partially unknown.
I think you should use the node's name instead of the node itself for comparison in the touchesBegan... try something like this:
import SpriteKit
class HomeScene: SKScene {
var playButton: SKSpriteNode?
var gameScene: SKScene!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "startButton") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "startButton" {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
print("touch")
}
}
}
}
Hope it helps! :)
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
}
}
So I've been trying to detect a long press of one of the Apple TV remote arrow buttons (triggered when touching the edges of the touch pad) and it seems like it doesn't work for the arrows, only for the physical buttons you click down. Basically:
// This works
let longSelectRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(_:)))
longSelectRecognizer.allowedPressTypes = [UIPressType.Select.rawValue)]
self.addGestureRecognizer(longSelectRecognizer)
// This doesn't
let longArrowRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(_:)))
longArrowRecognizer.allowedPressTypes = [NSNumber(integer: UIPressType.LeftArrow.rawValue), NSNumber(integer: UIPressType.RightArrow.rawValue)]
self.addGestureRecognizer(longArrowRecognizer)
I tried replacing it with a UITouchGestureRecognizer, but it doesn't detect when holding down the arrow (as expected)
I believe this is because down/up/right/left-arrows are virtual presses. They are re-broadcasted from touchesBegan/Ended.
The only way you can check for long presses on down/up/right/left is calculating touch.timestamp in touchesBegan/Ended.
var touchStart: TimeInterval?
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touches.forEach({ touch in
switch touch.type{
case .indirect:
self.touchStart = touch.timestamp
break
default:
break
}
})
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touches.forEach({ touch in
switch touch.type{
case .indirect:
if
let start = self.touchStart,
(touch.timestamp - start < 0.5){
super.touchesEnded(touches, with: event)
}else {
self.performSegue(withIdentifier: SegueEnum.showOverlaySegue.rawValue, sender: nil)
}
break
default:
break
}
})
}
I have this function and I want to remove it once the game is over. I tried using removeActionWithKey but that doesn't seem to work. Is there another way I could remove it once the game is over?
func doAction() {
let generateCircles = SKAction.sequence([
SKAction.runBlock(self.circleRandom),
SKAction.waitForDuration(1.5)])
let endlessAction = SKAction.repeatActionForever(generateCircles)
runAction(endlessAction)
}
if firstBody.categoryBitMask == HeroCategory && secondBody.categoryBitMask == RedCategory {
//Tried to remove the func here but that doesn't work.
removeActionForKey("stop")
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
touchingScreen = false
if let touch = touches.first {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "start" {
startGame.removeFromParent()
//Calling the doAction func here once start button is pressed.
let action = SKAction.runBlock(doAction)
runAction(action, withKey: "stop")
}
}
}
replace runAction(endlessAction) with runAction(endlessAction, withKey: "endlessAction1")
then call
removeActionForKey("stop")
removeActionForKey("endlessAction1")
where you want it to stop.