向量数学

简介

本教程是一个简短而实用的线性代数介绍,因为它适用于游戏开发。线性代数是研究向量及其用途的学科。向量在二维和三维开发中都有许多应用,Godot对它们的应用非常广泛。要成为一名优秀的游戏开发者,对向量数学的理解是必不可少的。

注解

本教程 不是 线性代数的正式教科书。我们将只关注它如何应用于游戏开发。要更全面地了解数学,请参见 https://www.khanacademy.org/math/linear-algebra

坐标系统(2D)

在2D空间中,使用水平轴(x)和垂直轴(y)定义坐标。2D空间中的特定位置被写成一对值,如``(4, 3)``。

../../_images/vector_axis1.png

注解

如果您是计算机图形学的新手,您可能会觉得很奇怪, y 轴的正方向是 向下,而不是向上(您在数学课上学到的就像那样)。然而,这在大多数计算机图形应用程序中是常见的。

二维平面上的任何位置都可以用一对数字来表示。然而,我们也可以将位置 (4, 3) 看作是从 (0, 0) 点或 原点 出发的 偏移 。画一个箭头从原点指向点:

../../_images/vector_xy1.png

这是一个 向量 。向量表示许多有用的信息。除了告诉我们点在 (4, 3) 之外,我们还可以把它看成角度 θ 和长度(或幅度) m。在这种情况下,箭头是一个 位置向量 -它表示空间中相对于原点的位置。

关于向量,需要考虑的一个重要点是它们仅表示 相对 方向和大小。没有一个向量的位置的概念。以下两个向量相同:

../../_images/vector_xy2.png

这两个向量都表示向右4个单位,在某个起始点以下3个单位。不管您在平面上画哪个向量,它总是代表一个相对的方向和大小。

向量运算

You can use either method (x and y coordinates or angle and magnitude) to refer to a vector, but for convenience, programmers typically use the coordinate notation. For example, in Godot, the origin is the top-left corner of the screen, so to place a 2D node named Node2D 400 pixels to the right and 300 pixels down, use the following code:

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

Godot supports both Vector2 and Vector3 for 2D and 3D usage, respectively. The same mathematical rules discussed in this article apply to both types.

成员访问

向量的各个组成部分可以直接通过名称访问。

# 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)

我们也可以通过在第一个向量的末尾加上第二个向量来直观地看到这一点:

../../_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

注解

向量乘以标量不会改变它的方向,只会改变它的幅值。这就是缩放向量的方法。

实际应用

让我们看看向量加法和减法的两种常见用法。

移动

向量可以表示具有大小和方向的任何量。典型的示例有:位置、速度、加速度和力。在这幅图像中,在第一步的飞船有一个位置向量为``(1,3)``和一个速度向量为``(2,1)``。速度向量表示船舶每一步移动的距离。通过将速度加到当前位置,我们可以求出步骤2的位置。

../../_images/vector_movement1.png

小技巧

速度测量单位时间内位置的变化。新的位置是通过在前一个位置上增加速度来找到的。

指向一个目标

在这个场景中,您有一个坦克,希望它的炮塔指向机器人。从机器人的位置减去坦克的位置就得到了从坦克指向机器人的向量。

../../_images/vector_subtract2.png

小技巧

要找到从 A 指向 B 的向量,请使用 B - A

单位向量

一个 大小1 的向量称为 单位向量 。它们有时也被称为 方向向量法线 。当您需要跟踪一个方向时,单位向量是有用的。

归一化

Normalizing a vector means reducing its length to 1 while preserving its direction. This is done by dividing each of its components by its magnitude. Because this is such a common operation, Vector2 and Vector3 provide a method for normalizing:

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);
}

点乘

点乘 是向量数学中最重要的概念之一,但经常被误解。点乘是对两个向量的操作,它返回一个 标量。与同时包含大小和方向的向量不同,标量值只有大小。

点乘公式有两种常见形式:

../../_images/vector_dot1.png

以及

../../_images/vector_dot2.png

然而,在大多数情况下,使用内置方法是最容易的。注意,两个向量的顺序并不重要:

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θ。这意味着我们可以使用点积来告诉我们关于两个向量之间的角度的一些信息:

../../_images/vector_dot3.png

当使用单位向量,结果总是会在 -1 (180°) 和 1 (0°) 之间。

面向问题

我们可以利用这个事实来检测一个物体是否面向另一个物体。在下图中,游戏角色 P 试图避开丧尸 AB 。假设一个丧尸的视野是 180° , 他们能看到游戏角色吗?

../../_images/vector_facing2.png

The green arrows fA and fB are unit vectors representing the zombies' facing directions and the blue semicircle represents its field of view. For zombie A, we find the direction vector AP pointing to the player using P - A and normalize it, however, Godot has a helper method to do this called direction_to. If the angle between this vector and the facing vector is less than 90°, then the zombie can see the player.

在代码中是这样的:

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!");
}

叉乘

Like the dot product, the cross product is an operation on two vectors. However, the result of the cross product is a vector with a direction that is perpendicular to both. Its magnitude depends on their relative angle. If two vectors are parallel, the result of their cross product will be a null vector.

../../_images/vector_cross1.png ../../_images/vector_cross2.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) 的结果不一样。得到的向量指向相反的方向。

法线计算

叉乘的一种常用方法是在三维空间中求平面或曲面的表面法向量。如果我们有三角形 ABC 我们可以用向量减法找到两条边 ABAC。通过叉乘, 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;
}

指向目标

In the dot product section above, we saw how it could be used to find the angle between two vectors. However, in 3D, this is not enough information. We also need to know what axis to rotate around. We can find that by calculating the cross product of the current facing direction and the target direction. The resulting perpendicular vector is the axis of rotation.

更多信息

有关在Godot中使用向量数学的更多信息,请参阅以下文章: