第22回 class – 5 override

前回メンバメソッドを学びました。
今回はclassの継承についてです。

継承?

継承というのは、とあるclassが別のclassの特性(つまりメンバ)を受け継いで、別のクラスを作ることを言います。
継承される元のclassの事を親クラス、もしくはスーパークラス、継承するclassの事を子クラスまたはサブクラスといいます。
継承の話をする時に、よく引き合い出されるのが動物です。

Cat classとDog classを作る場合に、犬と猫が共通して持っている特性を持つAnimal classを先に作ってあげると、Cat classもDog classも定義しなければならないメソッドの数が減ってお得!というような話です。
Processingの場合には、犬とか猫とか言っていても仕方ないので、Circle classとSquare classで考えてみましょう。
円と四角形に必要な共通する特性を書き出して、Shape classを先に定義します。

円と四角形を描くクラスにあると便利なもの

・移動する(上下左右)
・色を指定できる
・場所を指定できる
・大きさを指定できる

とりあえずこの辺でいいでしょうか。
まず親クラスのShapeに、上記の機能のためのメンバ変数とメンバメソッドを定義します。
そして次に、CircleクラスとSquareクラスを作るときに、Shapeクラスを継承します。

継承するためには、 extendsというキーワードを使って、

class Circle extends Shape {
}
という風にします。
つまり、いつも通りクラス名を書いた後に、extendsキーワード、スペースを開けて継承する元の親クラスの名前、という風に記述します。

そして、継承元クラスのコンストラクタをsuper()というメソッドで呼び出すことが出来ます。
もしメンバ変数があるクラスを継承するのであれば、基本的にsuper()メソッドで親クラスのコンストラクタを呼び出してあげる必要があります。
そうしないと、メンバ変数に何も代入されず空のままになってしまいエラーを起こすからです。

こんな感じになります。


Circle circle; //Circle型のcircleとう名前の変数を宣言
Square square; //同じく
void setup(){
  size(800,800);
  circle = new Circle(400,400,50,color(255,0,0)); //インスタンス化
  square = new Square(200,400,50,color(0,255,0)); //インスタンス化
}

void draw(){
background(0);//背景を黒で塗りつぶす

circle.disp();//円を表示させる
square.disp();//四角形を表示させる
circle.moveLeft();//円を左に移動させる
square.moveRight();//四角形を右に移動させる

  
}

//円と四角の親クラス、シェープクラスを定義
class Shape {

  float x, y, size;//メンバ変数たち
  color col;

  Shape(float _x, float _y, float _size, color _col) {//コンストラクタ
    x = _x;
    y = _y;
    size = _size;
    col = _col;
  }

  void moveRight() {//移動させる系のメソッドたち

    x++;
  }

  void moveLeft() {
    x--;
  }

  void moveDown() {

    y++;
  }

  void moveUp() {

    y--;
  }
}

class Circle extends Shape{//円を描くためのクラスを作る。Shapeクラスをextendsキーワードで継承する。
  
  Circle(float _x, float _y, float _size, color _col){//コンストラクタ
   super(_x,_y,_size,_col);// superで親クラスの今スタクラを呼び出している。
    
  }
  
  void disp(){
    
    fill(col);
    ellipse(x,y,size,size);
  }
}

class Square extends Shape{
  
  Square(float _x, float _y, float _size, color _col){
   super(_x,_y,_size,_col); 
  }
  void disp(){
    
    fill(col);
    rect(x,y,size,size);
  }
}

かなり長くなってしまっていますが、この move4種類とメンバ変数を両方のクラスに実装するよりは多分短く書けました。
コピペするのではなく一度自分のprocessingに書き写して、理解を深めてください。
コンストラクタの値を変えたり、Shapeクラスの持っているメソッドを追加してみたり、draw()の中で呼び出すメソッドを変更してみたりしてください。

次回はこの継承を用いた便利なパターンを紹介します。
では。

第21回 – class 4 メンバメソッド

さて、前回はclassにメンバ変数を加えて、様々な型の数値を1まとまりとして扱えるようになりました。
今回は、メンバメソッドを使って、class自身に処理や振る舞いが出来るようにしてあげます。

メンバメソッド?

メンバメソッドとは、メンバ変数のようにそのclassの中で定義されているメソッドです。
メンバ変数を使ったり、メンバ変数を書き換えたり出来るという点以外は、メソッドの回で紹介したものと殆ど変わりません。
引数を受け取ったり、返り値を返したりすることももちろんできます。
前回の例だとこんな感じです。

RectData rData;

void setup() {
  size(800, 800);

  rData = new RectData(100, 100, 50);
}

void draw() {
  rData.drawRect();
}

class RectData {
  int x = 0;
  int y = 0;
  int size = 0;
  int addX = 0;
  int addY = 0;
  color fillCol = color(0, 0, 0);
  color borderCol = color(0, 0, 0);
  String name = "name";

  RectData(int _x, int _y, int _size) {
    x = _x;
    y = _y;
    size = _size;
  } 

  void drawRect() {

    fill(fillCol);
    stroke(borderCol);
    rect(x, y, size, size);
  }
}

class RectDataの中に、

  
void drawRect() {

    fill(fillCol);
    stroke(borderCol);
    rect(x, y, size, size);
  }

が加わっています。
あえて解説するならば、これはdrawRectという名前のメソッドで、塗り色の指定をするfill、枠線の色の指定するstroke、そして四角形を描くrectメソッドを、自身のメンバ変数を使って実行しています。
そして、void draw(){ }の中で、メンバ変数を呼び出す時の同じように . を使ってインスタンスのメンバメソッドを呼び出しています。
前回はいちいちインスタンスであるrDataにアクセスしてメンバ変数を呼び出してから、それを使ってdraw(){ }の中で四角形を描いていたので、それに比べればかなり楽になりました。

ゲッタとセッタ

ところで、前回説明した通り、メンバ変数に代入されている数を変更する時、もしくは呼び出す時には
インスタンス名.メンバ変数名 = 値
int n = インスタンス名.メンバ変数名

のようにすればもちろん、書き換えたり呼び出したりすることが出来ますが、これはあまり推奨されていません。
ではどうするか、というと

void getX(){
  return x;
}

や、

void setX(int _x){
  x = _x;
}

のように、ただ対象のメンバ変数の値を代入する、もしくはリターンするだけのメソッドを定義して利用します。

理由は沢山あって、後からデータ構造を変えたくなった時、ポリモルフィズムを使いたくなった時、そのデータを代入していいかどうかのチェックをしたくなった時…など枚挙にいとまがない感じですが、今説明するとかなり長くなってしまうので、とりあえず「そういうもの」という風に覚えてください! =)
習慣づけておくことで、今後のプログラミングがかなり楽になります。

まとめ

メンバメソッド、ちょっと早足でしたがご理解いただけましたでしょうか?
次回は、classの継承についてです。

第20回 – class 3 メンバ変数

前回はclassの作り方と、インスタンス化の仕方について解説しました。
今回は、前回作ったsampleというなんの機能もないclassに、メンバ変数メンバメソッドを与えて、すこし実用的なclassにしてあげたいと思います。

メンバ変数?

メンバ変数というのは、そのclassのインスタンスが保持する変数の事です。

例えば、int型の変数ですと、int型の1つの値しか扱えません。
int型の配列も、数は沢山扱えますが、int型の値だけです。

もし四角形を描いて、それに名前をつけて、さらに好きな色で塗りたい、と思った時に、普通に考えれば以下の変数を用意しなければなりません。
int x(四角形の左上の位置)
int y(四角形の左上の位置)
int size(大きさ)
int addX(x方向に進む早さ)
int addY(y方向に進む早さ)
color fillCol(色)
color borderCol(外枠の色)
String name(名前)

しかし、そのプラグラムではもちろん他の形や他の機能も扱わなければなりませんし、そもそもこんなに沢山の変数の名前を考えたり覚えておいたり把握するのが大変です。同じ変数名は使えませんから、もし同じように円に名前をつけて塗る、という機能を追加しようとすると、倍の数の変数と変数名が必要になります。
頭のなかはごちゃごちゃですし、変数名は区別するためにどんどん長くなっていきます。
int ellipseX
int ellipseY
int ellipseSize
int ellipseAddX;
int ellipseAddY;
color ellipseFillCol
color ellipseBorderCol
String ellipseName;

classを使わずにやると、こんな感じです。

int x;
int y;
int size;
int addX;
int addY;
color fillCol;
color borderCol;
String name;
int ellipseX;
int ellipseY;
int ellipseSize;
int ellipseAddX;
int ellipseAddY;
color ellipseFillCol;
color ellipseBorderCol;
String ellipseName;

void setup(){
  
  size(800,800);
  x = 100;
  y = 100;
  addX = 2;
  addY = 1;
  size = 50;
  fillCol = color(255,0,0);
  borderCol = color(200,0,0);
  name = "SHIKAKUKEI";
  
  ellipseX = 50;
  ellipseY = 50;
  ellipseSize = 50;
  ellipseAddX = 1;
  ellipseAddY = 2;
  ellipseFillCol = color(0,255,0);
  ellipseBorderCol = color(0,200,0);
  ellipseName = "EN";
  
}


void draw(){
  
  background(0);
  
  fill(fillCol);
  stroke(borderCol);
  rect(x,y,size,size);
  x += addX;
  y += addY;
  
  fill(ellipseFillCol);
  stroke(ellipseBorderCol);
  ellipse(ellipseX, ellipseY, ellipseSize, ellipseSize);
  
  ellipseX += ellipseAddX ;
  ellipseY += ellipseAddY;
  
}

確かにこの方法でも、このくらいならなんとかならなくもないですが、これに何かしらの機能や動きを持たせると考えると、多分人間の頭では不可能…という感じになっていきます。あと図形の形が10個出てきたら、変数の数を5倍にしなければなりませんし、 +=などの演算がどこで何を行っているのかが把握できなくなっていきます。

そこで、classのメンバ変数としてこれらの値を四角形に名前をつけて色を塗るためのclassにまとめてあげれば、もう変数名等をそれぞれに覚えておく必要がなくなります。さらに、所属しているclassが違えば、同じ変数名を使用することが可能です。

class RectData {

  int x;
  int y;
  int size;
  int addX;
  int addY;
  color fillCol;
  color borderCol;
  String name;
}
class EllipseData {

  int x;
  int y;
  int addX;
  int addY;
  int size;
  color fillCol;
  color borderCol;
  String name;
}

こんな具合です。全く同じ変数名を使っていますが、classが違うので問題ありません。
使う時にはインスタンス化します。その時、コンストラクタをclassにもたせてあげると、変数を代入するのが楽になります。

class RectData {
  int x  = 0;;
  int y = 0;
  int size = 0;
  int addX = 0;
  int addY = 0;
  color fillCol = color(0,0,0);
  color borderCol = color(0,0,0);
  String name = "name";
  
  RectData(int _x, int _y, int _size) {
    x = _x;
    y = _y;
    size = _size;
  } 
}

RectData(int _x, int _y, int _size) { }この部分がコンストラクタと呼ばれる部分です。
コンストラクタを利用するには、class名と同じ名前をclassの { }の中に書いて、受け取りたい数の引数を()の中に書きます。
メソッドの時にやったのと同じように、引数の名前と型を指定してあげると、その名前で受け取った数を扱うことが出来るようになります。

コンストラクタは、classがインスタンス化される時に1度だけ呼ばれて、いくつかの引数を受け取ります。
そして、その受け取った引数をメンバ変数に渡します。
使用する場合には

RectData rData;

void setup(){
  size(800,800);
  
  rData = new RectData(100,100,50);
  
}
class RectData {
  int x  = 0;;
  int y = 0;
  int size = 0;
  int addX = 0;
  int addY = 0;
  color fillCol = color(0,0,0);
  color borderCol = color(0,0,0);
  String name = "name";
  
  RectData(int _x, int _y, int _size) {
  x = _x;
  y = _y;
  size = _size;
  } 
  
}

このようにします。
例ですと、100と100と50が、それぞれRectDataクラスの、xとyとsizeに代入されます。
さきほど setupの上に膨大にあったグローバルな変数が1つだけになっている点にも注目してください。
Ellipseを使っても2つですみます。

こうしておくことで、いくつかの代入を楽にする事ができます。もちろん、全てのメンバ変数に対してコンストラクタで初期値の代入を行っても問題ありません。ただ、コンストラクタが長すぎると、インスタンス化する時にその値と順番を毎回確認しなければならなくなってしまったり、渡すべき値を間違えてしまったりと問題が起こりやすいのでおすすめはしません。
(そういう場合にはBuilderパターンなどを使うのですが、その辺りはもう少し先の話です)

そして、このメンバ変数には
インスタンス名.変数名
でアクセス出来ます。
例えば、上の例だとこんな感じ。

RectData rData = new RectData(100,100,50);

rect(rData.x,rData.y,rData.size,rData.size);

しかし、実はこんな風にしなくても、もっと簡単にこのclassに持たせた変数を使って、図形を描く、移動するなどの振る舞いを実装する事が出来ます。
メンバメソッドという機能を利用するのですが、それはまた次回!

第19回 – class 2 – インスタンス化

前回、跳ね返るボールを作る場合に、classを使って作る場合と使わない場合の例を挙げました。
まずclassとは何かというところから説明していきます。

classの利点

classは、オブジェクト指向のために作られた概念で、処理や値(振る舞いやデータ)を1つのオブジェクトとして扱うことによって、機能を切り分け、プログラミングしやすい・考えやすい状態にする事ができます。
前回の例だと、

    x += addX;
    y += addY;
    if (x > width | x < 0) {
      addX *= -1;
    }
    if (y > height | y < 0) {
      addY *= -1;
    }

という一連の処理にmove()という名前をつけてあげることによって、そのclassを使う場合には「moveの中で何をしているか」、というのを考えなくても、「とにかくmoveという命令を実行すればballが動く」、という状態を作り出す事が出来ました。

classを使わない例だと、プログラミングしている間中、「ellipse()で描かれる円の位置を指定しているのはどの変数で、何をそれに足してあげれば思ったような挙動をするのか」というのを常に考える必要がありましたし、もし「この円が壁にあたったら色が変わる」という挙動を追加しようとするとプログラム全体を見なおす必要がありましたが、classを使った例だとclassの中だけを見なおせば良いので、変更や機能追加が簡単に行なえます。
さらに、classを作ってみるとわかりますが、ビジュアル系プログラミング言語とオブジェクト指向はとても相性が良く、プログラムを考えるのが簡単になります。

さて、利点の抽象的な説明はここまでにして、実際にclassを作ってみましょう。

classを作る

class Sample{

}

もっとも小さいクラスは、なんとこれで完成です。
Sampleという名前を持っています。
Processingでは、クラスの名前は必ず大文字で始める、というルール(というか習慣)があります。
これは、後からその名前がclassの名前なのかメソッドの名前なのかを見分けやすくすると言う目的があるので、出来る限り守りましょう。

「最も小さいクラスは」などと言っていますが、お察しの通り全く意味のないクラスです。
意味のないクラスですが、ちゃんとインスタンス化することが出来ます。

インスタンス化というのは、このclassという設計図を元に、新しい実体を作成することを言います。

インスタンス化というのは、このclassという設計図を元に、新しい実体を作成することを言います。

大切なので太字で2回書きました。
そうです、classというのは設計図に過ぎません。
インスタンス化してあげないと使うことは出来ないのです。(そうでもないstaticメソッドや変数などもありますが、またのちほど説明します。今は忘れてください。)

ではどうやったらインスタンス化出来るかと言うと、 new というキーワードを使います。
processingの場合、最初のうちは setup(){ }の中か、draw(){ }の中でインスタンス化することになります。
慣れてくると他のclassの中でインスタンス化したりする機会も増えるかと思いますが、とりあえず例としてSample classをインスタンス化する例を書きます。

Sample sample; //Sample型の変数を宣言

void setup(){
  size(800,800);
  
  sample = new Sample();//インスタンス化
  
}

void draw(){
  
  
}

class Sample{
   
}

まず、一番上の行でSample型のsampleという名前の変数を宣言しています。変数の名前は何でも構いません。
Sample popopo;
とかでもOKです。

classで定義した名前はintなどと同じように変数型として扱います。
Sampleという classのインスタンスが入るのは、Sampleという型の変数のみ、というわけです。

setup(){ }の中で Sample型の変数であるsampleに、

sample = new Sample();

という感じでインスタンス化したものを代入しています。
この時点で、sampleという変数の中にはSampleクラスのインスタンスが入っています。

しかし、このSampleクラスには何の機能もないので、インスタンス化しても使いみちがありません。
次回、このSampleクラスに機能を追加して、ちゃんと実用性のあるクラスにしていきましょう。

第18回 – class 1 オブジェクト指向プログラミング

さて、前回でメソッドを学びました。
メソッドは処理をまとめて、柔軟に処理を行うために引数を使って変数に代入し、返り値を使って処理した結果をメインに戻します。

今回からはclassという機能を使っていきます。
classを使えば、所謂オブジェクト指向プログラミングが可能になるのですが、ここではオブジェクト指向とはなんぞや、というよりも、classを使うことで今までやってきた作業がこんなに楽になる、という例をお示しします。

跳ね返るボール

今まで学習してきた、if文、for loop、配列を使って、複数の、壁にぶつかって跳ね返る円を実装してみたいと思います。


int num = 5;
float[] x = new float[num];
float[] y = new float[num];
float[] addX = new float[num];
float[] addY = new float[num];


void setup() {

  size(800, 800);
  background(0);

  for (int i = 0; i<x.length; i++) {
    x[i] = random(width);
    y[i] = random(height);
    addX[i] = random(3);
    addY[i] = random(3);
  }
}

void draw() {
  background(0);
  for (int i = 0; i<x.length; i++) {

    ellipse(x[i], y[i], 25, 25);
    x[i] += addX[i];
    y[i] += addY[i];
    if (x[i] > width | x[i] < 0) {
      addX[i] *= -1;
    }

    if (y[i] > height | y[i] < 0) {
      addY[i] *= -1;
    }
  }
}

まず、これがclassの機能を使わずに作った、5個の円が移動して、壁にぶつかると跳ね返る、という簡単なプログラムです。
num = 5の部分を10にすると10個になります。実行中でなければ、そこを変える事で数は自由に増減出来ます。

簡単な、といいながら、そして実際そんなに難しい事をしていない筈なのに、とても複雑に見えます。
これは、沢山の変数が出てきて、しかもそれぞれに何をやっているかが不明瞭だからです。
パッと見て、これがどんなプログラムか言い当てるのは難しいでしょう。
しかも、実際に作品として作るプログラムはこれよりももっと複雑になっていきます。円が壁に跳ね返るだけのプログラムでは面白くないですからね。

そこで、この複雑さを解消するために、classという機能を使います。
まずは何も説明せずにclassを使って実装したバージョンをお見せします。

Ball[] balls = new Ball[5];

void setup() {

  size(800, 800);
  background(0);

  for (int i = 0; i<balls.length; i++) {
    float x = random(width);
    float y = random(height);
    float addX = random(3);
    float addY = random(3);
    balls[i] = new Ball(x, y, addX, addY, 25);
  }
}


void draw() {
  background(0);
  for (int i = 0; i<balls.length; i++) {
    balls[i].disp();
    balls[i].move();
  }
}


class Ball {

  float x, y, addX, addY, size;

  Ball(float _x, float _y, float _addX, float _addY, float _size) {
    x = _x;
    y = _y;
    addX = _addX;
    addY = _addY;
    size = _size;
  }

  void disp() {
    fill(255);
    ellipse(x, y, size, size);
  }

  void move() {

    x += addX;
    y += addY;
    if (x > width | x < 0) {
      addX *= -1;
    }
    if (y > height | y < 0) {
      addY *= -1;
    }
  }
}

class Ballという新しいブロックが作られていますが、drawの中やsetupの中、そして使用しているグローバル変数(setupの前に宣言する変数)の数がぐっと減って、しかもdrawの中で何が行われているか、簡単に把握できるようになりました。
balls[i].disp()は、円を表示させているのだし、balls[i].move()は、円を移動させているのです。

そして、例えばこのプログラムからballs[i].move()の行を消すと、ボールは動かなくなります。
この形式であれば、新しい機能を足しても、draw(){ }のループの中は複雑になりすぎずに済みそうです。
コード全体で見れば量は多くなってしまいましたが、classの中の仕組みを知れば、とても簡単に読めるようになった事、そして書けるようになったことが解るはずです。
class自体の構造は次回以降説明していきます。
今日はここまで!

第17回 – メソッドを作ろう 3 – 返り値

前回は引数のあるメソッドを作りました。
今回は、返り値のあるメソッドを作ってみたいと思います。

返り値って?

返り値は、メソッドの中でされた計算結果を、呼び出した側に渡してあげるための機能です。

例えば前回の例では、メソッドは呼び出されただけで(画面に図形を表示するという仕事はしましたが)、メインのプログラムには特に影響を与えていませんでした。
…ちょっとわかりにくいですね、具体的な話をしましょう。

値を返すって?

squareという名前のメソッドを作って、1つ引数を渡すと、その引数を二乗した数を返してくれるというメソッドを作るとします。
今までの方法では、この「計算結果を返してくれる」という部分が作れませんでした。
どうしてかというと、メソッド名の前に void と書いていたからです。あのvoidというキーワードが実は、「このメソッドは値を返しません」という意味なのでした。
では値を返したい場合にはどうしたら良いでしょうか?
実は、その返したい値の型をvoidの代わりに書けば良いのです。

int square(int n){
  int sq = n * n;
  return sq;
}

これが、引数を二乗して返してくれるというメソッドの実装です。
引数に取った n という数を自分自身と掛けた数をsqという名前の変数に格納しています。
そして、 returnというキーワードの横にその変数名をスペースをあけてから書いています。
このreturnキーワードで指定した値が、返り値として呼び出し元に返ります。
ではその返り値をどうやって使うのか、という話ですが、このようにして使います。

void setup(){
  size(200,200);
  
  int n = square(5);
  println(n);
}

int square(int n){
  int sq = n * n;
  return sq;
}

メソッドを = の右辺に置いて、メソッド自体を変数に代入するかのように書きます。
nにはsquare(5)が代入されているように見えますが、実際にはこのメソッドの計算結果(返り値)が代入されます。
実際にご自分のprocessingに貼り付けて実行してみてください。
下の部分に5✕5の計算結果、つまり25が表示されるはずです。

まとめ

返り値は一見地味なように思えますが、プログラミングをする上ではかなり重要な機能です。
この後classを学ぶと、その重要性がさらによくわかるようになると思います。
では

第16回 – メソッドを作ろう 2 – 引数

さて、メソッドの2回目です。
前回は返り値も引数もないメソッドを扱いましたが、今回は引数のあるメソッドを扱ってみます。

引数って何だ?

引数とは、「メソッドの中の数値を変数にしておく事で、メソッドを呼び出す時に自由に変更できるようにする仕組み」の事です。
例えば、円を描くellipse()というメソッドには数値を4つコンマ区切りで入力しますが、あれが引数です。
自作メソッドに引数を持たせたい場合には、以下のようにします。

void drawShapes(int x, int y) {

  fill(0, 255, 0);
  ellipse(x, y, 200, 200);
  fill(255, 255, 255);
  rect(x, y, 100, 100);
  fill(255, 0, 0);
  ellipse(x, y, 100, 100);
}

名前の横に書く()の中に、引数の型と、その引数をメソッド内で使う時の変数名を書きます。
型[スペース]変数名 の形式です。複数引数をとりたい場合には、コンマ区切りで同じように並べます。

そして、メソッドが呼び出された時に入力された値を使いたい場所に、その変数の名前を書きます。
上の例だと、xとyの2つの引数をとり、四角形や丸を書く場所にそれを利用しています。
呼び出す場合には、このようにします。

void setup() {
  size(800, 800);
  rectMode(CENTER);
}

void draw() {
  background(255);
  drawShapes(300,300);
}

void drawShapes(int x, int y) {

  fill(0, 255, 0);
  ellipse(x, y, 200, 200);
  fill(255, 255, 255);
  rect(x, y, 100, 100);
  fill(255, 0, 0);
  ellipse(x, y, 100, 100);
}

前回と違う点は、drawShapes()の()の中にコンマ区切りで数値を入れているところでしょう。
そして、こうする事によってメソッドの恩恵を最大限に受けることが出来ます。
例えば、この図形を4つ書かなければならないとしましょう。
メソッドという機能がprocessingになかったら全部の行を4回コピペして、毎回場所を示す数値を書き換えなればならないところですが、こんなに簡単に4つ書けてしまいます。

void setup() {
  size(800, 800);
  rectMode(CENTER);
}

void draw() {
  background(255);
  drawShapes(300,300);
  drawShapes(500,300);
  drawShapes(500,500);
  drawShapes(300,500);
}
void drawShapes(int x, int y) {

  fill(0, 255, 0);
  ellipse(x, y, 200, 200);
  fill(255, 255, 255);
  rect(x, y, 100, 100);
  fill(255, 0, 0);
  ellipse(x, y, 100, 100);
}

sketch_160830c

では久々に問題。

問題

drawShapesメソッドの引数の数を増やし、図形のサイズも変更できるようにしなさい。
引数の数は4つとする。

次回は戻り値のあるメソッドについて書きたいと思います。
では。

第15回 – メソッドを作ろう 1

今回からメソッドについて見ていきたいと思います。

メソッドとは、平たく言えば「命令を集めたもの」です。
今まで何度も使ってきた円を描くためのellipse()や、塗りつぶすためのfill()なども実はメソッドです。
それらはprocessingが予め用意してくれているので自分で作る必要がなかった、というだけです。
しかし、複雑な処理や膨大な量のプログラムを書いていくと、それらを自作した方が便利な機会というのは山程あります。

例えば、
「青い円を描いて、その中に白い四角形を描いて、さらに直径ぴったりの小さい赤い円を描く。」
という処理が、1つのプログラムの中に何回も出てくるとしましょう。(例えば、です)

普通にやろうとすると、毎回こんな風に書く必要があります。

void setup() {
  size(800, 800);
  rectMode(CENTER);
}

void draw() {
  background(255);
  fill(0, 255, 0);
  ellipse(300, 300, 200, 200);
  fill(255, 255, 255);
  rect(300, 300, 100, 100);
  fill(255, 0, 0);
  ellipse(300, 300, 100, 100);
}

sketch_160830c

指定しなければならない数字が多くて既にちょっと面倒ですが、これを5回くらい、特に規則性のない位置に描かなくてはならない、となったらどうでしょう?
前回ご紹介した配列とloopで描く、という方法もありますが、その場合配列を一体いくつ作らないといけないのでしょうか?

…と、ここで自作メソッドの登場です。

メソッドを作る

メソッドには「返り値(かえりち)のあるもの」「引数(ひきすう)のあるもの」「両方あるもの」「両方ないもの」の4種類がありますが、そう難しく考える必要はありません。
まず第一回目の今回は「引数も返り値もないもの」だけを扱っていきます
processingにおいて、そのようなメソッドを作るには、以下のようにします。

void drawShapes(){
  
  //処理の内容
  
}

まず、一番最初の voidの部分が、「戻り値がないよ」という意味を表しています。
そして、その次の drawShapes()の部分が、このメソッドの名前です。
()の部分が、このメソッドに引数が無いことを表しています。(ちょっとわかりにくいですが、引数があるメソッドを次回学習するので、その時「引数がない」の意味が解ると思います)

で、処理の内容を書きます。

void drawShapes() {
  fill(0, 255, 0);
  ellipse(300, 300, 200, 200);
  fill(255, 255, 255);
  rect(300, 300, 100, 100);
  fill(255, 0, 0);
  ellipse(300, 300, 100, 100);
}

↑これは、さっきdrawの中に入っていた一連の処理をそのままコピペしただけです。
さて、これで自作メソッドの完成です。

全文は以下のようになります。

void setup() {

  size(800, 800);
  rectMode(CENTER);
}


void draw() {
  background(255);
  drawShapes();
}


void drawShapes() {

  fill(0, 255, 0);
  ellipse(300, 300, 200, 200);
  fill(255, 255, 255);
  rect(300, 300, 100, 100);
  fill(255, 0, 0);
  ellipse(300, 300, 100, 100);
}

呼び出す時にはname()のように、メソッド名の後に()をつければOKです。
今回の場合は drawShapes()とdrawの中に書いてあげれば呼び出すことが出来ます。

draw()やsetup()も、実は自作のメソッドの一種だったのですね。もっとも、この2つはちょっと特殊なので同じようには考えられませんが。

しかし、これではメソッドとして便利かと言われればとてもそうは思えません。
場所やサイズを自由に変更出来なければ意味がないですよね…?

次回はメソッドに引数を与えることによって、呼び出した命令の数字を後から簡単に変更出来るようにします。
では。

第14回 – 配列 2

さて、前回に引き続きfor loopで配列を扱う方法です。
(と言っても前回は殆ど扱いませんでしたが)
では早速例を。

int[] x = new int[3];
int[] y = new int[3];

void setup() {
  size(800, 600, FX2D);

}

void draw() {

  background(0);

  for (int i = 0; i<3; i++) {

    ellipse(x[i], y[i], 5, 5);
    y[i] = y[i] + i+1;
    x[i] = x[i] + i+1;
  }
}

解説

まず、

int[] x = int[3];
int[] y = int[3];

の部分で配列を宣言しています。
int型の配列は変数と同じく、何も代入していなければ最初は全てに0が入っています。

そして、loopの中で、
変数 i を使ってx[0]〜x[2]を呼び出して、自分自身に1とiを足したものを、再び代入しています。
行数としては、ほんの少し短くなってる程度に見えますが、この3の部分を10にしてみましょう。

int[] x = new int[10];
int[] y = new int[10];

void setup() {
  size(800, 600, FX2D);

}

void draw() {

  background(0);

  for (int i = 0; i<10; i++) {

    ellipse(x[i], y[i], 5, 5);
    y[i] = y[i] + i+1;
    x[i] = x[i] + i+1;
  }
}

はい、たったこれだけで10倍です。
100にすれば100倍ですが、こんな風にすると数の変更が簡単です。

マジックナンバーの排除

int number = 10;
int[] x = new int[number];
int[] y = new int[number];

void setup() {
  size(800, 600, FX2D);
}

void draw() {

  background(0);

  for (int i = 0; i<number; i++) {

    ellipse(x[i], y[i], 5, 5);
    y[i] = y[i] + i+1;
    x[i] = x[i] + i+1;
  }
}

こうしておけば、

int number = 10;

の、numberに代入する数字を変更するだけで自動的に全ての円の個数に関係のある場所が変更されて便利です。
また、for loopを使う際に、

for(int i = 0;i<3;i++){ }

のような書き方をすると、この i<3の3が何のための数字だったのか、という事が後から見てわかりにくくなってしまいます。(こういう、実数値で書かれた何に使うのかよくわからない数値の事をマジックナンバーと呼んだりします)

for(int i = 0;i<number;i++){ }

こうしておけば、numberの回数だけ繰り返す、という事がわかります。

100や1000なんかの、for loopを使わなければ出来ないような(流石にコピペで作るのは大変ですから)ものも、少し数値を変更するだけで簡単に作ることが出来ます。

配列とfor loopの便利さがお分かりいただけたでしょうか。

第13回 – 配列 1

さて、今回から配列を紹介していきますが、その前に変数をおさらいしておきましょう。
processingにおいて変数は、値を格納しておける箱のようなものでした。
図形の場所や大きさに変数を用いる事によって、その変数の値を変える(別の値を代入する)だけで図形が変化して見える(=アニメーションして見える)、数値に名前をつけて可読性を高める、同じ変数を複数の場所で利用する事で、後から値をまとめて変更出来る、など、様々な利用方法があります。
しかし、この変数をfor loopと組み合わせて使いたくなった時に、少し不具合が生じます。
例えば、for loopを利用して円を100回描いて、しかもそれをそれぞれアニメーションさせたい、という場合どうしたら良いでしょうか?
いきなり100個はちょっと想像するのが難しいので、試しに3個でやってみましょう。

  
int x1;
int y1;
int x2;
int y2;
int x3;
int y3;
  void setup(){
  size(800, 600); 
}

void draw(){
  
  for(int i = 0;i<3;i++){
    
    ellipse(x1, y1, 50, 50);
    ellipse(x2, y2, 50, 50);
    ellipse(x3, y3, 50, 50);
   
    
    x1 = x1 + 1;
    x2 = x2 + 2;
    x3 = x3 + 3;
    
    y1 = y1 + 1;
    y2 = y2 + 2;
    y3 = y3 + 3;
     //.....あれ?
  }
}

困ってしまいました。
これではfor loopを使う意味がありません。
試しにこのコードをご自分のprocessingにコピペして実行してみた後、for loop { }を削除して以下のコードに書き換えてから実行してみてください。

int x1;
int y1;
int x2;
int y2;
int x3;
int y3;

void setup() {
  size(800, 600);
}

void draw() {

  background(0);

  ellipse(x1, y1, 50, 50);
  ellipse(x2, y2, 50, 50);
  ellipse(x3, y3, 50, 50);

  x1 = x1 + 1;
  x2 = x2 + 2;
  x3 = x3 + 3;

  y1 = y1 + 1;
  y2 = y2 + 2;
  y3 = y3 + 3;
}

そうです、玉の数は3つのまま何も変わらず、スピードだけが3倍になっています。

どうしてこんな事が起きたのでしょうか?

…と、説明するまでもなく、ellipseが3つも書いてあるのだから、そりゃfor loopなんかなくても円は3つ描かれます。
つまり、forがあった時には円は9個描かれていたわけです。単に完全に重なってて見えてなかっただけで。
スピードが3倍になっていたのは、 x1 = x1 + 1;の部分が3回実行されていたからでした。(合計3回1が足されて、スピードが3倍になったのですね。)

この事からもわかるように、3種類の変数を使って、円をその変数と同じ数だけ書く、ということは、for loopには出来ないのです。

では、どうすれば良いのでしょうか?

そう。ここで配列です。

配列を使おう!

配列は、1つの変数(名前)に沢山の値を格納できるものです。
少し解りにくいので喩え話をします。

Aという箱があります。箱にはint型の値を1つ格納出来ます。←これが変数
Aという棚があります。この棚には10段仕切りが付いていて、何段目に入れるかを指定する事で、int型の値を10個格納出来ます。←これが配列

配列にいくつ値を格納できるようにするかは、名前をつける時に同時に宣言します。
int型の、xという名前の変数を宣言する方法が

int x = 0;

だったのに対し、int型の値が10個格納できるxという名前の配列を宣言するには

int[] x = new int[10];

のようにします。

普通の変数との違いは型の指定 int の後に []が付くことと、必ず = new int[10] と、new キーワード、改めて型、そして中に入れたい数の指定が必ず必要です。
100個値を入れたいなら int[100]となります。

名前が1つなのに、どうやって沢山の値を扱うの?

配列として宣言された変数は、名前だけで値を出し入れすることは出来ません。
その配列の何番目の値にアクセスしたいか、というのを必ず指定する必要があります。
そして、指定は配列の名前の後に [ ]  を付けて行います。

int[] x = new int[10];

void setup(){

  size(800,600);
  
 // x = 10; ←何番に入れるか指定していないのでエラーになる
  x[0] = 10;//←エラーにならない
  x[1] = 20;//←エラーにならない
  x[10] = 10;//←x[9]までしか存在しないのでエラーになる

}

ちなみに、配列の1番目の値は0から始まります。
少しややこしいので、慣れるまでは一呼吸置いてください。

int[] x = new int[10]
という配列なら、 x[0]〜x[9]までの10個が確保されます。
宣言時は10なので、x[10]もありそうですが存在しません。
そして、存在しないナンバーにアクセスしようとすると、プログラムはエラーを起こして動作を停止します。
必ず、存在しない値を指定していないか確認しましょう。

少し長くなりましたが今回はここまで、次回は実際にfor loopで配列を使ってみましょう。