去做题

第十八单元:三角函数 · 反正切与角度计算

Unit 18: Trigonometry — atan2 & Angle Calculation
计算机科学 6 · 封神冰川 Deified Glacier · 轰炸行动

18.1 三角函数概述

Overview of Trigonometry in Code

封神冰川上空,你的狮鹫轰炸机队严阵以待。食人魔从四面八方逼近——"Incoming oscars!"的战报此起彼伏。可是狮鹫要精准投弹,光知道"敌人在那边"还不够,你必须算出敌人相对你的精确角度,把这个数字喊出去,才能指挥轰炸机转到正确的方向开火。这一单元只有一关,却把数学中优雅的三角函数(trigonometry)搬进了实战:你将学会用 Math.atan2(y, x) 把一个方向向量转成角度,把令人头疼的"弧度"换算成直观的"度数",最终输出一个能指挥全军的角度值。

本单元主线:理解为什么需要"角度"来描述方向;用敌人位置减英雄位置得到方向向量;用 Math.atan2(y, x) 计算这个向量与 X 轴的夹角(弧度)——注意 Y 在前、X 在后;用 * 180 / Math.PI 把弧度转成度数;最后用 hero.say(angle) 把角度说出去指挥轰炸。这些正是游戏、机器人、AI 中"由坐标算方向"的通用数学工具。

18.1.1 为何要算角度(Why We Need Angles)

18.1.1.1 坐标告诉你"在哪",角度告诉你"朝哪" Coordinates say "where", angles say "which way"

我们平时用 (x, y) 坐标描述一个物体的位置。但"朝哪个方向开火""转多少度瞄准",描述的是方向——这时候角度才是最自然的语言。轰炸机不理解"敌人在 (100, 50)",它只听得懂"转到 30 度方向"。把"位置差"翻译成"角度",正是本关要解决的核心问题。

🦅 就像给狮鹫下达开火指令
你不能对狮鹫说"飞到那个食人魔身边再投弹"——太慢了。你要像炮兵一样,一眼算出敌人相对你在"几点钟方向、偏多少度",然后喊出这个角度。狮鹫立刻转到那个朝向,一轮齐射覆盖过去。把空间关系压缩成一个角度数字,是精确指挥的关键。

18.1.2 SOH-CAH-TOA(传说中的三角口诀)

18.1.2.1 正弦、余弦、正切的三个定义 Sine, cosine, tangent in one mnemonic

直角三角形里,三角函数把"角度"和"边长比例"联系起来。传说中的口诀 SOH-CAH-TOA 帮你记住三个定义:正弦(Sin)= 对边/斜边(O/H)、余弦(Cos)= 邻边/斜边(A/H)、正切(Tan)= 对边/邻边(O/A)。本关的关键是正切:已知一个方向向量的 x、y 分量(相当于邻边和对边),它们的比值 y/x 就是斜率,而由斜率反推角度,靠的正是正切的逆运算——反正切(arctangent)。

邻边 Adjacent (x) 对边 Opposite (y) 斜边 Hypotenuse θ SOH · CAH · TOA
SOHSin = 对/斜
CAHCos = 邻/斜
TOATan = 对/邻

18.2 向量与方向

Vectors and Direction

18.2.1 位置差得到向量(Position Difference as a Vector)

18.2.1.1 从我指向敌人的箭头 An arrow pointing from me to the enemy

方向向量(direction vector)描述的是"从一点指向另一点"的箭头。要得到"从英雄指向敌人"的向量,只需把敌人的位置减去英雄的位置。相减的结果是一个新向量,它的 x 分量表示"敌人在我右边多少",y 分量表示"敌人在我上面多少"。这个向量既包含了方向,也包含了距离。

英雄 敌人 方向向量 = 敌人pos - 英雄pos Δx Δy

18.2.2 subtract 求差(Computing the Difference)

18.2.2.1 用 subtract 一步得到向量 Get the vector with subtract

在 扣哒世界 里,位置对象 pos 提供了 subtract 方法,能直接做向量减法。enemy.pos.subtract(hero.pos) 返回的就是"从英雄指向敌人"的方向向量,它有 .x 和 .y 两个分量。这一步是整个角度计算的起点——先有向量,才能谈角度。

# 找到攻击的向量:敌人位置 - 英雄位置
enemy = hero.findNearestEnemy()
vectorDirection = enemy.pos.subtract(hero.pos)
# vectorDirection.x → 水平分量;vectorDirection.y → 垂直分量
相减顺序不能反:一定是敌人减英雄(enemy.pos.subtract(hero.pos)),得到的才是"从英雄指向敌人"的向量。如果写反成"英雄减敌人",箭头方向会完全相反,算出的角度也会差 180 度,轰炸机会朝反方向开火。

18.3 atan2 反正切

The atan2 Function

18.3.1 从斜率到角度(From Slope to Angle)

18.3.1.1 已知 y/x,反推那个角 Given y/x, find the angle

正切告诉我们 tan(θ) = y / x(对边比邻边)。现在情况反过来:我们已知向量的 x、y 分量(也就知道了斜率 y/x),想求出角度 θ。这需要正切的逆运算——反正切(arctangent,记作 atan 或 tan⁻¹)。反正切吃进一个斜率,吐出一个角度。

tan(角度) = y/x 互逆 ⇄ atan(y/x) = 角度

18.3.2 Math.atan2(y, x)(更聪明的反正切)

18.3.2.1 一次搞定所有方向 Handles every direction correctly

普通的 atan(y/x) 有个缺陷:它只把 y/x 当作一个比值,分不清向量到底在哪个象限——(1,1) 和 (-1,-1) 的比值都是 1,却指向完全相反的方向。计算机专门提供了更聪明的 Math.atan2(y, x):它分别接收 y 和 x 两个参数,因此能根据两者的正负号判断出正确的象限,返回 -π 到 π 之间覆盖全方向的准确角度(单位是弧度)。

# 用 atan2 计算向量与 X 轴的夹角(结果是弧度)
angle = Math.atan2(vectorDirection.y, vectorDirection.x)
# 注意:y 在前,x 在后!
+x +y x>0,y>0 x<0,y>0 x<0,y<0 x>0,y<0 atan2 靠 x、y 的符号分辨象限
atan2 是"工程版"反正切:正因为普通 atan 会丢失象限信息,几乎所有编程语言(JavaScript、Python、C……)都提供了 atan2。在游戏、机器人、图形学里,"由向量求朝向"几乎总是用 atan2。记住它,你就掌握了方向计算的万能钥匙。

18.3.3 Y 在前的原因(Why Y Comes First)

18.3.3.1 别把参数顺序写反 Don't swap the arguments

这是最容易犯的错:Math.atan2 的参数顺序是 (y, x)——y 在前,x 在后!这和我们平时写坐标 (x, y) 的习惯正好相反。之所以这样设计,是因为角度定义为"对边(y)比邻边(x)"的反正切,而 atan2 沿用了 atan(y/x) 里 y 在分子的顺序。写反成 atan2(x, y) 会得到一个完全错误的角度。

✅ 正确:y 在前
atan2(vec.y, vec.x)
❌ 错误:顺序颠倒
atan2(vec.x, vec.y)

18.4 弧度与度数

Radians and Degrees

18.4.1 弧度是什么(What Is a Radian)

18.4.1.1 数学天生说"弧度" Math speaks in radians

Math.atan2 返回的角度不是我们熟悉的"度",而是弧度(radian)。弧度是数学里更"自然"的角度单位:一整圈 360 度等于 2π 弧度,半圈 180 度等于 π 弧度。所有编程语言的三角函数(sin、cos、atan2……)默认都用弧度。可惜人类读不惯弧度——"1.05 弧度"远不如"60 度"直观,所以我们需要换算。

度数(直观)弧度(数学)方向
0°0正右方 +x
90°π/2 ≈ 1.57正上方 +y
180°π ≈ 3.14正左方 -x
360°2π ≈ 6.28转回一整圈

18.4.2 弧度转度数(Radians to Degrees)

18.4.2.1 乘以 180 / π Multiply by 180 over pi

换算规则来自 π 弧度 = 180 度:所以 1 弧度 = 180/π 度。要把弧度转成度数,只需乘以 (180 / Math.PI)。本关要求最终答案必须是度数,所以在 atan2 之后一定要做这步换算,否则喊出去的是弧度,轰炸机会转到完全错误的角度。

# 把弧度换算成度数:乘以 (180 / Math.PI)
angle = angle * (180 / Math.PI)
# 例如:π/2 弧度 × (180/π) = 90 度
弧度值 × (180/π) → 度数值(可用)
反过来:度数转弧度乘 π/180:如果哪天需要把度数转回弧度(比如要传给 sin/cos),规则正好相反——乘以 (Math.PI / 180)。记住这一对互逆系数:180/π 弧度变度,π/180 度变弧度,方向计算就再不会晕头转向。

18.5 输出角度指挥轰炸

Announcing the Angle to Command

18.5.1 say 说出角度(Say the Angle)

18.5.1.1 把数字喊给轰炸机 Call out the number to the bombers

算出度数后,用 hero.say(angle) 把它"喊"出去。在本关的设定里,狮鹫轰炸机会听从这个角度指令,转到对应方向发起轰炸。say 在这里不只是聊天,而是把计算结果作为指令传递——你的数学运算,直接变成了对全军的作战命令。

# 说出角度,指挥狮鹫轰炸机!
hero.say(angle)
找最近敌人findNearestEnemy 求方向向量subtract 算角度(弧度)atan2(y, x) 转成度数× 180/π say指挥

18.6 实战关卡精讲

Level Strategy & Analysis

这一节聚焦本关的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。

第 36 关 · 轰炸行动

🦅 轰炸行动 —— 算准角度指挥狮鹫投弹

扣哒世界 轰炸行动关卡 - koudashijie.com
图2:轰炸行动——食人魔来袭,英雄需实时算出敌人相对自己的角度并喊出,指挥狮鹫轰炸机转向开火

策略:找敌 → 求向量 → 算角度 → 转度数 → 喊出

本关的整体策略是一条清晰的"计算流水线":在 while True 循环里不断找到最近的敌人,用它的位置减去英雄位置得到方向向量,用 atan2 把向量转成弧度角,再乘 180/Math.PI 换成度数,最后 say 出去指挥轰炸。没有复杂的分支和数据结构,难点全在于把数学步骤一步不差地翻译成代码——尤其是 atan2 的参数顺序和弧度转度数这两处。

思路拆解一:先确认有敌人再计算

每轮循环先 findNearestEnemy(),并用 if enemy: 判空——只有确实有敌人时才继续算角度。这既避免了对 None 取 pos 而报错,也符合"有目标才开火"的作战逻辑。这个判空习惯在前面几个单元里已经反复强调过。

# 关键片段:循环找敌并判空
while True:
    enemy = hero.findNearestEnemy()
    if enemy:
        # 有敌人,开始计算攻击角度……
        pass

思路拆解二:三步算出度数角

核心三步环环相扣:(1) 用 enemy.pos.subtract(hero.pos) 得到方向向量;(2) 用 Math.atan2(向量.y, 向量.x) 求出弧度角(y 在前);(3) 乘以 (180 / Math.PI) 把弧度换成度数。三步都不能漏、顺序不能乱,任何一处出错角度都会偏。

# 关键片段:三步算出度数角
vectorDirection = enemy.pos.subtract(hero.pos)   # 1) 方向向量
angle = Math.atan2(vectorDirection.y, vectorDirection.x)  # 2) 弧度角(y在前)
angle = angle * (180 / Math.PI)            # 3) 转成度数

思路拆解三:喊出角度完成指挥

最后 hero.say(angle) 把度数喊出去,狮鹫轰炸机据此转向轰炸。由于是 while True 循环,敌人位置一旦变化,下一轮会重新计算并更新角度——实时跟踪、动态瞄准,这正是循环 + 三角计算的威力。

# 关键片段:说出角度,指挥轰炸
hero.say(angle)
关卡的调试小助手:本关在场景里画了一个标注各点度数与弧度的圆圈作为参照。当你算出角度后,可以对照这个圆检查:比如敌人正在右上方,算出的度数应落在 0°~90° 之间。用已知的几何直觉验算代码结果,是排查角度错误最快的办法。
两个"必查点"决定成败:本关九成的失败都出在这两处——① atan2 参数写成了 (x, y)(应为 (y, x));② 忘了乘 180/Math.PI、直接把弧度喊了出去。提交前务必逐字检查这两行,它们是整关的命门。
举一反三——这套算法无处不在:"两点位置 → 方向向量 → atan2 求角 → 转度数"这条链路,在让炮塔瞄准、让 NPC 面向玩家、计算子弹飞行方向、机器人转向目标等无数场景中通用。学会这一关,等于握住了游戏与机器人开发里"面向目标"的通用公式。

18.7 常见错误

Common Mistakes

18.7.1 新手最容易踩的坑(Pitfalls for Beginners)

18.7.1.1 角度计算的典型问题 Typical issues with angle calculation

扣哒世界 三角函数常见错误速查 - koudashijie.com
图3:参数顺序颠倒、忘转度数、相减方向反、忘记判空,是本单元最常见的失误
易错点清单:
  • atan2 参数顺序错——必须是 Math.atan2(y, x),y 在前;写成 (x, y) 角度全错。
  • 忘记弧度转度数——atan2 返回弧度,要 * 180 / Math.PI 才是关卡要的度数。
  • 相减方向反——要 enemy.pos.subtract(hero.pos);反过来向量朝向相反,差 180°。
  • 忘记判空——没写 if enemy: 就取 enemy.pos,没有敌人时会报错崩溃。
  • 把系数写反——弧度转度乘 180/π;度转弧度才乘 π/180,别搞混。
  • 用了 atan 而非 atan2——普通 atan(y/x) 分不清象限,方向可能整体差 180°。
  • 系数打错数字——是 180 不是别的数(原始注释里把它误写成 188 是笔误)。
  • 忘了放进循环——敌人会移动,计算要在 while True 里持续更新,才能实时瞄准。

本单元小结 ✨

恭喜你指挥狮鹫轰炸机精准覆盖了来犯的食人魔,也把数学中优雅的三角函数真正用在了实战里!这一关虽小,却串起了"由坐标求方向"的完整链路。你理解了为什么需要用角度描述方向,复习了 SOH-CAH-TOA 三角口诀;学会了用敌人位置减英雄位置(subtract)得到方向向量;掌握了用 Math.atan2(y, x) 从向量算出夹角,并牢记了Y 在前、atan2 能正确分辨象限;懂得了 atan2 返回的是弧度,要乘 180 / Math.PI 转成度数;最后用 hero.say(angle) 把结果作为指令输出。这套"位置差 → 向量 → atan2 → 度数"的公式,是游戏 AI 与机器人"面向目标"的通用武器。来回顾一下核心概念:

核心概念关键点对应写法
方向向量敌人位置减英雄位置enemy.pos.subtract(hero.pos)
反正切 atan2由向量求角度(弧度)Math.atan2(vec.y, vec.x)
参数顺序Y 在前,X 在后(y, x)
象限判断靠 x、y 符号分辨方向atan2 自动处理
弧度转度数乘 180 除以 πangle * (180 / Math.PI)
度数转弧度反向乘 π 除以 180angle * (Math.PI / 180)
输出指挥把角度喊出去hero.say(angle)
先判空有敌人才计算if enemy:

懂得"把位置差翻译成方向角"——三角函数这份连接坐标与朝向的智慧,是让程序"看得懂方向、瞄得准目标"的核心力量。
封神冰川的三角试炼圆满完成,愿你带着这把数学利器,征战更广阔的算法疆场!🦅📐❄️

扣哒世界 编程知识 · 计算机科学 6(封神冰川):第十八单元

扣哒世界教研团队