[Go to “teaching” in Morimoto Lab]

Game by ball 4

単位ベクトルとクラスの使い方について学びます。


1. 単位ベクトル

単位ベクトルは長さが1のベクトルです。

unitvector

1.1 単位ベクトルを自分で計算しよう (1)

以下のコードは画面中央からマウスの位置への線を描画します。

Alt text

void setup()
{
size(800, 800);
strokeWeight(5);
stroke(255);
}
void draw()
{
background(0);
line(width/2, height/2, mouseX, mouseY);
}

上記を、以下のように一定の長さの線にしてください。

Alt text

画面中央からマウスへの単位ベクトルを使いましょう。
以下のように、画面中央からマウスへの距離を使って単位ベクトルを計算できます。

以下をsetup()の前など(つまりグローバル変数)に書く:

float len = 200; //length of vector

以下をdraw()内に書く:

//cxとcyは画面中央の座標です。自分で定義してください。
float dist = sqrt(sq(mouseX - cx) + sq(mouseY - cy));
float vecx = (mouseX - cx)/dist;
float vecy = (mouseY - cy)/dist;
line(cx, cy, cx + vecx*len, cy + vecy*len);
//単位ベクトルを使えば上記のように掛け算で簡単に長さを調整できます

[sqrt() in the official web]
[sq() in the official web]

注意1 widthはsize関数の後に使おう:
widthやheightをグローバル変数の初期値などで使う場合、setup内のsize関数の前にそれらを使うことになります。
widthやheightはsize関数で初めて値が指定されるので、size関数を使う前にそれらを使うと、変な値になってしまうので注意です。

注意2 ベクトルを使ったlineの終点:
ExAの直前のline描画でやっているように、ベクトルを使った場合終点にそのままベクトルを入れないように注意です!
「終点=始点+ベクトル*長さ」 となります。
ベクトルは原点(0,0)中心のものなので、画面中心などにする場合はその分足さないといけません。


Ex A. 単位ベクトルを計算し、以下を実装しなさい。

Alt text


1.2 単位ベクトルを自分で計算しよう (2)

以下のコードはマウスの位置にボールを描画します。

Alt text

float bx = 1;
float by = 1;
float bs = 100;
//boolean bFollow=false;
void setup() {
size(800, 800);
bFollow = false;
}
void draw() {
background(0);
ellipse(bx, by, bs, bs);
// if (bFollow) {
bx = mouseX;
by = mouseY;
// }
}
void mouseClicked()
{
// if (bFollow==false)bFollow = true;
// else bFollow = false;
////bFollow = !bFollow;//this line causes the same result with the above two lines.
}

Note: boolean
booleanはintやfloatなどのようなデータ型です。
値はtrueかfalseを持っていて、if文ではそれ単体で真・偽(true・false)を表すことができます
true, falseは1, 0とイメージして扱うとよいです。

Note: booleanをスイッチにする
ここではbFollowはスイッチとして機能します。
//でコメントアウトしてある部分を使うと、マウスクリックでボールを止めたり動かしたりすることができる。

Note: ビット演算
mouseClicked()の関数内の////の行はその上の2行と同じ挙動になります。
この部分はビット演算をしています。
論理否定(論理演算)はProcessingやC言語では!で表される。
!は0を1に、1を0にするので、trueだったらfalse、falseだったらtrueに反転させる。


Ex B. 速度の単位ベクトルを計算して、一定の速度でボールがマウスを追うようにしてください。

Alt text

ボールからマウスへの単位ベクトルを計算し、それを使ってボールの速度を計算しましょう。
ボールを速度で移動するやり方はgame by ball 1の2.3などでやりましたね。

Ex C. ボールからマウスに向かって線(矢印)を描画しましょう。

以下が例です。

Alt text

注意1 ボールが消えてしまう
mouseX,Yの初期値は0なので、bx,byの初期値を0にすると、この後の課題でマウスとボールの距離を求める際、距離が0になる。
0で割り算した結果はおかしな値が入っているため、これをボールの位置などに代入すると、ボールが消えてしまう。
こうならないようにbx,byの初期値を0ではなく1にしたり、また、bFollowを使うとマウスの位置の初期値がほぼ0にならないので有効。

注意2 ボールのプルプルして止まらない?
ボールがマウスに追い付いても、プルプルして止まってくれません。
これはあなたのマウスを持つ手が微妙に震えているとかいうわけではなく、プログラム的にボールは必ず少しずつ動くので、静止しません。
割とちゃんとした物理シミュレーションでもこれは起きるので、摩擦力などの理由で速度を0にしていくとか、もっと強制的な方法だとマウスと十分近づいたら動くのをやめるとかのような、+αの処理をしてあげると止まります。
※この課題では上記のような+αの処理は求めません。


1.3 PVectorを使って単位ベクトルを計算しよう

[PVector in the official web]

Note:
PVectorはProcessingに用意されているクラスというものの一つです。
クラス変数や関数を持つことができます。とても便利です。

PVectorというクラス:x,yなどの変数、add, subなどの関数など、クラスでは関係するデータや関数をまとめて持つことができます。PVectorはベクトルを扱うクラスなので、それ関係のものが詰まっています。

Note 2:
クラスを使って変数を定義する場合、newを使います。newをせずにクラスの変数を使うとエラーが起こります。これはintやfloatなどとは少し違いますね。使い方の詳細は上記のリンクを見てください。newはメモリを確保するために必要ですが、ここでは詳細は割愛します。

↓PVectorのサンプルコード(公式ページより)

PVector v1, v2;//←ボールの位置などにする場合、意味のわかるような変数名にしましょう!
void setup() {
noLoop();//←ボールを動かす場合はこれは削除しないと、drawが一度しか呼ばれなくなります!!!
v1 = new PVector(40, 20);
v2 = new PVector(25, 50);
}
void draw() {
ellipse(v1.x, v1.y, 12, 12);
ellipse(v2.x, v2.y, 12, 12);
v2.add(v1);
ellipse(v2.x, v2.y, 24, 24);
}

Ex D. PVectorでEx Cのプログラムを書き換えてください。

ボールの位置や速度などをPVectorで表すなどしてください。
PVectorの変数をどこで定義し、どこでnew(生成)するか考えましょう。

Ex E. PVectorのadd(), mult(), div(), などのメソッドを使って単位ベクトルを計算するよう、Ex Cのプログラムを書き換えてください。

それぞれ、足し算、掛け算、割り算を行うメソッドです。
使い方はPVectorの説明から、更にリンクで飛んだ先にあります。
例えばadd()はPVector同士の足し算ができます。

※引数の数によって挙動が変わりますが、とりあえず引数が一つだけの使い方を試してみましょう。

他の変数もPVectorで定義すれば、PVector同士での計算もできます。

注意:PVectorのaddやsubなどが元の変数の値を書き換えてしまう。
PVectorの変数、v1, v2, v3 があったときに、
v1 = v2.sub(v3)
とするとv1だけでなく、v2の値も変わってしまいます。 これは注意です。
これをしたくない場合は以下のようにするとよいです。
v1= PVector.sub(v2, v3);
この書き方だとv2の値は変更されません👍

Ex F. PVectorのnormalize()メソッドを使って単位ベクトルを計算するよう、Ex Cのプログラムを書き換えてください。

今まで距離を計算し、ベクトルを割ることで自力で正規化(normalize)していました。
normalize()を使うと、それを一行でできます。

正規化(normalize)の色々な意味
Drawing & Pattern 0のExDで「正規化と補間」について解説していますが、今回の正規化は単位ベクトル化のことです。

以下はwikipediaより↓
「正規化(せいきか、英語: normalization)とは、データ等々を一定のルール(規則)に基づいて変形し、利用しやすくすること。別の言い方をするならば、正規形でないものを正規形(比較・演算などの操作のために望ましい性質を持った一定の形)に変形することをいう。」

場合によって正規化の意味が変わってくるので注意しましょう。

おまけ. ボールの数を配列で増やそう

次の次の回で扱う動的配列の前に、
ぜひ静的配列でPVectorを扱ってみてほしい。
ボールの位置だけ増やして大きさは同じでまずはやってみよう。
ただし、ボールの初期位置は少しずらしてあげるのがポイント。

Note: 配列を作るとき、newが必要でした。またPVectorのデータを使うにもnewが必要です。なので、PVectorの配列を作るときは “new”が二度必要になります。

以下はPVectorクラスを用いた配列の作成例。

//1回目のnew。配列を作ります。
PVector [] positions = new PVector[10];
//2回目のnew。インスタンスを作ります。配列で作った全てのデータをnewするのでforを使っています。
for(int i=0 ; i < 10; i++){//10の代わりにpositions.lengthを使ってもOK
positions[i] = new PVector();//初期値の指定はここでできる
}

※ただしこの実装だとボールはそのうちほとんど一か所に固まってしまい、少し残念な感じ(下図左)
もっといい感じにするには速度の調整係数をそれぞれ別にするなどの方法があるが、これは速度や調整係数をそれぞれ配列で用意しないといけないので、面倒である。
のちに出てくるクラスを使うと、このようなことがしやすくなる。

Alt text Alt text
右図は2022年度Kawasakiさんのアレンジしたプログラム。

おまけ. 増やしたボールを線で繋ごう

のちの課題に関係するので、かなり推奨の課題です。
増やしたボールを線でつなぐ(動かさなくてもOK)だけでもいいし、
1番目のボールはマウスを追いかけ、
2番目のボールは1番目のボールを追いかけ…、
と言う風にするのもいいと思います。
上記、どちらでもいいので、とにかく個別のボールだけで完結する内容ではなく、他のボールの情報を使うような内容をやっておくと今後役立ちます。

Alt textAlt text


おまけ. フロッキングアルゴリズムのBoids理論

余裕がある人はぜひ知識だけでも、できれば実装もしてみてください!

フロッキングアルゴリズムとは、群れ(flock)の動きをシミュレートするアルゴリズムです。
魚や動物の群れの動きをよりリアルに表現することができます。
その中でも非常に有名で簡単なBoids(ボイズ)と呼ばれるもののアルゴリズムを見てみましょう。
「鳥もどき(bird-oid)」からboidsと名付けられた、人工生命シミュレーションプログラムで、鳥や魚の動きに使われるほか、チームラボのエフェクトなどにも使われていたと思います。

基本的なルールは三つ。
1. 分離 separation:近づきすぎたら離れる
2. 整列 alignment:仲間と同じ方向、同じ速度に合わせて動く
3. 結束 cohesion:群れの中心に向かって動く

3Dにも適用できます。

参考URL:
Processingによる実装記事配列が増えるのが少し大変ですが、今までの知識+αくらいで理解可能です!?GameByBall2のボールをたくさん増やす課題が少し似ています。
enter image description here
Wikipedia ボイド(人工生命)
Coding challenge “124: Flocking Simulation”:こちらのページからもBoidsのProcessing実装に関する講義動画の閲覧やコードの取得が可能です!英語です!
群衆シミュレーション:こちらもフロッキングアルゴリズムと似ていますが、ボーンのあるような3Dキャラクターに適用するイメージ?Unreal Engineの例。
マルチエージェントシステム:ゲームキャラクタの動きの自動決定などに使われます。スクエニのAIエンジニアさんのスライドをこちらにリンクしておきます。ちょっと難解かも。。こっちが入門記事でわかりやすいかもです!