はじめてのMorphicチュートリアル(第11回)「状態」

今回はモーフに状態を持たせることについて学ぶ。
もちろん今までもモーフは大きさ、位置、色などさまざまな状態を持っていたが、それとは異なる内部的な状態を持たせたい。
例として今まで右に移動するだけだったMyMorphを、クリックするたびに90度ずつ向きを変えて移動するようにしよう。
さて、MyMorphで「移動」を実現しているのはstepメソッドである。

step
    self color: Color random.
    self topLeft: (self topLeft + (2@0))

MyMorphの左上のX座標を2増やすことで右に2ピクセル移動する。
例えばモーフが右に90度向きを変える(つまり下向きに移動する)とすれば、3行目は以下のようになるだろう。

self topLeft: (self topLeft + (0@2))

さらに90度向きを変えると左向きに進み、コードは以下のようになる。

self topLeft: (self topLeft - (2@0))

モーフの左上座標に対して、一定数の座標値を加減することで向きが変わると考えられるので、以下のようにコードを変更できる。

self topLeft: (self topLeft + vec)

つまり、座標値に加えるvecが2@0であれば右に進み、0@2であれば下に進むので、(vecは)モーフが進む方向を表す状態とみなせる。
このvecは動作しているモーフ固有の情報である。他にMyMorphモーフがあったとしても、それぞれの値は独立している。
このような値を表すのにSmalltalkではインスタンス変数を用いる。インスタンス変数はクラスを定義する際に宣言し、そのクラスに属する全てのモーフが独立した値を持つようになる。
MyMorphにインスタンス変数vecを加えるには、以下のようにクラス定義を変更する。

Morph subclass: #MyMorph
	instanceVariableNames: 'vec'
	classVariableNames: ''
	category: 'Hajimeteno Morphic Tutorial'

文字通りinstanceVariableNames:の後の文字列が、インスタンス変数の並びを表す。複数のインスタンス変数が必要ならば、スペースで区切り両端をシングルクオートで囲んで(つまり文字列として)指定する。
第4回のクラス定義と同じ方法で、クラス定義のテンプレートを上のように修正してAcceptすれば、既存のMyMorphの定義は上書きされる。上書きをしてもメソッド定義は消えないので心配しなくて良いが、動作中のモーフがあるとクラス定義の変更の影響を受ける場合があるので、あらかじめ削除しておいた方が無難である。
インスタンス変数vecを加えたMyMorphをAcceptしたら、stepの内容を以下のように変更する。

step
    self color: Color random.
    self topLeft: (self topLeft + vec)

この状態でMyMorphを動かすと、vecが未定義なためにエラーが発生する。モーフの生成と同時にvecの初期値を設定する必要がある。
そのため第6回で説明したようにinitializeメソッドを以下のように変更し、vecの初期値を2@0とする。(モーフは右に進む)

initialize
    super initialize.
    vec := 2@0.
    self extent: 16@16

クリックするたびにモーフが90度向きを変えるようにするため、mouseDown:メソッドを修正する。

mouseDown: anEvent
    self extent: self extent + (10@10).
    vec := vec y negated @ vec x

インスタンス変数vecの値を変更する部分で説明が必要なのはnegatedメッセージの意味である。negatedメッセージはメッセージを送った数値の正負を反転した値を返すメッセージである。
上記までの変更を行えば、MyMorphはクリックするたびに右へ90度向きを変えながら移動するようになる。
(第11回おわり)