[Go to “teaching” in Morimoto Lab]

Game by ball 7

①ボールに力を加える・投げる
②見えないボールでアトラクタを作る
クラスの中でクラスを使うという、慣れるまではちょっと難易度高い内容になっています。


1. ボールに力を加える

classの練習、Attractorのための助走としてボールに力を加えるプログラムを作る。

力を加えるということは、現在のボールの速さに別のベクトルを加えることです。
まず、ボールの移動プログラムを用意します(classを使いましょう)。
今回は初速度は0、重力はなし、壁もなし、空気抵抗は考慮しましょう。
次に、マウスを押したら適当なベクトルが速度に足されるコードを作りましょう。

以下はマウスを押したらボールに力を加えるプログラムのベースです。
updateメソッド内でボールの速度と位置を更新する部分は書いてみてください(Game by ball 1,2でやった内容です)。

Ball ball;
void setup(){
size(500,500);
ball = new Ball();
}
void draw(){
background(255);
ball.update();
if(mousePressed) {
ball.addForce();
}
}
class Ball{
PVector pos;
float diam = 10;
PVector vel;
float air_drag = 0.95f;//空気抵抗は考慮する
Ball()
{
pos = new PVector(width/2, height/2);
vel = new PVector(0, 0);//速度は最初0にしておく
}
void update()
{
//update velosity:ここは第1,2回にしたので省略
//update position:ここは第1,2回にしたので省略
//bound:壁とのバウンドは今回なくてよいです
display();
}
void display(){
stroke(0);
noFill();
ellipse(pos.x, pos.y, diam, diam);
}
void addForce(){
PVector vec = new PVector(3,-3);
vel.add(vec);
}
}

Ex A:上記のクラスを完成させて、マウスを押したらボールに力が加わるようにする。また、Ballクラスのコンストラクタに引数を設定し、初期位置を設定できるようにする。

コンストラクタはクラスと同じ名前で型の記載がない特殊なクラスでした。
newの時に呼び出しているのがコンストラクタです。
コンストラクタも関数なので、普通の関数のように、引数を変えて定義できます
(引数の違う複数のコンストラクタを定義することも可能です)。

//update velosity
vel.x *=air_drag;//空気抵抗
vel.y *=air_drag;//multメソッドなど使ってもよい

初歩的なミス:
Ballクラスの中では、メンバ変数であるposやvelをpos.xなどとして使います。
Ballクラスの中で、ball.pos.xなどとしないようにしましょう。

Alt text
↑結果例


Ex B: マウスとボールが近づいたときに距離に応じて、力が加わるようにする

<実装のステップ(例)>

  1. addForce内などで ※

  2. マウスとボールの距離を計算

  3. 一定の距離内にボールがあるかどうか判定(この「一定の距離=影響範囲」は適当に決めましょう)

  4. もし影響距離内にボールがあったら、マウスからボールへの単位ベクトルを計算し、適当な倍数にして調整して、ボールの速度にベクトルを足す

  5. 4で、一旦「適当な倍数」としましたが、距離に応じて倍数を変えるようにしましょう。近ければ近いほど強い力が加わるようにしましょう。

※上記、マウスとボールの距離を計算するメソッドを作り、draw()のところで距離判定をするのもよいかもしれません。適宜、やりやすいようにアレンジしてみてください。

ヒント:近いほど強くなるような力の計算方法
例えば、以下のように計算した値を調整した上で力の大きさとするとよさそうです。
①(1/距離)
②(影響範囲-マウスとボールの距離)※1
③(ガウス関数などの釣鐘型の関数に距離を変数として計算)※2
経験的には②の挙動がいいみたいですが、他にもよい方法があるかもしれません。

※1 値が0以下になるときはifで場合分けの処理をすること。
※2 指数関数はexp(), べき乗はpow()で計算できる。

Alt text
↑点を並べてマウスを押して周囲に力を与えた場合
※Ex. Bはボール一つでやってみてください。できる人は上記の図のようなボール配置でやってみても〇
※最終的にボールは上記のように配置されますが、講義では1次元配列しか教えていません。今回のAttractorの課題は1次元配列でできるように設計しています。2次元配列はどこかで学ぶとよいとは思いますが、今回に関してはだいぶ意図とは違う解き方になります。(ちょっとこの絵はミスリーディングかもしれません。。🙇)


(おまけ。余裕のある人へ)ボールをマウスドラッグでつかんで、マウスを離したときに投げるようにする

※この課題は横道にそれるのでとばしてもいいですが、ポケモンGOみたいにボールを投げるコンテンツに興味のある人はやってみてください。


2. Attractorを作ろう

Attractorは数値的値の集合が動的に発展をするもの、らしい。

Attractorの画像検索結果

Alt text

数学的Attractor:
Attractorについてのサイトをいくつか紹介。

今回実装するのはAttractorのような雰囲気のビジュアルのものである。
ここでの実装では、アトラクターは見えないボールとボールの間を線でつなぎ、
そのボールをマウスにひきつけたり、逆に離したりすることで、
不思議な模様を描きます。

参考にしたのは以下のGenerative Designという本の例です。
https://download.bnn.co.jp/support/generativedesign/code/M_4_3_01_TOOL.html

enter image description here

以下のようにコードをダウンロードして、
デスクトップ上でこのプログラムを実行してみよう。

  1. このページの右のボタン”code”から、”download zip”などでdownloadする

  2. 解凍したフォルダからCode-Package-Processing-3.x/02_M/M_4_3_01_TOOL/を開く

  3. pdeファイルをProcessingで開く。

  4. Generative Designのライブラリをimportする必要があります。
    メニュー>スケッチ>ライブラリをインポート>ライブラリを追加 より”GenerativeDesign”をインストールする

  5. 実行できるはず

クリックして、挙動を見てみましょう。
左上のボタンを押すと、UIが現れ、影響範囲や点の動かし方を変更できます。

しかしコードを見てみると、つらく感じる人もいるだろう。
プロのプログラムを読むのはつらい作業だ(教えてないこともあるし)
むしろ、これまで培ったGame by ballの知識で一から実装してみよう。
そっちの方が簡単だと思う。

※高みを目指す人には人のプログラムは色々見てもらいたい。
とても勉強になる。
今日の課題を終えてからもう一度見てみるとよい。


Ex C: Attractorを実装しなさい(横方向一列のみ)

Ex Bを発展してAttractor(もどき?)を作りましょう。画面にボールを並べて、横線で繋ぎ、クリックしたところから近いほど強い力で移動するようにします。ボールはy方向にしか移動しないようにします。
(この課題の途中までのイメージとしては、ExBの最後の図の一列だけを作る感じです。)
具体的なやり方は以下になります。

①まず横一列を作る

  1. Ballクラスの配列を作って、横一列にボールを並べる

    • 重力や壁とのバウンドはいらない

    • クラスの配列の定義の仕方はGameByBall5Cを参照。

  2. ボールをマウスから遠ざけるように力を加える

    • 距離に反比例するように力の大きさを加える

  3. ボールとボールの間に線を描く(横方向だけ)※下記ヒントを参照

  4. ボールはy方向にしか動かないようにする

ヒント:Ballクラスはあくまでも一つのBallのデータを扱う=Ballクラスに配列を入れない!
線を描画するときに、Ballの配列の複数のBallデータが必要なら、Ballクラス内だけで完結することはできません。
グローバルに定義したBall配列をBallクラス内で扱おうとする人がいますが、これはいけません。

もちろんPVectorの配列で位置を定義するのもNG。
ボールの位置データはBallクラス内に既にposがあり、Ballクラスの配列を作れば位置も一緒にたくさん作られます。

とにかく、一度完成したBallクラス内はその後ほとんどいじりません。

Ballクラスの配列を使って線を描画するための例を以下に二つ挙げます。

  1. drawメソッド内で、balls.get(i).posとballs.get(i+1).posを使ってlineを描画する。

  2. Ballクラス内のdisplayメソッドにPVectorの引数を指定できるようにする→drawメソッド内でdisplayメソッドを呼び出すように変更&balls.get(i).display( balls.get(i+1).pos )などとする→→displayメソッド内でlineを描画するようにする。

上記のように、一度実装した後、一本分の線の描画内容を今度はBallLineクラスとして作成します。
つまり、最終的にBall配列を定義する場所はBallLineクラス内です
そのときはBallLineクラスの配列をグローバル変数として作りましょう。下図参照。

Alt text
↑今回のクラスの入れ子関係:
Ballクラスの配列を持ったBallLineクラス、
BallLineクラスの配列をグローバル変数として定義。

Ex D: Attractorを実装しなさい(全体)

②縦に増やす

  1. 横一列できたら、それをクラス化(BallLineクラス)する ※

  2. y方向にBallLineをたくさん並べる

  3. ボールを消す

  4. 色や点の間隔や動き方を調整する

    • 線にきれいな色(グラデーションなど)を指定する
      (HSB色空間や透明色などを使う)

※Ballクラスには位置を指定する引数を持つコンストラクタが必要になります。BallLineクラスも同様。
※BallLineクラスにBallクラスの配列を作ります。
※このテキストの最後にBallLineクラスの例を載せていますが、できれば一度見ずに作成してみましょう。

Alt text


(余裕がある人へ) 自分なりの工夫をして美しいor面白いAttractorを作ろう。

例えば

もっとAttractorの例を見て、実装の参考にしよう。
ちなみにこのページのM.4アトラクターに7つもコードと動画付きの例がある。

ここにも別の例(ビジュアルのみ)。
https://www.dezeen.com/2012/11/20/competition-five-copies-of-generative-design-to-be-won/


コーヒーブレイク:ボールじゃなくてノード
今回、アトラクターで使ったようなボールのことをノード(Node)と呼ぶ。
「結び目」のことで、線と線のつなぎ目などのことを呼ぶことが多い。
グラフ理論の分野で頂点あるいは節点と言われるもの。
点の集合とそれを連結する情報があれば広義でノードと言ってよさそうです。

今回のようなプログラムでノードを増やすと、滑らかな形状が得られる反面、処理動作が多少遅くなることを確認してもらえればと思います。


※(できれば見ずに作ってほしい・・・)BallLineクラスの一例
以下は、横一列のボールとその間に線を描画するBallLineクラスの実装例です。






















🐤













































🐔


















































































Alt text





































































👁





































BallLine ballLine;
int xnum=10;//横方向のボールの数
void setup() {
size(500, 500);
ballLine = new BallLine(100);
}
void draw() {
background(255);
ballLine.update();
}
class BallLine {
Ball[] balls;
BallLine(int ofssetY) {
balls = new Ball[xnum];
for (int i=0; i<balls.length; i++) {
balls[i] = new Ball(i*50, 100+ofssetY);
}
}
void update() {
for (int i=0; i<balls.length; i++) {
balls[i].update();
if (mousePressed) {
balls[i].addForce();
}
//draw line
if (i+1<balls.length) {
PVector a =balls[i].pos;
PVector b =balls[i+1].pos;
line(a.x, a.y, b.x, b.y);
}
}
}
}
//Ballクラスの記述は省略

!Ballクラスは初期位置(x,y)を指定できる引数を持つコンストラクタが必要です