データをまとめる(クラス①)

2019-10-22

本章のあらすじ

  • クラスとは
  • クラスでデータをまとめる

可変長配列の復習

まずはシンプルな練習問題を通して、可変長配列の復習をしておきましょう。

練習:シンプルなパーティクルを作ってみる

キャンバスの中央から、ランダムウォークする粒が湧き出るスケッチを作ってみましょう。

ヒント

  • 粒は位置(X, Y)をもつので、2つぶんの配列が必要

(→particle_wo_class

練習:重力を加えてみる

このスケッチに手を加えて、粒のランダムウォークを一旦やめ、重力を加えてみましょう。重力について忘れてしまったら、「落ちる・加速する」を参照してください。

ヒント

  • 重力を持たせるには、X・Y方向の速度をそれぞれ導入する必要がある。つまり、配列を2つぶん追加する必要がある

(→particle_wo_class_vel

配列がどんどん増えていくわかりづらさ

これまでの配列の使いかたをおさらいしておくと、粒のもつデータ(X・Y位置、X・Y速度)に対してひとつずつ、計4つの配列を割り当てていました。

粒が持つ情報が増えていくほど、配列の数も増えていくのがわかるでしょうか。例えば、ここからさらに色情報を持たせようとなると…さらに増えるわけです。

より複雑なスケッチに進化させていく過程で配列だらけになってしまい、スケッチの見通しがどんどん悪くなっていきます。

ここで「クラス」という概念を導入して、データ構造をすっきりと整理してみましょう。

クラス:「データの集約」編

クラスとは、複数のデータをひとかたまりにできる便利な機能です。…と言っても何のことやらなので、パーティクルを例に考えていきましょう。

まずは1粒に着目して、粒のもつ情報について考えていきます。

クラスはデータの設計図

Processingの世界では、クラスはこのように書きます。

class クラス名 {
}

クラス名は変数と同様に、ほぼ自由に決められます。今回の例ではパーティクルシステムの「粒」を表現するので Particle としましょうか。クラス名は大文字で始めるのが慣習です。

// 粒を表現する Particle クラス
class Particle {
}

クラスの波括弧の中に、そのクラスに属するデータが入ります。さて、粒がもつデータとは何でしょうか?

  • X座標
  • Y座標
  • X方向の速度
  • Y方向の速度

…ですね。これをクラスで表現するとこんな感じ。

// 粒を表現する Particle クラス
class Particle {
  // 粒のもつデータ
  // 粒は x, y, vx, vy をもつ
  float x;
  float y;
  float vx;
  float vy;
}

これで、「粒子 Particle というクラスは、 x, y, vx, vy をもつデータ構造である」、と定められます。

身近な例で考えると…これはたいやきの型を作るのに似ています。「こういう形のたいやきを作る」というルールを定めるのが型、つまりクラスなのですね。

クラスはたいやきの型のようなもの

実体(たいやき)の生成

さて、たいやきの型を作ったところで安心するのはまだ早いです。せっかく用意した型を使って、肝心の中身・たいやきを作っていきます。ちなみに、型で作る実体をプログラミングの世界では「インスタンス」と呼びます。

インスタンス(実体)はたいやきのようなもの

使いかたは変数と同じ。変数の型を書いていた場所にクラス名が入り、変数名はこれまでどおり自由です。

// Particle という型で p を宣言(まだ空っぽ)
Particle p;

配列などと同じように、クラスのインスタンス(実体)も宣言したままではまだ空っぽです。 new を使って実体を生成することで、はじめて使えるようになります。

void setup() {
  // インスタンス(実体)を生成
  p = new Particle();
}

いちどクラス(たいやきの型)を作ってしまえば、インスタンス・実体(たいやき)は作り放題です。

// こんなこともできる(今回はしないけど)
Particle p1 = new Particle();
Particle p2 = new Particle();
Particle p3 = new Particle();
Particle p4 = new Particle();
Particle p5 = new Particle();
...

さて、インスタンスのもつデータ(ここでは x, y, vx, vy )には、 インスタンス名 . データの名前 とドットでつなぐことでアクセスできます。基本的な使いかたは変数と同じです。

void setup() {
  size(600, 600);
	fill(0);

  // インスタンス(実体)を生成
  p = new Particle();

  // インスタンスのデータに代入(変数と同じように利用できる)
  p.x = 300;
  p.y = 300;
  p.vx = 0;
  p.vy = 0;
}
実体(たいやき)は、型どおりのデータ構造をもつ

試しに、このまま draw() ブロックまで書いて、1粒動かして描画するところまで仕上げてみましょう。

// 粒を表現するクラス Particle を定義
class Particle {
  // 粒のもつデータ
  // 粒は x, y, vx, vy をもつ
  float x;
  float y;
  float vx;
  float vy;
}

// Particle という型で p を宣言
Particle p;

void setup() {
  size(600, 600);
	fill(0);

  // インスタンス(実体)を生成
  p = new Particle();

  // インスタンスのデータに代入(変数と同じように利用できる)
  p.x = 300;
  p.y = 300;
  p.vx = 0;
  p.vy = 0;
}

void draw() {
  background(255);

  // インスタンスのデータに代入(変数と同じように利用できる)
  p.x += p.vx;
  p.y += p.vy;
  p.vx += random(-0.1, +0.1);
  p.vy += random(-0.1, +0.1);

  // インスタンスのデータを参照(変数と同じように利用できる)
  ellipse(p.x, p.y, 10, 10);
}

(→particle_single

練習:粒を100粒に増やしてみる

さて、クラスを使って1粒動かすところまでできるようになりました。今度は100粒同時に動かせるように拡張してみましょう。

数があらかじめ決まっている場合は、固定長の配列が使えます。書きかたは float など基本的な型の配列と同様です。

// Particle の配列
Particle[] ps;

void setup() {
  size(600, 600);
	fill(0);

  // 配列を初期化
  ps = new Particle[100];

  for (int i = 0; i < 100; i++) {
    // i 番目の粒の実体を生成
    ps[i] = new Particle();

    // i 番目の粒のデータを初期化
    ps[i].x = 300;
    ps[i].y = 300;
    ps[i].vx = 0;
    ps[i].vy = 0;
  }
}

// ...

ひとつだけ、配列の初期化が終わったあとは、1粒ずつ実体を生成する必要がある点に気をつけてください。

このまま draw() の中身も書き換えて、スケッチが動作するように仕上げてみましょう。

(→particle_array

クラスと可変長配列

float の可変長配列が FloatList だったように、独自のクラスも可変長配列にできます。これを実現してくれるのが ArrayList です。書きかたがやや複雑になりますが、我慢…!

ArrayList< 配列にしたいクラス名 > 変数名 ;

先の Particle を例にするとこんな感じ。

// Particle クラスの可変長配列
ArrayList<Particle> ps;

操作はほぼ、これままで触れてきた可変長配列 FloatList と同じですが、要素の追加 .append().add() になっている点だけ気をつけましょう。

練習:可変長配列に書き換えてみる

先の100粒のスケッチについて、配列を可変長に書き換えて、粒が湧き出すようにしてみましょう。

// Particle の可変長配列
ArrayList<Particle> ps;

void setup() {
  size(600, 600);
  fill(0);

  // 配列を初期化(可変長なので配列の個数は指定しなくてよい)
  ps = new ArrayList<Particle>();

  for (int i = 0; i < 100; i++) {
    // 粒の実体を生成
    Particle p = new Particle();

    // 粒のデータを初期化
    p.x = 300;
    p.y = 300;
    p.vx = 0;
    p.vy = 0;
    
    // 生成・初期化した粒を可変長配列に追加
    ps.add(p);
  }
}

// ...

(→particle_arraylist

完成したら寿命を加えたり、粒のサイズを加えたりしてみてください。

(→particle_arraylist_plus

粒の機能拡張が簡単・簡潔でわかりやすいと思いませんか?これがクラスのもつ一側面、データ集約の威力です。

クラス導入前:配列地獄
クラス導入後:すっきり!

次回はクラスが持つ別の側面、「機能の集約」について見ていきましょう。

本章のまとめ

  • クラスとは、(今のところは)複数のデータをひとかたまりにできる便利な機能
  • クラスはたいやきの型のようなもので、生成する実体(たいやき)の形を定める

知らないとプログラムが書けないようなものではありませんが、複雑なプログラムを作ったり、他のエンジニア・デザイナーと協業したりする際には欠かせない概念です。