Up to date
This page is up to date for Godot 4.2
.
If you still find outdated information, please open an issue.
向量數學¶
前言¶
本教學是一個簡短而實用的線性代數介紹,因為它適用於遊戲開發。線性代數是研究向量及其用途的學科。向量在 2D 和 3D 開發中都有許多應用,Godot 對它們的應用也非常廣泛。要成為一名優秀的遊戲開發者,對向量數學的理解是必不可少的。
備註
本教學**不是**線性代數的正式教科書。我們將只關注它如何應用於遊戲開發。要更全面地瞭解數學,請參見 https://www.khanacademy.org/math/linear-algebra
坐標系(2D)¶
在 2D 空間中,使用水平軸(x
)和垂直軸(y
)定義座標。2D 空間中的特定位置會被寫成一對值,如 (4, 3)
。
備註
如果您是電腦圖形學的新手,可能會覺得很奇怪,y
軸的正方向**朝下**而不是向上(您在數學課上學到的就像那樣)。然而,這在大多數電腦圖形應用程式中是常見的。
2D 平面上的任何位置都可以用一對數字來表示。然而,我們也可以將位置 (4, 3)
看作是從 (0, 0)
點或**原點**出發的**偏移**。畫一個箭頭從原點指向點:
這是一個**向量**。向量表示許多有用的資訊。除了告訴我們點在 (4, 3)
之外,我們還可以把它看成角度 θ
和長度(或幅度)``m``。在這種情況下,箭頭是一個**位置向量**——它表示空間中相對於原點的位置。
關於向量, 需要考慮的一個重要點是它們僅表示 相對 方向和大小. 沒有一個向量的位置的概念. 以下兩個向量相同:
這兩個向量都表示向右4個單位, 在某個起始點以下3個單位. 不管您在平面上畫哪個向量, 它總是代表一個相對的方向和大小.
向量運算¶
您可以使用任意一種方法,x和y座標或角度和大小, 來引用向量, 但是為了方便起見, 程式師通常使用座標標記法. 例如, 在Godot中, 原點是螢幕左上角, 因此要將一個名為 Node2D
的2D節點向右400像素, 向下300像素, 請使用以下程式碼:
$Node2D.position = Vector2(400, 300)
var node2D = GetNode<Node2D>("Node2D");
node2D.Position = new Vector2(400, 300);
Godot支援 Vector2 和 Vector3 分別用於2D和3D. 本文討論的數學規則同樣適用於這兩種型別.
成員存取¶
向量的各個組成部分可以直接通過名稱存取.
# 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;
向量加法¶
當兩個向量相加或相減時, 相應的分量相加:
var c = a + b # (2, 5) + (3, 1) = (5, 6)
var c = a + b; // (2, 5) + (3, 1) = (5, 6)
我們也可以通過在第一個向量的末尾加上第二個向量來直觀地看到這一點:
注意,做 a + b
的加法和 b + a
得到的結果是一樣的。
標量乘法¶
備註
向量表示方向和幅度。僅表示幅值的值稱為**標量**。
向量可以乘以**標量**:
var c = a * 2 # (2, 5) * 2 = (4, 10)
var d = b / 3 # (3, 6) / 3 = (1, 2)
var e = d * -2 # (1, 2) * -2 = (-2, -4)
var c = a * 2; // (2, 5) * 2 = (4, 10)
var d = b / 3; // (3, 6) / 3 = (1, 2)
var e = d * -2; // (1, 2) * -2 = (-2, -4)
備註
向量乘以標量不會改變它的方向,只會改變它的幅值。這就是**縮放**向量的方法。
實際應用¶
讓我們看看向量加法和減法的兩種常見用法.
移動¶
向量可以表示具有大小和方向的**任何**量。典型的例子有:位置、速度、加速度、力。在這幅圖像中,在第 1 步的飛船的位置向量為 (1,3)
,速度向量為 (2,1)
。速度向量表示船舶每一步移動的距離。通過將速度加到目前位置,我們可以求出第 2 步的位置。
小訣竅
速度測量單位時間內位置的**變化**。新的位置是通過在前一個位置上增加速度來找到的。
In a typical 2D game scenario, you would have a velocity in pixels per
second, and multiply it by the delta
parameter (time elapsed since
the previous frame) from the _process()
or _physics_process()
callbacks.
指向目標¶
在這個場景中,您有一輛坦克,坦克希望讓炮塔指向機器人。把機器人的位置減去坦克的位置就得到了從坦克指向機器人的向量。
小訣竅
要找到從 A
指向 B
的向量,請使用 B - A
。
單位向量¶
大小**為 ``1`` 的向量稱為**單位向量,有時也被稱為**方向向量**或**法線**。當您需要記錄方向時就可以使用單位向量。
正規化¶
正規化 一個向量意味著將其長度縮減到 1
, 並保留其方向. 其方法是將每個分量除以其幅度. 由於這是一個很常見的操作, Vector2
和 Vector3
提供了正規化的方法:
a = a.normalized()
a = a.Normalized();
警告
因為正規化需要除以向量的長度, 所以不能對長度為 0
的向量進行正規化. 試圖這樣做會導致錯誤.
反射¶
單位向量的一種常見用法是表示**法線**。法向量是垂直於表面的單位向量,定義了表面的方向。它們通常用於照明、碰撞和涉及表面的其他操作。
例如, 假設我們有一個移動的球, 我們想從牆上或其他物體上彈回來:
因為這是一個水平曲面,所以曲面法線的值為 (0, -1)
. 當球碰撞時,我們取它的原運動方向(當它撞到表面時剩餘的量), 以法線為角平分線反射它。在Godot中, Vector2 類有一個 bounce()
方法來處理這個問題。這是上圖的 GDScript 範例,使用 KinematicBody2D:
var collision: KinematicCollision2D = move_and_collide(velocity * delta)
if collision:
var reflect = collision.get_remainder().bounce(collision.get_normal())
velocity = velocity.bounce(collision.get_normal())
move_and_collide(reflect)
KinematicCollision2D collision = MoveAndCollide(_velocity * (float)delta);
if (collision != null)
{
var reflect = collision.GetRemainder().Bounce(collision.GetNormal());
_velocity = _velocity.Bounce(collision.GetNormal());
MoveAndCollide(reflect);
}
點積¶
點積**是向量數學中最重要的概念之一,但經常被誤解。點積是對兩個向量的操作,返回一個**標量。與同時包含大小和方向的向量不同,標量值只有大小。
點積公式有兩種常見形式:
與
數學符號 ||A|| 表示向量 ''A' 的大小,A:sub:'x' 表示向量 ''A'' 的 'x' 分量。
然而, 在大多數情況下, 使用內建方法是最容易的. 注意, 兩個向量的順序並不重要:
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θ
。這意味著我們可以使用點積來告訴我們關於兩個向量之間的角度的一些資訊:
當使用單位向量, 結果總是會在 -1
(180°) 和 1
(0°) 之間.
面向問題¶
我們可以利用這個事實來偵測一個物體是否面向另一個物體. 在下圖中, 遊戲角色 P
試圖避開喪屍 A
和 B
. 假設一個喪屍的視野是 180° , 他們能看到遊戲角色嗎?
綠色箭頭 fA
和 fB
是 單位向量 , 代表僵屍的朝向, 藍色半圓代表其視野. 對於僵屍 A
, 我們用 P - A
找到指向玩家的方向向量 AP
, 並進行正規化處理, 不過,Godot有一個輔助方法可以做到這一點, 叫做 direction_to
. 如果這個向量和面對的向量之間的角度小於90°, 那麼僵屍就可以看到玩家.
在程式碼中是這樣的:
var AP = A.direction_to(P)
if AP.dot(fA) > 0:
print("A sees P!")
var AP = A.DirectionTo(P);
if (AP.Dot(fA) > 0)
{
GD.Print("A sees P!");
}
外積¶
和點積一樣,**外積**也是對兩個向量的運算。但是,叉乘積的結果是一個方向與兩個向量垂直的向量。它的大小取決於相對角度,如果兩個向量是平行的,那麼外積的結果將是一個空向量。
外積是這樣計算的:
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);
外積沒有在二維中以數學方式定義。 Vector2.cross() <class_Vector2_method_cross>` 方法是2D 向量 3D 外積的常用模擬。
備註
在外積中,順序很重要。a.cross(b)
和 b.cross(a)
的結果不一樣,會得到指向相反的向量。
法線計算¶
外積的一種常用方法是在 3D 空間中求平面或曲面的表面法向量。如果有三角形 ABC
,我們可以用向量減法找到兩條邊 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;
}
指向目標¶
在上面的點積部分,我們看到如何用它來搜尋兩個向量之間的角度。然而在 3D 中,這些資訊還不夠。我們還需要知道在圍繞什麼軸旋轉。我們可以通過計算目前面對的方向和目標方向的外積來搜尋。由此得到的垂直向量就是旋轉軸。
更多資訊¶
有關在Godot中使用向量數學的更多資訊, 請參閱以下文章: