はじめてのMorphicチュートリアル(第13回)「サブクラス化とキーイベントの処理」

クラスの機能を拡張するにはメソッドを追加していく必要があることを第5回の記事以降で学んできた。
既存のクラスの機能を維持しつつ、似たような新たな機能を持った異なるクラスを必要とする場合、第4回で学んだクラス定義によって新たなクラスを作成する。
例えば、今まで作成したMyMorphクラスと同様にScratch Catが移動していくのだが、マウスではなくキーボード操作によって移動するようなクラスが欲しい場合、MyMorphを親(基底)クラスとする新たなクラスを作成する。そうすればMyMorphの機能を引き継いだ、新たなクラスを作ることができる。
ここでは、そのような新しいクラスをMyMorph2として定義してみよう。

MyMorph subclass: #MyMorph2
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'Hajimeteno Morphic Tutorial'

この新しいMyMorph2では、モーフをキーボードで操作してみよう。以前の記事ではキーボードイベントの取り扱いが中途半端になっていたので、この回でより具体的にキーボードイベントを扱う。
MyMorph2の基本的な仕様としては、上下左右の矢印キーが押されている間だけ矢印の向きにモーフが動くようにする。
キーボード入力に反応するために定義しなければならないメソッドは以下の通りである。

  • handlesKeyDown:
  • handlesKeyUp:
  • keyDown:
  • keyUp:

前2者はモーフがキーボードイベントを受け取るためにtrueを返す必要がある。後2者で受け取ったキーボードイベントを実際に処理する。まず、前2者のメソッドは以下のようになる。

handlesKeyDown: evt
    ^ true
handlesKeyUp: evt
    ^ true

これらのメソッドにより、キーボードが押された瞬間にkeyDown:メッセージがモーフに送られ、離された瞬間にkeyUp:メッセージが送られる。それぞれキーボードイベントが引数となっている。
キーボードイベントの実体はKeyboardEventオブジェクトであり、keyメッセージを送るとKeyオブジェクトが得られる。Keyオブジェクトにnameメッセージを送ると押されたキーの情報を得ることができる。少し寄り道してkeyDown:メッセージの内容を表示して、どんな内容となっているのか確認してみよう。

keyDown: evt
    self inform: evt key name

今までの3つのメソッドを実装したら以下をDo itする。

ActiveHand keyboardFocus: MyMorph2 new openInWorld.

キーボードイベントの説明のところで述べたように、キーボードフォーカスをモーフに移動しないとキーボードイベントを受け取れない。そこで、生成したモーフに強制的にキーボードフォーカスを設定している。MyMorph2が現れたら適当なキーを押してみると、押した瞬間と押し続けている間、画面の左下にメッセージが現れ、Keyオブジェクトのnameが表示される。
矢印キーのnameは以下のような文字列である。

  • KP_UP — 上矢印
  • KP_DOWN — 下矢印
  • KP_LEFT — 左矢印
  • KP_RIGHT — 右矢印

これらの値を使えば押されたキーの判定を行うことができる。
矢印キーが押されている間だけ矢印の向きにモーフを動かすとすると、押されているキーを覚えておく必要がある。そこで、MyMorph2のクラス定義を修正して、keysという名前のインスタンス変数を追加する。

MyMorph subclass: #MyMorph2
    instanceVariableNames: 'keys'
    classVariableNames: ''
    category: 'Hajimeteno Morphic Tutorial'

押されたキーを全てkeysに覚えておくため、keyDown:メッセージを受け取ったタイミングでKeyオブジェクトの名前をkeysに追加し、keyUp:メッセージで除外する。キーが押し続けられるとkeyDown:メッセージを複数受け取ることになるが、そのあたりをうまく処理するためにSetオブジェクトを用いることにする。Setは集合のようにデータを扱うためのクラスで、同じデータを複数追加しても個数はカウントされない。MyMorph2のinitializeメソッドを用いて初期化しておく。ついでにキーボードフォーカスを自分自身にセットするようにしておこう。

initialize
    super initialize.
    ActiveHand keyboardFocus: self.
    keys := Set new

モーフの移動は今までと同じくstepメッセージを受け取ったタイミングで行うが、移動量であるvecはkeyDown:とkeyUp:のタイミングで更新することにする。keysの内容に基づいてvecの値を計算するメソッドcalcVecを新たに作成する。

calcVec
    vec := 0 @ 0.
    (keys includes: 'KP_UP')
        ifTrue: [ vec := vec + (0 @ -10) ].
    (keys includes: 'KP_DOWN')
        ifTrue: [ vec := vec + (0 @ 10) ].
    (keys includes: 'KP_LEFT')
        ifTrue: [ vec := vec + (-10 @ 0) ].
    (keys includes: 'KP_RIGHT')
        ifTrue: [ vec := vec + (10 @ 0) ]

Setオブジェクトであるkeysに送られているincludes:というメッセージは、引数のデータがSetオブジェクトにあるかどうかを調べるためのものである。calcVecメソッドでは押されているキーに応じて上下左右の移動量をセットしている。
keyDown:メソッドでは、単に受け取ったキーボードイベントのKeyオブジェクトの名前をkeysに追加し、自分自身にcalcVecメッセージを送るだけである。

keyDown: evt
    keys add: evt key name.
    self calcVec

Setなど複数のデータを扱うことのできるものをコレクションと呼ぶ。コレクションにデータを追加する場合、たいていadd:メッセージを用いる。
keyUp:メソッドでは、単に受け取ったキーボードイベントのKeyオブジェクトの名前をkeysから取り除き、自分自身にcalcVecメッセージを送る。

keyUp: evt
    keys remove: evt key name ifAbsent: [  ].
    self calcVec

Setオブジェクトからデータを削除するにはremove:メッセージを使うが、削除したいデータがないとエラーになるので、ここではremove:ifAbsent:というメッセージを用いている。2番目の引数は、データがなかった場合の処理を書くが、ここでは何もしないようにしている。
以上で矢印キーにより上下左右に移動するMyMorph2ができた。
(第13回おわり)