SpriteKit touchesMoved is erratic when used in a subclass of SKSpritenode - ios

I'm subclassing SKSpritenode (In Swift) to create coloured blocks that can then be dragged around the scene. The subclass is SoundNode.
import SpriteKit
class SoundNode: SKSpriteNode {
init() {
super.init(texture: nil, color: UIColor.blue, size: CGSize(width: 100, height: 100))
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch began")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touches moved")
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
self.position = touchLocation
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch ended")
}
}
in GameScene.swift
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
addSoundBlock()
}
func addSoundBlock() {
let soundBlock = SoundNode()
soundBlock.position = CGPoint(x: 800, y: 800)
addChild(soundBlock)
}
}
This works, sort of.
soundBlock is added, and can be dragged around the scene. But it flickers and sometimes disappears.
I have tried other methods within touchesMoved, none of them effected the jerkiness.
If I don't subclass the touchesBegan, touchesMoved, touchesEnded, and implement the actions in GameScene, then the dragging becomes smooth. But future plans hinge on being able to subclass these.
Xcode 8, Swift 3, iOS10

I created a class method to handle the movement of the subclass of SKSpriteNode, in my case 'Player' and passed it the location of the touch within GameScene.swift.
Class method declaration in Player.swift:
func movePlayer(location: CGPoint) {
//movement code
}
Calling the function in GameScene.swift:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let location = touches.first?.location(in: self) else { return }
player.movePlayer(location: location)
}
Somewhat cleaner, I suppose. And allows for further subclassing.

I had this same issue, with a subclass of SKNode as the child element of a SKNode on the GameScene parent:
GameScene (top level parent)
GridNode (SKNode subclass, child of GameScene)
TileNode (SKNode subclass, child of GridNode)
The touchesMoved method called from TileNode was returning erratic values for touches.first.location when swiping in a straight line:
134.6666259765625
-42.33331298828125
133.6666259765625
-41.6666259765625
132.33331298828125
-40.6666259765625
131.6666259765625
-40.33331298828125
I believe the issue stems from TileNode's size (and therefore position) being effectively 0 from the perspective of the GameScene.
I solved it like this (in the TileNode class):
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
guard let parent = self.parent as? GridNode else { return }
let movingPoint: CGPoint = touch.location(in: parent)
...
}
Note touch.location(in: parent) vs. touch.location(in: self). GridNode has a set position in its GameScene parent, whereas TileNode does not.
Now movingPoint returns smooth values, without the jitter.
Hope this helps someone.

I have sometimes found inconsistent appearance of a sprite (i.e. sometimes visible, sometimes not) when zPosition is not set to ensure that the sprite is in front of other nodes. I think this might be because if two nodes have the same zPosition, then there isn't a way for the graphics engine to ensure which one is on top.
Try setting zPosition to some number which ensures it's in front of other nodes.
For example after:
soundBlock.position = CGPoint(x: 800, y: 800)
add:
soundBlock.zPosition = 1000
I haven't tried running this but may be worth a go?

Related

Sprite Node position not updating with touch?

Essentially, what I want is for when I touch a node, I want to be able to move it across the screen. The problem is that whenever I move my finger too fast, the node just stops following it.
The spriteNodes in particular that I'm trying to do this with have physics bodies and animating textures so I tried to do the same code with a completely plain spriteNode and I've encountered the same problem.
The code that I have here is pretty simple so I'm not sure if this is a problem with what I've written or if it's just a lag problem that I can't fix. It's also basically the same all throughout touchesBegan, touchesMoved and touchesEnded
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "activeRedBomb"{
node.position = pos
}
if node.name == "activeBlackBomb"{
node.position = pos
}
if node.name == "test"{
node.position.x = pos.x
node.position.y = pos.y
}
}
What's happening is that if you move your finger too fast, then at some point, the touch location will no longer be on the sprite, so you code to move the node won't fire.
What you need to do is set a flag in touchesBegan() to indicate that this sprite is touched, move the sprite to the location of the touch in touchesMoved() if the flag is set and then reset the flag in touchesEnded().
Here's roughly what you need to add for this:
import SpriteKit
class GameScene: SKScene {
var bombIsTouched = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if activeRedBomb.contains(touch.location(in: self)) {
bombIsTouched = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
activeRedBomb.position = (touches.first?.location(in: self))!
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if bombIsTouched {
bombIsTouched = false
}
}

How to check if a child node has been touched Swift 3

I have sprites moving across the screen, and if they are clicked then they disappear (i.e deleted).
I have overridden the touchesBegan func as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let location = touch.location(in: self)
for child in self.children {
if child.position == location {
child.removeFromParent()
}
}
}
This doesn't seem to have any effect, can someone tell me where I am going wrong?
In which class did you implement this method?
If it was in SKNode itself, you simply do:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.removeFromParent()
}
However, if this method is in SKScene, this way that was implemented would probably not work. Because child.position returns a point (x, y) where the touch was made. And you're trying to compare the touch point and position of the SKNode (center point), it's unlikely to work.
Instead of using this way, try using .nodeAtPoint, a method of SKScene.
For this you will need to put a value in the 'name' property of your SKNode:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name
{
if name == "your-node-name"
{
touchedNode.removeFromParent()
}
}
}
Font: How do I detect if an SKSpriteNode has been touched

Detecting multiple touches at once (Swift)

Im using Xcode 7, Swift, and SpriteKit and I'm attempting to allow the user to use two fingers at once in my app.
Basically I have two halves to my screen, and I want separate touch-recognition for each side, and simultaneously.
Here is my current code below :
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
guard let touch = touches.first else {
return;
}
let location = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(location)
if (touchedNode == touch1){
//code1
}
else if (touchedNode == touch2){
//code2
}
}
touch1 and touch2 are SkSpriteNodes that each take up a different half of the screen.
This code works well, as long as you only have 1 finger on the screen at a time.
However if there are two(1 for each half), which ever one was placed on the screen first is the one that is registered.
How do I make it so that both are being registered, and therefore code1 and code2 are being run?
You need multipleTouchEnabled property set to true. From the docs about this property :
When set to YES, the receiver receives all touches associated with a
multi-touch sequence. When set to NO, the receiver receives only the
first touch event in a multi-touch sequence. The default value of this
property is NO.
EDIT:
Based on your comments, you might try this (making sprites responsive to touches):
class Button:SKSpriteNode {
init(size:CGSize, color:SKColor) {
super.init(texture: nil, color: color, size: size)
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let name = self.name {
print("Button with \(name) pressed")
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let name = self.name {
print("Button with \(name) pressed")
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let name = self.name {
print("Button with \(name) released")
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
let left = Button(size: CGSize(width: frame.size.width/2.0, height: frame.size.height), color: .blackColor())
left.name = "left"
left.position = CGPoint(x: left.size.width/2.0, y: frame.midY)
let right = Button(size: CGSize(width: frame.size.width/2.0, height: frame.size.height), color: .whiteColor())
right.name = "right"
right.position = CGPoint(x:frame.maxX-right.size.width/2.0, y: frame.midY)
addChild(left)
addChild(right)
}
}

SpriteKit - How can I catch the event when the touch moves over a sprite, but didn't actually begin on the sprite

In SpriteKit, I want to catch event when touch moves over a sprite, but hasn't actually started on this sprite, but on another piece of SKScene.
I can catch touchesBegan inside the SKSpriteNode A if the touch starts on it and then dragged over it, but not when touch started on another node - B - and then dragged over my node - A. Anyone knows how to catch this one, because I think I am doing something wrong here.
try this:
sorry this is swift.. but you can easily do the same thing in obj c
import SpriteKit
class GameScene: SKScene {
let sprite = SKSpriteNode(color: SKColor.redColor(), size: CGSizeMake(100, 100))
var startedOutsideSprite = true
override init(size: CGSize) {
super.init(size: size)
sprite.position = CGPointMake(size.width/2, size.height/2)
addChild(sprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let location = touch.locationInNode(self)
if !sprite.containsPoint(location) {
startedOutsideSprite = true
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let location = touch.locationInNode(self)
if sprite.containsPoint(location) && startedOutsideSprite {
print("yayyy")
// your code here
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
startedOutsideSprite = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thanks #Christian W. but I have a simpleer solution for now, although it is not what I had on mind actually:
Just put this in Scene and catch the touchesMoved inside it, having this code inside it:
SKNode * draggedOverNode = [self nodeAtPoint:location];
[draggedOverNode touchesMoved:touches withEvent:event];
And implement the touchesMoved function inside that object that actually extends the SKNode (which most of your classes will do).

Detect touch on child node of object in SpriteKit

I have a custom class that is an SKNode, which in turn has several SKSpriteNodes in it. Is there a way I can detect touches on these child SKSpriteNodes from my game scene?
I'm working in swift
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
if([yourSprite containsPoint: touchLocation])
{
//sprite contains touch
}
}
Source: http://www.raywenderlich.com/84434/sprite-kit-swift-tutorial-beginners
Examine Apple's SceneKitVehicle demo. Someone kindly ported it to Swift.
The code you want is in the GameView.swift file. In the GameView you'll see the touchesBegan override. Here's my version of it for Swift 2.1:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let scene = self.overlaySKScene else {
return
}
let touch = touches.first!
let viewTouchLocation = touch.locationInView(self)
let sceneTouchPoint = scene .convertPointFromView(viewTouchLocation)
let touchedNode = scene.nodeAtPoint(sceneTouchPoint)
if (touchedNode.name == "Play") {
print("play")
}
}
If it's not clear; the GameView is set as the app's view class by way of the Storyboard.
You'll need to compare the location of your Touch to the location of your SKNode
You can get the location of your touch at one of the following methods, using locationInNode():
touchesBegan()
touchesMoved()
touchesEnded()
Swift 5+
If you wish to encapsulate touch logic in a node and deal with it locally, you can simply set interaction to true in the corresponding node.
isUserInteractionEnabled = true
And then, of course, override touchesBegan to your desire.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
If you still wish to receive touches in the scene when touches occur inside a child node, you can, for instance, define a protocol and property for the child node's delegate and set the scene to be it.
e.g:
final class GameScene: SKScene {
private let childNode = ChildNode()
override func didMove(to view: SKView) {
addChild(childNode)
childNode.delegate = self
}
}
extension GameScene: TouchDelegate {}
protocol TouchDelegate {
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
}
final class ChildNode: SKSpriteNode {
var delegate: TouchDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
delegate?.touchesBegan(touches, with: event)
}
}

Resources