お絵かきプログラミングProcessingサンプルコード解説の第11弾です!
ProcessingをまだPCに導入されていない方は、こちらの記事を参考に導入してみてください!
今回は ArrayObjects というサンプルコードを見てみましょう。
難易度は ★★☆☆☆ くらい。自作クラスの配列を扱うサンプルで、汎用性は高いです。
わからない箇所があればコメントをください。
Table of Contents
もくじ
とりあえず動かしてみよう
Processnigを起動し、サンプルコードを開きましょう。
ファイル → サンプル と選択し現れたウィンドウで Basics → Arrays → ArrayObjects と選択してください。
このようなスケッチが現れます。ウィンドウの左上にある再生ボタンをクリックして実行してみましょう。
たくさんの点が左右に揺れ動きます。
今回のコードは 独自クラスの配列を扱います。
はじめに
今回のサンプルコードを開いてもらった時、これまで解説してきたサンプルとは異なる点に気づかれたと思います。タブが複数ありますね。
ProcessingのIDE (統合開発環境)では、タブは内部クラスを定義するのに使われます。
一般的なエディタでも、複数のファイルをタブごとに開いて同時に表示する機能はありますが、注意しておくべき点は、タブはそのタブを含むウィンドウの別のタブからしか参照できず、独立したコードファイルを保存できるわけではないことです。
今回のコードでは、ArrayObjectsとModuleという二つのタブが存在しますが、ModuleタブはArrayObjectsで使用するクラスの定義のみを行っています。
タブの使い方について詳細に知りたい方は公式の説明を参考にしてください。
コードを読む
とりあえずコードの全貌を確認しましょう。タブは2つありますが、とりあえず全部目を通してみましょう。
ArrayObjecs
Module
2つに分けるだけあって、長めですね。
軽く目を通してみると、setup()やdraw() などの見慣れた関数が揃っているのはArrayObjectsタブの方なので、こちらから読みましょう。
コメントから始まっていますね。読んでみましょう。
「ArrayObject。カスタムオブジェクトの配列を作成する構文をデモします。」とのことです。
詰まる箇所は特にないと思うのでサクサク進めましょう。
次にグローバル変数を定義していますね。読みましょう。
変数 | 意味 |
---|---|
unit | 単位? |
count | 何かの数? |
mods | あまりわからないけど"modules"を縮めて"mods"になってそう? |
ではsetup()から確認していきましょう。
setup
size()はウィンドウサイズを指定する関数、
noStroke()は図形の枠線が描かれなくなります。
どちらも毎度おなじみだと思うので、憶えていらっしゃる方も多いと思います。
次にwideCount, highCountを定義していますが、式にwidth, height が使われています。width, heightは、自分で定義せずに使用できる特殊な変数で、それぞれ描画ウィンドウの幅、高さを取得する時に使用します。描画ウィンドウのサイズは12行目で定義している通りなので、今回はwidth=640, height=360となっています。
ところでwideCount, highCountはその式の定義から、幅方向、高さ方向にそれぞれunitが何個取れるか、を表すことがわかります。
次にcountですが、wideCount, highCountの意味を考えれば、countは、一辺の長さがunitの正方形を描画ウィンドウに何個敷き詰められるかを表すことがわかります。
この直後に配列modsを定義しているのですが、配列の要素数にcountが使われています。
ところで、ここまで出てきた変数の関係を図にすると下のようになるのですが、概ね正方形の区画1つに対して1つの点が入っていることから、「1つの点が配列の1要素に対応していそう」と予想できますね。
(図の中の正方形の一辺の長さはunit (=40) です。)
コードの続きを読みましょう。ループを回していますね。
ついにModuleが出てきたので、Moduleタブのコードを読む必要が出てきたのですが、
一旦全体の流れを追いたいので、放置します!Module型の変数が入ることだけ憶えておきましょう。
これでsetup()はおしまいですね。
次にdraw()を読みます。
background()は毎度おなじみですね。描画ウィンドウ全体を塗りつぶす関数です。今回の引数は0なので黒で塗り潰すことになります。
次にループを回していますが、これまで見てきたfor文とは少し異なると思います。
これは拡張for文 (for-each文) と呼ばれる構文です。何が行われているかは一見して想像がつくと思いますが、modsの要素を順にmodという名前の変数として取り出し、処理を行います。
for文でも拡張for文でも同じ挙動の処理を書くことはできるので、好きな方を選んでください。個人的には拡張for文の方が直感に即した書き方ができるように思います。
繰り返し処理の中身なのですが、ここもModuleクラスの定義を読まないとわからないところでね。というわけで一旦放置しましょう。
これでdraw()が終わり、ArrayObjectsタブは終わってしまいましたね。
というわけでここまで放置してきたModuleを読みましょう!
まずフィールドの定義から見ていきましょう。
変数名から想像すると、それぞれの意味は次のような感じでしょうか。。
変数 | 意味 |
---|---|
xOffset, yOffset | x, y方向の初期位置のずれ? |
x, y | x, y座標? |
unit | 何かの単位 |
xDirection, yDirection | x, yの方向? |
speed | 移動速度? |
次にコンストラクタが書かれていますね。
コンストラクタは、変数(インスタンス)を生成するときの初期化処理を行う関数です。(ちなみに、コンストラクタは必ずしも全フィールドの値を引数にとるように定義されるわけではありません。)
Moduleのコンストラクタはフィールド8つのうち、xDirection, yDirection以外のフィールドの代入を行っています。
次にupdate()というメソッドが定義されていますね。
コメントには「変数を更新するためのメソッド」と書かれていますね。
このメソッドをこのまま読み進めてもいいのですが、x, yなどのフィールドを更新しているものの、図形描画は行われていません。
全体像を掴むためには、まず描画に直接使われている変数を把握するのが近道なので、update()は一旦飛ばして先にdisplay()を読んで見ましょう。
fill()は図形の塗り潰し色を指定します。今回は引数が255なので、白になります。
ellipse()は楕円を描く関数ですが、今回は中心が (xOffset + x, yOffset + y)、直径が6の円を描いています。
どちらもよく見る関数ですね。
これでx, y, xOffset, yOffsetは円の中心座標に関係する変数であることがわかりましたね。
とりあえずdisplay()は円を描いているということだけ憶えておきましょう。
ではupdate()に戻りましょう。
さっきdisplay()を読んだことで、x, yは円の中心の座標に関係していることがわかっています。
22行目をみると、x にspeed * xDirectionを加算しています。ということはspeedはx方向の移動速度に相当し、またxDirectionはxの正負いずれの方向に移動するかを表す変数であると予想できます。
次にxの値で分岐していますね、
xが大きすぎる/小さすぎる場合にxDirectionに-1をかけるという処理をしています。
つまりxがある範囲をはみ出してしまった場合に点の移動方向を反転させ、範囲内に戻るようにしています。
また移動方向が反転する時にyの値も少し移動させており、さらにyがある範囲をはみ出した場合は移動方向を反転させています。
Moduleを理解したところで、ArrayObjectsの読み飛ばした箇所に戻りましょう。
22行目のindex++ という記法に注目してください。
これは後置インクリメントと呼ばれる記法で、ループ構文でよくみかけると思います。
これはある処理と変数のインクリメントを纏めて行う際に使える記法で、このコードは次のコードと同じ処理を行います。
自分でこのような処理を各場合、書き方は好きなものを選択してください。(筆者は頭がごちゃごちゃしてしまうので、わかりやすく2行に分けて書くことが多いです。)
というわけでこの行では、正方形区画を順番に見て、Module型のインスタンスを生成してmodsの各要素に代入しています。
Moduleが一体どの位置に円を描くのか丁寧に考えてみましょう。
まずx, yそれぞれのオフセットはx*unit, y*unitなので、図で表すとこのような位置になります。
拡大してみましょう。x, yの値はunit/2なので、先ほどのオフセットにこれらを加算すると、円の初期位置は正方形区画の中心になることがわかります。
ちなみにrandom()は、範囲を指定して乱数を取得する関数です。今回は0.05~0.8の範囲からランダムに一つの値を取得しています。modsの各要素のspeedは乱数で決定しているんですね。
この描画を描画ウィンドウに収まるような全てのx, yで繰り返すと、はじめに見せたアニメーションが出来上がります。
おまけ 多次元配列
今回のコードに関してはmodsはModule型の配列として定義されていました。
ここで配列modsにはインデックスは1つしか存在しない(逆に言えば、mods[0], mods[2]などと1つのインデックスによりModule型の要素1つを取り出せる) ので敢えていうなら一次元配列です。
ところで、今回扱った作品の図形的な意味を考えると、Module型の配列はx, yの2方向に広がっているので、二次元配列として扱う方が直感に即しているように思われます。
というわけで二次元配列を使用する場合のコードを書きました。Moduleはサンプルと全く同じものを使用する想定です。
変更した箇所を説明します。
modsの定義のカギかっこが2つに増えていますが、これが二次元配列の変数を定義しているところです。
配列要素数にはwideCount, highCountをそれぞれ指定します。
ループしている箇所ではx, yを配列のインデックスとしてそのまま利用できるので、元あった変数indexは不要になります。
ここまでは元のコードより読みやすくなったと思うのですが、拡張for文で要素を取り出すところでは、二重ループを書く必要があるはずで、ちょっと汚くなってしまいますね。。(もっと綺麗に書く方法をご存知の方がいたら是非お教えいただきたいです)
以上のように書き換えれば元のサンプルと全く同じ動作のコードが出来上がります。
必要になったときに使えるよう是非憶えておいてください!
説明は以上です。お疲れ様でした!!
さいごに
今回は、自作クラスの配列の使い方を学びました。
クラスは慣れると便利な道具になります。雰囲気を感じ取って使いこなしてくださいね。
ではまた次回!