【问题标题】:Sprite with userInteractionEnabled set to YES does not receive touches when covered by normal sprites将 userInteractionEnabled 设置为 YES 的精灵在被普通精灵覆盖时不会接收到触摸
【发布时间】:2013-10-22 07:10:24
【问题描述】:

我放置了一个精灵 A(子类化以接收命中(userInteractionEnabled 为 YES)),然后放置一个不接受命中的普通精灵 B(userInteractionEnabled 默认为 NO),完全覆盖精灵 A。

点击精灵 B,我假设精灵 A 会得到触摸,但没有任何反应。文档中关于此事的部分如下。

我觉得这里有些不清楚,因为精灵 B 似乎仍然接收到触摸但将其丢弃。 或者,spriteA 已从可能的触摸接收器中移除,因为它不可见。

来自文档: https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Nodes/Nodes.html#//apple_ref/doc/uid/TP40013043-CH3-SW7

对于在命中测试期间要考虑的节点,其 userInteractionEnabled 属性必须设置为 YES。默认值 对于除场景节点之外的任何节点都为 NO。想要接收的节点 事件需要从其实现适当的响应者方法 父类(iOS 上的 UIResponder 和 OS X 上的 NSResponder)。这是一 您必须在其中实现特定于平台的代码的少数几个地方 雪碧套件

无论如何要解决这个问题? 只要某些东西的 userInteractionEnabled 为 NO,它就不会干扰其他触摸接收器。

更新:即使将精灵 B 的 alpha 设置为 0.2,使精灵 A 非常可见,也不会使精灵 A 可触摸。尽管没有启用交互,Sprite B 只是完全“吞下”了触摸。

【问题讨论】:

  • 我也有同样的问题。如果覆盖精灵隐藏 = YES,问题甚至存在。它必须是一个错误。如果我在场景中只创建一个可触摸的 SKSpriteNode,然后将其隐藏 = YES,同样的事情,它仍然会接收触摸。我正在考虑向 Apple 提交错误报告,但我的最后一个甚至没有得到任何回应,所以我对此并不感到兴奋。
  • 我绝对认为你应该提交错误报告。在我的日子里,我只提交了几个。它们中的大多数都作为重复文件关闭,但为了记录,他们修复了其中一个(iPad 的方向问题,应该从 iOS5 开始修复;^))。当然,对错误报告的响应可能需要几个月甚至更长时间,但仍然值得,因为这不会自行改变。
  • 确实如此。我会归档的。谢谢你的灵感!与使用 Cocos2D 相比,SpriteKit 中的错误令人讨厌的是,在新的 SpriteKit 版本中修复某些内容对用户提出了新的要求,即拥有最新版本的操作系统……而不是仅仅将其包含在新版本的应用程序。
  • 是的。使用 cocos2d,您可以随时编辑源代码以满足您的需求。开源的力量。 Sprite Kit 无法做到这一点。你所能做的就是提交错误报告,或者做一些丑陋的变通方法。这是我认为 cocos2d 在这一点上应该强调的地方:开放性和快速的 bug 修复、平台兼容性(超越苹果)。因为可以肯定,从现在开始,Sprite Kit 会很强大。
  • iOS 8 中仍未修复

标签: objective-c sprite-kit


【解决方案1】:

这是我的解决方案,直到 Apple 以正确的行为更新 SpriteKit,或者有人真正想出如何按照我们的意愿使用它。

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

将文件添加到您的项目中并将其导入您的前缀标题中。现在触摸应该按预期方式工作。

【讨论】:

  • 酷,以后一定会去看看。我正在移植一个 cocos2d 游戏,这次我做了一个丑陋的解决方法,将新的临时按钮(如果你愿意,可以称之为 sprite-button Cs)在 sprite B 上看起来像 sprite B。我想我们可以同意SpriteKit 在这里是不正确的,如果我没记错的话,UIView 中的不可交互视图不会阻止下方按钮下方,因此那里也存在不一致。无论如何,这应该进入错误报告......
  • 我查看了您的解决方案,但不太确定如何使用它。你能提供几句值得的方法吗?谢谢:)
  • 该解决方案同时扩展了 SKScene 和 SKNode,因此您无需进行任何其他操作即可使其正常工作。只需确保将文件导入到场景头文件的顶部,或者如果您想在所有场景中使用它#import 到名为 ProjectName-Prefix.pch 的前缀头文件中并包含在“支持文件”文件夹。
  • 我正好遇到了这个问题。 sklabelnode 覆盖了一个按钮(尽管文本并没有真正覆盖按钮,我仍然不确定 sklabelnode 的框架是如何确定的)。将 TouchPriority.m 和 TouchPriority.h 添加到项目中就足够了。您不需要将它们添加到您的 pch 文件中。谢谢bobmoff!
  • 我想我会详细说明它是如何工作的。由于重叠精灵阻止目标精灵接收触摸(但不接受触摸本身),触摸被转发到接受触摸的最低父节点。如果那是SKScene,则此答案中描述的类别方法会再次向下遍历节点树,并根据自己的选择过程找到合适的节点,这就是您想要的。不错的解决方案,帮助了我(在 iOS 9.0 public beta 1 中测试)。
【解决方案2】:

我的解决方案不需要 isKindOfClass 之类的东西...

在您的 SKScene 界面中:

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

在您的 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;
}

您将需要定义 CSTouchableNode 协议...随心所欲地调用它:)

@protocol CSTouchableNode <NSObject>

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

@end

对于您希望可触摸的 SKNode,它们需要符合 CSTouchableNode 协议。您需要根据需要在您的课程中添加如下内容。

@property (nonatomic, assign) BOOL wantsTouchEvents;

这样做,点击节点可以正常工作。当 Apple 修复“userInteractionEnabled”错误时,它不应该中断。是的,这是一个错误。一个愚蠢的错误。傻苹果。

更新

SpriteKit 中还有另一个错误...节点的 z 顺序很奇怪。我见过奇怪的节点顺序......因此我倾向于为需要它的节点/场景强制使用 zPosition。

我已经更新了我的 SKScene 实现以根据 zPosition 对节点进行排序。

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

【讨论】:

    【解决方案3】:

    这是一个获取触摸等的示例。

    场景

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

    子类精灵/英雄

    - (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
    

    尝试了很多东西,但触摸没有通过重叠的精灵。我想您可以使用场景类通过循环遍历节点来检测触摸,然后直接调用该节点。

    当粒子发射器覆盖按钮时我遇到了这个问题......

    【讨论】:

    • 此解决方案完美运行。重叠节点在 zOrder 中的位置上得到了触摸。较高的位置首先获得,然后是所有下面的位置。从这里开始,只需一步过滤您希望哪个对象接收触摸并拒绝其余的触摸事件。
    【解决方案4】:

    我认为这可能是一种设计选择,但肯定不是很灵活。我更喜欢使用 nodeAtPoint: 结合 isKindOfClass: 方法在场景级别处理 Sprite Kit 中可以重叠的对象(如游戏对象)。对于像按钮这样通常不重叠并放置在不同层中的对象(如覆盖层和按钮),我在其类中处理它们的用户交互。 我希望我的触摸事件在 Sparrow 框架中“冒泡”节点树,但我担心这更像是一个功能请求而不是错误报告。

    P.S.:通过在场景级别处理触摸,您可以轻松地触摸和移动节点,即使它们完全被不可移动和不可触摸的节点覆盖!

    【讨论】:

    • 我不认为这是一个设计选择,或者文档是完全不正确的“对于在命中测试期间要考虑的节点,它的 userInteractionEnabled 属性必须设置为 YES。默认值为 NO对于除场景节点之外的任何节点。“您是否也以这种方式处理按钮,包括突出显示?
    • 呃,我想你是对的,我没有准确地阅读它。我基本上在两个主要层上组织我的场景,游戏层(首先添加)和叠加层(在顶部)。鉴于按钮不会相互重叠,我在按钮类中处理它们的交互。我只是为 iOS 开发,然后我从未使用过突出显示,但我想我会这样做。
    • 我在柏林的 iOS 技术讲座上与 Apple 团队进行了交谈并解释了这个问题,他们说这可能是一个错误,因为我不是唯一一个报告这个问题的人,而且这种行为也没有按照文档说明的方式工作。
    【解决方案5】:

    代码行数更少的解决方案。请记住在您希望接收触摸的每个节点中设置 userInteractionEnabled。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多