나는 그것이에서 UIButtons잘 작동하지 않는다는 것을 발견하고 있으므로 에서 버튼을 만들기 SKScene위해 하위 클래스 SKNode를 시도하고 SpriteKit있습니다.
내가 작동하는 방식은 버튼을 초기화하고 SKScene터치 이벤트를 활성화하면 버튼을 SKScene눌렀을 때 버튼이 메소드를 호출한다는 것입니다.
이 문제에 대한 해결책을 찾는 데 도움이 될 조언을 주시면 감사하겠습니다. 감사.
나는 그것이에서 UIButtons잘 작동하지 않는다는 것을 발견하고 있으므로 에서 버튼을 만들기 SKScene위해 하위 클래스 SKNode를 시도하고 SpriteKit있습니다.
내가 작동하는 방식은 버튼을 초기화하고 SKScene터치 이벤트를 활성화하면 버튼을 SKScene눌렀을 때 버튼이 메소드를 호출한다는 것입니다.
이 문제에 대한 해결책을 찾는 데 도움이 될 조언을 주시면 감사하겠습니다. 감사.
답변:
SKSpriteNode를 버튼으로 사용할 수 있으며 사용자가 터치하면 노드가 터치되었는지 확인합니다. SKSpriteNode의 이름 속성을 사용하여 노드를 식별합니다.
//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 클래스를 만들었습니다. 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]있습니까?
self과 같이 끝에 추가하면 작동합니다 .objc_msgSend(_targetTouchUpInside, _actionTouchUpInside, self)
Swift로 게임을 작성하는 사람들을 위해! Graf 솔루션의 핵심 부분을 신속한 클래스로 다시 작성했습니다. 도움이되기를 바랍니다.
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아직 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원점은 왼쪽 하단에 있습니다.
Graf의 SKButton 클래스를 사용했습니다 .
SKButton을 사용하여 장면 탐색을 수행합니다. 즉, 사용자가 SKButton을 누를 때 다른 장면을 표시합니다. 나는 수 EXC_BAD_ACCESS에 오류 touchesEnded->[self setIsSelected:NO]. 이것은 특히 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);
}
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")
})
도움이 되었기를 바랍니다.
편집 : SKButtonNode에 대한 github 저장소를 만들었습니다.이 저장소는 신속하게 발전함에 따라 최신 상태를 유지하고 업데이트 할 것입니다!
불행히도 필립의 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)
}
}
}
이 문제에 대한 많은 훌륭한 해결책이 있습니다! 여기까지 내려 오는 하드 코어 스크롤러에게는 대접을 받고 있습니다! 나는 서브 클래 싱 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를 호출해야합니다! 그렇지 않으면 작동하지 않습니다.
그리고이 예에서는 NODE 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
}
}
class ConfusedScene : KCScene {. 그런 다음 내부 ConfusedScene에서 버튼을 눌렀을 때 원하는 것을 수행하는 기능을 만드십시오. 나는 이것을했다 : func playButtonPushed() { /*do whatever happens when play button is pushed*/}. 이것이 작동하는 이유는 여기에서 설명하기에는 너무 복잡하지만 여기에서 클로저에 대해 읽을 수 있습니다 .
이 문제를 해결하기위한 나의 해결책은 클로저를 사용하여 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 Node 라고 적절하게 불러야하지 않습니까?
어쨌든, 이것을 시도하십시오 ...
#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.
}
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 메서드 때문에 예외가 발생합니다. Scene이 아닌 [SKSpriteNode pauseButtonPressed]를 호출합니다.
self.parent를 터치 대상으로 변경하는 솔루션 :
- (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];
}}
실제로 이것은 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)
}
또 다른 중요한 것은 "선택 시간", 난 (아이폰 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)
}
}
불행히도 SpriteKit에는 버튼 노드가 없습니다. 매우 유용한 컨트롤이기 때문에 이유를 모르겠습니다. 그래서 CocoaPods를 통해 직접 만들고 공유하기로 결정했습니다 . OOButtonNode 를 사용하십시오 . 버튼은 Swift 4로 작성된 텍스트 / 배경 또는 이미지를 사용할 수 있습니다.
다음은 최신 Swift (4.1.2)로 작성된 간단한 버튼입니다.
풍모
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)
}
}