SKSceneでのボタンの設定


81

ではUIButtonsうまく機能しないことがわかったので、でボタンを作成するためSKSceneにサブクラス化しようとSKNodeしていますSpriteKit

私がそれを機能させたい方法は、ボタンを初期化してSKSceneタッチイベントを有効にすると、ボタンSKSceneが押されたときにボタンがメソッドを呼び出すことです。

この問題の解決策を見つけるためのアドバイスをいただければ幸いです。ありがとう。


2
私はより多くの学習経験と解決策を探しています。適切な解決策は、SKSceneをボタンのデリゲートとして設定することだと思いますが、これを行う方法がわかりません。SKSceneをボタンのインスタンス変数として設定し、そのメソッドを呼び出すことはできますか?
AlexHeuman 2013

NSNotificationを使用して、委任やより柔軟にさまざまなことを実行できるため、どのノードもそれに応答できます。デリゲートを使用する場合は、デリゲートプロパティを必ずweakに設定してください。
LearnCocos2D 2013

このコードは、スプライトキットボタンの作成に役立つことがわかりました。SKSpriteKitNodeを拡張し、ボタンにテキストを簡単に追加できるようにします。
sager89 2014年

回答:


102

ボタンとして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...
    }
}

8
ボタンとしてiVarを追加すると、名前のチェックを削除して、([_ fireNode containsPoint:location])が同じことをまったく異なる方法で実行する場合に使用できます。
dogCoffee 2013年

3
文字列を比較するのは汚い解決策です。@Smickのソリューションの方が優れていますが、これを実現するための他のよりクリーンな方法はありませんか?
Esteban Bouza 2013

SkLabelNodeのようにSpriteKitにボタンを追加することはできませんか?
Omer Obaid 2014

これはマルチタッチイベントを許可しますか?たとえば、2つのボタンが同時にクリックされましたか?それらの1つは移動ボタンで、もう1つは発射ボタンです。
duxfox-- 2014年

52

一緒に作業している独自のボタンクラスを作成しました。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]か?
ジェフリーW.

2
ああ、そうだ、ARCだ。その警告を忘れました:| あなたが興味を持っているなら、これは素晴らしい回避
Jeffrey W.

上記のコードは素晴らしいですが、-(void)changeToScene:(SKButtonNode *)sender {}を@selectorとして使用しようとするとエラーが発生します。可能であれば、sender.nameを使用してシーンを切り替える単一の方法を使用したいと思います。
Beau Nouvelle

1
これを共有してくれてありがとう!私はそれを私のコードに含めています。それが機能するかどうかを確認します。1つの提案:クラスの名前をSKButtonから、GRFButtonなどのより独自の名前に変更します。ある時点で、AppleはSKButtonを導入する可能性があり、名前空間を混乱させて後でコードを壊したくない場合があります。
James Paul Mason

1
@ BeauYoung-次のselfように最後にを追加すると機能します:objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)
元気

19

Swiftでゲームを書いている人のために!Grafのソリューションの重要な部分を迅速なクラスに書き直しました。それが役に立てば幸い:

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

}

5

あなたが望むならば、あなたはできるUIButton(またはその他のUIView)を使用します。

SKSceneが作成されたとき、それはまだに存在していませんSKView。サブクラスに実装する必要didMoveToView:がありますSKScene。この時点で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、ここに1つの例があります(ここでは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原点は左下にあります。


3

GrafのSKButtonクラスを使用しました。

SKButtonを使用してシーンナビゲーションを行います。つまり、ユーザーがSKButtonを押したときに別のシーンを表示します。でEXC_BAD_ACCESSエラーが発生しtouchesEnded->[self setIsSelected:NO]ます。これは、高速CPUを搭載した最新のiPadで特に頻繁に発生します。

チェックとトラブルシューティングを行った後、setIsSelected関数が呼び出されているときにSKButtonオブジェクトがすでに「割り当て解除」されていることに気付きました。これは、SKButtonを使用して次のシーンに移動するためです。これは、現在のシーンの割り当てをいつでも解除できることも意味します。

次のように、「else」部分にsetIsSelectedを配置して、小さな変更を加えました。

これが同じエラーが表示される他の開発者にも役立つことを願っています。

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

投稿とソースコードをフォーマットしてください。この方法で読むのは非常に困難です。
ウリケーラー

2

これは、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")
    })

お役に立てれば。


2

編集:SKButtonNodeのgithubリポジトリを作成しました。これは、最新の状態を維持し、迅速に進化するにつれて更新されることを願っています。

SKButtonNode


残念ながら、SwiftでのFilipの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)
    }
}

}


ここでswift2.1
richy

..そして私はここでSwift3に更新しました:github.com/jglasse/SKButtonSwift3
jglasse

2

この問題に対するすばらしい解決策はたくさんあります。ここまで到達したハードコアスクローラーの場合、あなたはご馳走になります!サブクラスSKScene化したので、UIButton!のように動作するノードを登録するには、1回の関数呼び出しが必要です。クラスは次のとおりです。

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

1の注意点は、あなたが実装する場合でtouchesBegantouchesMovedtouchesEnded、および/またはtouchesCancelled、あなたはSUPERを呼び出す必要があります!そうでなければ、それは機能しません。

そして、その例では、ノードのUIButton特性を与えるために必要なコードは実際には1行しかないことを理解してください。それはこの行でした:

addButton(playButton, withCompletionHandler: playButtonPushed)

私は常にアイデアや提案を受け付けています。コメントとハッピーコーディングにそれらを残してください!

おっと、私はこの気の利いた拡張機能を使用していることを言及するのを忘れました。拡張機能からそれを取り出し(おそらくすべてのノードで必要になるわけではないので)、私のクラスに配置することができます。私はそれを一箇所でしか使用しません。

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インスタンスから到達可能であることを確認しますか?
2016年

@Confused自分のシーンをSKSceneではなくKCSceneのサブクラスにしますclass ConfusedScene : KCScene {。次に、内部でConfusedSceneボタンが押されたときにやりたいことを実行する関数を作成します。私はこれをしました:func playButtonPushed() { /*do whatever happens when play button is pushed*/}。なぜこれが機能するのかはここで説明するには複雑すぎますが、クロージャーについてはここで読むことができます
mogelbuster 2016

1

クロージャを使用して、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..") }
}

0

かなり前に、SKSpriteNodeをボタンとして使用するためのクラスを作成しました。こちらのGitHubで見つけることができます。

AGSpriteButton

実装はUIButtonに基づいているため、iOSに既に精通している場合は、簡単に操作できるはずです。

また、ボタンが押されたときに実行されるブロックまたは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];

以上です。あなたは行ってもいいです。


UIColorの代わりにSKColorを使用できなかった理由はありますか?UIColorを使用する場合、iOSで立ち往生しています。
Maury Markowitz 2015

UIColorの代わりにSKColorを簡単に使用できます
ZeMoon

0

そして、私たち全員がiOSをターゲットにしているわけではないので、Macでのマウス操作を処理するために私が書いたコードの始まりを次に示します。

教祖への質問:MacOSはトラックパッドを使用するときにタッチイベントを提供しますか?または、これらはマウスイベントとしてSpriteKitに送信されますか?

教祖への別の質問ですが、このクラスは適切にSKButtonノードと呼ばれるべきではありませんか?

とにかく、これを試してください...

#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

私の知る限り、Spritekit for OSXはマウスのものだけを観察します:/そうです、おそらく最後にノードという単語が必要です。SKLabelNodeのように。
CodyMace 2015

0

私はクラスをサブクラス化し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.
 }

0

Grafのソリューションには1つの問題があります。例えば:

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メソッドが原因で、例外が発生します。シーンではなく、[SKSpriteNodepauseButtonPressed]を呼び出します。

self.parentをタッチターゲットに変更するソリューション:

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

0

上だけでなく、実際にこの作品スウィフト2.2のXcode 7.3

私はFTButtonNoderichy486 / 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)間のいつかの時間を新しいデバイスでそれを見てきたtouchesBeganし、touchesEnded速すぎると、あなたは間の変更を参照いけない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)
        }
}


0

残念ながら、SpriteKitにはボタンノードがありません。非常に便利なコントロールであるため、理由はわかりません。そこで、CocoaPodsを介して独自に作成して共有することにしました。OOButtonNodeを使用してください。ボタンは、Swift4で書かれたテキスト/背景または画像を使用できます。


0

これが最新のSwift(4.1.2)で書かれたシンプルなボタンです

特徴

  • 2つの画像名を受け入れます。1つはデフォルト状態用、もう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)

    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.