[Go to “teaching” in Morimoto Lab]
今回は、「クラスの中でクラスを使う」ことを学びます。
これまで作ってきたBallクラスを、今度は「部品」として使います。
小さなクラスを組み合わせて大きな構造を作ることは、ゲームやCGで非常に重要です。
内容的には、
①ボールに力を加える・投げる
②見えないボールで疑似アトラクタを作る
となっています。
慣れるまではちょっと難易度高い内容になっています。
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 velocity:ここは第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);
}
}
ボールを押したときがわかりにくければ、押したときに 文字が出る、背景色を変える、などしてもよい。
当たり判定は必要ない。
与える力の大きさや空気抵抗は適宜調整してください。
以下はupdate velocityのヒント。空気抵抗は今回必須です。
//update velocity(空気抵抗)(multメソッドなど使ってもよい)
vel.x *=air_drag;
vel.y *=air_drag;
初歩的なミス:
Ballクラスの中では、メンバ変数であるposやvelをpos.xなどとして使います。
Ballクラスの中で、ball.pos.xなどとしないようにしましょう。
↑結果例
Game by Ball (1)では、マウス接触したときにボールの速度を反転させましたが、今回はマウスから風が吹き出すイメージで、マウスからボールの方向に速度を加えるようにしましょう。マウスとボールの間のベクトルを使えばよいです。
また、今回は、マウスとボールの距離に応じた力を加えましょう。
<実装のステップ(例)>
addForce内などで ※
マウスとボールの距離を計算
一定の距離内にボールがあるかどうか判定(この「一定の距離=影響範囲」は適当に決めましょう)
もし影響距離内にボールがあったら、マウスからボールへの単位ベクトルを計算し、適当な倍数にして調整して、ボールの速度にベクトルを足す
4で、一旦「適当な倍数」としましたが、距離に応じて倍数を変えるようにしましょう。近ければ近いほど強い力が加わるようにしましょう。
※上記、マウスとボールの距離を計算するメソッドを作り、draw()のところで距離判定をするのもよいかもしれません。適宜、やりやすいようにアレンジしてみてください。
ヒント1:近いほど強くなるような力の計算方法
例えば、以下のように計算した値を調整した上で力の大きさとするとよさそうです。
①(1/距離)
②(影響範囲-マウスとボールの距離)※1
③(ガウス関数などの釣鐘型の関数に距離を変数として計算)※2
経験的には②の挙動がいいみたいですが、他にもよい方法があるかもしれません。※1 値が0以下になるときはifで場合分けの処理をすること。
※2 指数関数はexp(), べき乗はpow()で計算できる。
↑点を並べてマウスを押して周囲に力を与えた場合
ヒント2
Ex. Bはボール一つでやってみてください。できる人は上記の図のようなボール配置でやってみても〇
ヒント3
最終的にボールは上記のように配置されます。今回は個数が固定されているのでArrayListではなく普通の配列にしましょう。
注意
上の例では、ボールは2次元配列のように見えますが、当講義では1次元配列しか教えていません。
(2次元配列は int[][] a; みたいなものです。1次元だと int[] b; でした)
今回のAttractorの課題は1次元配列でできるように設計しています。
2次元配列はどこかで学ぶとよいとは思いますが、今回に関してはだいぶ意図とは違う解き方になります。
(ちょっとこの絵はミスリーディングかもしれません。。🙇)
※この課題は横道にそれるのでとばしてもいいですが、ポケモンGOみたいにボールを投げるコンテンツに興味のある人はやってみてください。
ボールをつかむには?
マウスとボールのあたり判定が必要
ボールをドラッグするには?
マウスを押している(ドラッグしている)かどうかの判定が必要。mouseDragged()
つかんだら、ボールは常にマウスの位置に描画する
ボールを離したとき、pmouseX, pmouseYなどを使って速度を計算する
pmouseX/Yは現在の一つ前のフレームでのマウス位置です
mouseReleased()はマウスボタンを離したときに呼ばれる関数です。
計算した速度ベクトルを適当に何倍かして調整し、ボールの速度に足す
Attractorとは?
Attractorは数値的値の集合が動的に発展をするもの、です。
「あるルールに従って点を動かしたときにあらわれる特徴的な軌跡や状態」のことです。
数学的Attractor:
Attractorについてのサイトをいくつか紹介。
今回実装するのはAttractorのような雰囲気のビジュアルのものである。
ここでの実装では、アトラクターは見えないボールとボールの間を線でつなぎ、
そのボールをマウスにひきつけたり、逆に離したりすることで、
不思議な模様を描きます。
”面白い動き”や”美しい模様”を作ることを重視します。
参考にしたのは以下のGenerative Designという本の例です。
https://download.bnn.co.jp/support/generativedesign/code/M_4_3_01_TOOL.html
以下のようにコードをダウンロードして、
デスクトップ上でこのプログラムを実行してみよう。
このページの右のボタン”code”から、”download zip”などでdownloadする
解凍したフォルダからCode-Package-Processing-3.x/02_M/M_4_3_01_TOOL/を開く
pdeファイルをProcessingで開く。
Generative Designのライブラリをimportする必要があります。
メニュー>スケッチ>ライブラリをインポート>ライブラリを追加 より”GenerativeDesign”をインストールする
実行できるはず
クリックして、挙動を見てみましょう。
左上のボタンを押すと、UIが現れ、影響範囲や点の動かし方を変更できます。
しかしコードを見てみると、つらく感じる人もいるだろう。
プロのプログラムを読むのはつらい作業だ(教えてないこともあるし)
むしろ、これまで培ったGame by ballの知識で一から実装してみよう。
そっちの方が簡単だと思う。
※高みを目指す人には人のプログラムは色々見てもらいたい。
とても勉強になる。
今日の課題を終えてからもう一度見てみるとよい。
Ex Bを発展してAttractor(もどき?)を作りましょう。画面にボールを並べて、横線で繋ぎ、クリックしたところから近いほど強い力で移動するようにします。
ボールはx座標は固定し、y方向にしか移動しないようにします。
(この課題のイメージとしては、ExBの最後の図の横一列だけを作り、上下移動のみさせる感じです。)
具体的なやり方は以下になります。
①まず横一列を作る
Ballクラスの配列を作って、横一列にボールを並べる
重力や壁とのバウンドはいらない
クラスの配列の定義の仕方はGameByBall5Cを参照。
ボールをマウスから遠ざけるように力を加える
距離に応じて力の大きさを加える
ボールとボールの間に線を描く(横方向だけ)※下記ヒントを参照
ボールはy方向にしか動かないようにする
ヒント:Ballクラスはあくまでも
一つのBallのデータを扱う=Ballクラスに配列を入れない!
Ballの配列は一旦グローバル変数として生成しましょう(BallLineクラスを作成していない段階では)。
また、このとき、線を描画するときに、Ballの配列の複数のBallデータが必要なので、Ballクラス内だけで完結することはできません。
あくまでBallクラスは「1個のボールだけを扱う部品」です。一度完成したBallクラス内はその後ほとんどいじりません。
グローバルに定義したBall配列やPVector配列をBallクラス内で扱おうとする人がいますが、これはいけません。
draw関数内で、balls[i].posとballs[i+1].posを使ってlineを描画してみましょう。
↑今回のクラスの入れ子関係:
Ballクラスの配列を持ったBallLineクラス、
BallLineクラスの配列をグローバル変数として定義。
②縦に増やす
横一列できたら、それをクラス化(BallLineクラス)する ※
y方向にBallLineをたくさん並べる
ボールを消す(ellipseを描かない)
色や点の間隔や動き方を調整する
線にきれいな色(グラデーションなど)を指定する
(HSB色空間や透明色などを使う)
※ヒント
BallLineクラスは線一本分を扱う部品です。
一本分の線の描画はBallLineクラス内で行います。
また、最終的にBall配列を定義する場所もBallLineクラス内です(=Ballクラスの配列をBallLineクラスのメンバ変数にする)。
BallLineクラスの配列はグローバル変数として作り、アニメーションできるようにします。
Ballクラスには位置を指定する引数を持つコンストラクタが必要になります。BallLineクラスも同様。
このテキストの最後にBallLineクラスの例を載せていますが、できれば一度見ずに作成してみましょう。