第13回 アニメーションの高度な利用
前回の復習
- アニメーションを扱うためにはアクティブモードでプログラミングを行う
- プログラムの開始時に1度だけ実行されるsetup関数
- 1フレームの描画を繰り返すdraw関数
- フレーム毎に切り替わる値をグローバル変数に置くことでスムーズなアニメーションを実現する
- アニメーションで変化させられる要素
- 図形の座標、大きさ、色、etc
// アニメーションに必要なグローバル変数を定義する
void setup() {
size(400, 400);
// 初期化処理を書く
}
void draw() {
// 画面のクリア
background(255);
// フレーム毎の描画処理を書く
}
円運動
前回は、図形の座標を変化させるアニメーションを中心に扱っていました。 フレーム毎に直接座標を変更するのではなく、角度を変化させ、現在の角度に基づいて座標を計算することで図形を円運動させることができます。
現在の角度をt、円運動の半径をr、円運動の中心の座標を(cx, cy)とした時、円運動している図形のx座標とy座標は以下のように表されます。
以下は、直径100の赤い円が画面の中心を中心として円運動するアニメーションのプログラムです。
float t = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int r = 150;
int cx = width / 2;
int cy = height / 2;
float x = r * cos(t) + cx;
float y = r * sin(t) + cy;
int d = 100;
fill(255, 0, 0);
noStroke();
ellipse(x, y, d, d);
t += 0.01;
}
配列を使ったアニメーション
同じようなアニメーションを行う図形が複数ある場合は、配列を用いることでプログラムをシンプルにできます。
以下は、10個の赤い円が画面中をバウンドしながら移動するアニメーションのプログラムです。
int[] x = new int[10];
int[] y = new int[x.length];
int[] dx = new int[x.length];
int[] dy = new int[x.length];
int r = 25;
void setup() {
size(400, 400);
for (int i = 0; i < x.length; ++i) {
x[i] = int(random(r, width - r));
y[i] = int(random(r, height - r));
dx[i] = int(random(1, 6));
dy[i] = int(random(1, 6));
}
}
void draw() {
background(255);
for (int i = 0; i < x.length; ++i) {
fill(255, 0, 0);
noStroke();
ellipse(x[i], y[i], 2 * r, 2 * r);
if (x[i] - r < 0 || width <= x[i] + r) {
dx[i] *= -1;
}
if (y[i] - r < 0 || height <= y[i] + r) {
dy[i] *= -1;
}
x[i] += dx[i];
y[i] += dy[i];
}
}
時間を使ったアニメーション
これまでは、決まったルールに従って同じ動きをし続けるアニメーションを扱ってきました。
もう一つのアニメーションの手段として、プログラムの開始からの経過時間を用いる方法があります。
プログラムの開始からの経過時間(ミリ秒)を取得するためにはmillis()
関数を使用します。
以下は、プログラムの開始から1秒後に、2秒間かけて赤い円を移動するプログラムです。
void setup() {
size(400, 400);
}
void draw() {
background(255);
int tStart = 1000;
int duration = 2000;
float dt = millis() - tStart;
float r = dt / duration;
if (r < 0) {
r = 0;
}
if (r > 1) {
r = 1;
}
int xStart = 100;
int xStop = 300;
float x = (xStop - xStart) * r + xStart;
fill(255, 0, 0);
noStroke();
ellipse(x, height / 2, 100, 100);
}
ユーザーインタラクション
一般的なGUIアプリケーションでは、マウスやキーボードの操作を通じてユーザーとアプリケーションが対話(インタラクション)を行う。 ここでは、Processingにおけるユーザーインタラクションについて紹介する。
イベントハンドラーとシステム変数
Processingでは」マウスがクリックされた時」や「キーボードのキーが押された時」に起こるイベントを処理することでユーザーインタラクションを実現する。 Processing以外のGUIアプリケーションにおいても、イベントの処理を中心とした イベント駆動プログラミング が行われている。
Processingのイベント処理では、イベントが起きた時の処理を記述するイベントハンドラーとその時の状態を表すシステム変数を用いる。
マウスとキーボードに関するイベントハンドラーは以下の表の通りである。
名前 | 役割 |
---|---|
mouseClicked() | マウスがクリックされた(押して離された)時 |
mousePressed() | マウスボタンが押された時 |
mouseReleased() | マウスボタンが離された時 |
mouseMoved() | マウスが移動した時 |
mouseDragged() | マウスがドラッグされた時 |
mouseWheel() | マウスホイールが操作された時 |
keyPressed() | キーボードのキーが押された時 |
keyReleased() | キーボードのキーが離された時 |
イベントハンドラーは以下のように関数として記述する。
void setup() {
size(400, 400);
}
void draw() {
background(255);
}
void mouseClicked() {
// マウスがクリックされた時の処理
}
void mousePressed() {
// マウスボタンが押された時の処理
}
void mouseReleased() {
// マウスボタンが離された時の処理
}
void mouseMoved() {
// マウスが移動した時の処理
}
void mouseDragged() {
// マウスがドラッグされた時の処理
}
void mouseWheel() {
// マウスホイールが操作された時の処理
}
void keyPressed() {
// キーボードのキーが押された時の処理
}
void keyReleased() {
// キーボードのキーが離された時の処理
}
イベントハンドラーやdraw関数の中で、現在のマウスやキーボードの状態を取得するためにはシステム変数を使用します。 システム変数は読み取りのみに使用して、システム変数への代入は行いません。 マウスとキーボードに関するシステム変数は以下の表の通りです。
名前 | 内容 |
---|---|
mouseX | そのフレームのマウスのx座標 |
mouseY | そのフレームのマウスのy座標 |
pmouseX | 前のフレームのマウスのx座標 |
pmouseY | 前のフレームのマウスのy座標 |
mousePressed | マウスボタンが押されているかどうかを表す真理値 |
mouseButton | 押されているマウスのボタンの種類 |
key | 押されているキーの文字 |
keyCode | 押されているキーのコード |
keyPressed | キーが押されているかどうかを表す真理値 |
例1
以下は、キーボードのキーが押される度に赤い円が画面左から画面右まで移動するアニメーションのプログラムである。 keyPressed関数内でグローバル変数tStartを設定することで、キーが押されてからアニメーションを開始するという処理を実現している。
int tStart = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int duration = 1000;
float dt = millis() - tStart;
float r = dt / duration;
if (tStart == 0) {
r = 0;
}
if (r > 1) {
r = 1;
}
int xStart = 0;
int xStop = width;
float x = (xStop - xStart) * r + xStart;
fill(255, 0, 0);
noStroke();
ellipse(x, height / 2, 100, 100);
}
void keyPressed() {
tStart = millis();
}
例2
以下は、マウスをクリックした位置に向かってボールが移動するアニメーションのプログラムである。 xStart、xStop、yStart、yStopに移動前と移動後の座標を格納しており、マウスがクリックされる度にmouseClicked関数内でそれらの値を更新している。
float xStart;
float xStop;
float yStart;
float yStop;
float x;
float y;
int tStart = 0;
void setup() {
size(400, 400);
xStart = width / 2;
xStop = width / 2;
yStart = height / 2;
yStop = height / 2;
}
void draw() {
background(255);
int t = millis();
int duration = 500;
float r;
if (t - tStart > duration) {
r = 1;
} else {
r = float(t - tStart) / duration;
}
x = (xStop - xStart) * r + xStart;
y = (yStop - yStart) * r + yStart;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 50, 50);
}
void mouseClicked() {
xStart = x;
yStart = y;
xStop = mouseX;
yStop = mouseY;
tStart = millis();
}
イージング(easing)
上では、開始時間から終了時間まで線形に変化する(変化量が常に一定である)アニメーションを行いました。 アニメーションに緩急を付けることで、人間がより自然に感じる動作の変化を実現することができます。 このようなテクニックをイージングと呼びます。 イージングは、映像作品やゲームなどのエンターテイメント性の強いアニメーションだけでなく、Webサイトやアプリのユーザーインタフェースでも使われています。
上のプログラムでは、現在の時間が開始時間から終了時間までの間のどこに位置しているかを表す変数としてr(0 ≦ r ≦ 1)を用いていました。 イージングでは、非線形な関数を用いてrから描画要素の変化量を決定します。
参考資料
int tStart = 0;
void setup() {
size(600, 350);
}
void draw() {
background(255);
int duration = 2000;
float dt = millis() - tStart;
float r = dt / duration;
if (tStart == 0) {
r = 0;
}
if (r > 1) {
r = 1;
}
int xStart = 0;
int xStop = width;
int d = 50;
fill(0);
noStroke();
// linear
float x1 = (xStop - xStart) * r + xStart;
ellipse(x1, 25, d, d);
// easeInQuad
float x2 = (xStop - xStart) * pow(r, 2) + xStart;
ellipse(x2, 75, d, d);
// easeInCubic
float x3 = (xStop - xStart) * pow(r, 3) + xStart;
ellipse(x3, 125, d, d);
// easeOutQuad
float x4 = -(xStop - xStart) * r * (r - 2) + xStart;
ellipse(x4, 175, d, d);
// eqseOutCubic
float x5 = (xStop - xStart) * (pow(r - 1, 3) + 1) + xStart;
ellipse(x5, 225, d, d);
// easeInOutQuad
float x6;
if (r < 0.5) {
float s = r * 2;
x6 = (xStop - xStart) / 2 * pow(s, 2) + xStart;
} else {
float s = r * 2 - 1;
x6 = -(xStop - xStart) / 2 * (s * (s - 2) - 1) + xStart;
}
ellipse(x6, 275, d, d);
// easeInOutCubic
float x7;
if (r < 0.5) {
float s = r * 2;
x7 = (xStop - xStart) / 2 * pow(s, 3) + xStart;
} else {
float s = r * 2 - 2;
x7 = (xStop - xStart) / 2 * (pow(s, 3) + 2) + xStart;
}
ellipse(x7, 325, d, d);
}
void keyPressed() {
tStart = millis();
}
授業内課題
授業内課題13-1
直径100の円が、画面の中心を中心として反時計回りに円運動するアニメーションのプログラムを作成せよ。 円運動の半径は150とする。 また、円の色はマウス位置が運動している円に含まれている時に赤色、そうでないときに黒色で塗りつぶせ。
授業内課題13-2
直径30の15個の赤い円が画面中をバウンドしながら移動するアニメーションのプログラムを作成せよ。 キーボードのキーが押されると全ての円の移動を停止し、その状態でもう一度キーが押されると移動を再開するようにせよ。
授業内課題13-3
ユーザーインタラクションの例2のプログラムにおいて、赤い円の移動にイージングを適用せよ。 イージング関数はlinear以外の任意のものを用いて良い。
授業内課題13-4
幅400、高さ400のウィンドウにおいて、(100, 200)の位置に直径100の赤い円を描画する。 プログラムの開始から1秒後に円を(100, 200)から(300, 200)へ2秒かけて移動させ、さらにプログラムの開始から3秒後に(300, 200)から(100, 200)に3秒かけて移動させよ。
授業内課題の解答例
授業内課題13-1
直径100の円が、画面の中心を中心として反時計回りに円運動するアニメーションのプログラムを作成せよ。 円運動の半径は150とする。 また、円の色はマウス位置が運動している円に含まれている時に赤色、そうでないときに黒色で塗りつぶせ。
解答例
float t = 0;
void setup() {
size(400, 400);
}
void draw() {
background(255);
int r = 150;
float x = r * cos(t) + width / 2;
float y = r * sin(t) + height / 2;
int d = 100;
if (dist(mouseX, mouseY, x, y) <= d / 2) {
fill(255, 0, 0);
} else {
fill(0);
}
noStroke();
ellipse(x, y, d, d);
t -= 0.01;
}
授業内課題13-2
直径30の15個の赤い円が画面中をバウンドしながら移動するアニメーションのプログラムを作成せよ。 キーボードのキーが押されると全ての円の移動を停止し、その状態でもう一度キーが押されると移動を再開するようにせよ。
解答例
int[] x = new int[15];
int[] y = new int[x.length];
int[] dx = new int[x.length];
int[] dy = new int[x.length];
int r = 15;
boolean stop = false;
void setup() {
size(400, 400);
for (int i = 0; i < x.length; ++i) {
x[i] = int(random(r, width - r));
y[i] = int(random(r, height - r));
dx[i] = int(random(1, 6));
dy[i] = int(random(1, 6));
}
}
void draw() {
background(255);
for (int i = 0; i < x.length; ++i) {
fill(255, 0, 0);
noStroke();
ellipse(x[i], y[i], 2 * r, 2 * r);
if (!stop) {
if (x[i] - r < 0 || width <= x[i] + r) {
dx[i] *= -1;
}
if (y[i] - r < 0 || height <= y[i] + r) {
dy[i] *= -1;
}
x[i] += dx[i];
y[i] += dy[i];
}
}
}
void keyPressed() {
stop = !stop;
}
授業内課題13-3
ユーザーインタラクションの例2のプログラムにおいて、赤い円の移動にイージングを適用せよ。 イージング関数はlinear以外の任意のものを用いて良い。
解答例
float xStart;
float xStop;
float yStart;
float yStop;
float x;
float y;
int tStart = 0;
void setup() {
size(400, 400);
xStart = width / 2;
xStop = width / 2;
yStart = height / 2;
yStop = height / 2;
}
void draw() {
background(255);
int t = millis();
int duration = 500;
float r;
if (t - tStart > duration) {
r = 1;
} else {
r = float(t - tStart) / duration;
}
x = (xStop - xStart) * pow(r, 3) + xStart;
y = (yStop - yStart) * pow(r, 3) + yStart;
fill(255, 0, 0);
noStroke();
ellipse(x, y, 50, 50);
}
void mouseClicked() {
xStart = x;
yStart = y;
xStop = mouseX;
yStop = mouseY;
tStart = millis();
}
授業内課題13-4
幅400、高さ400のウィンドウにおいて、(100, 200)の位置に直径100の赤い円を描画する。 プログラムの開始から1秒後に円を(100, 200)から(300, 200)へ2秒かけて移動させ、さらにプログラムの開始から3秒後に(300, 200)から(100, 200)に3秒かけて移動させよ。
解答例
void setup() {
size(400, 400);
}
void draw() {
background(255);
int tStart1 = 1000;
int tStart2 = 3000;
int now = millis();
float x;
if (now < 1000) {
x = 100;
} else {
float dt;
int xStart, xStop, duration;
if (now < tStart2) {
dt = millis() - tStart1;
xStart = 100;
xStop = 300;
duration = 2000;
} else {
dt = millis() - tStart2;
xStart = 300;
xStop = 100;
duration = 3000;
}
float r = dt / duration;
if (r < 0) {
r = 0;
}
if (r > 1) {
r = 1;
}
x = (xStop - xStart) * r + xStart;
}
fill(255, 0, 0);
noStroke();
ellipse(x, height / 2, 100, 100);
}