[Work/Class/Java with Processing/2_ProcessingBasic]

アート系学生のためのJAVA with Processing - その2-6 Processingの関数

導入

本記事では,Processingの関数について述べる.

関数の概念

関数とは,一まとまりの処理をプログラムの実行部分(Processingで言えば,「void draw(){}」から切り離して記述し,一行でその処理のまとまりを呼び出す事で,プログラムの可読性を高めたり,処理ブロックの再利用性を高めたりするものである.

例えば,今までアニメーションのプログラムで「void draw(){}」の冒頭で行っていた,四角を画面全体に描画することで画面をリフレッシュする一連の処理のまとまりを考えてみる.

void draw(){
 noStroke();
 fill(255,255,255);
 rect(0, 0, width, height);

 ...(実際の処理に続く)
    

この部分はdraw中で毎回同じ処理を行っており,またプログラムを書く人間も「ここでの処理の内容はわかりきっている」のに3行も使ってしまっており,プログラムの可読性を下げている.

そこで,このリフレッシュ処理部分を独立させて「関数」にまとめてdraw以外の場所に記述する事で,drawブロックの可読性を高める事が出来る.
関数の名前は任意に定義する事が出来る.

void setup(){
 ...セットアップ処理を記述
}

void draw(){
 //あとで定義する画面のリフレッシュ処理を行う関数drawRefreshRectを呼び出す
 drawRefreshRect();

 ...実際の描画処理を記述
}

//画面のリフレッシュ処理を行う関数
void drawRefreshRect(){
 noStroke();
 fill(255,255,255);
 rect(0, 0, width, height);
}

上記のコードでは,drawRefreshRectと名前をつけた関数を定義し,毎回のdraw()の中でそれを呼び出している.

ここで気がついたかもしれないが,実は,void setup(){}というブロックも,void draw(){}というブロックも,実は,Processing本体から自動的に呼ばれる関数を記述している事に他ならない.

引数

引数とは,関数を呼び出す時に,関数に値を与えることができる概念である.
関数を定義する時に,

void drawRefreshRect(int r, int g, int b, int alpha){
 noStroke();
 fill(r, g, b, alpha);
 rect(0, 0, width, height);
}

と記述し,drawブロックの中で関数を呼び出す時には,

drawRefreshRect(125, 100 ,65, 50);

のように記述する事により,drawRefreshRect関数内では自動的に変数r, g, b, alphaが定義され,関数内でその変数を利用できる.
そこに関数を呼び出す時にそれぞれ,125, 100, 65, 50という値を入れて呼び出すことで,実際には呼び出した時にその値が関数内の変数に入り,利用する事が出来る.
この変数のことを引数と呼ぶ.

配列を引数として取り描画を行う

配列も関数の引数として取る事が出来る.
関数定義の実例として,10個の円の描画のアニメーションを行うプログラムを関数を用いて記述してみる.
drawEllipses関数を定義し,その引数にxとyの配列を渡している.
(以下のコードでは上記のdrawRefreshRect関数も用いている)

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

void setup(){
  size(640, 480);
  colorMode(RGB, 255);
  background(255, 255, 255);
  frameRate(10);
  
  for(int i=0; i<10; i++){
   x[i] = (int)random(width);
   y[i] = (int)random(height);
   if(random(2) < 1){
    xdirection[i] = 1;
   }
   else{
    xdirection[i] = -1;
   }
  }
}

void draw(){
  drawRefreshRect(255, 255, 255, 60); //drawRefreshRect関数を引数を渡して呼び出す
  drawEllipses(x, y); //配列xと配列yを引数に渡してdrawEllipses関数を呼び出す
  
  //描画位置の更新
  for(int i=0; i<10; i++){
    if(x[i] > width){
      xdirection[i] = -1;
    }
    else if(x[i] < 0){
      xdirection[i] = 1;
    }
    x[i] = x[i] + xdirection[i] * 10; 
  }
}

void drawRefreshRect(int r, int g, int b, int alpha){
  //整数を四つ引数として取る関数であるという定義
  //引数がr, g, b, alphaという変数にそれぞれ格納されている
  noStroke();
  fill(r, g, b, alpha);
  rect(0, 0, width, height);
}

void drawEllipses(int xPosition[], int yPosition[]){
  //整数の配列を二つ引数として取る関数であるという定義
  //引数に渡された配列が,xPosition配列とyPosition配列にそれぞれ格納されている
  stroke(0, 0, 0);
  fill(100, 100, 100);
  for(int i=0; i<10; i++){
    ellipse(xPosition[i], yPosition[i], 20, 20);
  }
}

戻り値

戻り値とは,関数の処理が終わった時に,関数を呼び出した所に関数内で処理した値を戻すことができる概念である.
例えば単純な足し算を行う関数を定義してみる.

int add(int a, int b){
 return a + b;
}

上記関数定義は,変数aとbを足し合わせて,足した値を返す(return).
この関数を呼び出すためには,

int addedVar = add(15, 20);

という形で実行すると,変数addedVarの中には,15と20を足し合わせた値が入る.

今までは,関数の定義の頭には「void」というキーワードが入っていたが,これは返すものがない,つまりreturnが入っていない関数の定義の際につけるキーワードである.
整数型をreturnする場合には,voidの代わりにintを付けて宣言する.

以下のコードは,updatePositionsという,現在のxとxdirectionの配列を引数に取り,戻り値として次のフレームの配列を返す関数を定義して利用したものである.

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

void setup(){
  size(640, 480);
  colorMode(RGB, 255);
  background(255, 255, 255);
  frameRate(10);
 
  for(int i=0; i<10; i++){
   x[i] = (int)random(width);
   y[i] = (int)random(height);
   if(random(2) < 1){
    xdirection[i] = 1;
   }
   else{
    xdirection[i] = -1;
   }
  }
}

void draw(){
  drawRefreshRect(255, 255, 255, 60);
  drawEllipses(x, y); //x

  for(int i=0; i<10; i++){
    if(x[i] > width){
      xdirection[i] = -1;
    }
    else if(x[i] < 0){
      xdirection[i] = 1;
    }
  }
  //xの配列をupdatePositionsという関数により更新する
  x = updatePositions(x, xdirection);
}

void drawRefreshRect(int r, int g, int b, int alpha){
  noStroke();
  fill(r, g, b, alpha);
  rect(0, 0, width, height);
}

void drawEllipses(int xPosition[], int yPosition[]){
  stroke(0, 0, 0);
  fill(100, 100, 100);
  for(int i=0; i<10; i++){
    ellipse(xPosition[i], yPosition[i], 20, 20);
  }
}

int[] updatePositions(int xPositions[], int xDirections[]){
  //整数の配列を戻り値として返し,整数の配列二つを引数として取る関数である

  //引数に渡された配列は,それぞれxPositions, xDirectionsという名前の配列に格納されている
  int newXPositions[] = new int[10]; //戻り値として返すための配列を生成する
  for(int i=0; i<10; i++){
    newXPositions[i] = xPositions[i] + xDirections[i] * 10; 
    //新しい配列に格納していく
  }
  return newXPositions; //新しい配列を戻り値として返す
}