在SKScene中设置button
我发现UIButtons
不能很好的与SKScene
,所以我试图SKNode
在SpriteKit
的button。
我希望它的工作方式是,如果我在SKScene
初始化一个button并启用触摸事件,那么当按下button时,button将在我的SKScene
调用一个方法。
我会很感激任何意见,会导致我find解决这个问题。 谢谢。
您可以使用SKSpriteNode作为button,然后当用户触摸时,检查是否触摸了节点。 使用SKSpriteNode的name属性来标识节点:
//fire button - (SKSpriteNode *)fireButtonNode { SKSpriteNode *fireNode = [SKSpriteNode spriteNodeWithImageNamed:@"fireButton.png"]; fireNode.position = CGPointMake(fireButtonX,fireButtonY); fireNode.name = @"fireButtonNode";//how the node is identified later fireNode.zPosition = 1.0; return fireNode; }
添加节点到你的场景:
[self addChild: [self fireButtonNode]];
处理触摸:
//handle touch events - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInNode:self]; SKNode *node = [self nodeAtPoint:location]; //if fire button touched, bring the rain if ([node.name isEqualToString:@"fireButtonNode"]) { //do whatever... } }
我制作了我自己的Button-Class。 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); }
一个例子:要初始化一个button,你写下面几行:
SKButton *backButton = [[SKButton alloc] initWithImageNamedNormal:@"buttonNormal" selected:@"buttonSelected"]; [backButton setPosition:CGPointMake(100, 100)]; [backButton.title setText:@"Button"]; [backButton.title setFontName:@"Chalkduster"]; [backButton.title setFontSize:20.0]; [backButton setTouchUpInsideTarget:self action:@selector(buttonAction)]; [self addChild:backButton];
而且你需要在你的类中使用“buttonAction”方法。 *不保证这个class级在任何情况下都能正常工作。 我还是很新的objective-c。 *
对于用Swift编写游戏的人来说! 我已经将Graf的解决scheme的基本部分改写成了一个快速的课程。 希望它有助于:
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) } } }
如果你愿意,你可以使用UIButton(或任何其他的UIView)。
SKScene
创build时, SKScene
中不存在。 你应该在你的SKScene
子类上实现didMoveToView:
SKScene
此时,您可以访问放置场景的SKView
,并且可以向其中添加UIKit
对象。 为了可爱,我淡化了他们…
- (void)didMoveToView:(SKView *)view { UIView *b = [self _createButton]; // <-- performs [self.view addSubview:button] // create other UI elements, also add them to the list to remove … self.customSubviews = @[b]; b.alpha = 0; [UIView animateWithDuration:0.4 delay:2.4 options:UIViewAnimationOptionCurveEaseIn animations:^{ b.alpha = 1; } completion:^(BOOL finished) { ; }]; }
当你离开的时候,你将需要故意将它们从场景中移除,除非它们留在那里是完全合理的。
- (void)removeCustomSubviews { for (UIView *v in self.customSubviews) { [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ v.alpha = 0; } completion:^(BOOL finished) { [v removeFromSuperview]; }]; } }
对于那些不熟悉以编程方式创buildUIButton
,这里有一个例子(你可以在这里做100个不同的事情)…
- (UIButton *)_createButton { UIButton *b = [UIButton buttonWithType:UIButtonTypeCustom]; [b setTitle:@"Continue" forState:UIControlStateNormal]; [b setBackgroundImage:[UIImage imageNamed:@"GreenButton"] forState:UIControlStateNormal]; [b setBackgroundImage:[UIImage imageNamed:@"GreenButtonSelected"] forState:UIControlStateHighlighted]; b.titleLabel.adjustsFontSizeToFitWidth = YES; b.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:36]; b.frame = CGRectMake(self.size.width * .7, self.size.height * .2, self.size.width * .2, self.size.height * .1); [b addTarget:self action:@selector(continuePlay) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:b]; return b; }
提醒: UIView
原点位于左上angular, SKScene
原点位于左下angular。
我已经使用Graf的 SKButton类。
我使用SKButton来做场景导航。 即当用户按下SKButton时呈现另一个场景。 我在touchesEnded->[self setIsSelected:NO]
处得到EXC_BAD_ACCESS
错误。 在CPU速度最快的最新iPad上,这种情况尤其频繁发生。
在检查和排除故障之后,我意识到当调用setIsSelected
函数时, setIsSelected
对象已经“解除分配”了。 这是因为我使用SKButton导航到下一个场景,这也意味着当前场景可以随时解除分配。
我通过将setIsSelected放在“else”部分中做了一个小改动,如下所示。
希望这有助于其他开发人员也看到相同的错误。
(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); }
这是基于Filip的Swift代码的另一个版本。 我只是简化了一下,并允许它采取块而不仅仅是select器:
import Foundation import SpriteKit enum FTButtonTarget { case aSelector(Selector, AnyObject) case aBlock(() -> Void) } class FTButtonNode: SKSpriteNode { var actionTouchUp : FTButtonTarget? var actionTouchUpInside : FTButtonTarget? var actionTouchDown : FTButtonTarget? var isEnabled: Bool = true { didSet { if (disabledTexture != nil) { texture = isEnabled ? defaultTexture : disabledTexture } } } var isSelected: Bool = false { didSet { texture = isSelected ? selectedTexture : defaultTexture } } var defaultTexture: SKTexture var selectedTexture: SKTexture required init(coder: NSCoder) { fatalError("NSCoding not supported") } init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) { self.defaultTexture = defaultTexture self.selectedTexture = selectedTexture self.disabledTexture = disabledTexture super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size()) userInteractionEnabled = true // Adding this node as an empty layer. Without it the touch functions are not being called // The reason for this is unknown when this was implemented...? let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size()) bugFixLayerNode.position = self.position addChild(bugFixLayerNode) } var disabledTexture: SKTexture? func callTarget(buttonTarget:FTButtonTarget) { switch buttonTarget { case let .aSelector(selector, target): if target.respondsToSelector(selector) { UIApplication.sharedApplication().sendAction(selector, to: target, from: self, forEvent: nil) } case let .aBlock(block): block() } } override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (!isEnabled) { return } isSelected = true if let act = actionTouchDown { callTarget(act) } } override func touchesMoved(touches: NSSet, withEvent event: UIEvent) { if (!isEnabled) { return } let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (CGRectContainsPoint(frame, touchLocation)) { isSelected = true } else { isSelected = false } } override func touchesEnded(touches: NSSet, withEvent event: UIEvent) { if (!isEnabled) { return } isSelected = false let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (CGRectContainsPoint(frame, touchLocation) ) { if let act = actionTouchUpInside { callTarget(act) } } if let act = actionTouchUp { callTarget(act) } } }
像这样使用它:
aFTButton.actionTouchUpInside = FTButtonTarget.aBlock({ () -> Void in println("button touched") })
希望这可以帮助。
编辑:我已经做了我的SKButtonNode的github回购,我希望保持当前和更新迅速发展!
SKButtonNode
不幸的是,我还不能评论Filip在Swift中快速实现SKButton。 超级快乐,他在Swift做了这个! 但是,我注意到他没有包含一个函数来添加文本到button。 这对我来说是一个巨大的function,所以您不必为每个button创build单独的资源,而只需要为背景添加dynamic文本。
我添加了一个简单的函数来添加一个文本标签到SKButton。 这可能不是完美的 – 我和其他人一样,对Swift很陌生! 随时发表评论,并帮助我更新到最好的可能。 希望你们喜欢!
//Define label with the textures var defaultTexture: SKTexture var selectedTexture: SKTexture //New defining of label var label: SKLabelNode //Updated init() function: init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) { self.defaultTexture = defaultTexture self.selectedTexture = selectedTexture self.disabledTexture = disabledTexture //New initialization of label self.label = SKLabelNode(fontNamed: "Helvetica"); super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size()) userInteractionEnabled = true //Creating and adding a blank label, centered on the button self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center; self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center; addChild(self.label) // Adding this node as an empty layer. Without it the touch functions are not being called // The reason for this is unknown when this was implemented...? let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size()) bugFixLayerNode.position = self.position addChild(bugFixLayerNode) } /* New function for setting text. Calling function multiple times does not create a ton of new labels, just updates existing label. You can set the title, font type and font size with this function */ func setButtonLabel(#title: NSString, font: String, fontSize: CGFloat) { var title = title var font = font var fontSize = fontSize self.label.text = title self.label.fontSize = fontSize self.label.fontName = font }
样例创buildbutton:
var buttonTexture = SKTexture(imageNamed: "Button"); var buttonPressedTexture = SKTexture(imageNamed: "Button Pressed"); var button = SKButton(normalTexture:buttonTexture, selectedTexture:buttonPressedTexture, disabledTexture:buttonPressedTexture); button.setButtonLabel(title: "Play",font: "Helvetica",fontSize: 40); button.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2); self.addChild(button);
以下全部类别:
import Foundation import SpriteKit class SKButton: SKSpriteNode { enum FTButtonActionType: Int { case TouchUpInside = 1, TouchDown, TouchUp } var isEnabled: Bool = true { didSet { if (disabledTexture != nil) { texture = isEnabled ? defaultTexture : disabledTexture } } } var isSelected: Bool = false { didSet { texture = isSelected ? selectedTexture : defaultTexture } } var defaultTexture: SKTexture var selectedTexture: SKTexture var label: SKLabelNode required init(coder: NSCoder) { fatalError("NSCoding not supported") } init(normalTexture defaultTexture: SKTexture!, selectedTexture:SKTexture!, disabledTexture: SKTexture?) { self.defaultTexture = defaultTexture self.selectedTexture = selectedTexture self.disabledTexture = disabledTexture self.label = SKLabelNode(fontNamed: "Helvetica"); super.init(texture: defaultTexture, color: UIColor.whiteColor(), size: defaultTexture.size()) userInteractionEnabled = true self.label.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center; self.label.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center; addChild(self.label) // Adding this node as an empty layer. Without it the touch functions are not being called // The reason for this is unknown when this was implemented...? let bugFixLayerNode = SKSpriteNode(texture: nil, color: nil, size: defaultTexture.size()) bugFixLayerNode.position = self.position addChild(bugFixLayerNode) } /** * Taking a target object and adding an action that is triggered by a button event. */ func setButtonAction(target: AnyObject, triggerEvent event:FTButtonActionType, action:Selector) { switch (event) { case .TouchUpInside: targetTouchUpInside = target actionTouchUpInside = action case .TouchDown: targetTouchDown = target actionTouchDown = action case .TouchUp: targetTouchUp = target actionTouchUp = action } } func setButtonLabel(#title: NSString, font: String, fontSize: CGFloat) { var title = title; var font = font; var fontSize = fontSize; self.label.text = title; self.label.fontSize = fontSize; self.label.fontName = font; } var disabledTexture: SKTexture? var actionTouchUpInside: Selector? var actionTouchUp: Selector? var actionTouchDown: Selector? weak var targetTouchUpInside: AnyObject? weak var targetTouchUp: AnyObject? weak var targetTouchDown: AnyObject? override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) { let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (!isEnabled) { return } isSelected = true if (targetTouchDown != nil && targetTouchDown!.respondsToSelector(actionTouchDown!)) { UIApplication.sharedApplication().sendAction(actionTouchDown!, to: targetTouchDown, from: self, forEvent: nil) } } override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) { if (!isEnabled) { return } let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (CGRectContainsPoint(frame, touchLocation)) { isSelected = true } else { isSelected = false } } override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) { if (!isEnabled) { return } isSelected = false if (targetTouchUpInside != nil && targetTouchUpInside!.respondsToSelector(actionTouchUpInside!)) { let touch: AnyObject! = touches.anyObject() let touchLocation = touch.locationInNode(parent) if (CGRectContainsPoint(frame, touchLocation) ) { UIApplication.sharedApplication().sendAction(actionTouchUpInside!, to: targetTouchUpInside, from: self, forEvent: nil) } } if (targetTouchUp != nil && targetTouchUp!.respondsToSelector(actionTouchUp!)) { UIApplication.sharedApplication().sendAction(actionTouchUp!, to: targetTouchUp, from: self, forEvent: nil) } }
}
这个问题有什么好的解决scheme! 对于现在这样的铁杆滚轮来说,这是一种享受! 我已经子类SKScene
,它需要一个函数调用来注册任何节点行事像一个UIButton
! 这是class级:
class KCScene : SKScene { //------------------------------------------------------------------------------------ //This function is the only thing you use in this class!!! func addButton(_ node:SKNode, withCompletionHandler handler: @escaping ()->()) { let data = ButtonData(button: node, actionToPerform: handler) eligibleButtons.append(data) } //------------------------------------------------------------------------------------ private struct ButtonData { //TODO: make a dictionary with ()->() as the value and SKNode as the key. //Then refactor this class! let button:SKNode let actionToPerform:()->() } private struct TouchTrackingData { //this will be in a dictionary with a UITouch object as the key let button:SKNode let originalButtonFrame:CGRect } private var eligibleButtons = [ButtonData]() private var trackedTouches = [UITouch:TouchTrackingData]() //------------------------------------------------------------------------------------ //TODO: make these functions customizable, //with these implementations as defaults. private func applyTouchedDownEffectToNode(node:SKNode) { node.alpha = 0.5 node.xScale = 0.8 node.yScale = 0.8 } private func applyTouchedUpEffectToNode(node:SKNode) { node.alpha = 1 node.xScale = 1 node.yScale = 1 } //------------------------------------------------------------------------------------ override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { for touch in touches { let touchLocation = touch.location(in: self) let touchedNode = atPoint(touchLocation) for buttonData in eligibleButtons { if touchedNode === buttonData.button { //then this touch needs to be tracked, as it touched down on an eligible button! for (t, bD) in trackedTouches { if bD.button === buttonData.button { //then this button was already being tracked by a previous touch, disable the previous touch trackedTouches[t] = nil } } //start tracking this touch trackedTouches[touch] = TouchTrackingData(button: touchedNode, originalButtonFrame: touchedNode.frameInScene) applyTouchedDownEffectToNode(node: buttonData.button) } } } } //------------------------------------------------------------------------------------ override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { for touch in touches { if trackedTouches[touch] == nil {continue} //Now we know this touch is being tracked... let touchLocation = touch.location(in: self) //TODO: implement an isBeingTouched property on TouchTrackingData, so //applyTouchedDown(Up)Effect doesn't have to be called EVERY move the touch makes if trackedTouches[touch]!.originalButtonFrame.contains(touchLocation) { //if this tracked touch is touching its button applyTouchedDownEffectToNode(node: trackedTouches[touch]!.button) } else { applyTouchedUpEffectToNode(node: trackedTouches[touch]!.button) } } } //------------------------------------------------------------------------------------ override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { for touch in touches { if trackedTouches[touch] == nil {continue} //Now we know this touch is being tracked... let touchLocation = touch.location(in: self) if trackedTouches[touch]!.originalButtonFrame.contains(touchLocation) { applyTouchedUpEffectToNode(node: trackedTouches[touch]!.button) for buttonData in eligibleButtons { if buttonData.button === trackedTouches[touch]!.button { buttonData.actionToPerform() } } } trackedTouches[touch] = nil } } //------------------------------------------------------------------------------------ override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) { for touch in touches! { if trackedTouches[touch] == nil {continue} //Now we know this touch is being tracked... //Since this touch was cancelled, it will not be activating a button, //and it is not worth checking where the touch was //we will simply apply the touched up effect regardless and remove the touch from being tracked applyTouchedUpEffectToNode(node: trackedTouches[touch]!.button) trackedTouches[touch] = nil } } //------------------------------------------------------------------------------------
}
它包含了许多我还没有实现的想法和对代码的一些解释,只是复制并粘贴到你的项目中,你可以在你自己的场景中使用它。 这是一个完整的示例用法:
class GameScene : KCScene { var playButton:SKSpriteNode override init(size:CGSize) { playButton = SKSpriteNode(color: SKColor.red, size: CGSize(width:200,height:200)) playButton.position.x = size.width/2 playButton.position.y = size.height*0.75 super.init(size: size) } override func didMove(to view: SKView) { addChild(playButton) addButton(playButton, withCompletionHandler: playButtonPushed) } func playButtonPushed() { let scene = GameScene(size: CGSize(width: 768, height: 1024)) scene.scaleMode = .aspectFill view!.presentScene(scene) } }
一个警告,就是如果你执行touchesBegan
, touchesMoved
, touchesEnded
,和/或touchesCancelled
你必须调用超级! 否则它将无法工作。
请注意,在这个例子中,实际上只有一行代码需要赋予任何NODE UIButton
特性! 这是这一行:
addButton(playButton, withCompletionHandler: playButtonPushed)
我总是开放的想法和build议。 留下他们在评论和快乐编码!
哎呀,我忘了提及我使用这个漂亮的扩展名。 你可以把它从一个扩展中取出(因为你可能在每个节点都不需要它)并把它放在我的课程中。 我只在一个地方使用它。
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];
就是这样。 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. 例如:
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<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) } }