引言

前置阅读:建议先阅读标量数学三角函数

引擎语境:本文主要以 Unity / C# 视角讲解,概念同样适用于 Unreal / Godot / 自研引擎。

在游戏开发中,向量 (Vector) 是最基础也是最重要的数学工具之一。无论是角色的位置、速度,还是光照的方向、摄像机的朝向,几乎所有的空间概念都离不开向量。 可以把向量想成“有长度的箭头”:长度表示“有多大”,方向表示“往哪边”。

什么是向量?

在几何意义上,向量是一个既有大小 (Magnitude/Length) 又有方向 (Direction) 的量。 在编程中,我们通常用一个包含 \(x, y\) (2D) 或 \(x, y, z\) (3D) 分量的结构体或类来表示。

struct Vector3 {
  float x, y, z;
};

向量的基本运算

加法与减法

向量的加法符合三角形法则。 - 加法\(A + B\) 表示从 \(A\) 的起点出发,经过 \(A\) 到达 \(A\) 的终点,再以 \(A\) 的终点为起点,经过 \(B\) 到达 \(B\) 的终点。结果向量是从 \(A\) 的起点指向 \(B\) 的终点。 - 应用:位置更新 (新位置 = 旧位置 + 位移)。 - 减法\(B - A\) 表示从 \(A\) 的终点指向 \(B\) 的终点的向量。 - 应用:计算从一个物体指向另一个物体的向量 (目标位置 - 玩家位置)。

Vector Operations

标量乘法

将向量的每个分量乘以一个标量 \(k\)。 - 应用:缩放物体,或者根据时间步长缩放速度向量 (\(Position += Velocity * \Delta t\))。

点积 (Dot Product)

点积是两个向量之间的一种运算,结果是一个标量。 公式:\(A \cdot B = |A| |B| \cos \theta\) 或者代数形式:\(A \cdot B = A_x B_x + A_y B_y + A_z B_z\)

Dot Product

几何意义

点积描述了两个向量方向的相似程度。 - 如果 \(A \cdot B > 0\),夹角小于 90度 (方向大致相同)。 - 如果 \(A \cdot B = 0\),夹角等于 90度 (垂直)。 - 如果 \(A \cdot B < 0\),夹角大于 90度 (方向大致相反)。

游戏中的应用

  1. 视野检测:判断敌人是否在玩家的前方。计算 (敌人位置 - 玩家位置) 与 玩家朝向向量 的点积。
  2. 光照计算:漫反射光照 (Lambert) 取决于光线方向与表面法线的点积。

叉积 (Cross Product)

叉积仅在 3D 空间中有定义,结果是一个新的向量,该向量垂直于参与运算的两个向量构成的平面。 公式:\(A \times B = (A_y B_z - A_z B_y, A_z B_x - A_x B_z, A_x B_y - A_y B_x)\)

Cross Product

几何意义

结果向量的方向遵循右手定则。大小等于以 \(A\) and \(B\) 为边的平行四边形的面积。

游戏中的应用

  1. 计算法线:通过三角形的两个边向量计算表面的法线。
  2. 判断左右:在 2D 游戏中 (将 z 轴视为 0),可以通过叉积的 z 分量判断一个向量在另一个向量的左侧还是右侧。
  3. 构建坐标系:给定前方向量和上方向量,通过叉积计算右方向量。

归一化 (Normalization)

归一化是将一个向量的长度变为 1,但保持方向不变的过程。结果向量称为单位向量 (Unit Vector)。

公式

\[ \hat{v} = \frac{v}{|v|} \] 即:将向量的每个分量除以它的长度。

游戏中的应用

  • 只关心方向:当你需要一个方向(如“向右走”),但不希望速度受向量长度影响时。
  • 点积计算:如果两个向量都是单位向量,它们的点积直接等于 \(\cos(\theta)\)
  • 法线:光照计算中的法线必须是单位向量,否则光照强度会出错。
Vector3 direction = (targetPos - currentPos).normalized;
// 现在 direction 的长度是 1,我们可以乘以速度
velocity = direction * speed;

性能与常见技巧

平方长度优化

计算向量长度需要开平方根(sqrt),这是一个相对昂贵的操作。如果只是比较两个距离的大小,可以直接比较平方长度:

// 慢:计算两次 sqrt
if ((a - b).magnitude < (a - c).magnitude) { ... }

// 快:省掉 sqrt
if ((a - b).sqrMagnitude < (a - c).sqrMagnitude) { ... }

在每帧需要对成百上千个物体做范围检测时,这个优化非常显著。

2D 叉积的特殊用法

严格来说叉积只定义在 3D 空间,但在 2D 游戏中,我们经常用一个”伪叉积”来判断方向:

\[cross_{2D}(A, B) = A_x B_y - A_y B_x\]

  • 结果 > 0:\(B\)\(A\)左侧(逆时针方向)
  • 结果 < 0:\(B\)\(A\)右侧(顺时针方向)
  • 结果 = 0:\(A\)\(B\) 共线
float Cross2D(Vector2 a, Vector2 b) {
    return a.x * b.y - a.y * b.x;
}

// 判断转弯方向(AI 寻路、赛车游戏)
float turn = Cross2D(forward, toTarget);
if (turn > 0) TurnLeft();
else TurnRight();

投影 (Projection)

将向量 \(A\) 投影到方向 \(B\) 上:

\[proj_B A = rac{A \cdot B}{B \cdot B} B\]

如果 \(B\) 是单位向量,则简化为 \((A \cdot B) B\)

投影在游戏中非常常用: - 斜坡移动:将移动向量投影到斜面上 - 阴影计算:将光线投影到平面上 - 速度分解:将速度分解为沿墙面和垂直墙面的两个分量

实战:斜坡移动

角色在斜坡上移动时,我们不希望移动方向”钻入”地面。解决方法是将移动向量投影到斜坡平面上:

// slopeNormal: 斜坡法线(单位向量)
// moveDir: 玩家输入的移动方向
Vector3 projectedMove = moveDir - Vector3.Dot(moveDir, slopeNormal) * slopeNormal;
// projectedMove 现在平行于斜面,长度略小于原 moveDir

实战:墙体滑动

角色撞墙后不应该完全停下,而应该沿着墙面滑动:

// wallNormal: 墙面法线
// velocity: 碰撞前的速度
Vector3 slideVelocity = velocity - Vector3.Dot(velocity, wallNormal) * wallNormal;

这两个场景的数学本质完全一样:从向量中减去法线方向的分量

总结

掌握向量运算是进行 3D 编程和物理模拟的第一步。下一章我们将讨论矩阵与坐标变换。


< 上一篇: 三角函数入门 | 回到目录 | 下一篇: 矩阵与变换 >

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-11-30 · game_math

游戏中的数学

游戏开发数学专题索引。从标量函数到四元数,从碰撞检测到骨骼动画——一条完整的游戏数学学习路径。