Sprite with userInteractionEnabled set to YES does not get strokes when covered with normal sprites

I put sprite A (subclassed for getting hits (userInteractionEnabled to YES)) and then a regular sprite B that doesn't take hits on top of it (userNteractionEnabled default NO), completely covering sprite A.

By clicking on sprite B, I assume that sprite A will get a touch, but nothing will happen. Part of the documentation on this subject is below.

I feel that something is unclear here because it seems that sprite B gets touched but discards it. OR, spriteA is removed from a possible touch receiver because it is not displayed.

From the docs: https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Nodes/Nodes.html#//apple_ref/doc/uid/TP40013043-CH3-SW7

For a node to be considered during impact testing, its userInteractionEnabled property must be set to YES. The default value is NO for any node except the node scene. A node that wants to receive events needs to implement appropriate response methods from its parent class (UIResponder on iOS and NSResponder on OS X). This is one of several places where you must implement platform-specific code in the Sprite Kit.

Anyway, to fix it? As long as something userInteractionEnabled is NO, it should not interfere with other touch receivers.

Update: even setting the sprite B alpha to 0.2, which makes the sprite very noticeable, will not cope with Touch. Sprite B simply swallows the touch completely, even though it is not turned on for interaction.

+8
objective-c sprite-kit
source share
5 answers

Here is my solution until Apple updates SpriteKit with the correct behavior, or someone does not know how to use it the way we want.

https://gist.github.com/bobmoff/7110052

Add the file to the project and import it into the prefix header. Now the touch should work as it should.

+10
source share

My solution does not require the use of isKindOfClass things ...

In your SKScene interface:

@property (nonatomic, strong) SKNode* touchTargetNode; // this will be the target node for touchesCancelled, touchesMoved, touchesEnded 

In the implementation of SKScene

 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch* touch = [touches anyObject]; NSArray* nodes = [self nodesAtPoint:[touch locationInNode:self]]; self.touchTargetNode = nil; for( SKNode* node in [nodes reverseObjectEnumerator] ) { if( [node conformsToProtocol:@protocol(CSTouchableNode)] ) { SKNode<CSTouchableNode>* touchable = (SKNode<CSTouchableNode>*)node; if( touchable.wantsTouchEvents ) { self.touchTargetNode = node; [node touchesBegan:touches withEvent:event]; break; } } } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self.touchTargetNode touchesCancelled:touches withEvent:event]; self.touchTargetNode = nil; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self.touchTargetNode touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self.touchTargetNode touchesEnded:touches withEvent:event]; self.touchTargetNode = nil; } 

You will need to define the CSTouchableNode protocol ... name it whatever you like :)

 @protocol CSTouchableNode <NSObject> - (BOOL) wantsTouchEvents; - (void) setWantsTouchEvents:(BOOL)wantsTouchEvents; @end 

For your SKNodes that you want to be tangible, they must comply with the CSTouchableNode protocol. You will need to add something like below to your classes as needed.

 @property (nonatomic, assign) BOOL wantsTouchEvents; 

Performing this, click on the nodes, as I expect it to work. And it should not break when Apple fixes the "userInteractionEnabled" error. And yes, this is a mistake. Dumb mistake. Stupid Apple.

Update

There is another mistake in SpriteKit ... the order of the z nodes is strange. I saw strange node ordering ... so I try to force zPosition for nodes / scenes that need it.

I updated the SKScene implementation for sorting nodes based on zPosition.

 nodes = [nodes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"zPosition" ascending:false]]]; for( SKNode* node in nodes ) { ... } 
+2
source share

Here is an example of getting strokes, etc.

Scene

 -(id)initWithSize:(CGSize)size { if (self = [super initWithSize:size]) { /* Setup your scene here */ HeroSprite *newHero = [HeroSprite new]; newHero.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)); [self addChild:newHero]; SKSpriteNode *overLapSprite = [SKSpriteNode spriteNodeWithColor:[UIColor orangeColor] size:CGSizeMake(70, 70)]; overLapSprite.position = CGPointMake(CGRectGetMidX(self.frame) + 30, CGRectGetMidY(self.frame)); overLapSprite.name = @"overlap"; [self addChild:overLapSprite]; } return self; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInNode:self]; SKNode *node = [self nodeAtPoint:location]; if ([node.name isEqualToString:@"heroNode"]) { NSLog(@"Scene detect hit on hero"); } if ([node.name isEqualToString:@"overlap"]) { NSLog(@"overlap detect hit"); } // go through all the nodes at the point NSArray *allNodes = [self nodesAtPoint:location]; for (SKNode *aNode in allNodes) { NSLog(@"Loop; node name = %@", aNode.name); } } 

Subclass / Hero

 - (id) init { if (self = [super init]) { [self setUpHeroDetails]; self.userInteractionEnabled = YES; } return self; } -(void) setUpHeroDetails { self.name = @"heroNode"; SKSpriteNode *heroImage = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"]; [self addChild:heroImage]; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint locationA = [touch locationInNode:self]; CGPoint locationB = [touch locationInNode:self.parent]; NSLog(@"Hero got Hit at, %@ %@", NSStringFromCGPoint(locationA), NSStringFromCGPoint(locationB)); } @end 

I tried a lot of things, but the touch does not go through the overlapping sprite. I think you can use a scene class to detect touching through a loop through nodes, and then directly call that node.

I had this problem when the particle emitter closed the button ...

+1
source share

I think it might be a design choice, but certainly not very flexible. I prefer to handle objects that may overlap (such as game objects) in the Sprite Kit at the scene level with nodeAtPoint: in combination with the isKindOfClass: methods. For objects, such as buttons, which usually do not overlap and are placed at another level (for example, overlays and buttons), I process their interaction with the user inside my classes. I would like my touch events to bubble up in the node tree, as happens in the Sparrow Framework, but I'm afraid this is more a function request than an error report.

PS: By processing strokes at the scene level, you can easily touch and move nodes, even if they are completely covered by non-moving and non-touching nodes!

+1
source share

A solution with fewer lines of code. Remember to set userInteractionEnabled in every node you want to get.

 - (void)handleTap:(UITapGestureRecognizer *)gestureRecognizer { CGPoint touchLocation = self.positionInScene; SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation]; NSArray *nodes = [self nodesAtPoint:touchLocation]; for (SKSpriteNode *node in [nodes reverseObjectEnumerator]) { NSLog(@"Node in touch array: %@. Touch enabled: %hhd", node.name, node.isUserInteractionEnabled); if (node.isUserInteractionEnabled == 1) touchedNode = node; } NSLog(@"Newly selected node: %@", touchedNode); } 
0
source share

All Articles