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

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

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

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

【问题讨论】:

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

标签: ios objective-c uibutton sprite-kit


【解决方案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。我忘记了那个警告:|如果您有兴趣,这里有一个不错的解决方法stackoverflow.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)
            }
        }
        

        }

        【讨论】:

        【解决方案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)
                    
                        }
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-04-08
                      • 2010-10-16
                      • 1970-01-01
                      相关资源
                      最近更新 更多