Estoy descubriendo que UIButtons
no funciona muy bien con SKScene
, así que intento SKNode
crear una subclase para hacer un botón SpriteKit
.
La forma en que me gustaría que funcionara es que si inicializo un botón SKScene
y habilito los eventos táctiles, 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.
ios
objective-c
uibutton
sprite-kit
AlexHeuman
fuente
fuente
Respuestas:
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 de nombre de 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 un 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... } }
fuente
He creado mi propia Button-Class con la que estoy trabajando. 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); }
Un ejemplo: para inicializar un botón, escribe 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, necesita el método 'buttonAction' en su clase. * No hay garantía de que esta clase funcione correctamente en todos los casos. Todavía soy bastante nuevo en el objetivo-c. *
Si cree que tener que hacer esto es molesto y sin sentido, puede deshabilitar la verificación en la configuración de compilación configurando 'Habilitar la verificación estricta de
objc_msgSend Calls'
aNo
'fuente
objc_msgSend
lugar de[target performSelector:selector]
?self
al final así:objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)
¡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) } } }
fuente
Si lo desea, puede utilizar UIButton (o cualquier otro UIView).
Cuando
SKScene
se crea un, todavía no existe en unSKView
. Debe implementardidMoveToView:
en suSKScene
subclase. En este punto, tiene acceso a laSKView
escena en la que se coloca y puede agregarleUIKit
objetos. Por bondad, los desvanecí ...- (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) { ; }]; }
Deberá eliminarlos deliberadamente de la escena cuando haga la transición, 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 programática de un
UIButton
, aquí hay un ejemplo (podría hacer 100 cosas de manera diferente aquí) ...- (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
UIView
origen está en la parte superior izquierda, elSKScene
origen está en la parte inferior izquierda.fuente
He usado la clase SKButton de Graf .
Utilizo SKButton para navegar por la escena. es decir, presentar otra escena cuando el usuario presione el botón SK. Me sale un
EXC_BAD_ACCESS
error entouchesEnded->[self setIsSelected:NO]
. Esto sucede con especial 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 el
setIsSelected
se llama a función. Esto se debe a que utilizo SKButton para navegar a la siguiente escena y esto también significa que la escena actual se puede desasignar en cualquier momento.Hice un pequeño cambio colocando setIsSelected en la parte "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); }
fuente
Aquí hay otra versión basada en el código Swift de Filip. 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.
fuente
Editar: ¡He creado un repositorio de github para mi SKButtonNode que espero mantener actualizado y actualizado a medida que evolucione Swift!
SKButtonNode
Desafortunadamente, todavía no puedo comentar sobre la rápida implementación de SKButton en Swift por parte de Filip. ¡Muy 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 característica enorme para mí, por lo que no tiene que crear activos separados para cada botón, sino solo el fondo y agregar texto dinámico.
Agregué una función simple para agregar una etiqueta de texto a SKButton. Probablemente 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 }
Ejemplo de creación 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) } }
}
fuente
¡Qué grandes soluciones a este problema! Para los scrollers incondicionales que llegan hasta aquí, ¡les espera un regalo! He subclasificado
SKScene
, y se necesita UNA llamada de función para registrar CUALQUIER nodo para que actúe como unUIButton
! 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<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 } } //------------------------------------------------------------------------------------
}
Incluye muchas ideas que aún no he implementado y algunas explicaciones del código, pero simplemente cópielo y péguelo en su proyecto, y puede usarlo como está en su propia escena. Aquí hay un ejemplo de uso completo:
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 se implementa
touchesBegan
,touchesMoved
,touchesEnded
, y / otouchesCancelled
que debe llamar a super! 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
UIButton
característica de NODO ! Fue esta línea:Siempre estoy abierto a ideas y sugerencias. Déjalos en los comentarios y Happy Coding !!
Ups, olvidé mencionar que uso esta ingeniosa extensión. Puede sacarlo de una extensión (ya que probablemente no lo necesite en todos los nodos) y colocarlo en mi clase. Solo lo uso en un 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 }
}
fuente
class ConfusedScene : KCScene {
. Luego, en el interior,ConfusedScene
solo crea una función para hacer lo que quieras cuando se presiona el botón. Hice esto:func playButtonPushed() { /*do whatever happens when play button is pushed*/}
. Por qué esto funciona es demasiado complicado para explicarlo aquí, pero puede leer sobre los cierres aquí .Mi solución para resolver este problema escrita completamente en SWIFT, usando cierres.
¡Es bastante simple de usar! 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..") } }
fuente
Había creado una clase para usar SKSpriteNode como botón hace bastante tiempo. Puedes encontrarlo en GitHub aquí.
AGSpriteButton
Su implementación se basa en UIButton, por lo que si ya está familiarizado con iOS, le resultará fácil trabajar con él.
También se le puede asignar un bloque o una SKAction para que se ejecute cuando se presione el botón.
También incluye un método para configurar una etiqueta.
Normalmente, un botón se declarará así:
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. Eres bueno para ir.
fuente
Y dado que no todos estamos apuntando a iOS, aquí está el comienzo de un código que escribí para manejar la interacción del mouse en la Mac.
Pregunta para los gurús: ¿MacOS ofrece eventos táctiles cuando se usa un trackpad? ¿O se envían a SpriteKit como eventos de mouse?
Otra pregunta para los gurús, ¿no debería esta clase llamarse correctamente SKButton Node ?
De todos modos, prueba esto ...
#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
fuente
He subclasificado la
SKScene
clase y he logrado el problema de resolver los toques de botones en este proyecto.https://github.com/Prasad9/SpriteKitButton
En él, se deben nombrar todos los nodos que es necesario conocer en el momento de la extracción.
Además de detectar el toque de un botón, este proyecto también le permite detectar si el toque en un nodo en particular ha comenzado o terminado.
Para obtener la acción de tocar, anule el siguiente método en su archivo de escena.
- (void)touchUpInsideOnNodeName:(NSString *)nodeName atPoint:(CGPoint)touchPoint { // Your code here. }
Para conocer el inicio del toque en un cuerpo en particular, anule el siguiente método en su archivo de escena.
- (void)touchBeginOnNodeName:(NSString *)nodeName { // Your code here. }
Para conocer el final del toque en un cuerpo en particular, anule el siguiente método en su archivo de escena.
- (void)touchEndedOnNodeName:(NSString *)nodeName { // Your code here. }
fuente
La solución de Graf tiene un problema. 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 es un SKNode, una propiedad de mi escena. Entonces, obtendrá una excepción, debido al método touchchesEnded en SKButton. Llamará a [SKSpriteNode pauseButtonPressed], no con scene.
La solución para cambiar self.parent a 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]; }}
fuente
En realidad, esto funciona bien en Swift 2.2 en Xcode 7.3
Me gusta FTButtonNode ( richy486 / FTButtonNode.swift ) pero no es posible especificar otro tamaño (en lugar del tamaño de textura predeterminado) directamente durante la inicialización, así que agregué este método simple:
Debe copiar eso bajo el método de inicio personalizado oficial (similar a este) para que tenga otro método de inicio para usar:
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) }
Otra cosa importante es el "tiempo de selección", he visto que en los nuevos dispositivos (iPhone 6) en algún momento el tiempo entre
touchesBegan
ytouchesEnded
es demasiado rápido y no ves los cambios entredefaultTexture
yselectedTexture
.Con esta función:
func dispatchDelay(delay:Double, closure:()->()) { dispatch_after( dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)) ), dispatch_get_main_queue(), closure) }
puede volver a escribir el
touchesEnded
método para mostrar correctamente la variación de textura: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) } }
fuente
No estaba convencido de ninguna de las opciones anteriores, así que basándome en el último Swift4 creé mi propia solución .
fuente
Lamentablemente SpriteKit no tiene un nodo de botón, no sé por qué, porque es un control muy útil. Así que decidí crear el mío y compartirlo a través de CocoaPods, utilícelo OOButtonNode . Los botones pueden usar texto / fondo o imágenes, escritas en Swift 4.
fuente
Aquí hay un botón simple escrito con Swift moderno (4.1.2)
Caracteristicas
touchBeganCallback
ytouchEndedCallback
para agregar un comportamiento personalizadoCó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<UITouch>, with event: UIEvent?) { self.texture = textureActive touchBeganCallback?() } override func touchesEnded(_ touches: Set<UITouch>, 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) } }
fuente