大量生産(while文)

2019-05-14

本章のあらすじ

  • Processingで処理を繰り返す
  • 繰り返しで図形を大量生産する
作例:繰り返しで同心円の波紋を描き重ねる

コンピューターは繰り返しが得意

コンピューターは繰り返しが得意です。一度ルールを決めて(コードを書いて)しまえば、その高い処理能力を活かしてものすごい勢いで繰り返すことができます。

  • 膨大な数のユーザーに対して、1件ずつメールを送信していく
  • カレンダーに登録されている今月分の予定を、1件ずつ表示していく
  • フィールド上にたくさんいるキャラクターを、1体ずつ動かしていく

ビジュアルアートの観点では、例えば図形を描く処理を繰り返すことで、大量の図形を生産できます。

今回は、Processingで繰り返し処理を書くための方法を学びながら、そこで実現できることのひとつ「大量生産」に触れてみましょう。

例:波紋

復習:クリックした位置に円を描いてみる

まずは、クリックした位置を中心に円を描くスケッチを作成してみましょう。ヒントなしで書けますか?

練習:円を重ねて波紋を描いてみる

このままでは何とも味気ないですね。サイズの異なる円を重ねて、波紋のようにしてみましょう。

void mousePressed() {
  // 小さな円を塗りつぶさないように塗りをなくす
  noFill();

  ellipse(mouseX, mouseY, 100, 100);
  ellipse(mouseX, mouseY, 120, 120);
  ellipse(mouseX, mouseY, 140, 140);
}

少しだけ趣が出てきたでしょうか。さて、このまま波紋を増やすには、サイズを変えながら ellipse() の文を増やしていけばよいわけです。例えば1000個の円を描きたくなったとき、1000回コピペと変更を繰り返せば実現できますが…やりたくないですよね。

単純作業の繰り返しは、人間ではなくコンピューターに任せしましょう

while

while文は、処理を繰り返すための文です。繰り返す処理のかたまり=ブロックを持っています。

while {
  // 繰り返し実行するブロック
}

このままでは何回繰り返すかがわからないので、繰り返すかどうかを毎回判定するための条件式が () の中に入ります。

while ( 繰り返すかどうかを決める条件式 ) {
  // 繰り返し実行するブロック
}

例えば10回繰り返したい場合はどうするかというと、変数を使って何回繰り返したかを覚えておくようにすればOKです。

// 繰り返し回数を覚えておく用の変数
int i = 0;

// i が 10 より小さい間繰り返す
while ( i < 10 ) {
  println(i);

  // 繰り返し終わったので回数を 1 増やす
  i = i + 1;
}

この繰り返し回数を決めている変数を「ループ変数」と呼びます。慣習的に ij がよく使われますが、あとで見たときに意味の思い出せる名前をつけておくのがよいでしょう。

while文で波紋を大量生産する

さて、while文の書きかたを理解したところで、先ほどの波紋のスケッチをwhile文で書き直してみましょう。

繰り返すのは円を描く処理ですから、 ellipse() をwhile文のブロックで囲います。

void mousePressed() {
  while () {
    ellipse(mouseX, mouseY, 100, 100);
  }
}

この状態では繰り返し条件がないので文法エラーが出てしまいます。ループ変数を足して繰り返し回数を決めてあげましょう。

// 繰り返し回数を決める用のループ変数
int i;

void setup() {
  
}

void mousePressed() {
  // ループ変数を初期化
  i = 0;

  // i が 20 より小さいとき、ブロックを繰り返す
  // ブロックの最後で 1 ずつ足しているので、計 20 回繰り返すことになる
  while (i < 20) {
    // ループ変数を円の大きさに反映
    ellipse(mouseX, mouseY, 100, 100);

    // i を 1 増やして繰り返す
    i = i + 1;
  }
}

void draw() {
}

このまま実行しても特に変化はありませんね。同じ場所に同じサイズで繰り返し円を描いているので、ひとつしか見えていないのです。何度もなぞっているだけになっているということですね。

なので、最後にループ変数 i を円のサイズに反映してあげましょう。

ellipse(mouseX, mouseY, i * 10, i * 10);

うまく実行できたでしょうか?

while文がどのように動作しているのか、1ステップずつ見ておきましょう。

繰り返しの継続条件( i をどこまで大きくするか)や更新処理( i をどのくらいの間隔で増やしていくか)を書き換えると、多様な模様が出てきます。パラメーターを書き換えて試しながら、絵柄の変化とwhile文の挙動を理解しましょう。

while文の挙動がピンとこない人は、デバッガを利用してwhile文の手前で停止し、1ステップずつ動作を確認してみるとよいでしょう。

そろそろ知っておきたい、変数の有効範囲

変数は、宣言した位置によって有効範囲「スコープ」が変わります。Processingにおける変数は、基本的には「宣言した場所よりあと・そのブロック内で有効」です。

先頭で宣言するとグローバルに

これまでは、何も考えずにコードの先頭で宣言してきました。先頭で宣言した変数は、プログラム全体で有効になります。

// 変数をコードの先頭で宣言
int i;

void setup() {
  // ここからでも使える
  i = 0;
}

void draw() {
  // 別のブロックからでも使える
  i = i + 1;
}

setup()draw() など、どのブロックの中からでも利用できます。全体で(=グローバルに)利用できるので「グローバル変数」と呼びます。

ブロック内で宣言するとローカルに

変数は、 setup()draw() 、while文の {} で囲まれたブロック内で宣言することもできます。この場合、ブロック内でのみ有効になります。局所的(=ローカル)なので「ローカル変数」と呼びます。

void setup() {
  // 変数をsetup() ブロック内で宣言
  int i = 0;

  // 同じブロック内では使える
  i = i + 1;

  while (i < 10) {
    // ブロック内のブロックでも使える
    i = i + 1;
  }
}

void draw() {
  // 別のブロックでは使えない
}

ちなみに、別のブロックで同名の変数が宣言できますが、これらは同名の別物になります。値が連動したりはしませんので気をつけましょう。

void setup() {
  // setup() ブロック内の変数x
  int x = 0;
}

void draw() {
  // draw() ブロック内の変数x (別人)
  float x = 0;
}

グローバル/ローカルの使いわけ

ローカル変数の寿命はそのブロック内のみです。ブロックを実行し終わった時点でその内容は捨てられてしまうので、一時的な計算結果を入れておくのに使うことが多いでしょう。

逆に、プログラムの実行中にずっと覚えておきたいことは、グローバル変数を使いましょう。

グローバル変数は全体で使えて便利な反面、どこの処理で変更されたかがわかりづらくバグを生み出しやすいです。変数のスコープはできる限り狭い範囲に絞るのが破綻しないコードを書くコツです。

while文のループ変数はローカルに

ループ変数の役割は、繰り返し回数を決めるという局所的なもので、そのループを終えてしまえば用済みです。この場合はローカル変数を使うのがよいでしょう。

void setup() {
  size(600, 600);
  background(255, 255, 255);

  noFill();
  stroke(0, 0, 0, 255);
}

void mousePressed() {
  // ループの直前でループ変数を宣言すればよい(ローカル変数)
  int i = 0;

  while (i < 20) {
    ellipse(mouseX, mouseY, i * 10, i * 10);
    i = i + 1;
  }
}

void draw() {
}

ループ変数を活用する

ループ変数を太さや位置にも反映

波紋の例は、ループ変数を図形の大きさに適用したものでした。

ループ変数自体はwhile文の繰り返しを決めているただの数値なので、それをどう使うかは自由です。位置や太さ、色など、何に反映してもよいのです。(→ripple_more

飛沫を大量生産する

カーソル位置を中心に、ランダムにずらして円を描くと飛沫のような効果になります。これをwhile文で繰り返せば、飛沫の量を自在に増やせます。 (→brush

void mouseDragged() {
  // while文で10回繰り返す
  int i = 0;
  while (i < 10) {
    // 円のサイズをランダムで決める
    float sz = random(10);

    // 塗りの不透明度もランダムで決める
    fill(0, 0, 0, random(100));
    noStroke();

    // ランダムにずれた位置に円を描く
    ellipse(
      mouseX + random(-20, 20),
      mouseY + random(-20, 20),
      sz, sz
    );

    // ループ変数を 1 増やして繰り返す
    i = i + 1;
  }
}

汚れを大量生産する

クリックした場所を始点として、ランダムに移動しながら円を描き重ねることで、汚れのような効果が得られます。これをwhile文で繰り返せば、汚れの量を簡単に増やせますね。 (→grunge

void mousePressed() {
  // 円の中心をカーソル位置で初期化
  float ox = mouseX;
  float oy = mouseY;

  // while文で1000回繰り返し
  int i = 0;
  while (i < 1000) {
    // 変数 x, y の位置に円を描く
    ellipse(ox, oy, 4, 4);

    // ランダムで位置を移動させる
    ox += random(-4, 4);
    oy += random(-4, 4);

    // ループ変数を 1 増やす
    i = i + 1;
  }
}

本章のまとめ

  • while文でブロックを繰り返せる
  • while文での繰り返し回数はループ変数で制御する
  • ループ変数を活用すると、図形を大量生産できる