在 SKScene 中设置按钮

Posted

技术标签:

【中文标题】在 SKScene 中设置按钮【英文标题】:Setting up buttons in SKScene 【发布时间】:2013-10-05 14:33:58 【问题描述】:

我发现UIButtons 不能很好地与SKScene 配合使用,所以我试图继承SKNode 以在SpriteKit 中创建一个按钮。

我希望它的工作方式是,如果我在SKScene 中初始化一个按钮并启用触摸事件,那么该按钮将在按下时调用我的SKScene 中的一个方法。

如果有任何建议可以引导我找到解决此问题的方法,我将不胜感激。谢谢。

【问题讨论】:

我正在寻找更多的学习经验和解决方案。我认为正确的解决方案是将 SKScene 设置为按钮的代表,但我不确定如何执行此操作。我可以将 SKScene 设置为按钮的实例变量并调用它的方法吗? 您可以做很多事情,委托或更灵活地使用 NSNotification 以便任何节点都可以响应它。如果您使用委托,请确保将委托属性设置为弱。 我发现this code 有助于创建精灵套件按钮。它扩展了 SKSpriteKitNode 并允许您轻松地将文本添加到按钮。 【参考方案1】:

您可以使用 SKSpriteNode 作为您的按钮,然后当用户触摸时,检查该节点是否被触摸。使用 SKSpriteNode 的 name 属性来识别节点:

//fire button
- (SKSpriteNode *)fireButtonNode

    SKSpriteNode *fireNode = [SKSpriteNode spriteNodeWithImageNamed:@"fireButton.png"];
    fireNode.position = CGPointMake(fireButtonX,fireButtonY);
    fireNode.name = @"fireButtonNode";//how the node is identified later
    fireNode.zPosition = 1.0;
    return fireNode;

将节点添加到您的场景中:

[self addChild: [self fireButtonNode]];

手柄触摸:

//handle touch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];
    SKNode *node = [self nodeAtPoint:location];

    //if fire button touched, bring the rain
    if ([node.name isEqualToString:@"fireButtonNode"]) 
         //do whatever...
    

【讨论】:

如果您添加一个 iVar 作为按钮,您可以删除名称检查并只使用 if ([_fireNode containsPoint:location]) 做同样的事情只是不同。 比较字符串是一个肮脏的解决方案。尽管@Smick 的解决方案更好,但没有其他更清洁的方法可以实现这一点吗? 嘿,我们不能像 SkLabelNode 那样在 SpriteKit 中添加按钮吗? 这是否允许多点触控事件?例如同时单击 2 个按钮?其中一个是移动按钮,另一个是开火按钮。【参考方案2】:

我已经制作了自己的 Button-Class 并正在使用它。 SKButton.h:

#import <SpriteKit/SpriteKit.h>
@interface SKButton : 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;

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

- (id)initWithImageNamedNormal:(NSString *)normal selected:(NSString *)selected;
- (id)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

SKButton.m:

#import "SKButton.h"
#import <objc/message.h>


@implementation SKButton

#pragma mark Texture Initializer

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


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


/**
 * This is the designated Initializer
 */
- (id)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

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


- (id)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]) 
        objc_msgSend(_targetTouchDown, _actionTouchDown);
        [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)) 
        objc_msgSend(_targetTouchUpInside, _actionTouchUpInside);
    
    [self setIsSelected:NO];
    objc_msgSend(_targetTouchUp, _actionTouchUp);

一个例子:要初始化一个按钮,你写下面几行:

    SKButton *backButton = [[SKButton alloc] initWithImageNamedNormal:@"buttonNormal" selected:@"buttonSelected"];
    [backButton setPosition:CGPointMake(100, 100)];
    [backButton.title setText:@"Button"];
    [backButton.title setFontName:@"Chalkduster"];
    [backButton.title setFontSize:20.0];
    [backButton setTouchUpInsideTarget:self action:@selector(buttonAction)];
    [self addChild:backButton];

此外,您还需要类中的“buttonAction”方法。 * 不保证此类在任何情况下都能正常工作。我对objective-c还是很陌生。 *

如果您认为必须这样做很烦人且毫无意义,您可以通过将“启用objc_msgSend Calls' 的严格检查”设置为“No”来禁用构建设置中的检查

【讨论】:

感谢分享。您使用objc_msgSend 而不是[target performSelector:selector] 有什么原因吗? 啊,是的,该死的 ARC。我忘记了那个警告:|如果您有兴趣,这里有一个不错的解决方法***.com/questions/11895287/… 上面的代码很棒,但是在尝试使用 - (void)changeToScene:(SKButtonNode *)sender 作为@selector 时出现错误。如果可以的话,我宁愿使用单一方法通过 sender.name 切换场景。 感谢分享!我将它包含在我的代码中。我们会看看它是否有效。一个建议:将类的名称从 SKButton 更改为对您而言更独特的名称,例如 GRFButton。在某些时候,Apple 可能会引入一个 SKButton,而您不想混淆命名空间并在以后破坏您的代码。 @BeauYoung - 当您在末尾添加 self 时,它可以工作,如下所示:objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)【参考方案3】:

献给使用 Swift 编写游戏的人们! 我已经将 Graf 解决方案的基本部分重写为 swift 类。希望对您有所帮助:

import Foundation
import SpriteKit

class FTButtonNode: SKSpriteNode 

    enum FTButtonActionType: Int 
        case TouchUpInside = 1,
        TouchDown, TouchUp
    

    var isEnabled: Bool = true 
    didSet 
        if (disabledTexture != nil) 
            texture = isEnabled ? defaultTexture : disabledTexture
        
    
    
    var isSelected: Bool = false 
    didSet 
        texture = isSelected ? selectedTexture : defaultTexture
    
    
    var defaultTexture: SKTexture
    var selectedTexture: SKTexture

    required init(coder: NSCoder) 
        fatalError("NSCoding not supported")
    

    init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) 

        self.defaultTexture = defaultTexture
        self.selectedTexture = selectedTexture
        self.disabledTexture = disabledTexture

        super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size())

        userInteractionEnabled = true

        // Adding this node as an empty layer. Without it the touch functions are not being called
        // The reason for this is unknown when this was implemented...?
        let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size())
        bugFixLayerNode.position = self.position
        addChild(bugFixLayerNode)

    

    /**
    * Taking a target object and adding an action that is triggered by a button event.
    */
    func setButtonAction(target: AnyObject, triggerEvent event:FTButtonActionType, action:Selector) 

        switch (event) 
        case .TouchUpInside:
            targetTouchUpInside = target
            actionTouchUpInside = action
        case .TouchDown:
            targetTouchDown = target
            actionTouchDown = action
        case .TouchUp:
            targetTouchUp = target
            actionTouchUp = action
        

    

    var disabledTexture: SKTexture?
    var actionTouchUpInside: Selector?
    var actionTouchUp: Selector?
    var actionTouchDown: Selector?
    weak var targetTouchUpInside: AnyObject?
    weak var targetTouchUp: AnyObject?
    weak var targetTouchDown: AnyObject?

    override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!)  
        let touch: AnyObject! = touches.anyObject()
        let touchLocation = touch.locationInNode(parent)

        if (!isEnabled) 
            return
        
        isSelected = true
        if (targetTouchDown != nil && targetTouchDown!.respondsToSelector(actionTouchDown!)) 
            UIApplication.sharedApplication().sendAction(actionTouchDown!, to: targetTouchDown, from: self, forEvent: nil)
        


    

    override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!)  

        if (!isEnabled) 
            return
        

        let touch: AnyObject! = touches.anyObject()
        let touchLocation = touch.locationInNode(parent)

        if (CGRectContainsPoint(frame, touchLocation)) 
            isSelected = true
         else 
            isSelected = false
        

    

    override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) 

        if (!isEnabled) 
            return
        

        isSelected = false

        if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) 
            let touch: AnyObject! = touches.anyObject()
            let touchLocation = touch.locationInNode(parent)

            if (CGRectContainsPoint(frame, touchLocation) ) 
                UIApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, forEvent: nil)
            

        

        if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) 
            UIApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self, forEvent: nil)
        
    


【讨论】:

【参考方案4】:

如果您愿意,您可以使用 UIButton(或任何其他 UIView)。

创建SKScene 时,SKView 中尚不存在它。你应该在你的SKScene 子类上实现didMoveToView:。此时,您可以访问场景所在的SKView,并且可以向其中添加UIKit 对象。为了漂亮,我把它们淡化了……

- (void)didMoveToView:(SKView *)view 
  UIView *b = [self _createButton];  // <-- performs [self.view addSubview:button]
  // create other UI elements, also add them to the list to remove …
  self.customSubviews = @[b];

  b.alpha = 0;

  [UIView animateWithDuration:0.4
                        delay:2.4
                      options:UIViewAnimationOptionCurveEaseIn
                   animations:^
                     b.alpha = 1;
                    completion:^(BOOL finished) 
                     ;
                   ];

当你过渡离开时,你需要故意将它们从场景中移除,当然,除非它们留在那里完全有意义。

- (void)removeCustomSubviews 
  for (UIView *v in self.customSubviews) 
    [UIView animateWithDuration:0.2
                          delay:0
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:^
                       v.alpha = 0;
                    completion:^(BOOL finished) 
                       [v removeFromSuperview];
                 ];
  


对于那些不熟悉以编程方式创建 UIButton 的人,这里有一个示例(您可以在这里做 100 件不同的事情)……

- (UIButton *)_createButton 
  UIButton *b = [UIButton buttonWithType:UIButtonTypeCustom];
  [b setTitle:@"Continue" forState:UIControlStateNormal];
  [b setBackgroundImage:[UIImage imageNamed:@"GreenButton"] forState:UIControlStateNormal];
  [b setBackgroundImage:[UIImage imageNamed:@"GreenButtonSelected"] forState:UIControlStateHighlighted];
  b.titleLabel.adjustsFontSizeToFitWidth = YES;
  b.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:36];
  b.frame = CGRectMake(self.size.width * .7, self.size.height * .2, self.size.width * .2, self.size.height * .1);
  [b addTarget:self action:@selector(continuePlay) forControlEvents:UIControlEventTouchUpInside];
  [self.view addSubview:b];

  return b;

提醒:UIView 原点在左上角,SKScene 原点在左下角。

【讨论】:

【参考方案5】:

我用过Graf的SKButton类。

我使用 SKButton 进行场景导航。即当用户按下 SKButton 时呈现另一个场景。我在touchesEnded-&gt;[self setIsSelected:NO] 收到EXC_BAD_ACCESS 错误。这种情况在具有快速 CPU 的最新 iPad 上尤为常见。

经过检查和故障排除后,我意识到在调用setIsSelected 函数时,SKButton 对象已经“释放”了。这是因为我使用 SKButton 导航到下一个场景,这也意味着可以随时释放当前场景。

我做了一个小改动,将 setIsSelected 放在“else”部分,如下所示。

希望这对也遇到相同错误的其他开发人员有所帮助。

(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInNode:self.parent];

    if ([self isEnabled] && CGRectContainsPoint(self.frame, touchPoint)) 
        objc_msgSend(_targetTouchUpInside, _actionTouchUpInside);
     else 
       [self setIsSelected:NO];
    
    objc_msgSend(_targetTouchUp, _actionTouchUp);

【讨论】:

请格式化您的帖子和源代码,这样很难阅读!【参考方案6】:

这是另一个基于 Filip 的 Swift 代码的版本。我只是稍微简化了它,并允许它使用块,而不仅仅是选择器:

import Foundation
import SpriteKit

enum FTButtonTarget 
    case aSelector(Selector, AnyObject)
    case aBlock(() -> Void)


class FTButtonNode: SKSpriteNode 

    var actionTouchUp : FTButtonTarget?
    var actionTouchUpInside : FTButtonTarget?
    var actionTouchDown : FTButtonTarget?

    var isEnabled: Bool = true 
        didSet 
            if (disabledTexture != nil) 
                texture = isEnabled ? defaultTexture : disabledTexture
            
        
    
    var isSelected: Bool = false 
        didSet 
            texture = isSelected ? selectedTexture : defaultTexture
        
    

    var defaultTexture: SKTexture
    var selectedTexture: SKTexture

    required init(coder: NSCoder) 
        fatalError("NSCoding not supported")
    

init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) 

    self.defaultTexture = defaultTexture
    self.selectedTexture = selectedTexture
    self.disabledTexture = disabledTexture

    super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size())

    userInteractionEnabled = true

    // Adding this node as an empty layer. Without it the touch functions are not being called
    // The reason for this is unknown when this was implemented...?
    let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size())
    bugFixLayerNode.position = self.position
    addChild(bugFixLayerNode)



var disabledTexture: SKTexture?

func callTarget(buttonTarget:FTButtonTarget) 

    switch buttonTarget 
    case let .aSelector(selector, target):
        if target.respondsToSelector(selector) 
            UIApplication.sharedApplication().sendAction(selector, to: target, from: self, forEvent: nil)
        
    case let .aBlock(block):
        block()
    



override func touchesBegan(touches: NSSet, withEvent event: UIEvent)  
    let touch: AnyObject! = touches.anyObject()
    let touchLocation = touch.locationInNode(parent)

    if (!isEnabled) 
        return
    
    isSelected = true

    if let act = actionTouchDown 
        callTarget(act)
    



override func touchesMoved(touches: NSSet, withEvent event: UIEvent)  

    if (!isEnabled) 
        return
    

    let touch: AnyObject! = touches.anyObject()
    let touchLocation = touch.locationInNode(parent)

    if (CGRectContainsPoint(frame, touchLocation)) 
        isSelected = true
     else 
        isSelected = false
    



 override func touchesEnded(touches: NSSet, withEvent event: UIEvent) 

     if (!isEnabled) 
         return
     

     isSelected = false

     let touch: AnyObject! = touches.anyObject()
     let touchLocation = touch.locationInNode(parent)

     if (CGRectContainsPoint(frame, touchLocation) ) 

         if let act = actionTouchUpInside 
             callTarget(act)
         
     

     if let act = actionTouchUp 
         callTarget(act)
     
 

像这样使用它:

       aFTButton.actionTouchUpInside = FTButtonTarget.aBlock( () -> Void in
        println("button touched")
    )

希望这会有所帮助。

【讨论】:

【参考方案7】:

编辑:我已经为我的 SKButtonNode 创建了一个 github 存储库,希望能保持最新状态并随着 swift 的发展而更新!

SKButtonNode


很遗憾,我还不能评论 Filip 在 Swift 中快速实现 SKButton。非常高兴他在 Swift 中做到了这一点!但是,我注意到他没有包含向按钮添加文本的功能。这对我来说是一个巨大的功能,因此您不必为每个按钮创建单独的资源,而只需为背景和添加动态文本。

我添加了一个简单的函数来将文本标签添加到 SKButton。它可能并不完美——我和其他人一样是 Swift 新手!随时发表评论并帮助我将其更新到最好的状态。希望大家喜欢!

 //Define label with the textures
 var defaultTexture: SKTexture
 var selectedTexture: SKTexture

 //New defining of label
 var label: SKLabelNode

 //Updated init() function:

 init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) 

    self.defaultTexture = defaultTexture
    self.selectedTexture = selectedTexture
    self.disabledTexture = disabledTexture

    //New initialization of label
    self.label = SKLabelNode(fontNamed: "Helvetica");

    super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size())
    userInteractionEnabled = true

    //Creating and adding a blank label, centered on the button
    self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center;
    self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center;
    addChild(self.label)

    // Adding this node as an empty layer. Without it the touch functions are not being called
    // The reason for this is unknown when this was implemented...?
    let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size())
    bugFixLayerNode.position = self.position
    addChild(bugFixLayerNode)

  




    /*
      New function for setting text. Calling function multiple times does 
      not create a ton of new labels, just updates existing label.
      You can set the title, font type and font size with this function
    */

    func setButtonLabel(#title: NSString, font: String, fontSize: CGFloat) 
        var title = title
        var font = font
        var fontSize = fontSize

        self.label.text = title
        self.label.fontSize = fontSize
        self.label.fontName = font        
      

按钮创建示例:

    var buttonTexture = SKTexture(imageNamed: "Button");
    var buttonPressedTexture = SKTexture(imageNamed: "Button Pressed");
    var button = SKButton(normalTexture:buttonTexture, selectedTexture:buttonPressedTexture, disabledTexture:buttonPressedTexture);
    button.setButtonLabel(title: "Play",font: "Helvetica",fontSize: 40);
    button.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
    self.addChild(button);

下面列出的完整课程:

import Foundation
import SpriteKit


class SKButton: SKSpriteNode 




enum FTButtonActionType: Int 
    case TouchUpInside = 1,
    TouchDown, TouchUp


var isEnabled: Bool = true 
    didSet 
        if (disabledTexture != nil) 
            texture = isEnabled ? defaultTexture : disabledTexture
        
    

var isSelected: Bool = false 
    didSet 
        texture = isSelected ? selectedTexture : defaultTexture
    

var defaultTexture: SKTexture
var selectedTexture: SKTexture
var label: SKLabelNode


required init(coder: NSCoder) 
    fatalError("NSCoding not supported")


init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) 

    self.defaultTexture = defaultTexture
    self.selectedTexture = selectedTexture
    self.disabledTexture = disabledTexture
    self.label = SKLabelNode(fontNamed: "Helvetica");
    super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size())
    userInteractionEnabled = true


    self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center;
    self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center;
    addChild(self.label)

    // Adding this node as an empty layer. Without it the touch functions are not being called
    // The reason for this is unknown when this was implemented...?
    let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size())
    bugFixLayerNode.position = self.position
    addChild(bugFixLayerNode)



/**
* Taking a target object and adding an action that is triggered by a button event.
*/
func setButtonAction(target: AnyObject, triggerEvent event:FTButtonActionType, action:Selector) 

    switch (event) 
    case .TouchUpInside:
        targetTouchUpInside = target
        actionTouchUpInside = action
    case .TouchDown:
        targetTouchDown = target
        actionTouchDown = action
    case .TouchUp:
        targetTouchUp = target
        actionTouchUp = action
    




func setButtonLabel(#title: NSString, font: String, fontSize: CGFloat) 
    var title = title;
    var font = font;
    var fontSize = fontSize;

    self.label.text = title;
    self.label.fontSize = fontSize;
    self.label.fontName = font;



var disabledTexture: SKTexture?
var actionTouchUpInside: Selector?
var actionTouchUp: Selector?
var actionTouchDown: Selector?
weak var targetTouchUpInside: AnyObject?
weak var targetTouchUp: AnyObject?
weak var targetTouchDown: AnyObject?

override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!)  
    let touch: AnyObject! = touches.anyObject()
    let touchLocation = touch.locationInNode(parent)

    if (!isEnabled) 
        return
    
    isSelected = true
    if (targetTouchDown != nil && targetTouchDown!.respondsToSelector(actionTouchDown!)) 
        UIApplication.sharedApplication().sendAction(actionTouchDown!, to: targetTouchDown, from: self, forEvent: nil)
    




override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!)  

    if (!isEnabled) 
        return
    

    let touch: AnyObject! = touches.anyObject()
    let touchLocation = touch.locationInNode(parent)

    if (CGRectContainsPoint(frame, touchLocation)) 
        isSelected = true
     else 
        isSelected = false
    



override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) 

    if (!isEnabled) 
        return
    

    isSelected = false

    if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) 
        let touch: AnyObject! = touches.anyObject()
        let touchLocation = touch.locationInNode(parent)

        if (CGRectContainsPoint(frame, touchLocation) ) 
            UIApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, forEvent: nil)
        

    

    if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) 
        UIApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self, forEvent: nil)
    

【讨论】:

在此处更新了 swift 2.1 的代码:gist.github.com/richy486/5d408c442ac1c0c2891f ..我在这里更新到 Swift 3:github.com/jglasse/SKButtonSwift3【参考方案8】:

这个问题有很多很棒的解决方案!对于能够做到这一点的铁杆卷轴,你会得到款待!我已将SKScene 子类化,并且需要一个函数调用来注册任何节点以像UIButton 一样工作!这是课程:

class KCScene : SKScene 
//------------------------------------------------------------------------------------
//This function is the only thing you use in this class!!!
func addButton(_ node:SKNode, withCompletionHandler handler: @escaping ()->()) 
    let data = ButtonData(button: node, actionToPerform: handler)
    eligibleButtons.append(data)

//------------------------------------------------------------------------------------
private struct ButtonData 
    //TODO: make a dictionary with ()->() as the value and SKNode as the key.
    //Then refactor this class!
    let button:SKNode
    let actionToPerform:()->()


private struct TouchTrackingData 
    //this will be in a dictionary with a UITouch object as the key
    let button:SKNode
    let originalButtonFrame:CGRect


private var eligibleButtons = [ButtonData]()
private var trackedTouches = [UITouch:TouchTrackingData]()
//------------------------------------------------------------------------------------
//TODO: make these functions customizable,
//with these implementations as defaults.
private func applyTouchedDownEffectToNode(node:SKNode) 
    node.alpha  = 0.5
    node.xScale = 0.8
    node.yScale = 0.8

private func applyTouchedUpEffectToNode(node:SKNode)   
    node.alpha  = 1
    node.xScale = 1
    node.yScale = 1

//------------------------------------------------------------------------------------
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
    for touch in touches 
        let touchLocation = touch.location(in: self)
        let touchedNode = atPoint(touchLocation)

        for buttonData in eligibleButtons 
            if touchedNode === buttonData.button 
                //then this touch needs to be tracked, as it touched down on an eligible button!
                for (t, bD) in trackedTouches 
                    if bD.button === buttonData.button 
                        //then this button was already being tracked by a previous touch, disable the previous touch
                        trackedTouches[t] = nil
                    
                
                //start tracking this touch
                trackedTouches[touch] = TouchTrackingData(button: touchedNode, originalButtonFrame: touchedNode.frameInScene)
                applyTouchedDownEffectToNode(node: buttonData.button)
            
        
    

//------------------------------------------------------------------------------------
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) 
    for touch in touches 
        if trackedTouches[touch] == nil continue
        //Now we know this touch is being tracked...
        let touchLocation = touch.location(in: self)
        //TODO: implement an isBeingTouched property on TouchTrackingData, so 
        //applyTouchedDown(Up)Effect doesn't have to be called EVERY move the touch makes
        if trackedTouches[touch]!.originalButtonFrame.contains(touchLocation) 
            //if this tracked touch is touching its button
            applyTouchedDownEffectToNode(node: trackedTouches[touch]!.button)
         else 
            applyTouchedUpEffectToNode(node: trackedTouches[touch]!.button)
        

    

//------------------------------------------------------------------------------------
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
    for touch in touches 
        if trackedTouches[touch] == nil continue
        //Now we know this touch is being tracked...
        let touchLocation = touch.location(in: self)

        if trackedTouches[touch]!.originalButtonFrame.contains(touchLocation) 
            applyTouchedUpEffectToNode(node: trackedTouches[touch]!.button)

            for buttonData in eligibleButtons 
                if buttonData.button === trackedTouches[touch]!.button 
                    buttonData.actionToPerform()
                
            
        
        trackedTouches[touch] = nil
    

//------------------------------------------------------------------------------------
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) 
    for touch in touches! 
        if trackedTouches[touch] == nil continue
        //Now we know this touch is being tracked...
        //Since this touch was cancelled, it will not be activating a button,
        //and it is not worth checking where the touch was
        //we will simply apply the touched up effect regardless and remove the touch from being tracked
        applyTouchedUpEffectToNode(node: trackedTouches[touch]!.button)
        trackedTouches[touch] = nil
    

//------------------------------------------------------------------------------------

它包含了很多我还没有实现的想法和对代码的一些解释,但只需将其复制并粘贴到您的项目中,您就可以在自己的场景中使用它。下面是一个完整的用法示例:

class GameScene : KCScene 
var playButton:SKSpriteNode
override init(size:CGSize) 
    playButton = SKSpriteNode(color: SKColor.red, size: CGSize(width:200,height:200))
    playButton.position.x = size.width/2
    playButton.position.y = size.height*0.75
    super.init(size: size)

override func didMove(to view: SKView) 
    addChild(playButton)
    addButton(playButton, withCompletionHandler: playButtonPushed)

func playButtonPushed() 
    let scene = GameScene(size: CGSize(width: 768, height: 1024))
    scene.scaleMode = .aspectFill
    view!.presentScene(scene)


需要注意的是,如果您实施 touchesBegantouchesMovedtouchesEnded 和/或 touchesCancelled,您必须调用 SUPER!否则将无法正常工作。

请注意,在该示例中,您实际上只需要一行代码即可为任何节点提供UIButton 特征!就是这一行:

addButton(playButton, withCompletionHandler: playButtonPushed)

我总是乐于接受想法和建议。把它们留在 cmets 中,祝编程愉快!!

糟糕,我忘了提到我使用了这个漂亮的扩展程序。您可以将其从扩展中取出(因为您可能不需要在每个节点中使用它)并将其放在我的课堂上。我只在一个地方使用它。

extension SKNode 
var frameInScene:CGRect 
    if let scene = scene, let parent = parent 
        let rectOriginInScene = scene.convert(frame.origin, from: parent)
        return CGRect(origin: rectOriginInScene, size: frame.size)
    
    return frame

【讨论】:

这如何确保 playButtonPushed 完成功能可访问?或者我应该把 playButtonPushed 函数放在哪里以确保 KScene 实例可以访问它,我假设它是按钮? @Confused 你会让你自己的场景成为 KCScene 的子类而不是 SKScene:class ConfusedScene : KCScene 。然后在ConfusedScene 中创建一个函数来在按下按钮时执行您想要的操作。我这样做了:func playButtonPushed() /*do whatever happens when play button is pushed*/。为什么这个工作太复杂了,无法在这里解释,但你可以阅读关于闭包的信息here。【参考方案9】:

我解决这个问题的解决方案完全用 SWIFT 编写,使用闭包。

使用起来非常简单! https://github.com/txaidw/TWControls

class Test 
    var testProperty = "Default String"

    init() 
        let control = TWButton(normalColor: SKColor.blueColor(), highlightedColor: SKColor.redColor(), size: CGSize(width: 160, height: 80))
        control.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
        control.position.allStatesLabelText = "PLAY"
        control.addClosureFor(.TouchUpInside, target: self, closure:  (scene, sender) -> () in
            scene.testProperty = "Changed Property"
        )
    

    deinit  println("Class Released..") 

【讨论】:

【参考方案10】:

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

AGSpriteButton

它的实现是基于 UIButton,所以如果你已经熟悉 ios,你应该会发现它很容易上手。

它也可以被分配一个block或者一个SKAction在按钮被按下时执行。

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

按钮通常会这样声明:

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

就是这样。你可以走了。

【讨论】:

我们有什么理由不能使用 SKColor 代替 UIColor 吗?如果我们使用 UIColor,我们就会卡在 iOS 上。 您可以轻松地使用 SKColor 代替 UIColor【参考方案11】:

由于我们所有人都不是针对 iOS,这里是我为处理 Mac 上的鼠标交互而编写的一些代码的开始。

专家的问题:MacOS 在使用触控板时是否提供触摸事件?还是这些作为鼠标事件发送到 SpriteKit?

大师们的另一个问题,这个类不应该被正确地称为SKButtonNode吗?

不管怎样,试试这个...

#if os(iOS)
    override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!)  
        let touch: AnyObject! = touches.anyObject()
        let touchLocation = touch.locationInNode(parent)

        if (!isEnabled)  return 

        isSelected = true
        if (targetTouchDown != nil && targetTouchDown!.respondsToSelector(actionTouchDown!)) 
            UIApplication.sharedApplication().sendAction(actionTouchDown!, to: targetTouchDown, from: self, forEvent: nil)
        
    

    override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!)  
        if (!isEnabled)  return 

        let touch: AnyObject! = touches.anyObject()
        let touchLocation = touch.locationInNode(parent)

        if (CGRectContainsPoint(frame, touchLocation)) 
            isSelected = true
         else 
            isSelected = false
        
    

    override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) 
        if (!isEnabled)  return 

        isSelected = false

        if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) 
            let touch: AnyObject! = touches.anyObject()
            let touchLocation = touch.locationInNode(parent)

            if (CGRectContainsPoint(frame, touchLocation) ) 
                UIApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, forEvent: nil)
            
        

        if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) 
            UIApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self, forEvent: nil)
        
    
#else

    // FIXME: needs support for mouse enter and leave, turning on and off selection

    override func mouseDown(event: NSEvent) 
        if (!isEnabled)  return 

        if (targetTouchDown != nil && targetTouchDown!.respondsToSelector(actionTouchDown!)) 
            NSApplication.sharedApplication().sendAction(actionTouchDown!, to: targetTouchDown, from: self)
        
    

    override func mouseUp(event: NSEvent) 
        if (!isEnabled)  return 

        if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) 
            let touchLocation = event.locationInNode(parent)

            if (CGRectContainsPoint(frame, touchLocation) ) 
                NSApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self)
            
        

        if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) 
            NSApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self)
        
    
#endif

【讨论】:

据我所知,OSX 的 Spritekit 只观察鼠标的东西:/ 是的,它的末尾可能应该有 Node 一词。像 SKLabelNode。【参考方案12】:

我继承了SKScene类,并在这个项目中实现了解决按钮点击的问题。

https://github.com/Prasad9/SpriteKitButton

在其中,所有需要知道的节点都应该被命名。

除了检测按钮点击之外,该项目还可以让您检测特定节点上的触摸是开始还是结束。

要获得点击动作,请在场景文件中覆盖以下方法。

- (void)touchUpInsideOnNodeName:(NSString *)nodeName atPoint:(CGPoint)touchPoint 
    // Your code here.
 

要了解对特定身体的触摸开始,请在场景文件中覆盖以下方法。

 - (void)touchBeginOnNodeName:(NSString *)nodeName 
    // Your code here.
 

要了解对特定身体的触摸结束,请在场景文件中覆盖以下方法。

 - (void)touchEndedOnNodeName:(NSString *)nodeName 
    // Your code here.
 

【讨论】:

【参考方案13】:

Graf 的解决方案有一个问题。 例如:

self.pauseButton = [[AGSKBButtonNode alloc] initWithImageNamed:@"ButtonPause"];
self.pauseButton.position = CGPointMake(0, 0);
[self.pauseButton setTouchUpInsideTarget:self action:@selector(pauseButtonPressed)];

[_hudLayer addChild:_pauseButton];

_hudLayer 是一个 SKNode,我的场景的一个属性。因此,由于 SKButton 中的 touchesEnded 方法,您会遇到异常。它将调用 [SKSpriteNode pauseButtonPressed],而不是场景。

修改self.parent为touch target的解决方法:

- (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)
        [_targetTouchUpInside performSelectorOnMainThread:_actionTouchUpInside withObject:_targetTouchUpInside waitUntilDone:YES];
    

[self setIsSelected:NO];
if (_actionTouchUp)
    [_targetTouchUp performSelectorOnMainThread:_actionTouchUp withObject:_targetTouchUp waitUntilDone:YES];

【讨论】:

【参考方案14】:

实际上,这在 Xcode 7.3

上的 Swift 2.2 上运行良好

我喜欢 FTButtonNode (richy486/FTButtonNode.swift ),但无法在初始化期间直接指定其他大小(而不是默认纹理大小),所以我添加了这个简单的方法:

您必须在官方自定义 init 方法下复制它(类似于此),以便您可以使用另一个 init 方法:

init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?, size:CGSize) 

        self.defaultTexture = defaultTexture
        self.selectedTexture = selectedTexture
        self.disabledTexture = disabledTexture
        self.label = SKLabelNode(fontNamed: "Helvetica");

        super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: size)
        userInteractionEnabled = true

        //Creating and adding a blank label, centered on the button
        self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center;
        self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center;
        addChild(self.label)

        // Adding this node as an empty layer. Without it the touch functions are not being called
        // The reason for this is unknown when this was implemented...?
        let bugFixLayerNode = SKSpriteNode(texture: nil, color: UIColor.clearColor(), size: size)
        bugFixLayerNode.position = self.position
        addChild(bugFixLayerNode)

    

另一个重要的事情是“选择时间”,我已经看到在新设备(iPhone 6)中,touchesBegantouchesEnded 之间的时间太快了,你看不到defaultTexture 之间的变化和selectedTexture

使用此功能:

func dispatchDelay(delay:Double, closure:()->()) 
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)

您可以重新编写touchesEnded 方法以正确显示纹理变化:

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) 
        if (!isEnabled) 
            return
        

        dispatchDelay(0.2) 
            self.isSelected = false
        

        if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) 
            let touch: AnyObject! = touches.first
            let touchLocation = touch.locationInNode(parent!)

            if (CGRectContainsPoint(frame, touchLocation) ) 
                UIApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, forEvent: nil)
            

        

        if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) 
            UIApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self, forEvent: nil)
        

【讨论】:

【参考方案15】:

我不相信上述任何选项,因此基于最新的 Swift4 我创建了my own solution。

【讨论】:

【参考方案16】:

可惜SpriteKit没有按钮节点,我不知道为什么,因为它是非常有用的控件。所以我决定创建自己的并通过 CocoaPods 分享,请使用它OOButtonNode。 按钮可以使用文本/背景或图像,用 Swift 4 编写。

【讨论】:

【参考方案17】:

这是一个用现代 Swift (4.1.2) 编写的简单按钮

特点

它接受 2 个图像名称,1 个用于默认状态,一个用于活动状态 开发者可以设置touchBeganCallbacktouchEndedCallback闭包来添加自定义行为

代码

import SpriteKit

class SpriteKitButton: SKSpriteNode 

    private let textureDefault: SKTexture
    private let textureActive: SKTexture

    init(defaultImageNamed: String, activeImageNamed:String) 
        textureDefault = SKTexture(imageNamed: defaultImageNamed)
        textureActive = SKTexture(imageNamed: activeImageNamed)
        super.init(texture: textureDefault, color: .clear, size: textureDefault.size())
        self.isUserInteractionEnabled = true
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("Not implemented")
    

    var touchBeganCallback: (() -> Void)?
    var touchEndedCallback: (() -> Void)?

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        self.texture = textureActive
        touchBeganCallback?()
    

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
        self.texture = textureDefault
        touchEndedCallback?()
    

如何使用

class GameScene: SKScene 

    override func didMove(to view: SKView) 

        // 1. create the button
        let button = SpriteKitButton(defaultImageNamed: "default", activeImageNamed: "active")

        // 2. write what should happen when the button is tapped
        button.touchBeganCallback = 
            print("Touch began")
        

        // 3. write what should happen when the button is released
        button.touchEndedCallback = 
            print("Touch ended")
        

        // 4. add the button to the scene
        addChild(button)

    

【讨论】:

以上是关于在 SKScene 中设置按钮的主要内容,如果未能解决你的问题,请参考以下文章

在 SKScene 中隐藏和取消隐藏按钮

touches* 事件 - SKScene 与 ViewController

在界面生成器中隐藏 SKScene 中的节点

SKScene 在 presentViewController 之后重复

如何在 MyScene(SKScene 的子类)中打开 xib 或故事板窗口?

从 UIViewController 呈现 SKScene - Swift