Konfigurowanie przycisków w SKScene

81

Odkryłem, że UIButtonsto nie działa zbyt dobrze SKScene, więc próbuję SKNodeutworzyć podklasę, aby utworzyć przycisk SpriteKit.

Sposób, w jaki chciałbym, aby to działało, polega na tym, że jeśli zainicjuję przycisk SKScenei włączę zdarzenia dotykowe, przycisk wywoła metodę w moim SKScenepo naciśnięciu.

Byłbym wdzięczny za każdą radę, która doprowadziłaby mnie do znalezienia rozwiązania tego problemu. Dzięki.

AlexHeuman
źródło
2
Szukam czegoś więcej do nauki, a także rozwiązania. Myślę, że właściwym rozwiązaniem byłoby skonfigurowanie SKScene jako delegata przycisku, ale nie jestem pewien, jak to zrobić. Czy mogę ustawić SKScene jako zmienną instancji przycisku i wywołać jej metodę?
AlexHeuman,
Możesz zrobić wiele rzeczy, delegować lub bardziej elastycznie używać NSNotification, aby każdy węzeł mógł na nie odpowiedzieć. Jeśli używasz delegata, upewnij się, że właściwość delegata jest słaba.
LearnCocos2D
Zauważyłem, że ten kod jest pomocny przy tworzeniu przycisków zestawu sprite. Rozszerza SKSpriteKitNode i umożliwia łatwe dodawanie tekstu do przycisków.
sager89

Odpowiedzi:

102

możesz użyć SKSpriteNode jako przycisku, a następnie, gdy użytkownik dotknie, sprawdź, czy to był dotknięty węzeł. Użyj właściwości nazwy SKSpriteNode, aby zidentyfikować węzeł:

//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;
}

Dodaj węzeł do swojej sceny:

[self addChild: [self fireButtonNode]];

Uchwyty:

//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...
    }
}
AndyOS
źródło
8
Jeśli dodasz iVar jako przycisk, możesz usunąć sprawdzanie nazwy i po prostu użyć if ([_fireNode containsPoint: location]) robi to samo, tylko inaczej.
DogCoffee
3
Porównywanie strun to brudne rozwiązanie. Chociaż rozwiązanie @Smick jest lepsze, czy nie ma innego czystszego sposobu na osiągnięcie tego?
Esteban Bouza,
hej, nie możemy dodać przycisku w SpriteKit, takiego jak SkLabelNode?
Omer Obaid
czy to pozwala na zdarzenia wielodotykowe? na przykład 2 przyciski kliknięte jednocześnie? jeden z nich to przycisk ruchu, drugi to przycisk ognia.
duxfox
52

Stworzyłem własną klasę przycisku, z którą pracuję. 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);
}

Przykład: Aby zainicjować przycisk, napisz następujące wiersze:

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

Ponadto w swojej klasie potrzebujesz metody „buttonAction”. * Brak gwarancji, że ta klasa działa poprawnie w każdym przypadku. Nadal jestem nowy w Objective-c. *

Jeśli uważasz, że zrobienie tego jest denerwujące i bezcelowe, możesz wyłączyć sprawdzanie w ustawieniach kompilacji, ustawiając opcję „Włącz ścisłe sprawdzanie objc_msgSend Calls'na No

dennis-tra
źródło
Dzięki za udostępnienie. Czy jest powód, dla którego używasz objc_msgSendzamiast [target performSelector:selector]?
Jeffrey W.
2
Ach tak, cholera ARC. Zapomniałem o tym ostrzeżeniu: | Oto niezła praca, jeśli jesteś zainteresowany stackoverflow.com/questions/11895287/ ...
Jeffrey W.
Powyższy kod jest świetny, ale otrzymuję błędy podczas próby użycia - (void) changeToScene: (SKButtonNode *) sender {} jako @selector. Wolałbym użyć jednej metody do przełączania scen przy użyciu sender.name, gdybym mógł.
Beau Nouvelle
1
Dzięki za udostępnienie tego! Dołączam to do mojego kodu. Zobaczymy, czy zadziała. Jedna sugestia: zmień nazwę klasy z SKButton na coś bardziej unikalnego dla Ciebie, np. GRFButton. W pewnym momencie Apple może wprowadzić SKButton i nie chcesz później mylić przestrzeni nazw i łamać kodu.
James Paul Mason,
1
@BeauYoung - Działa po dodaniu selfna końcu w ten sposób:objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)
Genki,
19

Dla osób piszących gry w języku Swift! Przepisałem podstawowe części rozwiązania Grafa na szybką klasę. Mam nadzieję, że to pomoże:

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

}
Groot
źródło
5

Jeśli pragniesz, to może użyć UIButton (lub innego UIView).

Kiedy SKScenejest tworzony, nie istnieje jeszcze w SKView. Należy zaimplementować didMoveToView:w swojej SKScenepodklasie. W tym momencie masz dostęp do SKViewsceny, w której jest umieszczona i możesz dodawać UIKitdo niej obiekty. Dla urody, wyblakłam je w…

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

będziesz musiał celowo usunąć je ze sceny, kiedy się zmieniasz, chyba że oczywiście ma to całkowity sens, aby tam pozostały.

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

Dla tych, którzy nie są zaznajomieni z programowym tworzeniem UIButton, oto jeden przykład (tutaj możesz zrobić 100 rzeczy inaczej)…

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

Przypomnienie: UIViewpoczątek znajduje się w lewym górnym rogu, SKScenepoczątek w lewym dolnym rogu.

bshirley
źródło
3

Użyłem klasy SKButton autorstwa Graf .

Używam SKButton do nawigacji po scenach. tj. przedstawia inną scenę, gdy użytkownik naciśnie przycisk SKButton. Otrzymuję EXC_BAD_ACCESSbłąd w touchesEnded->[self setIsSelected:NO]. Dzieje się tak szczególnie często na najnowszym iPadzie z szybkim procesorem.

Po sprawdzeniu i rozwiązaniu problemu zdałem sobie sprawę, że obiekt SKButton jest już „zwalniany”, gdy setIsSelectedfunkcja jest wywoływana. Dzieje się tak, ponieważ używam SKButton, aby przejść do następnej sceny, a to oznacza również, że bieżąca scena może zostać cofnięta w dowolnym momencie.

Dokonałem małej zmiany, umieszczając setIsSelected w części „else” w następujący sposób.

Mam nadzieję, że to pomoże innym programistom, którzy również widzą ten sam błąd.

(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);
}
user3204765
źródło
Sformatuj swój post i kod źródłowy, bardzo trudno go odczytać w ten sposób!
Uli Köhler
2

Oto kolejna wersja oparta na kodzie Filipa w języku Swift. Po prostu trochę to uprościłem i pozwoliłem mu przyjmować bloki, a nie tylko selektory:

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

Użyj tego w ten sposób:

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

Mam nadzieję że to pomoże.

Guillaume Laurent
źródło
2

Edycja: stworzyłem repozytorium github dla mojego SKButtonNode, które mam nadzieję, że będę na bieżąco aktualizowane w miarę szybkiego rozwoju!

SKButtonNode


Niestety nie mogę jeszcze wypowiedzieć się na temat szybkiego wdrożenia SKButton przez Filipa w Swift. Bardzo się cieszę, że zrobił to w Swift! Ale zauważyłem, że nie uwzględnił funkcji dodawania tekstu do przycisku. Jest to dla mnie ogromna funkcja, dzięki której nie musisz tworzyć osobnych zasobów dla każdego przycisku, a jedynie tło i dodawać dynamiczny tekst.

Dodałem prostą funkcję dodawania etykiety tekstowej do SKButton. Prawdopodobnie nie jest idealny - jestem nowy w Swift, tak jak wszyscy inni! Zapraszam do komentowania i pomóż mi zaktualizować to najlepiej, jak to możliwe. Mam nadzieję, że polubicie!

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

Przykładowe tworzenie przycisku:

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

Pełna lista klas poniżej:

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

}

Morgan Wesemann
źródło
Zaktualizowałem kod dla Swift 2.1 tutaj: gist.github.com/richy486/5d408c442ac1c0c2891f
richy
.. i zaktualizowałem do Swift 3 tutaj: github.com/jglasse/SKButtonSwift3
jglasse
2

Ile świetnych rozwiązań tego problemu! Dla zapalonych scrollerów, którzy dotarli tak daleko, czeka Cię nie lada gratka! Mam podklasę SKScenei potrzeba JEDNEGO wywołania funkcji, aby zarejestrować DOWOLNY węzeł, aby działał jak UIButton! Oto klasa:

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

}

Zawiera wiele pomysłów, których jeszcze nie zaimplementowałem, oraz kilka wyjaśnień dotyczących kodu, ale po prostu skopiuj i wklej go do swojego projektu, a będziesz mógł używać go tak, jak jest we własnej scenie. Oto kompletny przykład użycia:

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

Jedno zastrzeżenie, to jeśli wdrożenie touchesBegan, touchesMoved, touchesEnded, i / lubtouchesCancelled MUSISZ ZADZWONIĆ SUPER! Albo to nie zadziała.

I proszę, uświadom sobie, że w tym przykładzie jest naprawdę tylko JEDNA LINIA KODU, którą musisz podać DOWOLNYM WĘZŁOM UIButton! To była ta linia:

addButton(playButton, withCompletionHandler: playButtonPushed)

Jestem zawsze otwarta na pomysły i sugestie. Zostawcie je w komentarzach i szczęśliwego kodowania !!

Ups, zapomniałem wspomnieć, że używam tego fajnego rozszerzenia. Możesz wyjąć go z rozszerzenia (ponieważ prawdopodobnie nie potrzebujesz go w każdym węźle) i umieścić go w mojej klasie. Używam go tylko w jednym miejscu.

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
}

}

mogelbuster
źródło
W jaki sposób zapewnia to dostępność funkcji uzupełniania playButtonPushed? Albo gdzie umieścić funkcję playButtonPushed, aby zapewnić, że jest ona osiągalna przez instancję KScene, którą zakładam, że jest przyciskiem?
Zmieszany
@Confused Można by stworzyć własną scenę podklasa KCScene zamiast SKScene: class ConfusedScene : KCScene {. Następnie w środku ConfusedScenepo prostu utwórz funkcję, która robi, co chcesz, po naciśnięciu przycisku. Zrobiłem to: func playButtonPushed() { /*do whatever happens when play button is pushed*/}. Dlaczego to działa, jest zbyt skomplikowane, aby wyjaśnić tutaj, ale możesz przeczytać o zamknięciach tutaj .
mogelbuster
1

Moje rozwiązanie tego problemu napisane w całości w SWIFT, przy użyciu domknięć.

Jest całkiem prosty w użyciu! 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..") }
}
txaidw
źródło
0

Już jakiś czas temu stworzyłem klasę do używania SKSpriteNode jako przycisku. Możesz go znaleźć na GitHub tutaj.

AGSpriteButton

Jego implementacja opiera się na UIButton, więc jeśli znasz już iOS, powinieneś łatwo z nim pracować.

Można również przypisać blok lub SKAkcję do wykonania po naciśnięciu przycisku.

Zawiera również metodę konfiguracji etykiety.

Przycisk będzie zazwyczaj deklarowany w następujący sposób:

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

I to wszystko. Jesteś gotowy.

ZeMoon
źródło
Czy jest jakiś powód, dla którego nie mogliśmy użyć SKColor zamiast UIColor? Jeśli używamy UIColor, utknęliśmy na iOS.
Maury Markowitz,
Równie łatwo możesz użyć SKColor zamiast UIColor
ZeMoon
0

A ponieważ wszyscy nie jesteśmy ukierunkowani na system iOS, oto początek kodu, który napisałem, aby obsługiwać interakcję myszy na komputerze Mac.

Pytanie do guru: czy MacOS oferuje zdarzenia dotykowe podczas korzystania z gładzika? A może są one wysyłane do SpriteKit jako zdarzenia myszy?

Kolejne pytanie dla guru, nie powinno to klasa właściwie nazwać SKButton węzła ?

W każdym razie spróbuj tego ...

#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
Maury Markowitz
źródło
O ile wiem, Spritekit dla OSX obserwuje tylko rzeczy związane z myszą: / I tak, prawdopodobnie powinien mieć na końcu słowo Node. Podobnie jak SKLabelNode.
CodyMace
0

Mam podklasę SKScene klasę i osiągnąłem problem rozwiązania dotknięcia przycisków w tym projekcie.

https://github.com/Prasad9/SpriteKitButton

Należy w nim nazwać wszystkie węzły, które są niezbędne do rozpoznania po dotknięciu.

Oprócz wykrywania dotknięcia przycisku, projekt ten umożliwia również wykrycie, czy dotknięcie określonego węzła się rozpoczęło, czy zakończyło.

Aby uzyskać akcję dotknięcia, zastąp następującą metodę w pliku sceny.

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

Aby poznać początek dotyku na konkretnym ciele, zastąp następującą metodę w pliku sceny.

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

Aby poznać koniec dotknięcia określonej treści, zastąp następującą metodę w pliku sceny.

 - (void)touchEndedOnNodeName:(NSString *)nodeName {
    // Your code here.
 }
Prasad
źródło
0

Rozwiązanie Grafa ma jeden problem. Na przykład:

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

[_hudLayer addChild:_pauseButton];

_hudLayer to SKNode, właściwość mojej sceny. Otrzymasz więc wyjątek, ponieważ metoda touchesEnded w SKButton. Wywoła [SKSpriteNode pauseButtonPressed], a nie ze sceną.

Rozwiązanie pozwalające zmienić cel na osobę rodzicielską:

- (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];
}}
kartpick
źródło
0

W rzeczywistości działa to dobrze w Swift 2.2 na Xcode 7.3

Lubię FTButtonNode ( richy486 / FTButtonNode.swift ), ale nie jest możliwe określenie innego rozmiaru (zamiast domyślnego rozmiaru tekstury) bezpośrednio podczas inicjalizacji, więc dodałem tę prostą metodę:

Musisz to skopiować zgodnie z oficjalną niestandardową metodą init (podobną do tej), aby mieć inną metodę init do użycia:

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)

    }

Kolejną ważną rzeczą jest „czas selekcji”, widziałem, że w nowych urządzeniach (iPhone 6) czasami czas między touchesBegani touchesEndedjest zbyt szybki i nie widać zmian między defaultTexturea selectedTexture.

Dzięki tej funkcji:

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

możesz ponownie napisać touchesEndedmetodę, aby poprawnie pokazać zmienność tekstury:

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)
        }
}
Alessandro Ornano
źródło
0

Nie byłem przekonany do żadnej z powyższych opcji, dlatego w oparciu o najnowszy Swift4 stworzyłem własne rozwiązanie .

Bersaelor
źródło
0

Niestety SpriteKit nie posiada węzła przycisku, nie wiem dlaczego, bo jest to bardzo przydatne sterowanie. Postanowiłem więc stworzyć własne i udostępniać za pośrednictwem CocoaPods, użyj go OOButtonNode . Przyciski mogą zawierać tekst / tło lub obrazy zapisane w języku Swift 4.

Oleg O
źródło
0

Oto prosty przycisk napisany za pomocą nowoczesnego języka Swift (4.1.2)

funkcje

  • akceptuje 2 nazwy obrazów, 1 dla stanu domyślnego i jedną dla stanu aktywnego
  • programista może ustawić zamknięcia touchBeganCallbacki, touchEndedCallbackaby dodać niestandardowe zachowanie

Kod

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

Jak tego użyć

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)

    }
}
Luca Angeletti
źródło