ベクトル演算

はじめに

このチュートリアルは、ゲーム開発に適用される線形代数の短くて実用的な入門書です。線形代数は、ベクトルとその使い方の研究です。ベクトルは2Dおよび3D開発の両方で多くの利用されます。Godotはそれらを広範囲に使用しています。強力なゲーム開発者になるには、ベクトル数学の十分な理解を深めることが不可欠です。

注釈

このチュートリアルは、線形代数に関する正式な教科書ではありません。それがゲーム開発にどのように適用されるかだけを見ていきます。数学の詳細については、https://www.khanacademy.org/math/linear-algebraを見てください(英語)

座標系 (2D)

2D空間では、座標は水平軸 (x) と垂直軸 (y) を使用して定義されます。 2D空間の特定の位置は、(4, 3)などの値のペアとして書き込まれます。

../../_images/vector_axis1.png

注釈

コンピュータグラフィックスが初めての場合、数学のクラスで学んだように、正のy軸が上向きではなく下向きを指すのは奇妙に思えるかもしれません。ただし、これはほとんどのコンピューターグラフィックアプリケーションで一般的です。

この方法で、2D平面内の任意の位置を1組の数字で識別できます。ただし、位置(4、3)は(0、0)ポイントまたは原点からのオフセットと考えることもできます。原点からポイントを指す矢印を描画します。

../../_images/vector_xy1.png

これはベクトルです。ベクトルは多くの有用な情報を表します。点が(4、3)にあることを伝えるだけでなく、角度θと長さ(または大きさ)mとして考えることもできます。この場合、矢印は位置ベクトルです。これは、原点を基準とした空間内の位置を示します。

ベクトルについて考慮すべき非常に重要な点は、ベクトルが相対的な方向と大きさのみを表すことです。ベクトルのには位置の概念はありません。次の2つのベクトルは同一です。

../../_images/vector_xy2.png

両方のベクトルは、開始点から右に4単位、下に3単位の点を表します。平面上のどこでベクトルを描くかは問題ではなく、常に相対的な方向と大きさを表します。

ベクトル操作

いずれかの方法(xおよびy座標または角度と大きさ)を使用してもベクトルを参照できますが、プログラマーは通常、座標表記を使用します。たとえば、Godotでは、原点は画面の左上隅であるため、Node2Dという名前の2Dノードを右に400ピクセル、下に300ピクセル配置するには、次のコードを使用します:

$Node2D.position = Vector2(400, 300)
var node2D = (Node2D) GetNode("Node2D");
node2D.Position = new Vector2(400, 300);

Godotは、2Dおよび3Dの使用に対してそれぞれVector2とVector3の両方をサポートします。この記事で説明している同じ数学的ルールが両方のタイプに適用されます。

メンバアクセス

ベクトルの個々のコンポーネントには、名前で直接アクセスできます。

# create a vector with coordinates (2, 5)
var a = Vector2(2, 5)
# create a vector and assign x and y manually
var b = Vector2()
b.x = 3
b.y = 1
// create a vector with coordinates (2, 5)
var a = new Vector2(2, 5);
// create a vector and assign x and y manually
var b = new Vector2();
b.x = 3;
b.y = 1;

ベクトルの加算

2つのベクトルを加算または減算すると、対応するコンポーネントが追加されます。

var c = a + b  # (2, 5) + (3, 1) = (5, 6)
var c = a + b;  // (2, 5) + (3, 1) = (5, 6)

また、最初のベクトルの終点に2番目のベクトルを追加することで、これを視覚的に確認できます。

../../_images/vector_add1.png

a + bは、b + aと同じ結果になることに注意してください。

スカラー乗算

注釈

ベクトルは方向と大きさの両方を表します。大きさのみを表す値はスカラーと呼ばれます。

ベクトルにはスカラーを掛けることができます。

var c = a * 2  # (2, 5) * 2 = (4, 10)
var d = b / 3  # (3, 6) / 3 = (1, 2)
var c = a * 2;  // (2, 5) * 2 = (4, 10)
var d = b / 3;  // (3, 6) / 3 = (1, 2)
../../_images/vector_mult1.png

注釈

ベクトルにスカラーを掛けても方向は変わらず、大きさだけが変わります。これで、ベクトルの大きさを変更できます。

実際の応用

ベクトルの加算と減算の2つの一般的な使用方法を見てみましょう。

移動

ベクトルは、大きさと方向を持つ任意の量を表すことができます。典型的な例は次のとおりです。位置、速度、加速度、力。この図では、ステップ1の宇宙船の位置ベクトルは (1,3)、速度ベクトルは (2,1) です。速度ベクトルは、船が各ステップで移動する距離を表します。現在の位置に速度を追加することにより、ステップ2の位置を見つけることができます。

../../_images/vector_movement1.png

ちなみに

速度は、単位時間あたりの位置の変化を測定します。新しい位置は、前の位置に速度を追加することで計算できます。

ターゲットへ向く

このシナリオでは、砲塔をロボットに向けたい戦車があります。ロボットの位置からタンクの位置を引くと、タンクからロボットを指すベクトルが得られます。

../../_images/vector_subtract2.png

ちなみに

AからBへ向くベクトルを求めるにはB-Aとします。

単位ベクトル

大きさが1のベクトルを、単位ベクトルと呼びます。また、方向ベクトルまたは法線と呼ばれることもあります。単位ベクトルは、方向を追跡する必要がある場合に役立ちます。

正規化

ベクトルの正規化とは、方向を維持しながら長さを1に減らすことを意味します。これは、各コンポーネントをその大きさで除算することにより行われます。

var a = Vector2(2, 4)
var m = sqrt(a.x*a.x + a.y*a.y)  # get magnitude "m" using the Pythagorean theorem
a.x /= m
a.y /= m
var a = new Vector2(2, 4);
var m = Mathf.Sqrt(a.x*a.x + a.y*a.y);  // get magnitude "m" using the Pythagorean theorem
a.x /= m;
a.y /= m;

これはよくある操作なので、Vector2Vector3には正規化のためのメソッドがあります。

a = a.normalized()
a = a.Normalized();

警告

正規化はベクトルの長さで割り算するので、長さ0のベクトルは正規化できません。エラーになります。

反射

単位ベクトルの一般的な用途は、法線を示すことです。法線ベクトルは、面の表面に垂直に配置され、その方向を定義する単位ベクトルです。これらは一般に、照明、衝突、およびサーフェスに関連する他の操作に使用されます。

例として、動いているボールを想像してみましょう。壁や他の物体に当たったら跳ね返ってほしいですね:

../../_images/vector_reflect1.png

図の面は水平面であるため、表面法線の値は(0, -1)です。ボールが衝突すると、残りの動き(表面に当たったときに残った量)を計算し、法線を使用してそれを反射させます。 Godotでは、Vector2クラスにこれを処理するbounce()メソッドがあります。以下は、KinematicBody2Dを使用した上記の図のGDScriptの例です。

# object "collision" contains information about the collision
var collision = move_and_collide(velocity * delta)
if collision:
    var reflect = collision.remainder.bounce(collision.normal)
    velocity = velocity.bounce(collision.normal)
    move_and_collide(reflect)
// KinematicCollision2D contains information about the collision
KinematicCollision2D collision = MoveAndCollide(_velocity * delta);
if (collision != null)
{
    var reflect = collision.Remainder.Bounce(collision.Normal);
    _velocity = _velocity.Bounce(collision.Normal);
    MoveAndCollide(reflect);
}

内積 (ドット積)

内積は、ベクトル数学の最も重要な概念の1つですが、誤解されることがよくあります。内積は、2つのベクトルを演算してスカラーを返します。大きさと方向の両方を含むベクトルとは異なり、スカラーは大きさのみを持ちます。

内積の式には、2つの一般的な形式があります。

A \cdot B = \left \| A \right \|\left \| B \right \|\cos \Theta

そして

A \cdot B = A_{x}B_{x} + A_{y}B_{y}

ただし、ほとんどの場合、組み込みのメソッドを使用するのが最も簡単です。 2つのベクトルの順序は重要ではないことに注意してください。

var c = a.dot(b)
var d = b.dot(a)  # these are equivalent
float c = a.Dot(b);
float d = b.Dot(a);  // these are equivalent

内積は、単位ベクトルと一緒に使用すると最も有用であり、最初の式からcosθを求めます。これは、内積を利用して、2つのベクトル間の角度について情報を得ることができることを意味します。

../../_images/vector_dot3.png

単位ベクトルを利用すると結果は-1(180°)から 1 (0°)になります。

正面

これを利用して、オブジェクトが別のオブジェクトに向いているかどうかを検出できます。下の図では、プレイヤーPはゾンビAとBを避けようとしています。ゾンビの視界が180度であると仮定すると、ゾンビはプレイヤーを見ることができますか?

../../_images/vector_facing2.png

緑の矢印fAとfBはゾンビの向きを表す単位ベクトルであり、青い半円はその視野を表します。ゾンビAの場合、P-Aを使用してプレイヤーを指す方向ベクトルAPを見つけて正規化します。このベクトルとゾンビの向きを表すベクトルの角度が90°未満の場合、ゾンビはプレイヤーを見ることができます。

コードでは次のように表せます。

var AP = (P - A).normalized()
if AP.dot(fA) > 0:
    print("A sees P!")
var AP = (P - A).Normalized();
if (AP.Dot(fA) > 0)
{
    GD.Print("A sees P!");
}

外積 (クロス積)

内積と同様に、外積は2つのベクトルの演算です。ただし、外積の結果は、両方に垂直な方向を持つベクトルです。その大きさは、相対的な角度に依存します。 2つのベクトルが平行である場合、それらの外積の結果はヌルベクトルになります。

\left \|a \times b  \right \| = \left \| a \right \|\left \| b \right \|\ |\sin(a,b)|

../../_images/tutovec16.png

外積は以下のように求めます。

var c = Vector3()
c.x = (a.y * b.z) - (a.z * b.y)
c.y = (a.z * b.x) - (a.x * b.z)
c.z = (a.x * b.y) - (a.y * b.x)
var c = new Vector3();
c.x = (a.y * b.z) - (a.z * b.y);
c.y = (a.z * b.x) - (a.x * b.z);
c.z = (a.x * b.y) - (a.y * b.x);

Godotでは組み込みのメソッドが使えます。

var c = a.cross(b)
var c = a.Cross(b);

注釈

外積では、順序が重要です。a.cross(b)b.cross(a)と同じ結果にはなりません。結果のベクトルは反対方向を指します。

法線の計算

外積の一般的な使用法の1つは、3D空間で平面またはサーフェスの法線を見つけることです。三角形ABCがある場合、ベクトル減算を使用して2つのエッジABおよびACを見つけることができます。外積を使用して、AB x ACは両方に垂直なベクトルを生成します:これが表面の法線となります。

三角形の法線を計算する関数は以下のとおりです。

func get_triangle_normal(a, b, c):
    # find the surface normal given 3 vertices
    var side1 = b - a
    var side2 = c - a
    var normal = side1.cross(side2)
    return normal
Vector3 GetTriangleNormal(Vector3 a, Vector3 b, Vector3 c)
{
    // find the surface normal given 3 vertices
    var side1 = b - a;
    var side2 = c - a;
    var normal = side1.Cross(side2);
    return normal;
}

ターゲットへの方向

上記の内積セクションでは、2つのベクトル間の角度を見つけるためにそれをどのように使用できるかを見ました。ただし、3Dでは、これは十分な情報ではありません。さらに、どの軸を中心に回転するかを知る必要があります。これは、現在の向きとターゲットの方向の外積を計算することでわかります。結果の垂直ベクトルが回転の軸です。

より多くの情報

Godotでのベクターについてのより詳しいことは以下の記事をご覧ください。