SpriteKitでシーンにクリック可能なオブジェクトを配置するための仕組み

qiita.com qiita.com

上記の記事はiOS向けの記事です。 OSX用のゲームを作っている場合,touchesBegan はないはずなのでどうするのだろうと少し考えてみました。

前述の記事はオブジェクトに名前をつけてdelegateで処理させる戦略のようです。 名前を付けてSwitchで分岐させたり,delegateを用意するのはちょっと大げさだなぁという気もしなくもない…。 onClickedという名前のクロージャを一度渡せばおしまい,というような仕組みがあればいいなと思ったので簡単に実装してみました。

import SpriteKit

protocol Clickable {
  var onClicked: ((SKNode) -> Void)? { get set }
}

class TextButton: SKLabelNode, Clickable {
  var onClicked: ((SKNode) -> Void)?
}

class BasicSene: SKScene {
  override func mouseDown(with event: NSEvent) {
    let location = event.location(in: self)
    
    let clicked = nodes(at: location)
    clicked.forEach {
      if let node = $0 as? Clickable {
        node.onClicked?($0)
      }
    }
  }
}

class DebugScene: BasicSene {

  override func didMove(to view: SKView) {
    scaleMode = .aspectFill

    let label = TextButton(text: "test")
    label.onClicked = { _ in
      print("label is clicked")
    }
    label.position = CGPoint(x: 200.0, y: 200.0)
    addChild(label)
  }

  override func mouseDown(with event: NSEvent) {
    // 独自実装があればここに記述,その際superは必ず呼ぶ
    super.mouseDown(with: event)
  }
}

Clickableプロトコルを用意するのでSpriteKitが最初から用意しているクラスをクリック可能にすることはできませんが,それなりの規模のゲームを作る場合はどうせ独自クラスを作りたくなるし,実用性はそこそこありそうです。 というかこのくらいのことはどこも当たり前にやってるはずなので今更感あるのですが,もしかしたらクロージャを渡しておしまいという仕組みがSpriteKitに最初からあるかもしれないという期待を込めてググってみても,Qiitaのような情報しかでてこなかったので一応。

同じ階層に重なってるオブジェクトもClickableであれば,上のオブジェクトから順に発火されます。また階層構造になっている場合は子要素から順に発火されるようです。これは node(at:) の仕様であり,バージョンや環境によって異なるかもしれません。 クロージャの返り値をBoolにしてpropagationの制御もできればなお良かったのですが,簡易実装ということでご勘弁を。