Search code examples
iossprite-kithittestzposition

In SpriteKit, how is zPosition used for rendering and for hit tests?


Edit: Apple fixed this in iOS8, so it was certainly not expected/intended but a bug.

It appears that the zPosition is interpreted differently when used for rendering a node vs when used for determining which node is touched.

When I create a hierarchy of parent and childnodes (all with zPosition set), the topmost node for rendering is determined by the summation of all zPositions leading to the node, so nodes from different parents can be interleaved.
When determining the topmost node for receiving touches however, zPosition is only used to distinguish between siblings of the same parent, when nodes have a different parent, their parents z-order takes precedence. (all childs of one parent are all underneath or above the nodes of a different parent)

To illustrate, some sample code with results:

#import "MyScene.h"

@interface NodeWithHitTest : SKSpriteNode
-(instancetype)initWithColor:(UIColor *)color size:(CGSize)size;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
@end


@implementation NodeWithHitTest
-(instancetype)initWithColor:(UIColor *)color size:(CGSize)size {
    self = [super initWithColor:color size:size];
    self.userInteractionEnabled = YES;
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@ hit", self.name);
}
@end

@implementation MyScene

-(id)initWithSize:(CGSize)size {    
    if (self = [super initWithSize:size]) {
        SKNode* container1 = [SKNode new];
        SKNode* container2 = [SKNode new];
        NodeWithHitTest* sprite1 = [NodeWithHitTest spriteNodeWithColor:[UIColor yellowColor] size:CGSizeMake(200, 200)];
        sprite1.name = @"yellow";
        sprite1.position = CGPointMake(self.size.width/2, self.size.height/2);
        NodeWithHitTest* sprite2 = [NodeWithHitTest spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(150, 150)];
        sprite2.name = @"blue";
        sprite2.position = sprite1.position;
        NodeWithHitTest* sprite3 = [NodeWithHitTest spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(100, 100)];
        sprite3.name = @"red";
        sprite3.position = sprite1.position;

        [container1 addChild:sprite1];
        [container1 addChild:sprite3];
        [container2 addChild:sprite2];

        [self addChild:container1];
        [self addChild:container2];
//        sprite1.zPosition = 1;
//        sprite2.zPosition = 2;
//        sprite3.zPosition = 3;
    }
    return self;
}

@end

When run with zPosition lines commented out, you get the following result:
without zPosition

As expected, first container1 is rendered (with a yellow and red square), and on top container2 is rendered (with blue square), so the red square gets hidden. When clicking inside the yellow 'border', the output is yellow hit, clicking inside the blue sqaure prints blue hit, all as expected.

When the zPosition lines are inserted however, you get the following:
with zPosition

As you can see, the blue square from container2 is interleaved with the two squares of container1, when you click inside the red square however, you get blue hit!
When determining which node is topmost for touches, everything from container2 is still above container1.

Is this expected behaviour? And if so, is there a common way to deal with this without losing one's sanity?


Solution

  • I do not know if anything changed since your post or if you just did something wrong, but in my case it worked.

    I just copy paste your code in a fresh project and tested it.

    I get red hit if the red square is at top and i get blue square if blue square is at top.

    enter image description here

    enter image description here