第12回 アニメーション
アクティブモード
Processingでアニメーションを扱うためには、アクティブモード でプログラムを記述する必要があります。
アクティブモードでは、setup関数とdraw関数の中にプログラムの処理を記述します。setup関数は、プログラム実行開始時に一度だけ実行されます。draw関数は、プログラムの実行中に何度も繰り返し実行されます。draw関数の一度の呼び出しを1フレームと呼びます。
デフォルトでは、1秒間に最大60回draw関数の実行が行われます。つまり、1回あたりの実行時間は約16ミリ秒となります。コンピュータが16ミリ秒という短い時間で何度も画面の更新処理を行うことで、人間にとっては画面の内容がスムーズに変化しているように見えます。
1秒間あたりの画面更新回数はFPS(frames per seconds)という単位で表されます。人間が画面の変化をスムーズなアニメーションとして認識するためには最低60FPS程度が必要だと言われています。draw関数で長時間かかる処理を行なった場合は、画面更新が間に合わずアニメーションに遅延が起こります。この現象は処理落ちとも呼ばれます。
アクティブモードのProcessingプログラムは以下のような形式になります。アニメーションしている物体の座標など、フレームが切り替わっても記憶しておく必要がある情報は、プログラム先頭のグローバル変数に置きます。
アクティブモードでは、size関数の呼び出しはsetup関数内で行う必要があります。また、draw関数の処理を繰り返しても、前フレームで描画された結果の上にそのまま新たな描画が行われるので、draw関数内の先頭でbackground関数を呼び出し、前フレームで描画された結果をクリアしておきましょう。
// アニメーションに必要なグローバル変数を定義する
void setup() {
size(400, 400);
// 初期化処理を書く
}
void draw() {
// 画面のクリア
background(255);
// フレーム毎の描画処理を書く
}
基本的なアニメーション
以下は、赤い円が画面の上部から画面の下部へとゆっくり移動するアニメーションを表示するプログラムです。
円の座標をグローバル変数yで表しています。draw関数の最後にyの値を更新することで次フレームで円が移動するようにしています。
int r = 50;
int y = r;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int x = width / 2;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
y += 1;
}
draw関数でbackground関数を呼び出し、画面のクリアを行わないと以下のように前フレームまでの結果が残像のように残ってしまいます。
int r = 50;
int y = r;
void setup() {
size(400, 400);
background(255);
}
void draw() {
int x = width / 2;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
y += 1;
}
次は、赤い円の移動する速さを変更してみます。フレーム毎のyの移動量をdyという変数で与えています。xやdyの値、yの初期値を変更したりして、動作確認してみましょう。dyを負の値にするとどうなるでしょうか?
int r = 50;
int y = r;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int x = width / 2;
int dy = 5;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
y += dy;
}
ここまでのプログラムでは、赤い円が画面の下部に消え去ってしまいます。今度は、画面の下部に到達すると赤い円を上方向に移動させるようにしましょう。
画面の下部に赤い円の下端(y + r
)が到達したことを表す条件として、y + r >= height
を使いましょう。移動方向を逆転させるためには、移動量を表す変数dyの正負を入れ替えます。
以下のプログラムは、赤い円が画面の下端に到達すると移動方向が上方向に代わり、そのまま上へと消え去ります。
int r = 50;
int y = r;
int dy = 5;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int x = width / 2;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
if (y + r >= height) {
dy *= -1;
}
y += dy;
}
さらに、赤い円が上方向へ移動し、画面上端に到達するとまた下方向に方向を変えるように変更します。赤い円の上端が画面の上端に到達したという条件はy - r < 0
で表されます。上方向から下方向へ切り替える際も、dy *= -1
でdyの正負を入れ替えればよく、最終的なプログラムは以下のようになります。
int r = 50;
int y = r;
int dy = 5;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int x = width / 2;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
if (y - r < 0 || height <= y + r) {
dy *= -1;
}
y += dy;
}
大きさのアニメーション
座標以外にも様々な要素をアニメーションに用いることができます。円の半径や長方形の幅、高さをアニメーションで変化させてみましょう。
以下は円の大きさが変化するアニメーションです。
int r = 0;
int dr = 5;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int x = width / 2;
int y = height / 2;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
if (r < 0 || width / 2 < r) {
dr *= -1;
}
r += dr;
}
以下は長方形の幅が変化するアニメーションです。
int w = 0;
int dw = 5;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int h = 50;
fill(255, 0, 0);
noStroke();
rect(0, height / 2 - h / 2, w, h);
if (w < 0 || width <= w) {
dw *= -1;
}
w += dw;
}
色のアニメーション
図形の色をアニメーションで変化させることもできます。
以下は、円の色が変化するアニメーションです。
int c = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int r = 100;
int x = width / 2;
int y = height / 2;
fill(c);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
c = (c + 1) % 255;
}
アニメーションの組み合わせ
上では、一つの要素だけを変化させるアニメーションを作成しました。複数のグローバル変数を用いることで、x座標とy座標や、大きさと色など複数の要素を組み合わせたり、複数の図形を同時にアニメーションさせたりすることができます。
以下は、赤い円が画面内を跳ね返りながら上下左右に移動するアニメーションのプログラムです。
int r = 50;
int x = r;
int vx = 6;
int y = r;
int vy = 8;
void setup() {
size(400, 400);
}
void draw() {
background(255);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
if (x - r < 0 || width <= x + r) {
vx *= -1;
}
if (y - r < 0 || height <= y + r) {
vy *= -1;
}
x += vx;
y += vy;
}
以下も赤い円が画面中を移動するアニメーションですが、跳ね返りではなく画面の端に到達した場合反対側の端から現れます。
int r = 50;
int x = r;
int vx = 5;
int y = r;
int vy = 8;
void setup() {
size(400, 400);
}
void draw() {
background(255);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
x += vx;
y += vy;
if (x < 0 || width <= x) {
x = (x + width) % width;
}
if (y < 0 || height <= y) {
y = (y + height) % height;
}
}
複雑な運動
アニメーションにおいて、変化量をうまく調整することで複雑な運動を表現することができます。
以下は、加速度を使うことで赤い円が画面の下端に到達したときにバウンドするようなアニメーションを行なっています。
int y = 0;
int f = 1;
int v = 1;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int r = 50;
fill(255, 0, 0);
noStroke();
ellipse(width / 2, y, 2 * r, 2 * r);
if (y + r + v> height) {
v *= -1;
} else {
v += f;
}
y += v;
}
上のプログラムに左右の移動を組み合わせると以下のようになります。
int r = 50;
int x = r;
int vx = 5;
int y = r;
int fy = 1;
int vy = 1;
void setup() {
size(400, 400);
}
void draw() {
background(255);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
if (x - r < 0 || width <= x + r) {
vx *= -1;
}
if (y + r + vy> height) {
vy *= -1;
} else {
vy += fy;
}
x += vx;
y += vy;
}
関数とアニメーション
関数の描く曲線に沿って図形を移動させることができます。
以下では、赤い円が放物線に沿って移動します。
int cx = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int x0 = 0;
int y0 = height;
for (int x = 1; x < width; ++x) {
int y = (x - width / 2) * (x - width / 2) * 4 / width;
line(x, y, x0, y0);
x0 = x;
y0 = y;
}
int cy = (cx - width / 2) * (cx - width / 2) * 4 / width;
ellipse(cx, cy, 50, 50);
cx += 5;
}
以下は、赤い円のy座標がsin関数に沿って移動します。
float t = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
stroke(0);
int x0 = 0;
int y0 = height / 2;
for (int x = 1; x < width; ++x) {
int y = int(height * sin(TWO_PI * x / width - t) / 2 + height / 2);
line(x, y, x0, y0);
x0 = x;
y0 = y;
}
int cx = width / 2;
int cy = int(height * sin(TWO_PI * cx / width - t) / 2 + height / 2);
fill(255, 0, 0);
noStroke();
ellipse(cx, cy, 100, 100);
t += 0.05;
}
複雑なアニメーション
これまでに紹介したような要素を使うことで様々なアニメーションを表現することができます。
オリジナルの作品を作ってみましょう。
int r = 50;
int x, y;
int dv = 5;
int vx = dv;
int vy = 0;
int tRange = 30;
void setup() {
size(400, 400);
x = width / 2;
y = height / 2;
}
void draw() {
background(255);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
float theta = atan2(vy, vx);
theta += random(radians(-tRange), radians(tRange));
vx = int(dv * cos(theta));
vy = int(dv * sin(theta));
x += vx;
y += vy;
if (x < 0 || width <= x) {
x = (x + width) % width;
}
if (y < 0 || height <= y) {
y = (y + height) % height;
}
}
float theta = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int r = int(20 * sin(theta) + 30);
int x = int(width * cos(theta) / 2 + width / 2);
int y = height / 2;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 2 * r, 2 * r);
theta += 0.05;
}