Odkryłem, że UIButtons
to nie działa zbyt dobrze SKScene
, więc próbuję SKNode
utworzyć podklasę, aby utworzyć przycisk SpriteKit
.
Sposób, w jaki chciałbym, aby to działało, polega na tym, że jeśli zainicjuję przycisk SKScene
i włączę zdarzenia dotykowe, przycisk wywoła metodę w moim SKScene
po naciśnięciu.
Byłbym wdzięczny za każdą radę, która doprowadziłaby mnie do znalezienia rozwiązania tego problemu. Dzięki.
ios
objective-c
uibutton
sprite-kit
AlexHeuman
źródło
źródło
Odpowiedzi:
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... } }
źródło
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'
naNo
”źródło
objc_msgSend
zamiast[target performSelector:selector]
?self
na końcu w ten sposób:objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)
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) } } }
źródło
Jeśli pragniesz, to może użyć UIButton (lub innego UIView).
Kiedy
SKScene
jest tworzony, nie istnieje jeszcze wSKView
. Należy zaimplementowaćdidMoveToView:
w swojejSKScene
podklasie. W tym momencie masz dostęp doSKView
sceny, w której jest umieszczona i możesz dodawaćUIKit
do 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:
UIView
początek znajduje się w lewym górnym rogu,SKScene
początek w lewym dolnym rogu.źródło
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_ACCESS
błąd wtouchesEnded->[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
setIsSelected
funkcja 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); }
źródło
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.
źródło
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) } }
}
źródło
Ile świetnych rozwiązań tego problemu! Dla zapalonych scrollerów, którzy dotarli tak daleko, czeka Cię nie lada gratka! Mam podklasę
SKScene
i potrzeba JEDNEGO wywołania funkcji, aby zarejestrować DOWOLNY węzeł, aby działał jakUIButton
! 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: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 }
}
źródło
class ConfusedScene : KCScene {
. Następnie w środkuConfusedScene
po 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 .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..") } }
źródło
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.
źródło
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
źródło
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. }
źródło
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]; }}
źródło
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
touchesBegan
itouchesEnded
jest zbyt szybki i nie widać zmian międzydefaultTexture
aselectedTexture
.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ć
touchesEnded
metodę, 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) } }
źródło
Nie byłem przekonany do żadnej z powyższych opcji, dlatego w oparciu o najnowszy Swift4 stworzyłem własne rozwiązanie .
źródło
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.
źródło
Oto prosty przycisk napisany za pomocą nowoczesnego języka Swift (4.1.2)
funkcje
touchBeganCallback
i,touchEndedCallback
aby dodać niestandardowe zachowanieKod
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) } }
źródło