SpriteKitのSKSceneでShiftKey(装飾キー)をハンドリングする方法

SKSceneはNSResponderを継承しているはずなのに,なぜかfunc flagsChanged(with event: NSEvent) を継承してもコールバックが呼ばれないので,extensionで解決してみました。

extension SKView {
  override open func flagsChanged(with event: NSEvent) {
    scene?.flagsChanged(with: event)
  }
}
class DebugScene: SKScene {
  override func flagsChanged(with event: NSEvent) {
    if event.modifierFlags.contains(NSShiftKeyMask) {
      print("Shift key is pressed")
    }
  }
}

SKSceneは必ずSKViewのメンバsceneとして保持されるので,SKViewでせき止められているイベントをオーバーライドして明示的発火させてあげたところ無事にイベントが渡って来ました。 通常のキーイベントは何もしなくてもわたってきます。装飾キーのイベントだけが渡って来ないのには何かしらの理由がありそうなので,なにか副作用があるかもしれません。

または

class DebugScene: SKScene {
  override func didMove(to view: SKView) {
    view.nextResponder = self
  }

  override func flagsChanged(with event: NSEvent) {
    if event.modifierFlags.contains(NSShiftKeyMask) {
      print("Shift key is pressed")
    }
  }
}

didMoveで自身を保持するviewの参照が渡って来るので,nextResponderに自分の参照をいれることでも解決します。SKViewのnextResponderにセットされてるNSViewがなんなのかはわからないのですが,階層構造的にもSKView->Sceneという順番で処理できた方がシンプルで上の実装よりResponder Chainのあるべき姿な気がします。なんならSKViewのpresentSceneをオーバーライドして必ずnextResponderにシーンの参照をセットするようにしてもいいかもしれません。

ちょっと心配なのが元のNSViewを迂回する必要性があるのはか不明なところ。たぶんNSViewの実装でflagChangedが止まってる気がします。

東方とかBMSでもShiftキーはバリバリ使うので,似たようなゲームを実装したいとなったときにハマるかもしれませんね。 ググっても解決策が全然でてこないくらいには人気のないSpriteKitくんのTipsでした。