在 SKScene 中设置按钮
Posted
技术标签:
【中文标题】在 SKScene 中设置按钮【英文标题】:Setting up buttons in SKScene 【发布时间】:2013-10-05 14:33:58 【问题描述】:我发现UIButtons
不能很好地与SKScene
配合使用,所以我试图继承SKNode
以在SpriteKit
中创建一个按钮。
我希望它的工作方式是,如果我在SKScene
中初始化一个按钮并启用触摸事件,那么该按钮将在按下时调用我的SKScene
中的一个方法。
如果有任何建议可以引导我找到解决此问题的方法,我将不胜感激。谢谢。
【问题讨论】:
我正在寻找更多的学习经验和解决方案。我认为正确的解决方案是将 SKScene 设置为按钮的代表,但我不确定如何执行此操作。我可以将 SKScene 设置为按钮的实例变量并调用它的方法吗? 您可以做很多事情,委托或更灵活地使用 NSNotification 以便任何节点都可以响应它。如果您使用委托,请确保将委托属性设置为弱。 我发现this code 有助于创建精灵套件按钮。它扩展了 SKSpriteKitNode 并允许您轻松地将文本添加到按钮。 【参考方案1】:您可以使用 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...
【讨论】:
如果您添加一个 iVar 作为按钮,您可以删除名称检查并只使用 if ([_fireNode containsPoint:location]) 做同样的事情只是不同。 比较字符串是一个肮脏的解决方案。尽管@Smick 的解决方案更好,但没有其他更清洁的方法可以实现这一点吗? 嘿,我们不能像 SkLabelNode 那样在 SpriteKit 中添加按钮吗? 这是否允许多点触控事件?例如同时单击 2 个按钮?其中一个是移动按钮,另一个是开火按钮。【参考方案2】:我已经制作了自己的 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);
一个例子:要初始化一个按钮,你写下面几行:
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还是很陌生。 *
如果您认为必须这样做很烦人且毫无意义,您可以通过将“启用objc_msgSend Calls'
的严格检查”设置为“No
”来禁用构建设置中的检查
【讨论】:
感谢分享。您使用objc_msgSend
而不是[target performSelector:selector]
有什么原因吗?
啊,是的,该死的 ARC。我忘记了那个警告:|如果您有兴趣,这里有一个不错的解决方法***.com/questions/11895287/…
上面的代码很棒,但是在尝试使用 - (void)changeToScene:(SKButtonNode *)sender 作为@selector 时出现错误。如果可以的话,我宁愿使用单一方法通过 sender.name 切换场景。
感谢分享!我将它包含在我的代码中。我们会看看它是否有效。一个建议:将类的名称从 SKButton 更改为对您而言更独特的名称,例如 GRFButton。在某些时候,Apple 可能会引入一个 SKButton,而您不想混淆命名空间并在以后破坏您的代码。
@BeauYoung - 当您在末尾添加 self
时,它可以工作,如下所示:objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)
【参考方案3】:
献给使用 Swift 编写游戏的人们! 我已经将 Graf 解决方案的基本部分重写为 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)
【讨论】:
【参考方案4】:如果您愿意,您可以使用 UIButton(或任何其他 UIView)。
创建SKScene
时,SKView
中尚不存在它。你应该在你的SKScene
子类上实现didMoveToView:
。此时,您可以访问场景所在的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
原点在左下角。
【讨论】:
【参考方案5】:我用过Graf的SKButton类。
我使用 SKButton 进行场景导航。即当用户按下 SKButton 时呈现另一个场景。我在touchesEnded->[self setIsSelected:NO]
收到EXC_BAD_ACCESS
错误。这种情况在具有快速 CPU 的最新 iPad 上尤为常见。
经过检查和故障排除后,我意识到在调用setIsSelected
函数时,SKButton 对象已经“释放”了。这是因为我使用 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);
【讨论】:
请格式化您的帖子和源代码,这样很难阅读!【参考方案6】:这是另一个基于 Filip 的 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")
)
希望这会有所帮助。
【讨论】:
【参考方案7】:编辑:我已经为我的 SKButtonNode 创建了一个 github 存储库,希望能保持最新状态并随着 swift 的发展而更新!
SKButtonNode
很遗憾,我还不能评论 Filip 在 Swift 中快速实现 SKButton。非常高兴他在 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)
【讨论】:
在此处更新了 swift 2.1 的代码:gist.github.com/richy486/5d408c442ac1c0c2891f ..我在这里更新到 Swift 3:github.com/jglasse/SKButtonSwift3【参考方案8】:这个问题有很多很棒的解决方案!对于能够做到这一点的铁杆卷轴,你会得到款待!我已将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
,您必须调用 SUPER!否则将无法正常工作。
请注意,在该示例中,您实际上只需要一行代码即可为任何节点提供UIButton
特征!就是这一行:
addButton(playButton, withCompletionHandler: playButtonPushed)
我总是乐于接受想法和建议。把它们留在 cmets 中,祝编程愉快!!
糟糕,我忘了提到我使用了这个漂亮的扩展程序。您可以将其从扩展中取出(因为您可能不需要在每个节点中使用它)并将其放在我的课堂上。我只在一个地方使用它。
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
【讨论】:
这如何确保 playButtonPushed 完成功能可访问?或者我应该把 playButtonPushed 函数放在哪里以确保 KScene 实例可以访问它,我假设它是按钮? @Confused 你会让你自己的场景成为 KCScene 的子类而不是 SKScene:class ConfusedScene : KCScene
。然后在ConfusedScene
中创建一个函数来在按下按钮时执行您想要的操作。我这样做了:func playButtonPushed() /*do whatever happens when play button is pushed*/
。为什么这个工作太复杂了,无法在这里解释,但你可以阅读关于闭包的信息here。【参考方案9】:
我解决这个问题的解决方案完全用 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..")
【讨论】:
【参考方案10】:很久以前,我创建了一个使用 SKSpriteNode 作为按钮的类。你可以在 GitHub 上找到它。
AGSpriteButton
它的实现是基于 UIButton,所以如果你已经熟悉 ios,你应该会发现它很容易上手。
它也可以被分配一个block或者一个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];
就是这样。你可以走了。
【讨论】:
我们有什么理由不能使用 SKColor 代替 UIColor 吗?如果我们使用 UIColor,我们就会卡在 iOS 上。 您可以轻松地使用 SKColor 代替 UIColor【参考方案11】:由于我们所有人都不是针对 iOS,这里是我为处理 Mac 上的鼠标交互而编写的一些代码的开始。
专家的问题:MacOS 在使用触控板时是否提供触摸事件?还是这些作为鼠标事件发送到 SpriteKit?
大师们的另一个问题,这个类不应该被正确地称为SKButtonNode吗?
不管怎样,试试这个...
#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
【讨论】:
据我所知,OSX 的 Spritekit 只观察鼠标的东西:/ 是的,它的末尾可能应该有 Node 一词。像 SKLabelNode。【参考方案12】:我继承了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.
【讨论】:
【参考方案13】:Graf 的解决方案有一个问题。 例如:
self.pauseButton = [[AGSKBButtonNode alloc] initWithImageNamed:@"ButtonPause"];
self.pauseButton.position = CGPointMake(0, 0);
[self.pauseButton setTouchUpInsideTarget:self action:@selector(pauseButtonPressed)];
[_hudLayer addChild:_pauseButton];
_hudLayer 是一个 SKNode,我的场景的一个属性。因此,由于 SKButton 中的 touchesEnded 方法,您会遇到异常。它将调用 [SKSpriteNode pauseButtonPressed],而不是场景。
修改self.parent为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];
【讨论】:
【参考方案14】:实际上,这在 Xcode 7.3
上的 Swift 2.2 上运行良好我喜欢 FTButtonNode (richy486/FTButtonNode.swift ),但无法在初始化期间直接指定其他大小(而不是默认纹理大小),所以我添加了这个简单的方法:
您必须在官方自定义 init 方法下复制它(类似于此),以便您可以使用另一个 init 方法:
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)
【讨论】:
【参考方案15】:我不相信上述任何选项,因此基于最新的 Swift4 我创建了my own solution。
【讨论】:
【参考方案16】:可惜SpriteKit没有按钮节点,我不知道为什么,因为它是非常有用的控件。所以我决定创建自己的并通过 CocoaPods 分享,请使用它OOButtonNode。 按钮可以使用文本/背景或图像,用 Swift 4 编写。
【讨论】:
【参考方案17】:这是一个用现代 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)
【讨论】:
以上是关于在 SKScene 中设置按钮的主要内容,如果未能解决你的问题,请参考以下文章
touches* 事件 - SKScene 与 ViewController
SKScene 在 presentViewController 之后重复