touchesEnded:withEvent:未检测到SpriteKit中的顶级节点

时间:2015-01-15 05:11:24

标签: objective-c sprite-kit uiresponder

我的自定义类Object派生自SKSpriteNode,其成员变量为SKLabelNode

#import <SpriteKit/SpriteKit.h>

@interface Object : SKSpriteNode {
    SKLabelNode* n;
}

@end

在实施中,我设置了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;
}

注意:我还没有将SKLabelNode作为孩子添加到Object。我把这条线注释掉了。

我有一个派生自SKScene的单独类。在本课程中,我将Object的实例添加为孩子。这是此类中的touchesBegan:withEvent:方法:

- (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]);
    }
}

当我点击Object课程中SKScene的实例时,它会按照我的预期运作。它检测到节点是Object的实例。它记录:

TOP: Object
Object

现在,如果我回到我的Object课程并取消注释[self addChild:n],以便将SKLabelNode作为子项添加到Object,则会记录下来:

TOP: SKLabelNode
Object
SKLabelNode
SKSpriteNode

为什么将SKLabelNode作为子项添加到源自SKSpriteNode的类中会导致触摸检测到对象本身,以及SKLabelNodeSKSpriteNode

此外,为什么SKLabelNode位于顶部?我的zPosition为-1所以我假设在另一个类的touchesBegan:withEvent:中,Object会被检测到顶部?

当然,我不理解一个重要的概念。

1 个答案:

答案 0 :(得分:2)

如果您使用nodeAtPoint检查-touchesEnded:中的节点,它肯定会返回触摸点处的最顶层节点,在本例中为SKLabelNode。有几种方法可以解决这个问题:


让子类检测到它自己的触摸

最干净的方法是让Object子类检测它自己的触摸。这可以通过将userInteractionEnabled属性设置为YES来完成。这可以在init方法本身完成。

-(instanceType) init {
    if (self = [super init]) {
        self.userInteractionEnabled = YES;
    }
    return self;
}

现在,在类本身中实现touch delegate方法。由于LabelNode默认将userInteractionEnabled属性设置为NO,因此触摸代理在点击时仍会被触发。

您可以使用委派,NSNotificationCenter或直接消息给联系代表中的父级。

-(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];
}


检查节点的父级

在SKScene的touch委托中,您可以检查从nodeAtPoint返回的SKLabelNode的父节点是否是C类的实例。

//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];


使用nodesAtPoint而不是nodeAtPoint

此方法返回在某个点检测到的所有节点的数组。因此,如果轻触SKLabelNode,nodesAtPoint方法也将返回Object节点。

//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;
    }
}

修改

zPosition和节点树之间存在差异。

zPosition仅告诉场景以特定顺序呈现节点,无论其子顺序如何。引入此属性是为了更容易指定节点子节点的绘制顺序。如果通过以特定顺序将节点添加到父节点来实现相同的目的,则管理节点变得很困难。

当调用nodesAtPoint方法时,场景通过节点树进行深度优先搜索,以查看哪些节点与给定的触摸点相交。这解释了填充数组的顺序。

nodeAtPoint方法根据场景可以找到的zPosition返回最深的节点。

Apple的文档中的以下部分对此进行了广泛的解释:Creating The Node Tree