为SpriteKit创建按钮

时间:2017-06-07 14:13:21

标签: swift sprite-kit

我正在为我正在构建的精灵工具包应用程序创建主菜单。在整个项目中,我使用SKScenes来保持我的水平和实际的游戏玩法。但是,现在我需要一个主菜单,其中包含“Play”,“Levels”,“Shop”等按钮......但是,我现在添加按钮的方式感觉不太舒服,就像这样:

let currentButton = SKSpriteNode(imageNamed: button)  // Create the SKSpriteNode that holds the button

self.addChild(currentButton) // Add that SKSpriteNode to the SKScene

我检查按钮的触摸如下:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let touchLocation = touch!.location(in: self)

    for node in self.nodes(at: touchLocation) {
        guard let nodeName = node.name else {
            continue
        }
        if nodeName == ButtonLabel.Play.rawValue {
            DispatchQueue.main.asyncAfter(deadline: .now()) {
                let transition = SKTransition.reveal(with: .left, duration: 1)
                self.view?.presentScene(self.initialLevel, transition: transition)
                self.initialLevel.loadStartingLevel()
            }
            return
        }
        if nodeName == ButtonLabel.Levels.rawValue {
            slideOut()
        }

    }
}

但是,我不知道这是否有效。 我正在考虑使用UIButtons,但为此我必须使用UIView?

或者我可以将UIButtons添加到SKView (我真的没有区分SKView,SKScene和UIView)推荐什么菜单?

2 个答案:

答案 0 :(得分:5)

我完全赞同@Whirlwind,为你的按钮创建一个单独的类来处理你的工作。我不认为@ElTomato的建议是正确的建议。如果您创建一个包含按钮的图像,则您对这些按钮的放置,大小,外观和按钮状态没有灵活性。

这是一个非常简单的按钮类,它是SKSpriteNode的子类。它使用委托将信息发送回父级(例如已按下哪个按钮),并为您提供简单的状态更改(单击时变小,释放后恢复正常大小)

import Foundation
import SpriteKit

protocol ButtonDelegate: class {
    func buttonClicked(sender: Button)
}

class Button: SKSpriteNode {

    //weak so that you don't create a strong circular reference with the parent
    weak var delegate: ButtonDelegate!

    override init(texture: SKTexture?, color: SKColor, size: CGSize) {

        super.init(texture: texture, color: color, size: size)

        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        setup()
    }

    func setup() {
        isUserInteractionEnabled = true
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        setScale(0.9)
        self.delegate.buttonClicked(sender: self)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        setScale(1.0)
    }
}

此按钮可以通过2种方式实例化。您可以在场景编辑器中创建它的实例,或在代码中创建实例。

creating the button in Scene editor

class MenuScene: SKScene, ButtonDelegate {

    private var button = Button()

    override func didMove(to view: SKView) {

        if let button = self.childNode(withName: "button") as? Button {
            self.button = button
            button.delegate = self
        }

        let button2 = Button(texture: nil, color: .magenta, size: CGSize(width: 200, height: 100))
        button2.name = "button2"
        button2.position = CGPoint(x: 0, y: 300)
        button2.delegate = self
        addChild(button2)
    }
}

func buttonClicked(sender: Button) {
    print("you clicked the button named \(sender.name!)")
}
  

你必须记住使场景符合代表

class MenuScene: SKScene, ButtonDelegate

func buttonClicked(sender: Button) {
    print("you clicked the button named \(sender.name!)")
}

答案 1 :(得分:1)

对于简单场景,您正在做的事情很好,实际上是首选,因为您可以使用.SKS文件。

但是,如果你有一个复杂的场景我想做的是子类化Sprite,然后覆盖该节点的touchesBegan。

这是我在所有项目中使用的节点......这是一个简单的&#34;开关&#34;按钮。我使用&#34;指针&#34;通过我自己制作的自定义引用类到布尔值,这样这个节点就不需要关心你的其他场景,节点等 - 它只是将Bool的值改为其他代码位的做他们想做的事情:

public final class Reference<T> { var value: T; init(_ value: T) { self.value = value } }

// MARK: - Toggler:
public final class Toggler: SKLabelNode {

  private var refBool: Reference<Bool>
  var value: Bool { return refBool.value }

  var labelName: String
  /*
   var offText = ""
   var onText = ""
   */

  func toggleOn() {
    refBool.value = true
    text = labelName + ": on"
  }

  func toggleOff() {
    refBool.value = false
    text = labelName + ": off"
  }

  /*init(offText: String, onText: String, refBool: Reference<Bool>) {
   ref = refBool
   super.init(fontNamed: "Chalkduster")
   if refBool.value { toggleOn() } else { toggleOff() }
   isUserInteractionEnabled = true
   }
   */

  init(labelName: String, refBool: Reference<Bool>) {
    self.refBool = refBool
    self.labelName = labelName
    super.init(fontNamed: "Chalkduster")
    isUserInteractionEnabled = true

    self.refBool = refBool
    self.labelName = labelName
    if refBool.value { toggleOn() } else { toggleOff() }
  }

  public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if refBool.value { toggleOff() } else { toggleOn() }
  }

  public required init?(coder aDecoder: NSCoder) { fatalError("") }
  override init() {
    self.refBool = Reference<Bool>(false)
    self.labelName = "ERROR"
    super.init()
  }
};

这是一个更精细的按钮,而不是说点击它时只运行一些代码的东西。

重要的是,如果你走这条路,那么你需要确保将节点.isUserInteractionEnabled设置为true,否则它将不会接收到触摸输入。

根据你正在做的事情的另一个建议是将逻辑与行动分开:

// Outside of touches func:
func touchPlay() {
  // Play code
}

func touchExit() {
  // Exit code
}

// In touches began:
guard let name = node.name else { return }

switch name {
  case "play": touchPlay()
  case "exit": touchExit()
  default:()
}

PS:

以下是如何使用Toggler的一个非常基本的示例:

class Scene: SKScene {

  let spinnyNode = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))

  // This is the reference type instance that will be stored inside of our Toggler instance:
  var shouldSpin = Reference<Bool>(true)

  override func didMove(to view: SKView) {
    addChild(spinnyNode)
    spinnyNode.run(.repeatForever(.rotate(byAngle: 3, duration: 1)))

    let toggleSpin = Toggler(labelName: "Toggle Spin", refBool: shouldSpin)
    addChild(toggleSpin)
    toggleSpin.position.y += 100
  }

  override func update(_ currentTime: TimeInterval) {
    if shouldSpin.value == true {
      spinnyNode.isPaused = false
    } else if shouldSpin.value == false {
      spinnyNode.isPaused = true
    }
  }
}