在Sprite Kit中创建按钮的正确方法是什么?

15
我在制作游戏,想要添加一个按钮。如何处理它被点击的事件?
我使用了单独的UI类,其中包含所有按钮和界面元素的SKSpriteNode,我不想让Scene在touches began方法中为我处理这些按钮按下事件。
据我所知,在touches began中我们可以检查正在被触碰的节点,因此要实现触摸弹起式的普通按钮,我需要在touchesBegan和touchesEnded中编写代码,这看起来有点过度设计。
或者我应该使用常规的UIButton?但我知道你不能动态添加它们,只能在didMoveToView方法中添加,这看起来很糟糕。

你必须使用touchesBegan/Ended,否则你怎么获取触摸输入呢?一种方法是:https://github.com/KoboldKit/KoboldKit/blob/master/KoboldKit/KoboldKitFree/Framework/Behaviors/UserInterface/KKButtonBehavior.m - CodeSmile
我的切换按钮示例 https://dev59.com/wXjZa4cB1Zd3GeqPfpOb#19694729 - DogCoffee
在 didMoveToView 中添加 UIButton 对你来说有什么不好的感觉吗?如果它比场景加载稍后出现,请尝试设置 "pausesIncomingScene" 或用于显示 SKScene 的 SKTransition。我个人喜欢使用具有 touches began 方法的 SKSpriteNode... - BSevo
2个回答

11

我一段时间前在网上发现了这个内容,由一个名叫Graf的成员提供,这对我的问题很有效。

#import <SpriteKit/SpriteKit.h>
@interface SKBButtonNode : SKSpriteNode

@property (nonatomic, readonly) SEL actionTouchUpInside;
@property (nonatomic, readonly) SEL actionTouchDown;
@property (nonatomic, readonly) SEL actionTouchUp;
@property (nonatomic, readonly, weak) id targetTouchUpInside;
@property (nonatomic, readonly, weak) id targetTouchDown;
@property (nonatomic, readonly, weak) id targetTouchUp;

@property (nonatomic) BOOL isEnabled;
@property (nonatomic) BOOL isSelected;
@property (nonatomic, readonly, strong) SKLabelNode *title;
@property (nonatomic, readwrite, strong) SKTexture *normalTexture;
@property (nonatomic, readwrite, strong) SKTexture *selectedTexture;
@property (nonatomic, readwrite, strong) SKTexture *disabledTexture;

- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected;
- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected disabled:(SKTexture *)disabled; // Designated Initializer

- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected;
- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected disabled:(NSString *)disabled;

/** Sets the target-action pair, that is called when the Button is tapped.
 "target" won't be retained.
 */
- (void)setTouchUpInsideTarget:(id)target action:(SEL)action;
- (void)setTouchDownTarget:(id)target action:(SEL)action;
- (void)setTouchUpTarget:(id)target action:(SEL)action;

@end

实现方法如下:

//
//
//  Courtesy of Graf on Stack Overflow
//
//
//
#import "SKBButtonNode.h"



@implementation SKBButtonNode

#pragma mark Texture Initializer

/**
 * Override the super-classes designated initializer, to get a properly set SKButton in every case
 */
- (instancetype)initWithTexture:(SKTexture *)texture color:(UIColor *)color size:(CGSize)size {
    return [self initWithTextureNormal:texture selected:nil disabled:nil];
}

- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected {
    return [self initWithTextureNormal:normal selected:selected disabled:nil];
}

/**
 * This is the designated Initializer
 */
- (instancetype)initWithTextureNormal:(SKTexture *)normal selected:(SKTexture *)selected disabled:(SKTexture *)disabled {
    self = [super initWithTexture:normal color:[UIColor whiteColor] size:normal.size];
    if (self) {
        [self setNormalTexture:normal];
        [self setSelectedTexture:selected];
        [self setDisabledTexture:disabled];
        [self setIsEnabled:YES];
        [self setIsSelected:NO];

        _title = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
        [_title setVerticalAlignmentMode:SKLabelVerticalAlignmentModeCenter];
        [_title setHorizontalAlignmentMode:SKLabelHorizontalAlignmentModeCenter];

        [self addChild:_title];
        [self setUserInteractionEnabled:YES];
    }
    return self;
}

#pragma mark Image Initializer

- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected {
    return [self initWithImageNamedNormal:normal selected:selected disabled:nil];
}

- (instancetype)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected disabled:(NSString *)disabled {
    SKTexture *textureNormal = nil;
    if (normal) {
        textureNormal = [SKTexture textureWithImageNamed:normal];
    }

    SKTexture *textureSelected = nil;
    if (selected) {
        textureSelected = [SKTexture textureWithImageNamed:selected];
    }

    SKTexture *textureDisabled = nil;
    if (disabled) {
        textureDisabled = [SKTexture textureWithImageNamed:disabled];
    }

    return [self initWithTextureNormal:textureNormal selected:textureSelected disabled:textureDisabled];
}




#pragma -
#pragma mark Setting Target-Action pairs

- (void)setTouchUpInsideTarget:(id)target action:(SEL)action {
    _targetTouchUpInside = target;
    _actionTouchUpInside = action;
}

- (void)setTouchDownTarget:(id)target action:(SEL)action {
    _targetTouchDown = target;
    _actionTouchDown = action;
}

- (void)setTouchUpTarget:(id)target action:(SEL)action {
    _targetTouchUp = target;
    _actionTouchUp = action;
}

#pragma -
#pragma mark Setter overrides

- (void)setIsEnabled:(BOOL)isEnabled {
    _isEnabled = isEnabled;
    if ([self disabledTexture]) {
        if (!_isEnabled) {
            [self setTexture:_disabledTexture];
        } else {
            [self setTexture:_normalTexture];
        }
    }
}

- (void)setIsSelected:(BOOL)isSelected {
    _isSelected = isSelected;
    if ([self selectedTexture] && [self isEnabled]) {
        if (_isSelected) {
            [self setTexture:_selectedTexture];
        } else {
            [self setTexture:_normalTexture];
        }
    }
}

#pragma -
#pragma mark Touch Handling

/**
 * This method only occurs, if the touch was inside this node. Furthermore if
 * the Button is enabled, the texture should change to "selectedTexture".
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([self isEnabled]) {
        if (_actionTouchDown){
            [self.parent performSelectorOnMainThread:_actionTouchDown withObject:_targetTouchDown waitUntilDone:YES];
            [self setIsSelected:YES];
        }
    }
}

/**
 * If the Button is enabled: This method looks, where the touch was moved to.
 * If the touch moves outside of the button, the isSelected property is restored
 * to NO and the texture changes to "normalTexture".
 */
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([self isEnabled]) {
        UITouch *touch = [touches anyObject];
        CGPoint touchPoint = [touch locationInNode:self.parent];

        if (CGRectContainsPoint(self.frame, touchPoint)) {
            [self setIsSelected:YES];
        } else {
            [self setIsSelected:NO];
        }
    }
}

/**
 * If the Button is enabled AND the touch ended in the buttons frame, the
 * selector of the target is run.
 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInNode:self.parent];

    if ([self isEnabled] && CGRectContainsPoint(self.frame, touchPoint)) {
        if (_actionTouchUpInside){
            [self.parent performSelectorOnMainThread:_actionTouchUpInside withObject:_targetTouchUpInside waitUntilDone:YES];
        }
    }
    [self setIsSelected:NO];
        if (_actionTouchUp){
        [self.parent performSelectorOnMainThread:_actionTouchUp withObject:_targetTouchUp waitUntilDone:YES];
    }
}
@end

1
希望能为每次使用 objc_msgSend 给你一个负一分。 - Sulthan
已经替换了所有的 objc_msgSend() - Andrew97p
1
@AndrewShmig 我并不是真的需要这样做,我这么做是因为我将我的按钮添加到场景中,所以当我使用sel.parent时,我告诉场景执行选择器,这让我在开发应用程序时更容易看到哪个场景调用了哪些方法。这并不重要,因为你说的是在给定目标上执行选择器,所以如果你想在使用时更改它,请务必使用self。 - Andrew97p
修改后的代码做出了错误的假设,可能会导致崩溃。例如,如果您将按钮添加到另一个SKNode中,而该SKNode是您的SKScene,则Self.parent可能不是目标。如果([_targetTouchUpInside respondsToSelector:_actionTouchUpInside]){如果(_actionTouchUpInside){ [_targetTouchUpInside performSelectorOnMainThread:_actionTouchUpInside withObject:_targetTouchUpInside waitUntilDone:YES]; } } - Kenrik March
1
@Graf 对不起,你是正确的。在我提供这个答案的代码版本中,我只改变了返回类型并替换了objc_msgSend()调用。当我说我已经改变它来接受纹理时,我犯了一个错误。在我没有分享的另一个代码版本中,我也让它接受UIImages,并且在发布时匆忙,可能在我所说的话上犯了一个错误。我很抱歉,会适当地编辑我的答案。 - Andrew97p
显示剩余4条评论

8

我之前创建了一个使用SKSpriteNode作为按钮的类。你可以在GitHub上找到它。

AGSpriteButton

它的实现基于UIButton,所以如果你已经熟悉iOS,你应该会觉得很容易使用。

它还包括一个设置标签的方法。

一个按钮通常会这样声明:

AGSpriteButton *button = [AGSpriteButton buttonWithColor:[UIColor redColor] andSize:CGSizeMake(300, 100)];
[button setLabelWithText:@"Button Text" andFont:nil withColor:nil];
button.position = CGPointMake(self.size.width / 2, self.size.height / 3);
[button addTarget:self selector:@selector(someSelector) withObject:nil forControlEvent:AGButtonControlEventTouchUpInside];
[self addChild:button];

就是这样,你可以开始使用了。

编辑:自发布此答案以来,对AGSpriteButton进行了一些增强。

现在,您可以将块分配给触摸事件执行:

[button performBlock:^{
        [self addSpaceshipAtPoint:[NSValue valueWithCGPoint:CGPointMake(100, 100)]];
    } onEvent:AGButtonControlEventTouchUp];

此外,可以为 SKNode 的实例(或其任何子类)分配要执行的 SKAction 对象:
[button addTarget:self 
        selector:@selector(addSpaceshipAtPoint:) 
        withObject:[NSValue valueWithCGPoint:CGPointMake(self.size.width / 2, self.size.height / 2)]         
        forControlEvent:AGButtonControlEventTouchUpInside];

AGSpriteButton同样可以与Swift一起使用!

let button = AGSpriteButton(color: UIColor.greenColor(), andSize: CGSize(width: 200, height: 60))
button.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
button.addTarget(self, selector: "addSpaceship", withObject: nil, forControlEvent:AGButtonControlEvent.TouchUpInside)
button.setLabelWithText("Spaceship", andFont: nil, withColor: UIColor.blackColor())
addChild(button)

2
你应该在代码中使用nil而不是Nil。Nil用于空类。 - Dvole

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接