나는 그것이에서 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)
}
}