Objective c touchesEnded:withEvent:未检测到SpriteKit中的顶部节点

Objective c touchesEnded:withEvent:未检测到SpriteKit中的顶部节点,objective-c,sprite-kit,uiresponder,Objective C,Sprite Kit,Uiresponder,我有一个自定义类对象,它派生自SKSpriteNode,带有SKLabelNode成员变量 #import <SpriteKit/SpriteKit.h> @interface Object : SKSpriteNode { SKLabelNode* n; } @end 注意:我还没有将SKLabelNode作为子对象添加到对象中。我已经把这句话注释掉了 我有一个从SKScene派生的独立类。在这个类中,我将对象的一个实例作为子对象添加。这是我在这个类中的touchsb

我有一个自定义类
对象
,它派生自
SKSpriteNode
,带有
SKLabelNode
成员变量

#import <SpriteKit/SpriteKit.h>

@interface Object : SKSpriteNode {
    SKLabelNode* n;
}

@end
注意:我还没有将
SKLabelNode
作为子对象添加到
对象中。我已经把这句话注释掉了

我有一个从
SKScene
派生的独立类。在这个类中,我将
对象的一个实例作为子对象添加。这是我在这个类中的
touchsbegind: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]);
    }
}
当我在我的
SKScene
类中点击
Object
的实例时,它会像我预期的那样工作。它检测到节点是
对象的实例。它记录:

TOP: Object
Object
现在,如果我返回到我的
对象
类并取消注释
[self addChild:n]
,以便
SKLabelNode
作为子对象添加到
对象
,这将被记录:

TOP: SKLabelNode
Object
SKLabelNode
SKSpriteNode
为什么将
SKLabelNode
作为子节点添加到从
SKSpriteNode
派生的类中会导致触摸检测对象本身,以及
SKLabelNode
SKSpriteNode

此外,为什么
SKLabelNode
位于顶部?我将其zPosition设置为-1,因此我假设在另一个类的
touchesbeated:withEvent:
中,会检测到
对象在顶部


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

如果您使用
nodeAtPoint
检查
-touchesend:
中的节点,它肯定会返回触摸点的最顶端节点,在这种情况下,它就是
SKLabelNode
。有几种方法可以解决这个问题:


让子类检测自己的触碰

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

-(instanceType) init {
    if (self = [super init]) {
        self.userInteractionEnabled = YES;
    }
    return self;
}
现在,在类本身中实现touch委托方法。由于LabelNode的
userInteractionEnabled
属性默认设置为
NO
,轻触时仍会触发触摸代理

您可以使用delegation、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];

使用nodesapoint而不是nodeapoint

此方法返回在某一点检测到的所有节点的数组。因此,如果点击了SKLabelNode,则
NodeStatPoint
方法也将返回对象节点

//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
仅告诉场景以特定顺序渲染节点,不管它们的子顺序是什么。引入此属性可以更轻松地指定节点子节点的绘制顺序。如果要通过按特定顺序将节点添加到父节点来实现同样的效果,那么管理节点就变得很困难

调用
NodeStatPoint
方法时,场景通过节点树执行深度优先搜索,以查看哪些节点与给定的接触点相交。这说明了数组的填充顺序

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


苹果文档中的以下部分对此进行了详细解释:

我提供了更多信息并缩小了问题范围。我添加了进一步的解释。你已经接受了答案,你选择了哪种方法?是的,你的答案最终解决了我最初的问题。我检查了检测到的节点的父节点是否是类对象的实例(在编辑问题之前是类C)。
//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;
    }
}