Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sprite with userInteractionEnabled set to YES does not receive touches when covered by normal sprites

I put a sprite A (subclassed to receive hits (userInteractionEnabled to YES)), and then a normal sprite B that does not take hits on top of that (userInteractionEnabled default NO), completely covering sprite A.

Tapping on sprite B, I assume that sprite A would get the touch but nothing happens. The part from docs about the matter is below.

I feel something is unclear here because it seems still that sprite B receives the touch but throws it away. OR, spriteA is removed from possible touch receiver because it's not visible.

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 hit-testing, its userInteractionEnabled property must be set to YES. The default value is NO for any node except a scene node. A node that wants to receive events needs to implement the appropriate responder methods from its parent class (UIResponder on iOS and NSResponder on OS X). This is one of the few places where you must implement platform-specific code in Sprite Kit

Anyway to fix this? As long as something's userInteractionEnabled is NO, it shouldn't interfere with other touch receivers.

Update: Even setting sprite B's alpha to 0.2, making sprite A very visible, will not make sprite A touchable. Sprite B just totally "swallows" the touch, despite being not enabled for interaction.

like image 517
Jonny Avatar asked Oct 22 '13 07:10

Jonny


People also ask

How to load sprites from a script without a reference?

Now that the Sprites are addressable it’s possible to load them from a script, without a reference. Here’s how to do it: First, add the following two namespaces to the top of the script.

Can I change a sprite Renderer’s existing sprite to a different one?

The example will easily change a Sprite Renderer’s existing sprite to use a different one instead. But what if you want to change to one of a number of different Sprites?

How to add sprites to a sprite sheet?

Next place the Sprites into the Resources folder. Lastly, from a script, load the Sprite with Resources.Load<Type> (“filename”) passing in a String for the filename (without its extension). Instead of manually adding the Sprites to the array, we can now, instead, load all of the Sprites associated with a Sprite Sheet by using Load All. Like this:

How to change the sprite of a game object?

Alternatively, if the script is on the same Game Object, you can easily get it by using Get Component in Start. Then, when you want to change the Sprite, just call the Change Sprite function.


3 Answers

Solution with fewer lines of code. Remember to set userInteractionEnabled in every node you want to receive touch.

- (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);
}
like image 63
Rodrigo Pinto Avatar answered Oct 12 '22 02:10

Rodrigo Pinto


Here is my solution until Apple updates SpriteKit with correct behaviour, or someone acctually figures out how to use it like we want.

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

Add the file to your project and import it in your Prefix header. Now touches should work the way intended.

like image 39
bobmoff Avatar answered Oct 12 '22 00:10

bobmoff


My solution doesn't require isKindOfClass sort of things...

In your SKScene interface:

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

In your SKScene implementation

- (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... call it what you wish :)

@protocol CSTouchableNode <NSObject>

- (BOOL) wantsTouchEvents;
- (void) setWantsTouchEvents:(BOOL)wantsTouchEvents;

@end

For your SKNodes that you want to be touchable, they need to conform to the CSTouchableNode protocol. You will need to add something like below to your classes as required.

@property (nonatomic, assign) BOOL wantsTouchEvents;

Doing this, tapping on nodes works as I would expect it to work. And it shouldn't break when Apple fixes the "userInteractionEnabled" bug. And yes, it is a bug. A dumb bug. Silly Apple.

Updated

There is another bug in SpriteKit... the z order of nodes is weird. I have seen strange orderings of nodes... thus I tend to force a zPosition for nodes/scenes that need it.

I've updated my SKScene implementation to sorting the nodes based on zPosition.

nodes = [nodes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"zPosition" ascending:false]]];
for( SKNode* node in nodes )
{
    ...
}
like image 40
AutomatonTec Avatar answered Oct 12 '22 00:10

AutomatonTec