Configuración de botones en SKScene

Estoy descubriendo que los UIButtons no funcionan muy bien con SKScene , así que estoy intentando subclasificar SKNode para crear un botón en SpriteKit .

La forma en que me gustaría que funcionara es que si inicializo un botón en SKScene y SKScene los eventos táctiles, entonces el botón llamará a un método en mi SKScene cuando se presione.

Agradecería cualquier consejo que me lleve a encontrar la solución a este problema. Gracias.

podría usar un SKSpriteNode como su botón y luego, cuando el usuario toque, verifique si ese fue el nodo tocado. Utilice la propiedad del nombre SKSpriteNode para identificar el nodo:

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

Agrega nodo a tu escena:

 [self addChild: [self fireButtonNode]]; 

Manejar toques:

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

Creé mi propia Button-Class con la que estoy trabajando. SKButton.h:

 #import  @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  @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 restred * 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); } 

Un ejemplo: para inicializar un botón, escriba las siguientes líneas:

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

Además, necesitas el método ‘buttonAction’ en tu clase. * No hay garantía de que esta clase esté funcionando en todos los casos. Todavía soy bastante nuevo en Objective-c. *

¡Para las personas que escriben sus juegos en Swift! He reescrito las partes esenciales de la solución de Graf para una clase rápida. Espero eso ayude:

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

Si lo desea, puede usar UIButton (o cualquier otra UIView).

Cuando se crea un SKScene , todavía no existe en un SKView . Debe implementar didMoveToView: en su subclase SKScene . En este punto, tiene acceso a la SKView la que se SKView la escena y puede agregarle objetos UIKit . Para la belleza, los desvanecí en …

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

Tendrás que sacarlos deliberadamente de la escena cuando te alejes, a menos que, por supuesto, tenga sentido que permanezcan allí.

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

Para aquellos que no estén familiarizados con la creación de un UIButton mediante progtwigción, aquí hay un ejemplo (aquí se pueden hacer 100 cosas de forma diferente) ...

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

Recordatorio: el origen de UIView está en la SKScene superior izquierda, el origen de SKScene está en la SKScene inferior izquierda.

He usado la clase SKButton por Graf .

Yo uso SKButton para hacer navegación de escena. es decir, presente otra escena cuando el usuario presione el botón SKButton. EXC_BAD_ACCESS error EXC_BAD_ACCESS en touchesEnded->[self setIsSelected:NO] . Esto ocurre especialmente con frecuencia en el último iPad con CPU rápida.

Después de verificar y solucionar problemas, me di cuenta de que el objeto SKButton ya está “desasignado” cuando se setIsSelected función setIsSelected . Esto se debe a que utilizo SKButton para navegar a la siguiente escena y esto también significa que la escena actual puede desasignarse en cualquier momento.

Hice un pequeño cambio colocando el setIsSelected en la porción “else” de la siguiente manera.

Espero que esto ayude a otros desarrolladores que también ven el mismo error.

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

Aquí hay otra versión basada en el código Swift de Filip. Simplemente lo simplifiqué un poco y le permití tomar bloques en lugar de solo selectores:

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

Úselo así:

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

Espero que esto ayude.

Editar: ¡He hecho un repository github para mi SKButtonNode que con suerte mantendré al día y actualizando tan rápido como evolucione!

SKButtonNode


Lamentablemente, no puedo comentar sobre la rápida implementación de SKButton en Swift por parte de Filip. ¡Súper feliz de haber hecho esto en Swift! Pero noté que no incluía una función para agregar texto al botón. Esta es una gran característica para mí, por lo que no tiene que crear activos por separado para cada botón, solo el fondo y agregar texto dynamic.

Agregué una función simple para agregar una etiqueta de texto a SKButton. Es probable que no sea perfecto. ¡Soy nuevo en Swift como todos los demás! Siéntase libre de comentar y ayudarme a actualizar esto lo mejor posible. Espero que les guste!

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

Creación de muestra de botón:

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

Clase completa enumerada a continuación:

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

}

¡Qué gran cantidad de soluciones para este problema! Para los jugadores de hardcore que llegan tan lejos, ¡estás de enhorabuena! He subclasificado SKScene , y se necesita UNA función llamada para registrar CUALQUIER nodo para que actúe como un UIButton ! Aquí está la clase:

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

}

Incluye muchas ideas que aún no he implementado y algunas explicaciones del código, pero solo copie y pegue en su proyecto, y puede usarlo tal como está en su propia escena. Aquí hay un ejemplo completo de uso:

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

La única advertencia es si implementa touchesBegan , touchesMoved , touchesEnded y / o touchesCancelled ¡DEBE LLAMAR SUPER! O de lo contrario no funcionará.

¡Y tenga en cuenta que en ese ejemplo, realmente solo hay UNA LÍNEA DE CÓDIGO que necesita para dar CUALQUIER característica de UIButton NODO! Fue esta línea:

 addButton(playButton, withCompletionHandler: playButtonPushed) 

Siempre estoy abierto para ideas y sugerencias. Déjalos en los comentarios y Happy Coding !!

Vaya, olvidé mencionar que uso esta ingeniosa extensión. Puedes sacarlo de una extensión (ya que probablemente no lo necesites en cada nodo) y colocarlo en mi clase. Solo lo uso en un solo lugar.

 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 } 

}

My solution to solve this problem written completely in SWIFT, using closures.

Its pretty simple to use! 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..") } } 

I had created a class for using SKSpriteNode as a button quite a while ago. You can find it on GitHub here.

AGSpriteButton

It’s implementation is based on UIButton, so if you are already familiar with iOS, you should find it easy to work with.

It can also be assigned a block or an SKAction to be executed when the button is pressed.

It includes a method to set up a label as well.

A button will typically be declared like so:

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

Y eso es. You’re good to go.

And since all of us aren’t targeting iOS, here’s the start of some code I wrote to handle mouse interaction on the Mac.

Question for the gurus: does MacOS offer touch events when using a trackpad? Or are these sent into SpriteKit as mouse events?

Another question for the gurus, shouldn’t this class properly be called SKButton Node ?

Anyway, try this…

 #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 

I have subclassed SKScene class and achieved the problem of solving button taps in this project.

https://github.com/Prasad9/SpriteKitButton

In it, all the nodes which are necessary to be known upon tapped should be named.

In addition to detecting button tap, this project also enables you to detect whether the touch on a particular node has started or ended.

To get tap action, override the following method in your Scene file.

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

To get to know the start of touch on a particular body, override the following method in your Scene file.

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

To get to know the end of touch on a particular body, override the following method in your Scene file.

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

Graf`s solution has one issue. Por ejemplo:

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

_hudLayer is a SKNode, a property of my scene. So, you`ll get exception, because of method touchesEnded in SKButton. It will call [SKSpriteNode pauseButtonPressed], not with scene.

The solution to change self.parent to touch target:

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint touchPoint = [touch locationInNode:self.parent]; if ([self isEnabled] && CGRectContainsPoint(self.frame, touchPoint)) { if (_actionTouchUpInside){ [_targetTouchUpInside performSelectorOnMainThread:_actionTouchUpInside withObject:_targetTouchUpInside waitUntilDone:YES]; } } [self setIsSelected:NO]; if (_actionTouchUp){ [_targetTouchUp performSelectorOnMainThread:_actionTouchUp withObject:_targetTouchUp waitUntilDone:YES]; }} 

Actually this work well on Swift 2.2 on Xcode 7.3

I like FTButtonNode ( richy486/FTButtonNode.swift ) but it’s not possible to specify another size (rather then default texture size) directly during initialization so I’ve added this simple method:

You must copy that under the official custom init method (similar to this) so you have another init method to use:

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

Another important thing is the “selection time”, I’ve seen that in the new devices (iPhone 6) sometime the time between touchesBegan and touchesEnded is too fast and you dont see the changes between defaultTexture and selectedTexture .

With this function:

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

you can re-write the touchesEnded method to show correctly the texture variation:

 override func touchesEnded(touches: Set, 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) } } 

I wasn’t convinced of any of the above options, so based on the latest Swift4 I created my own solution .

Unfortunately SpriteKit does not have button node, I do not know why, because it is very useful control. So I decided to create my own and share via CocoaPods, please use it OOButtonNode . Buttons can use text/background or images, written in Swift 4.

Here’s a simple button written with modern Swift (4.1.2)

Caracteristicas

  • it accepts 2 image names, 1 for the default state and one for the active state
  • the developer can set the touchBeganCallback and touchEndedCallback closures to add custom behaviour

Código

 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, with event: UIEvent?) { self.texture = textureActive touchBeganCallback?() } override func touchesEnded(_ touches: Set, with event: UIEvent?) { self.texture = textureDefault touchEndedCallback?() } } 

Cómo usarlo

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