I have a custom class Object
derived from SKSpriteNode
with an SKLabelNode
member variable.
#import <SpriteKit/SpriteKit.h>
@interface Object : SKSpriteNode {
SKLabelNode* n;
}
@end
In the implementation, I set up the SKLabelNode
.
#import "Object.h"
@implementation Object
- (instancetype) initWithImageNamed:(NSString *)name {
self = [super initWithImageNamed:name];
n = [[SKLabelNode alloc] initWithFontNamed:@"Courier"];
n.text = @"Hello";
n.zPosition = -1;
//[self addChild:n];
return self;
}
Note: I have not yet added the SKLabelNode
as a child to Object
. I have the line commented out.
I have a separate class derived from SKScene
. In this class, I add an instance of Object
as a child. This is my touchesBegan:withEvent:
method in this class:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint loc = [touch locationInNode:self];
NSArray* nodes = [self nodesAtPoint:loc]; //to find all nodes at the touch location
SKNode* n = [self nodeAtPoint:loc]; //to find the top most node at the touch location
NSLog(@"TOP: %@",[n class]);
for(SKNode* node in nodes) {
NSLog(@"%@",[node class]);
}
}
When I tap on the instance of Object
in my SKScene
class, it works as I expected. It detects the node is an instance of Object
. It logs:
TOP: Object
Object
Now if I go back to my Object
class and uncomment [self addChild:n]
so that the SKLabelNode
is added as a child to Object
, this is logged:
TOP: SKLabelNode
Object
SKLabelNode
SKSpriteNode
Why does adding an SKLabelNode
as a child to a class derived from SKSpriteNode
cause the touch to detect the object itself, along with an SKLabelNode
and an SKSpriteNode
?
Furthermore, why is the SKLabelNode
on top? I have its zPosition as -1 so I would assume that in touchesBegan:withEvent:
of another class, the Object
would be detected as on top?
Surely, I am not understanding an important concept.
If you use nodeAtPoint
to check for the node in -touchesEnded:
, it will definitely return the topmost node at the point of the touch, which in this case, is the SKLabelNode
. There are a few ways you can go around this:
Making the subclass detect it's own touches
The cleanest way would be to make the Object subclass detect it's own touches. This can be done by setting it's userInteractionEnabled
property to YES
. This can be done in the init method itself.
-(instanceType) init {
if (self = [super init]) {
self.userInteractionEnabled = YES;
}
return self;
}
Now, implement the touch delegate methods in the class itself. Since the LabelNode has its userInteractionEnabled
property set by default as NO
, the touch delegates will still be triggered when it is tapped.
You can use either delegation, NSNotificationCenter or a direct message to the parent in the touch delegate.
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
//Send a message to the delegate, which is the SKScene, should be included in protocol
[self.delegate handleTouchOnObjectInstance:self];
//or, send a message directly to the SKScene, the method should be included in the interface.
MyScene *myScene = (MyScene*)self.scene;
[myScene handleTouchOnObjectInstance:self];
}
Checking for the node's parent
In the touch delegate of the SKScene, you can check whether the parent node of the SKLabelNode returned from nodeAtPoint
is an instance of the C class.
//Inside the touch delegate, after extracting touchPoint
SKNode *node = [self nodeAtPoint: touchPoint];
if ([node isKindOfClass: [SKLabelNode class]]) {
if ([node.parent isKindOfClass: [Object class]])
[self performOperationOnCInstance: (Object*)node.parent];
} else if ([node isKindOfClass: [Object class]])
[self performOperationOnCInstance: (Object*)node];
Use nodesAtPoint instead of nodeAtPoint
This method returns an array of all nodes detected at a certain point. So if the SKLabelNode is tapped, the nodesAtPoint
method will return the Object node as well.
//Inside the touch delegate, after extracting touchPoint
NSArray *nodes = [self nodesAtPoint: touchPoint];
for (SKNode *node in nodes) {
if ( [node isKindOfClass: [Object class]]) {
[self performOperationOnObjectInstance: (Object*)node];
break;
}
}
EDIT:
There is a difference between the zPosition
and the node tree.
The zPosition
only tells the scene to render nodes in a certain order, whatever their child order may be. This property was introduced to make it easier to specify the order in which a node's children are drawn. If the same were to be achieved by adding the nodes to the parent in a specific order, it becomes difficult to manage the nodes.
When the nodesAtPoint
method is called, the scene conducts a depth-first search through the node tree to see which nodes intersect the given touch point. This explains the order in which the array is populated.
The nodeAtPoint
method returns the deepest node according to zPosition which the scene could find.
The following section from Apple's documentation explains it all extensively: Creating The Node Tree