第 37 章 向量运算实战:点积、叉积、插值怎么变成游戏逻辑
上一章我们把”旋转”这件坑事刨到底,填上了第 35 章点名的”四元数 0、万向锁 0”硬缺口。但第 35 章那张图里,模块一”数学与空间”还列了两个更基础的零:向量基础虽散见、叉积 0、点积也不成体系。你说你 Go 后端写惯了,数组、结构体、循环门儿清——那向量对你来说本该是最亲切的东西:它不就是”一组带方向的浮点数”吗?
可你真到游戏里写逻辑时,八成会卡在这种地方:“敌人是不是在我正前方?""目标在我左边还是右边?""这颗子弹该往哪飞?""这个面朝外还是朝里?” 这些你以为是”if 判断”的活,背后全是向量运算。这一章我就把你最熟的”数组思维”接上游戏逻辑,让点积、叉积、插值变成你手里的真刀真枪。老规矩:现象 → 现实问题 → 工程师方案 → 落进引擎成了哪些 API。
37.1 为什么主程要会向量:逻辑的地基
先问你一个扎心的问题:你写的游戏逻辑,到底在算什么? 移动?算两点之间的位移向量。攻击范围?算敌我距离(向量长度)。朝向判断?算两个方向向量的夹角。碰撞?算法线和穿透深度。可见性?算视线向量和表面法线的点积。
你会发现,游戏逻辑 80% 的”判断”,本质都是向量运算。 你不会向量,就像后端不会写 SQL——业务逻辑全卡在”算不出来”上。第 35 章把”向量/点积/叉积”列为缺口,正是因为这个:它是”逻辑地基”,没有它,你连”敌人是否在面前 90 度内”这种最朴素的需求都写不利索。
对比你的老本行:后端里你查”用户是否在某个城市范围内”,本质是”算两个经纬度点的向量距离 + 判断夹角”,和游戏里”敌人是否在前方扇形”是同一个数学。你其实一直在用向量的思想,只是游戏把它逼到了台前、要求你每帧都算几十上百次。所以向量对你不是”新学”,是”把已经在用的思路正式化、高频化”。
向量(Vector),最朴素的定义就是”既有大小、又有方向的量”。游戏里一个 Vector3 就是 (x, y, z) 三个数:它可以表示”位置”(从原点到这点的箭头),也可以表示”方向”(比如敌人朝哪),还可以表示”力 / 速度”(大小和方向都有)。区分”位置向量”和”方向向量”很重要:位置是”相对于原点在哪”,方向是”指向哪、不关心起点”——这正是第 27 章讲的齐次坐标里 w=1(点)和 w=0(方向)的差别,你前面学过,这里直接复用。
37.2 向量加减与长度:位移、距离、归一化
从最朴素的运算开始,因为你写代码天天碰。
向量减法 = 位移 / 相对位置。 给你两个点 A 和 B(都是位置向量),B - A 得到的是一个”从 A 指向 B 的箭头”,它的方向和长度,就是”从 A 到 B 要移动多少”。游戏里”敌人相对我的位置""子弹飞行方向”,全是这一条。
Vector3 toEnemy = enemy.position - transform.position; // 从我指向敌人的向量向量长度(模,Length / Magnitude)= 距离。 toEnemy.magnitude 就是我和敌人之间的直线距离。判断”敌人进入 10 米射程”就是 toEnemy.magnitude < 10。注意 magnitude 底层是 sqrt(x²+y²+z²),开方有成本;如果你只想”比大小”(比如判断在不在范围内),用 toEnemy.sqrMagnitude(平方长度,不开方)更快——这是主程写高频逻辑时的性能小习惯,Unity 也专门给了 sqrMagnitude 这个属性。
归一化(Normalize)= 只要方向、不要大小。 很多时候你只关心”朝哪”,不关心”多远”。比如子弹速度,你只想要”飞行方向”,再乘以一个速度值。归一化就是把向量”拉长/压短到长度为 1”,方向不变、大小变 1:
Vector3 dir = toEnemy.normalized; // 长度为 1 的方向
Vector3 velocity = dir * bulletSpeed; // 方向 × 速度 = 带大小的速度向量类比:归一化像你导航 App 说”往东走”——只告诉你方向(东),不告诉你走多远;乘上速度,才变成”每小时往东 50 公里”这种具体量。游戏里”方向”和”带大小的速度”必须分开想,否则你会写出”离得越近子弹飞越慢”的诡异 bug(因为没归一化,方向向量的长度混进了速度里)。
37.3 点积:夹角、投影、亮度
点积(Dot Product),也叫数量积、内积。两个向量 a、b 的点积记作 a·b,公式是:
a·b = a.x*b.x + a.y*b.y + a.z*b.z // 分量相乘再相加
= |a| * |b| * cosθ // |a|是a长度,θ是两向量夹角
第二条等式是灵魂:点积等于”两向量长度之积,再乘它们夹角的余弦”。 当 a、b 都是单位向量(归一化过,长度为 1)时,点积就直接等于 cosθ——一个在 [-1, 1] 之间的数,精确告诉你两向量夹角多大、以及”朝不朝向同一边”。
基于这个,点积有三个你必用的场景:
① 判前后(最常用)。 你归一化”我的朝向”和”我指向敌人的方向”,点积结果:
> 0:夹角小于 90°,敌人在我前方(朝同一边)。≈ 0:夹角约 90°,敌人在我侧面。< 0:夹角大于 90°,敌人在我后方。
这就是”敌人在不在我视野正前方”的标准判法,比任何 if 角度都干净。
② 投影(Projection)。 点积能算”一个向量在另一个方向上的投影长度”。比如你想把重力分解到斜坡方向、或算”这个力有多少在推动物体前进”,都用点积。本质是 a·b 当 b 是单位向量时,等于 a 在 b 方向上的投影分量。
③ 亮度(连第 6 章、第 31 章光照)。 这是点积最漂亮的落点。你还记得第 6 章的朗伯余弦律和第 31 章的漫反射吗?一个表面被光照亮的程度,取决于”表面法线”和”指向光源的方向”之间的夹角余弦——而那正好是一个点积!diffuse = max(0, N·L),其中 N 是法线、L 是指向光的单位向量。换句话说,你第 31 章在 shader 里写的”漫反射用点积算亮度”,它的数学根就在这里。学完这一章,你回头看光照公式会”咯噔”一下:原来不是引擎魔法,是点积。
Unity 里直接用 Vector3.Dot(a, b)。代码判前后长这样:
Vector3 myForward = transform.forward; // 我的朝向(单位向量)
Vector3 toEnemy = (enemy.position - transform.position).normalized;
float dot = Vector3.Dot(myForward, toEnemy);
bool inFront = dot > 0; // 在面前37.4 叉积:垂直向量、法线、判左右
叉积(Cross Product),也叫向量积、外积。两个向量 a、b 叉乘得到一个新向量 c = a × b,而且这个 c 垂直于 a 和 b 所在的平面。它的长度公式是 |a×b| = |a|*|b|*sinθ。
叉积三个核心用途:
① 求法线(连第 4/6 章)。 你给一个三角形(三个顶点),想求它”朝外的面”的方向(法线),怎么办?取两条边向量 e1 = v1 - v0、e2 = v2 - v0,Normal = e1 × e2(再归一化)就得到垂直于这个三角形表面的法线。这直接连回第 4 章、第 6 章讲的”法线”——法线不是凭空来的,是相邻边叉乘算出来的。第 27 章还讲过”法线要用模型矩阵的逆转置来变换”,根因也在叉积:非均匀缩放会把叉出来的法线扭歪,所以得用逆转置修正。你今天学的叉积,是那些章节的”成因层”。
② 判左右 / 内外。 在 2D 或”同一水平面”上,叉积结果的”垂直分量符号”能告诉你 b 在 a 的左边还是右边。具体地,在 Unity 的 y-up 世界里,你常看叉积结果的 y 分量符号:正表示目标在右侧(按右手定则),负表示左侧。这是”AI 该往左拐还是往右拐追目标”的标准解法。
③ 求扭矩 / 旋转轴。 物理里力矩、旋转轴方向,常由叉积给出(叉积天然垂直于作用面和力,正好是转轴方向)。
Unity 里用 Vector3.Cross(a, b)。判左右的代码:
Vector3 toTarget = (target.position - transform.position).normalized;
Vector3 right = transform.right; // 我的右方向
float side = Vector3.Cross(toTarget, right).y; // 看 y 分量符号
// side > 0 : 目标在我右侧,应右转
// side < 0 : 目标在我左侧,应左转一个易混点,必须点清:点积得到一个数(标量),用来判夹角 / 前后 / 亮度;叉积得到一个新向量(且垂直于原两向量),用来求法线 / 判左右 / 求转轴。 你面试时被问”点积和叉积区别”,就这一句:点积出标量管”夹角类”,叉积出向量管”垂直类”。两个都是”两向量运算”,但产物和用途天差地别。
37.3.5 由点积反推夹角:Mathf.Acos 的用场
37.3 说了点积 = |a||b|cosθ。如果你把 a、b 都归一化(长度都变 1),那点积就等于 cosθ。反过来,已知点积想求”到底偏了多少度”,就用反余弦:θ = acos(dot)(再乘 Mathf.Rad2Deg 转成角度)。这是”算两向量具体夹角”的标准动作:
float angleDeg = Mathf.Acos(Mathf.Clamp(Vector3.Dot(a.normalized, b.normalized), -1f, 1f)) * Mathf.Rad2Deg;注意 Mathf.Clamp(..., -1, 1) 这一步——浮点误差可能让点积略超出 [-1,1],acos 遇到超出范围会返回 NaN(不是数), clamp 是防御性写法,老手必加。这个”算夹角”的能力,用在:判断”我和敌人的视线夹角是否够小才算发现”、计算”两辆车碰撞时的相对方向”、做”朝向对齐度”评分等。它和 37.3 的”判前后”是同一工具的两面:判前后看符号,算角度看 acos。
37.4.5 反射向量:子弹反弹与光照高光(点积/叉积的近亲)
叉积和反射(Reflect)常一起出现,主程值得顺手会。所谓反射向量,是”一个向量撞到平面后,按法线对称弹开的方向”。公式(你记结论即可):r = d - 2*(d·n)*n,其中 d 是入射方向、n 是平面法线、d·n 是点积。你看,反射本身就是”点积 + 向量减法”的组合,不依赖叉积,但和法线(叉积算出来的,见 37.4)绑定。
用处极实在:
- 子弹 / 光线反弹:激光打墙反弹、台球碰边,方向就是反射向量。Unity 有现成
Vector3.Reflect(d, n)。 - 光照高光(连第 31 章):镜面高光(Blinn-Phong)要算”反射光线”和”视线”的夹角,那个反射光线正是这里的
r。你第 31 章 shader 里写的reflect(-L, N),根就在这条公式——又一处”游戏里的酷效果,拆开是点积和向量运算”。
Vector3 incoming = bulletVelocity.normalized; // 入射方向(指向墙面)
Vector3 normal = surfaceNormal; // 墙面法线(由相邻边叉积得来)
Vector3 reflected = Vector3.Reflect(incoming, normal); // 反弹方向
bullet.velocity = reflected * speed;这一小段把本章的”点积(算 d·n)+ 法线(叉积得来)+ 归一化(只取方向)“三条全串起来了,是你写物理 / 特效时最典型的”向量三件套”组合拳。
37.5 插值 lerp 与向量:连回第 36 章 slerp
你第 36 章学了 Quaternion.Slerp——旋转版插值,在球面上走最短弧。向量这边有它的亲兄弟:Vector3.Lerp(a, b, t),做的是”从向量 a 到向量 b 的直线插值”。
result = a + (b - a) * t // t=0 给 a,t=1 给 b,中间线性过渡
它干嘛用?最典型:让物体从 A 点平滑移动到 B 点(位置插值)、让颜色从红渐变到蓝、让一个方向向量从 oldDir 平滑变到 newDir。注意:向量 lerp 是在”直线空间”里插值(因为位置/方向本来就在欧几里得直线空间),而旋转 slerp 是在”球面空间”里插值(因为朝向构成球面)——这就是为什么”旋转用 slerp、位置用 lerp”,两者几何空间不同,但套路一致:插值必须走在正确的空间里。你学完这两章,这条”空间要对”的原则就彻底坐实了。
// 让物体每帧朝目标点平滑靠拢(帧率无关)
transform.position = Vector3.Lerp(
transform.position,
targetPoint,
Time.deltaTime * moveSpeed
);钩子:如果你 lerp 的是”方向向量”(归一化后的),出来的中间向量长度可能不为 1,记得再
normalized一下,否则方向带进了大小。这条和第 37.2 的”归一化”呼应,是老手才会注意的细节。
37.6 实战三例:点积、叉积、插值直接落地
光讲概念你过两天就忘。我给你三个你做塔防/射击 MVP 必定会写的真实例子,每段都短、都能直接抄进项目。
例①:敌人必须在我前方 90 度扇形内才攻击(点积判夹角)。
bool CanAttack(Transform self, Transform enemy, float halfAngleDeg = 45f)
{
Vector3 fwd = self.forward; // 我的朝向
Vector3 toE = (enemy.position - self.position).normalized; // 指向敌人
float dot = Vector3.Dot(fwd, toE); // = cosθ
float cosLimit = Mathf.Cos(halfAngleDeg * Mathf.Deg2Rad); // 半角的余弦
float dist = (enemy.position - self.position).sqrMagnitude; // 用平方比距离,省开方
return dot > cosLimit && dist < attackRange * attackRange;
}思路:dot > cos(半角) 等价于”夹角小于半角”,即敌人在我前方扇形内;再叠一个距离判断。全程点积 + 平方长度,零开方、零角度转换,干净利落。这就是”视野 / 攻击扇形”的标准实现。
例②:判断目标在我左还是右,决定 AI 往哪拐(叉积 y 符号)。
int WhichSide(Transform self, Vector3 targetPos)
{
Vector3 toT = (targetPos - self.position).normalized;
Vector3 right = self.right;
float y = Vector3.Cross(toT, right).y; // 看 y 分量
if (y > 0.001f) return 1; // 右侧,右转追
if (y < -0.001f) return -1; // 左侧,左转追
return 0; // 几乎正前/正后
}这就是”转向行为(steering)“里最朴素的”该往哪边打方向盘”。配合第 36 章的 Quaternion.Slerp 平滑转过去,一个会追人的敌人就活了。
例③:子弹飞行方向(归一化方向 × 速度)。
void Fire(Vector3 from, Vector3 target, float speed)
{
Vector3 dir = (target - from).normalized; // 只取方向,长度=1
Vector3 velocity = dir * speed; // 方向 × 速度 = 真实速度向量
bullet.velocity = velocity;
// 顺便让子弹头朝飞行方向(连第36章 LookRotation)
bullet.rotation = Quaternion.LookRotation(dir);
}注意 normalized 这一步——没它,离目标越远子弹飞越快(因为方向向量本身带了距离长度混进速度)。这是新手最常见的”子弹速度诡异” bug 根因,归一到第 37.2 的教训。
三个例子串起来你会发现:点积管”在不在范围/朝哪边”、叉积管”左还是右”、归一化 + 乘法管”往哪飞多快”、slerp(36 章)管”怎么平滑转过去”。它们合起来,就是你游戏 AI 和战斗逻辑的全部空间判断骨架。第 35 章担心的”向量缺口”,到这儿被彻底填平,而且能直接写进你那个塔防 MVP。
37.6.5 进阶实战:把向量投影到地面、沿斜面移动
前面三例是”点乘判、叉乘分、归一化取方向”。我再给你两个稍进阶、但你做 3D 游戏迟早要写的,它们都用上 37.3 的”投影”概念。
例④:把任意方向”投影到水平地面”(去掉 y,只留平面移动)。 第一人称角色走路,你不想让他”看天看地时也前后飞”,要把”朝向”压到地面平面。做法:把朝向向量的 y 分量清零(或点积投影到水平面),再归一化。
Vector3 moveDir = (target - transform.position);
moveDir.y = 0; // 投影到水平面:去掉垂直分量
moveDir.Normalize(); // 只取水平方向
character.velocity = moveDir * walkSpeed;为什么 y=0 等于”投影到地面”?因为地面法线是 (0,1,0),去掉 y 就是”扣掉沿法线的分量”,剩下的就是平行于地面的部分——这正是点积投影”扣掉法线方向分量”的几何直觉(连 37.3 ②投影)。
例⑤:沿斜坡下滑方向(重力在斜面上的分量)。 物理里一个物体放在斜面上,重力 g 不会整体压进去,只有”平行于斜面”的分量让它下滑。算法:斜面法线 n,重力沿法线的分量是 (g·n)*n(点积投影),滑下方向 = g - (g·n)*n。
Vector3 gravity = Physics.gravity; // 通常是 (0, -9.8, 0)
Vector3 n = slopeNormal; // 斜坡法线(叉积算得)
Vector3 slide = gravity - n * Vector3.Dot(gravity, n); // 扣掉法线分量
rigidbody.AddForce(slide); // 沿斜面的下滑力这段把”点积投影”直接变成物理力,是角色控制器、载具上坡的底层逻辑。你做塔防里”怪物沿斜坡走”、做赛车”上下坡减速”,根都在这。
这两个例子再次印证 37.1 那句话:游戏逻辑 80% 的判断与力,都是向量运算。投影(点积)、法线(叉积)、归一化(取方向),三件套翻来覆去组合,就是你物理与移动的几乎全部数学。
37.6.6 向量的性能与批量:主程该有的自觉
你做联机或几百个敌人同屏时,向量运算会每帧跑成千上万次。几个主程级习惯:
- 能比平方就不开方:距离判断用
sqrMagnitude比magnitude(37.2 讲过),省掉sqrt。几千次/帧就能感知帧时间差异。 - 复用向量、少 new:Unity 的
Vector3是 struct(值类型),本身不进堆,但频繁(a-b)产生临时量在高频率循环里仍有开销;热路径里尽量用Vector3.Subtract之类的原地写法或缓存。 - 知道 API 成本:
normalized会做开方(内部1/length),而Normalize()原地改;Vector3.Dot/Cross是纯乘加,极便宜。所以”判前后”用 Dot(便宜),“求方向”才用 normalized(稍贵)。
这些不是”提前优化”,是你写主循环(第 35 章模块二)时自然要守住的底线——向量是游戏里调用频次最高的运算,省一点,全局就顺。
37.7 排查:向量相关 bug 的常见根因
你写上面这些逻辑,常翻车在这几处,对号入座:
- “敌人明明在面前却不攻击” → 多半忘了归一化:
Dot的两向量没归一化,点积就不是cosθ,阈值判断全乱。对策:fwd和toE都.normalized再 Dot。 - “子弹越近飞越慢 / 越近伤害越高” → 方向向量没归一化,距离长度混进了速度或伤害。对策:方向先
normalized。 - “AI 左右判断反了” → 叉积的顺序或符号约定搞反(右手定则 vs 你的坐标系)。对策:确认你比较的是哪个分量的符号,实在不行把
side正负翻一下再测试,但要在注释里写清依据(连第 2 章 2.7.5 的”手性”坑)。 - “距离判断忽灵忽不灵” → 用了
magnitude比较却忘了开方误差,或该用sqrMagnitude比平方却直接比了magnitude。对策:比大小统一用sqrMagnitude比平方值。
一句口诀:“向量判向先归一,点积出标量管夹角,叉积出向量管垂直,比距离用平方省开方。” 背住,你 90% 的向量 bug 当场消失。
37.7.5 向量与第 2 章坐标:同一套思维的再咬合
你学第 2 章时,我把”坐标”定性为”参照系的选择”——局部、世界、屏幕三套门牌指向同一点。现在你把向量装进来,会发现向量也是”参照系相关”的,而且正是第 2 章那套思维的延伸:
transform.forward是”相对这个物体自身坐标系的正前方”,它换算到世界坐标,要乘上第 27 章讲的 Model 矩阵。所以你代码里self.forward拿到的是已经转好世界的朝向向量——引擎替你做完了”局部→世界”那道变换(连第 2 章 2.3、第 27 章 27.2)。- 你在世界坐标里算
enemy.position - self.position得到的”指向敌人的向量”,是世界坐标系下的;若敌人挂在不同父级下,它的position(世界)和局部坐标不同,但你用transform.position(世界)去减,就自动在同一套参照系里算,不会错乱。这正是第 2 章”先问相对谁”的纪律在向量层面的体现。
一句话:向量运算必须在”同一套坐标系”里做,否则减出来的方向是错的。 你以前踩过的”模型导入躺平”(第 2 章 2.9,手性/坐标轴约定没对齐),本质上也是”向量参照系没对齐”——法线方向在右手系和左手系里镜像,你却用同一套叉积符号去判,自然反。所以”坐标参照系”和”向量运算”是绑定的:第 2 章给你参照系纪律,本章给你运算工具,两条合起来,你空间逻辑才算真正闭环。
37.8 小结与连回主线
按溯源学习法收尾:
- 现象 / 现实问题:游戏逻辑大量是”判前后、判左右、算距离、算亮度、算法线”,这些不能靠 if 角度硬写,否则又慢又错又难维护。
- 工程师的方案:用向量运算系统化解决——加减得位移、长度得距离、归一化取方向;点积(标量) 管夹角 / 前后 / 投影 / 亮度(连第 6/31 章光照);叉积(向量) 管法线 / 左右 / 转轴(连第 4/6/27 章);lerp 在直线上插值(连第 36 章 slerp 在球面上插值)。
- 落进引擎的 API:
Vector3的+ -、magnitude/sqrMagnitude、normalized、Vector3.Dot、Vector3.Cross、Vector3.Lerp,以及transform.forward / right这种现成方向向量。
连回前面的章节:第 6/31 章的漫反射亮度(N·L)在这里被揭示”就是点积”;第 4/6/27 章的法线,在这里被揭示”是边向量叉积算出来的、且要逆转置修正”;第 36 章的 slerp,在这里和 Vector3.Lerp 形成”球面 vs 直线”的对称。到第 37 章结束,第 35 章点名的”向量 / 点积 / 叉积 / 四元数 / 万向锁”整片缺口,全部填平——你模块一”数学与空间”这块主程地基,正式立住了。
你回头看第 35 章那张图,模块一当时画着”向量 0、叉积 0、四元数 0、万向锁 0”四个刺眼的零。现在你手里有了:四元数躲万向锁(第 36 章)、点积判前后与算亮度、叉积求法线与判左右、归一化取方向、lerp/slerp 插值——这一整套,正是主程写战斗、写 AI、写摄像机、写物理时每天在敲的东西。我早期跟你说”复杂的游戏现象,根子常是最朴素的数学”,这两章就是这句话的实锤:你以为高深的旋转和光照,拆开全是四元数乘法和点积。下一站如果你照第 35 章的路线图走,该去补”主循环架构与固定步长”了——那是把今天这些每帧运算,稳稳跑起来的骨架。你今天啃下的向量与四元数,正是主程每天离不开的计算母语。
【思考题】
- 用点积怎么判断”一个点在平面哪一侧”?(提示:平面的法线 n 已知,点 p 相对平面上一点 p0 的向量是 p-p0,算它和 n 的点积,符号代表什么?)这和第 37.3 的”判前后”是不是同一个原理?
- 法线为什么用”两条边向量叉积”来算?叉积结果的”垂直于原两向量”这个性质,在这里解决了什么问题?连回第 4/6 章”法线朝外”和第 27 章”法线要用逆转置变换”。
- 例③里如果忘了
normalized,子弹会出现什么诡异现象?为什么”距离长度”会混进”速度”里?这和第 37.2 讲的”归一化只取方向”如何对应? - 点积得到的是”标量(一个数)“、叉积得到的是”向量”,这个区别决定了它们各自的用途。请各举一个”如果错用(用点积去求法线 / 用叉积去判前后)会怎样”的反例,说明为什么不行。
【参考答案】
- 平面法线 n 已知,取平面上任一点 p0,点 p 相对平面的向量 v = p - p0。算 dot = n·v:dot > 0 表示 p 在法线指向的那一侧(“前面”),dot < 0 在另一侧(“后面”),dot ≈ 0 在平面上。这和第 37.3”判前后”完全是同一原理——都是”目标方向(这里是相对平面的向量)与某基准方向(这里是法线)做点积看符号/余弦”,只不过基准从”我的朝向”换成了”平面法线”。本质一句话:点积判”朝不朝向同一边”。
- 三角形法线必须是”垂直于这个三角形表面”的方向,而叉积
e1 × e2的数学性质恰恰是”结果垂直于 e1、e2 所在的平面”,所以叉积天然给出该表面的垂直方向即法线(再归一化)。这连回第 4/6 章”法线朝外”:叉积方向由右手定则决定,顶点顺序对了法线就朝外;也连第 27 章”法线用逆转置变换”——因为非均匀缩放下叉出来的法线会被扭歪,所以变换法线要用模型矩阵的逆转置而非直接乘。叉积是法线的”成因”,逆转置是法线的”修正”。 - 忘了
normalized,(target - from)这个向量的长度等于”我到目标的距离”。乘上 speed 后,子弹速度 = 距离 × speed,于是”离目标越远,算出的速度越大”,子弹越远的敌人飞得越快、越近反而越慢——完全违背物理直觉,且伤害/命中逻辑若用这向量也会错。第 37.2 讲”归一化只取方向、去掉大小”,正是为切断”距离”混进”速度”。先normalized把长度压成 1,再乘 speed,才是”方向 × 速度”的正确语义。 - 反例一:想”求法线”却用点积——点积输出是一个数,根本给不出”垂直于表面的方向向量”,所以求法线必须用叉积,错用点积直接得不到向量,无从谈起。反例二:想”判敌人在前还是在后”却用叉积——叉积给的是垂直向量,它的某分量符号虽然也能间接反映左右,但要判”前后(夹角小于/大于 90 度)“看的是夹角余弦的符号,那是点积(标量、直接等于 cosθ 当两向量归一化)的活;用叉积去判前后要么绕弯、要么在 3D 里因多轴而含糊。一句话:夹角/前后/亮度用点积(出标量),垂直/法线/左右用叉积(出向量),各司其职,错用则要么类型不对、要么语义含糊。