Настройка кнопок в SKScene
Я открываю это UIButtons
не очень хорошо работает с SKScene
, поэтому я пытаюсь подкласс SKNode
сделать закладку в SpriteKit
.
способ, которым я хотел бы, чтобы это работало, заключается в том, что если я инициализирую кнопку в SKScene
и включить события касания, то кнопка вызовет метод в my SKScene
при нажатии.
Я был бы признателен за любые советы, которые привели бы меня к поиску решения этой проблемы. Спасибо.
17 ответов:
вы можете использовать SKSpriteNode в качестве кнопки, а затем, когда пользователь коснется, проверьте, был ли это затронутый узел. Используйте свойство 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... } }
Я сделал свой собственный класс кнопок, с которым я работаю. Скаттон.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.м:
#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); }
пример: чтобы инициализировать кнопку, вы пишете следующие строки:
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" в вашем классе. * нет гарантии, что этот класс работает в каждом случае. Я еще совсем новичок в objective-c.*
для людей, пишущих свои игры в Swift! Я переписал основные части решения графа для быстрого класса. Надеюсь, это поможет:
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).
Когда a
SKScene
создается, он еще не существует вSKView
. Вы должны реализовать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]; }]; } }
для тех, кто не знаком с программным созданием
UIButton
, вот один пример (вы могли бы сделать 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
начало координат находится в левом верхнем углу,SKScene
начало координат находится в левом нижнем углу.
я использовал SKButton class by Граф.
Я использую кнопку SKButton для навигации по сцене. т. е. представить другую сцену, когда пользователь нажимает кнопку SKButton. Я получаю
EXC_BAD_ACCESS
ошибкаtouchesEnded->[self setIsSelected:NO]
. Это происходит особенно часто на последнем iPad с быстрым процессором.после проверки и устранения неполадок я понял, что объект SKButton уже "освобожден", когда
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); }
вот еще одна версия, основанная на Swift код Филипа. Я просто немного упростил его и позволил ему принимать блоки, а не только селекторы:
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") })
надеюсь, что это помогает.
Edit: я сделал репозиторий github для своего SKButtonNode, который, надеюсь, будет обновляться и обновляться по мере развития swift!
к сожалению, я пока не могу прокомментировать быструю реализацию Филипом SKButton в Swift. Очень рад, что он сделал это в Swift! Но я заметил, что он не включил функцию добавления текста к кнопке. Это огромная функция для меня, так что вам не нужно создавать отдельные активы для каждого одна кнопка, а просто фон и добавить динамический текст.
я добавил простую функцию для добавления текстовой метки в 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 }
пример создания кнопки:
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) } }
}
какое множество отличных решений этой проблемы! Для хардкорных скроллеров, которые делают это так далеко, вы находитесь в для удовольствия! У меня есть подкласс
SKScene
, и для регистрации любого узла требуется один вызов функции, чтобы действовать какUIButton
! Вот класс: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
вы должны позвонить супер! Иначе ничего не получится.и, пожалуйста, поймите, что в этом примере действительно есть только одна строка кода, которую вам нужно дать любому узлу
UIButton
характеристики! Это была такая строчка:addButton(playButton, withCompletionHandler: playButtonPushed)
Я всегда открыт для идей и предложений. Оставьте их в комментариях и счастливого кодирования!!
Упс, Я забыл упомянуть, что я использую это отличное расширение. Вы можете взять его из расширения (поскольку вам, вероятно, не нужно это в каждом узле) и хлопнуть его в моем классе. Я использую его только в одном месте.
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 }
}
мое решение для решения этой проблемы написано полностью в SWIFT, используя замыкания.
его довольно простой в использовании! 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..") } }
Я создал класс для использования SKSpriteNode в качестве кнопки довольно давно. Вы можете найти его на GitHub здесь.
это реализация основана на UIButton, так что если вы уже знакомы с iOS, вы должны найти его легко работать.
Он также может быть назначен блок или SKAction для выполнения при нажатии кнопки.
Он включает в себя метод для настройки метки как что ж.
кнопка обычно объявляется так:
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];
и это все. Ты можешь идти.
и поскольку все мы не нацелены на iOS, вот начало некоторого кода, который я написал для обработки взаимодействия с мышью на Mac.
вопрос для гуру: предлагает ли MacOS сенсорные события при использовании трекпада? Или они отправляются в SpriteKit как события мыши?
еще один вопрос для гуру, не должен ли этот класс правильно называться SKButtonузел?
в любом случае, попробовать это...
#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
у меня есть подкласс
SKScene
класс и достиг проблемы решения кнопок кранов в этом проекте.https://github.com/Prasad9/SpriteKitButton
в нем должны быть названы все узлы, которые необходимо знать при нажатии.
в дополнение к обнаружению нажатия кнопки, этот проект также позволяет определить, началось ли касание на определенном узле или закончилось.
чтобы получить действие крана, переопределите следующее метод в вашем файле сцены.
- (void)touchUpInsideOnNodeName:(NSString *)nodeName atPoint:(CGPoint)touchPoint { // Your code here. }
чтобы узнать начало прикосновения к определенному телу, переопределите следующий метод в файле сцены.
- (void)touchBeginOnNodeName:(NSString *)nodeName { // Your code here. }
чтобы узнать конец прикосновения к определенному телу, переопределите следующий метод в файле сцены.
- (void)touchEndedOnNodeName:(NSString *)nodeName { // Your code here. }
решение графа имеет одну проблему. Например:
self.pauseButton = [[AGSKBButtonNode alloc] initWithImageNamed:@"ButtonPause"]; self.pauseButton.position = CGPointMake(0, 0); [self.pauseButton setTouchUpInsideTarget:self action:@selector(pauseButtonPressed)]; [_hudLayer addChild:_pauseButton];
_hudLayer-это SKNode, свойство моей сцены. Таким образом, вы получите исключение, из-за метода touchesEnded в SKButton. Он будет вызывать [SKSpriteNode pauseButtonPressed], а не со сценой.
решение изменить себя.родитель, чтобы коснуться цели:
- (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]; }}
на самом деле это хорошо работать на Swift 2.2 on Xcode 7.3
мне нравится FTButtonNode (richy486 / FTButtonNode.быстрый ) но невозможно указать другой размер (а не размер текстуры по умолчанию) непосредственно во время инициализации, поэтому я добавил Этот простой метод:
вы должны скопировать это в соответствии с официальным пользовательским методом инициализации (подобным этому), чтобы у вас был другой метод инициализации использование:
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) }
еще одна важная вещь-это "время выбора", я видел, что в новых устройствах (iPhone 6) Иногда время между
touchesBegan
иtouchesEnded
слишком быстро и вы не видите изменений междуdefaultTexture
иselectedTexture
.С помощью этой функции:
func dispatchDelay(delay:Double, closure:()->()) { dispatch_after( dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)) ), dispatch_get_main_queue(), closure) }
вы можете переписать
touchesEnded
метод, чтобы правильно показать изменение текстуры: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) } }
Я не был убежден ни в одном из вышеперечисленных вариантов, поэтому на основе последней Swift4 я создал мое собственное решение.
к сожалению SpriteKit не имеет узла кнопки, я не знаю, почему, потому что это очень полезный контроль. Поэтому я решил создать свой собственный и поделиться через CocoaPods, пожалуйста, используйте его OOButtonNode. Кнопки могут использовать текст / фон или изображения, написанные в Swift 4.
вот простая кнопка, написанная с помощью современного Swift (4.1.2)
особенности
- он принимает 2 имени изображений, 1 для состояния по умолчанию и один для активного состояния
- разработчик может установить
touchBeganCallback
иtouchEndedCallback
закрытие для добавления пользовательского поведениякод
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?() } }
как использовать
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) } }