スクリーンセーバー的なアニメーション動画の作成|Processing
Processingについての投稿3回目です。今回は、ずっと見ていられるスクリーンセーバーのようなものを作りたいと思い、2つ実装してみました。どちらも、ぼーっと見ていられるアニメーションになっていると思います。
アニメーション
1つ目は、複数のボールが動きながら色を塗りつぶしていく動画です。ボールが跳ね返る度に、少しずつボールの色が変化していきます。スマートフォン向けに縦動画です。
youtube.com
2つ目は、ボール同士が衝突しながら合体し、大きくなっていく動画です。全てのボールが合体するとまたバラバラになります。PC向けに横動画です。
youtu.be
アニメーションのコード
カラフルなボールが平面を塗りつぶしていく動画
// 変数宣言 int ballnum = 60; // 玉の数(初期値) Point[] points = new Point[ballnum]; void setup() { size(540, 980); background(255); // カラーモード指定 colorMode( HSB, 360, 100, 100, 100 ); // 初期化 for (int i=0; i<points.length; i++) { points[i] = new Point(random(width), random(height), random(-5, 5), random(-5, 5), random(20,100), random(0, 360), random(50, 60), random(90, 100)); // x方向,y方向の移動速度を制御する変数は、√(x^2+y^2)で標準化した上で、サイズに応じた値を掛け算する points[i].speedX = 4 * 30/points[i].ballsize * points[i].speedX / sqrt(sq(points[i].speedX)+sq(points[i].speedY)); points[i].speedY = 4 * 30/points[i].ballsize * points[i].speedY / sqrt(sq(points[i].speedX)+sq(points[i].speedY)); } } void draw() { frameRate(60); //background(0); // 移動と描画 for (int i=0; i<points.length; i++) { points[i].move(); points[i].drawEllipse(); } // 動画作成用にpngファイルを保存 ※保存時にコメントインする //saveFrame("frames/######.png"); } class Point { // 変数を宣言 float x; // float y; float speedX; float speedY; float ballsize; float col1; float col2; float col3; // constractorを初期化 Point(float _x, float _y, float _speedX, float _speedY, float _ballsize, float _col1, float _col2, float _col3) { x = _x; y = _y; speedX = _speedX; speedY = _speedY; ballsize = _ballsize; col1 = _col1; col2 = _col2; col3 = _col3; } // method 関数 void move() { // x,y方向にそれぞれ移動 x += speedX; y += speedY; // 玉が左端に到達したらx軸方向を逆向きにする // ※半径の幅を考慮してballsize/2としている if (x < ballsize/2){ x = ballsize/2; speedX = -speedX; col1 += 10; } // 玉が右端に到達したらx軸方向を逆向きにする else if (x > width-ballsize/2) { x = width-ballsize/2; speedX = -speedX; col1 += 10; } // 玉が上端に到達したらy軸方向を逆向きにする if (y < ballsize/2){ y = ballsize/2; speedY = -speedY; col1 += 10; } // 玉が下端に到達したらy軸方向を逆向きにする else if (y > height-ballsize/2){ y = height-ballsize/2; speedY = -speedY; col1 += 10; } // 色相が360を超えたら360引く if (col1 > 360){ col1 -= 360; } } // 描画 void drawEllipse() { // 縁なし noStroke(); // 玉の色を指定 fill(col1, col2, col3, 50); // 位置とサイズを指定して、玉を表示 ellipse(x, y, ballsize, ballsize); } }
玉が衝突しながら大きくなっていく動画
// 変数宣言 int ballnum = 100; // 玉の数(初期値) int default_ballsize = 40; // 玉のサイズ(初期値) int base_speed=12; // 速度の基準値 int counter = 0; // リセット処理用カウンタ boolean reset_flag = false; // リセットフラグ Point[] points = new Point[ballnum]; void setup() { // フルスクリーン表示 fullScreen(); // カラーモード指定 colorMode( HSB, 360, 100, 100, 100 ); // 初期化 for (int i=0; i<points.length; i++) { points[i] = new Point(random(width), random(height), random(-5, 5), random(-5, 5), default_ballsize, random(0, 360), random(90, 100), random(90, 100)); // x方向,y方向の移動速度を制御する変数は、2次元上での移動速度が全ての玉で一緒になるように、base_speed/√(x^2+y^2)で標準化する points[i].speedX = base_speed*points[i].speedX/sqrt(sq(points[i].speedX)+sq(points[i].speedY)); points[i].speedY = base_speed*points[i].speedY/sqrt(sq(points[i].speedX)+sq(points[i].speedY)); } } void draw() { //frameRate(20); background(0,0,0); // 移動と描画 for (int i=0; i<points.length; i++) { points[i].move(); points[i].drawEllipse(); } // 玉同士の接触判定 for (int i=0; i<points.length; i++) { for (int j=i+1; j<points.length; j++) { // どちらかの玉が既に接触済で、サイズ0になっていればスキップ if ((points[i].ballsize == 0) || (points[j].ballsize == 0)) {continue;} // 接触範囲 = 2つの玉の半径を足し算 float range = points[i].ballsize/2 + points[j].ballsize/2; // 玉の中心同士の距離 = √((x1-x2)^2+(y1-y2)^2) float dist = sqrt(sq(points[i].x - points[j].x) + sq(points[i].y - points[j].y)); // 距離が半径の和よりも近い場合は接触 if (dist <= range) { // 2玉のうち、サイズが大きい方が残る if (points[i].ballsize >= points[j].ballsize){ // 新しい玉の面積が、2つの玉の面積の足し算になるように調整 points[i].ballsize = sqrt(sq(points[i].ballsize)+sq(points[j].ballsize)); // 玉の大きさに応じて移動速度を設定 points[i].speedX = base_speed*points[i].speedX / sqrt(sq(points[i].speedX)+sq(points[i].speedY)) * sqrt(default_ballsize / points[i].ballsize); points[i].speedY = base_speed*points[i].speedY / sqrt(sq(points[i].speedX)+sq(points[i].speedY)) * sqrt(default_ballsize / points[i].ballsize); // 小さい方の玉のサイズを0にして表示から消す points[j].ballsize = 0; } else { //jの方がサイズが大きい場合の処理。内容は上と同じ points[j].ballsize = sqrt(sq(points[i].ballsize)+sq(points[j].ballsize)); points[j].speedX = base_speed*points[j].speedX / sqrt(sq(points[j].speedX)+sq(points[j].speedY)) * sqrt(default_ballsize / points[j].ballsize); points[j].speedY = base_speed*points[j].speedY / sqrt(sq(points[j].speedX)+sq(points[j].speedY)) * sqrt(default_ballsize / points[j].ballsize); points[i].ballsize = 0; } } } } // リセット判定 int del_count = 0; // サイズが0になっている玉の数を確認 for (int i=0; i<points.length; i++) { if (points[i].ballsize == 0){ del_count += 1; } } // 全数-1がサイズ0になっていたらreset_flagをtrueにする if (del_count >= ballnum-1){ reset_flag = true; } // リセット処理 if (reset_flag){ // 回数カウンタを1追加 counter += 1; // ボールの位置などを全て初期化 for (int i=0; i<ballnum; i++) { points[i].x = random(width); points[i].y = random(height); points[i].speedX = random(-5, 5); points[i].speedY = random(-5, 5); points[i].speedX = base_speed*points[i].speedX/sqrt(sq(points[i].speedX)+sq(points[i].speedY)); points[i].speedY = base_speed*points[i].speedY/sqrt(sq(points[i].speedX)+sq(points[i].speedY)); points[i].ballsize = default_ballsize; points[i].col1 = random(0, 360); points[i].col2 = random(90, 100); points[i].col3 = random(90, 100); } // 演出のため、ボールの位置の初期化を50回繰り返した後でreset_flagをfasleに戻す if (counter >= 50){ counter = 0; reset_flag = false; } } // 動画作成用にpngファイルを保存 ※保存時にコメントインする //saveFrame("frames1/######.png"); } class Point { // 変数を宣言 float x; // float y; float speedX; float speedY; float ballsize; float col1; float col2; float col3; // constractorを初期化 Point(float _x, float _y, float _speedX, float _speedY, float _ballsize, float _col1, float _col2, float _col3) { x = _x; y = _y; speedX = _speedX; speedY = _speedY; ballsize = _ballsize; col1 = _col1; col2 = _col2; col3 = _col3; } // method 関数 void move() { // x,y方向にそれぞれ移動 x += speedX; y += speedY; // 玉が左端に到達したらx軸方向を逆向きにする // ※半径の幅を考慮してballsize/2としている if (x < ballsize/2){ x = ballsize/2; speedX = -speedX; } // 玉が右端に到達したらx軸方向を逆向きにする else if (x > width-ballsize/2) { x = width-ballsize/2; speedX = -speedX; } // 玉が上端に到達したらy軸方向を逆向きにする if (y < ballsize/2){ y = ballsize/2; speedY = -speedY; } // 玉が下端に到達したらy軸方向を逆向きにする else if (y > height-ballsize/2){ y = height-ballsize/2; speedY = -speedY; } } // 描画 void drawEllipse() { // 縁なし noStroke(); // 玉の色を指定 fill(col1, col2, col3, 90); // 位置とサイズを指定して、玉を表示 ellipse(x, y, ballsize, ballsize); } }
だいぶやりたいことが表現できるようになってきました。今回で、一旦、Processingの実践は終わりにしますが、また作りたいアニメーションができたら紹介します。
最後まで読んでいただきありがとうございました。