まずは配列の復習として、100体のエージェントがランダムウォークするスケッチを作ってみましょう。
(→array_agents
)
いま作ってもらったとおり、配列は宣言時に個数を指定する必要があります。つまり、あらかじめ個数は決まっています。配列の個数=長さが固定なので「固定長配列」と呼ぶこともあります。
固定長の配列では、例えば「水源から無限に湧き出る水」のような、どんどんと数が増えていくようなものが表現できません。
要素の数を自由に増やしていければいいのに…そんな願いを叶えてくれるのが「可変長配列」です。
可変長配列とは、その名のとおり要素数が可変の配列です。要素を自由に足したり除いたりできます。
Processingでは、変数の種類に応じていくつか型が用意されています。
変数の種類 | 単体の型 | 対応する可変長配列の型 |
---|---|---|
小数 | float | FloatList |
整数 | int | IntList |
これ以外にも、より複雑な型を扱う ArrayList
なるものも存在しますが、これについては後ほど必要になったときに触れることにしましょう。
ここからは、もっともよくお世話になるであろう FloatList
を例に説明していきます。
可変長配列も変数の一種です。宣言してはじめて使えるようになります。
// float型の可変長配列を宣言
FloatList values;
固定長配列と同様、可変長配列も宣言したままでは空っぽです。
可変長配列の実体は new
キーワードで作成します。
// 可変長配列を作成
values = new FloatList();
これで可変長配列を使う準備が整いましたが、初期状態では要素がありません。(整地して準備はできたけどまだ家が建っていない…ようなものでしょうか)
ちなみに、固定長配列は初期化の際にあらかじめ要素数を決めておく必要がありましたが、可変長配列はあとで自由に変更できるので不要です。
可変長配列には、いつでも要素を追加できます。 配列の名前.append( 追加する要素 )
と書くことで、配列の末尾に要素を追加できます。
// 末尾に 1.4141 を追加
values.append(1.4141);
// 末尾に 3.1416 を追加
values.append(3.1416);
可変長配列の要素の個数は 配列の名前.size()
で取得できます。
// 可変長配列の要素数を出力
println( values.size() );
可変長配列の要素を取得するには、先頭から何番目か=インデックスを使って 配列の名前.get( インデックス)
と書きます。
// 0番目の要素を取得するので「1.4141」が出力される
println( values.get(0) );
// 1番目の要素を取得するので「3.1416」が出力される
println( values.get(1) );
可変長配列の要素を更新するには、 配列の名前.set( インデックス , 更新後の数値 )
と書きます。
// 0番目の要素を「7.7777」に更新する
values.set(0, 7.77);
もし要素の数値に四則演算をしたいときは、 .get()
と .set()
を組み合わせてもよいですが…。
// 0番目の要素に 123 を足す:
// 0番目の要素を get(…) で取得してから 123 を足して、 set(…) で更新する
values.set(0, values.get(0) + 123);
このようにちょっと込み入ってしまうので、簡単に書ける方法が用意されています。
// 0番目の要素に 123 を足す
values.set(0, values.get(0) + 123);
// 0番目の要素に 123 を足す(上の文と同じ)
values.add(0, 123);
四則演算をまとめておくとこんな感じ。
演算 | 書きかた | コード例 |
---|---|---|
足し算 | 配列.add( インデックス , 数値 ) | values.add(0, 3.14) |
引き算 | 配列.sub( インデックス , 数値 ) | values.sub(0, 3.14) |
掛け算 | 配列.mult( インデックス , 数値 ) | values.mult(0, 3.14) |
割り算 | 配列.div( インデックス , 数値 ) | values.div(0, 3.14) |
固定長配列との操作もまとめておきます。
操作 | 固定長 | 可変長 |
---|---|---|
配列の宣言 | float[] values; | FloatList values; |
配列の初期化 | values = new float[100]; | values = new FloatList(); |
i 番目の要素の取得 | values[i] | values.get(i) |
i 番目の要素の更新 | values[i] = 123; | values.set(i, 123); |
i 番目の要素に 123 を足す | values[i] += 123; | values.add(i, 123); または values.set(i, values.get(i) + 123); |
i 番目の要素に 0.5 をかける | values[i] *= 0.5; | values.mult(i, 0.5); または values.set(i, values.get(i) * 0.5); |
一番はじめに固定長配列で作ったエージェント100体のランダムウォークを、可変長配列を使って書き直してみましょう。
まずスケッチの先頭、変数を宣言する際の型が float[]
から FloatList
に変わります。
// エージェントの位置を覚えておくための配列
FloatList xs;
FloatList ys;
次に setup()
ブロックを見ていきます。初期化の際は、要素数を指定する必要がなくなります。
// 可変長配列を初期化
xs = new FloatList();
ys = new FloatList();
初期設定では、エージェントの数ぶん .append()
で要素を追加していきます。
// エージェントの初期位置を設定
for (int i = 0; i < 100; i++) {
xs.append(width / 2);
ys.append(height / 2);
}
for文で100回要素を追加しているので、それぞれの可変長配列には100体ぶんのX/Y座標が入っている状態になりますね。
最後に draw()
ブロックを見ていきます。エージェントの位置をランダムにずらす処理は、 .add()
を使って既存の要素に数値を足し算していきます。
for (int i = 0; i < 100; i++) {
// i番目のエージェントの位置をずらす
xs.add(i, random(-5, 5));
ys.add(i, random(-5, 5));
…
}
エージェントを描くときは、可変長配列に入っている座標を .get()
で取得して利用すればOKです。
for (int i = 0; i < 100; i++) {
…
// i番目のエージェントを描く
ellipse(xs.get(i), ys.get(i), 10, 10);
}
以上で書き換えが完了しました。書き換え前と同じように動作しましたか?
(→vla_agents
)
さて、ここからが可変長配列の出番です。例として、クリックしてエージェントを増やしてみましょう。
クリックしたときに実行されるブロックは mousePressed()
でしたね。この中で .append()
を使ってエージェントのX/Y座標をひとつずつ追加しましょう。
void mousePressed() {
// 新たなエージェントを追加
xs.append(mouseX);
ys.append(mouseY);
}
これでエージェント1体ぶんの情報が追加されました。次に、 draw()
ブロックを見ていきます。この中で、100体ぶんのエージェントをまとめて移動・描画するのにfor文を使っていましたが、0 ~ 99まで固定で繰り返しているので、せっかくクリックで配列の要素を増やしても100体ぶんしか処理されません。
このfor文の終了条件を書き換えて、配列の個数が増えてもすべての要素に対して処理されるようにしましょう。配列の要素数は .size()
で取得できるので…。
for (int i = 0; i < xs.size(); i++) {
…
}
このように書けますね。これでクリックするたびエージェントが増えていくようになりました!
(→vla_agents_append
)
このまま、エージェントに速度・加速度の概念を導入して重力をかけてみましょう。重力を忘れてしまった人は、「物理演算」の回を振り返りましょう。
ここまではクリックでエージェントを増やしていましたが、自然発生させてみることも可能です。 mousePressed()
ブロックに書いた処理を draw()
ブロックに移して実行してみましょう。
(→vla_agents_emitter
)
粒子に寿命を持たせて、消えるようにしてみましょう。寿命用の可変長配列をもうひとつ追加します。
// 寿命の配列
FloatList lifes;
setup()
ブロック内で、他の可変長配列と同様に初期化しておきましょう。寿命は 1
から 0
まで少しずつ減らしていくことにします。
void setup() {
…
// 可変長配列を初期化
lifes = new FloatList();
…
// エージェントの初期位置を設定
for (int i = 0; i < 100; i++) {
…
// 寿命を設定
lifes.append(1);
}
}
最後に draw()
ブロック内で、寿命を減らす処理と、寿命が残っているときだけ粒を描くようにします。
void draw() {
…
for (int i = 0; i < xs.size(); i++) {
…
// i番目のエージェントの寿命を減らす
lifes.sub(i, 0.05);
// 寿命が残っているときだけ、i番目のエージェントを描く
if (lifes.get(i) > 0) {
ellipse(xs.get(i), ys.get(i), 10, 10);
}
}
}
これで、エージェントに寿命の概念が入り、途中で消えるようになりました。
(→hello
)
このように、粒子がたくさん集まって構成されるエフェクトを「パーティクル」と呼びます。パーティクルは、粒子の色や形状・動きを工夫することで、爆発や炎・水など、さまざまな効果を再現することができます。
ちなみに、パーティクルの発生源を「エミッター (emitter)」と呼びます。
先の例では寿命が尽きたとたん消えてしまうので、不自然に見えます。寿命に応じて不透明度を変化させることで、より滑らかにしてみましょう。
ellipse()
で粒を描く直前に、寿命 lifes
から不透明度を計算して、塗り色を調整します。
for (int i = 0; i < xs.size(); i++) {
…
// 寿命を塗りの不透明度に反映する
// 寿命 lifes.get(i) が 1 ~ 0 で変化するとき、
// 透明度 al を 255 ~ 0 まで変化させる
float al = map(
lifes.get(i),
1, 0,
255, 0
);
fill(0, 0, 0, al);
…
}
これで、滑らかに消えていくようになりました。
(→fade
)
先のスケッチで、要素数をコンソールに出力して見てみましょう。 draw()
ブロックに以下のコードを差し込んで実行します。
// 可変長配列 xs の要素数をコンソールに出力
println(xs.size());
どうでしょうか?時間が経つほど増えていくのが確認できると思います。
寿命が尽きて不要になった要素を削除してあげないと、for文の繰り返し回数も負荷も増え続け、動作は次第に重くなり…最終的にはプログラムが停止してしまいます。展示など長期間実行し続ける状況では死活問題です。
可変長配列の要素を削除するには、 配列の名前.remove( インデックス )
を使います。後続の要素がある場合は、空席が出ないように詰められる=インデックスが調整されます。
// 1番目の要素を削除
values.remove(1);
寿命が尽きた要素を削除するには、先ほど触れた .remove()
を使って、以下のように書けば一見うまくいきそうです。
// これで不要な要素を削除できる…?
for (int i = 0; i < xs.size(); i++) {
if (lifes.get(i) <= 0) {
xs.remove(i);
ys.remove(i);
vxs.remove(i);
vys.remove(i);
lifes.remove(i);
}
}
…本当でしょうか?このコードはうまくいくこともありますが、少しだけ欠陥があります。特定の条件下—例えば寿命切れの粒子が2つ並んでいる場合など—で見落としが発生してしまうのです。
これを防ぐには、for文のインデックスの巡っていく順序を逆にします。つまり、先頭から末尾ではなく、末尾から先頭にかけて不要な要素を見ていくのです。
// 末尾から要素をチェックしていくことで、漏れなく不要な要素を削除できる
for (int i = xs.size() - 1; i >= 0; i--) {
if (lifes.get(i) <= 0) {
xs.remove(i);
ys.remove(i);
vxs.remove(i);
vys.remove(i);
lifes.remove(i);
}
}
(→remove
)
粒子に画像を利用すると、複雑な表情を手軽に出すことができます。例えば輪郭のぼけた粒子で描くと、全体の塊感が出てまったく違った趣を受けます。
画像を描く際は、 tint()
で色を被せることができます。透明な色を指定すれば、画像を半透明に描くこともできるので、これを利用しましょう。
(→img
)
規則的に飛ばすようにすると、複雑なパターンを描くことができます。発生源を動かすとまた違った表情になりますね。
(→bullet
)
パーティクルの外観や動きを工夫して、さまざまな効果を再現してみましょう。
爆発(→explosion
)、
火花、
炎(→fire
)、
水、蛍、…