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

Posted

技术标签:

【中文标题】touchesEnded:withEvent: 未检测到 SpriteKit 中的顶部节点【英文标题】:touchesEnded:withEvent: not detecting the top node in SpriteKit 【发布时间】:2015-01-15 05:11:24 【问题描述】:

我有一个自定义类 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]);
    

当我在SKScene 类中点击Object 的实例时,它会按预期工作。它检测到节点是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】:

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

让子类检测自己的接触

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

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

现在,在类本身中实现触摸委托方法。由于 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 delegate中,可以查看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

【讨论】:

我提供了更多信息并缩小了问题范围。 我已经添加了进一步的解释。您已经接受了答案,您选择了哪种方法? 是的,您的回答最终解决了我最初的问题。我检查了检测到的节点的父节点是否是 Object 类的实例(在我编辑问题之前是 C 类)。

以上是关于touchesEnded:withEvent: 未检测到 SpriteKit 中的顶部节点的主要内容,如果未能解决你的问题,请参考以下文章

如何从 - (void)touchesEnded:withEvent: 获取 UITouch 位置的 UIView 对象?

touchesEnded:withEvent: 没有从我的 UIView 调用,它位于 UITableView 的顶部

将触摸结束事件发送到 UIControl 子类

UIResponder学习 手势

讲解iOS开发中拖动视图的实现

UIResponder 类和第一响应者角色