atan2
2026/4/14大约 5 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — atan2
atan2
定义
atan2() 是双参数反正切函数——给你一个点的 Y 坐标和 X 坐标,它告诉你从原点到这个点的方向角度。
如果 atan() 是"只知道斜率(坡度),猜角度",那 atan2() 就是"知道具体位置(坐标),精确判断角度"。因为它同时拿到了 X 和 Y 的信息,所以能区分所有四个方向,不会像 atan() 那样把"右上角"和"左下角"搞混。
打个比方:你站在操场的中心(原点),告诉朋友"我正前方偏右 2 米,前方偏左 3 米有一个球"(也就是 x=2, y=3)。朋友不仅能告诉你球离你多远,还能精确告诉你球在你哪个方向——这就是 atan2(3, 2) 做的事情。
特别注意:参数顺序是 Y 在前,X 在后!即 atan2(y, x) 而不是 atan2(x, y)。这和数学上的习惯一致(先写纵坐标再写横坐标),但和直觉可能相反,是初学者最容易搞混的地方。
角度单位是弧度,不是度数
atan2() 返回的结果是弧度,不是度数。如果需要度数,用 rad_to_deg() 转换。
函数签名
C#
// 使用 Godot 的 Mathf(推荐,返回 float)
// 注意:第一个参数是 Y,第二个参数是 X!
public static float Atan2(float y, float x)GDScript
# 注意:第一个参数是 Y,第二个参数是 X!
func atan2(y: float, x: float) -> float参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
y | float | 是 | 目标点的 Y 坐标(纵向分量)。这是第一个参数! |
x | float | 是 | 目标点的 X 坐标(横向分量)。这是第二个参数! |
返回值
类型: float
返回一个弧度值,范围在 -PI 到 PI 之间(即 -180 度到 180 度)。覆盖完整的圆周方向。
| 输入 (y, x) | 输出(弧度) | 输出(度数) | 说明 |
|---|---|---|---|
| (0, 1) | 0 | 0 度 | 正右方 |
| (1, 0) | PI/2 | 90 度 | 正上方 |
| (0, -1) | PI | 180 度 | 正左方 |
| (-1, 0) | -PI/2 | -90 度 | 正下方 |
| (1, 1) | PI/4 | 45 度 | 右上角 |
| (0, 0) | 0 | 0 度 | 原点(特殊情况) |
代码示例
基础用法:从坐标计算方向角度
C#
using Godot;
public partial class Atan2Example : Node
{
public override void _Ready()
{
// 正右方 (x=1, y=0)
float a1 = Mathf.Atan2(0f, 1f);
GD.Print("atan2(0, 1) = " + Mathf.RadToDeg(a1) + " 度");
// 运行结果: atan2(0, 1) = 0 度
// 正上方 (x=0, y=1)
float a2 = Mathf.Atan2(1f, 0f);
GD.Print("atan2(1, 0) = " + Mathf.RadToDeg(a2) + " 度");
// 运行结果: atan2(1, 0) = 90 度
// 右上角 (x=1, y=1)
float a3 = Mathf.Atan2(1f, 1f);
GD.Print("atan2(1, 1) = " + Mathf.RadToDeg(a3) + " 度");
// 运行结果: atan2(1, 1) = 45 度
// 左下角 (x=-1, y=-1) — atan 无法区分这个和右上角,但 atan2 可以
float a4 = Mathf.Atan2(-1f, -1f);
GD.Print("atan2(-1, -1) = " + Mathf.RadToDeg(a4) + " 度");
// 运行结果: atan2(-1, -1) = -135 度
}
}GDScript
extends Node
func _ready():
# 正右方 (x=1, y=0)
var a1 = atan2(0.0, 1.0)
print("atan2(0, 1) = ", rad_to_deg(a1), " 度")
# 运行结果: atan2(0, 1) = 0 度
# 正上方 (x=0, y=1)
var a2 = atan2(1.0, 0.0)
print("atan2(1, 0) = ", rad_to_deg(a2), " 度")
# 运行结果: atan2(1, 0) = 90 度
# 右上角 (x=1, y=1)
var a3 = atan2(1.0, 1.0)
print("atan2(1, 1) = ", rad_to_deg(a3), " 度")
# 运行结果: atan2(1, 1) = 45 度
# 左下角 (x=-1, y=-1) — atan 无法区分这个和右上角,但 atan2 可以
var a4 = atan2(-1.0, -1.0)
print("atan2(-1, -1) = ", rad_to_deg(a4), " 度")
# 运行结果: atan2(-1, -1) = -135 度实际场景:让炮台朝向鼠标位置
C#
using Godot;
public partial class TurretAim : Node2D
{
public override void _Process(double delta)
{
// 获取鼠标位置相对于炮台的偏移
Vector2 mousePos = GetGlobalMousePosition();
Vector2 toMouse = mousePos - GlobalPosition;
// 用 atan2 计算朝向鼠标的角度
// 注意:先传 Y(纵向),再传 X(横向)
float targetAngle = Mathf.Atan2(toMouse.Y, toMouse.X);
// 直接旋转炮台朝向鼠标
Rotation = targetAngle;
GD.Print($"鼠标偏移: {toMouse}, 旋转角度: {Mathf.RadToDeg(targetAngle):F1} 度");
// 运行结果: 鼠标偏移: (150, -80), 旋转角度: -28.1 度
}
}GDScript
extends Node2D
func _process(delta):
# 获取鼠标位置相对于炮台的偏移
var mouse_pos = get_global_mouse_position()
var to_mouse := mouse_pos - global_position
# 用 atan2 计算朝向鼠标的角度
# 注意:先传 Y(纵向),再传 X(横向)
var target_angle := atan2(to_mouse.y, to_mouse.x)
# 直接旋转炮台朝向鼠标
rotation = target_angle
print("鼠标偏移: %s, 旋转角度: %.1f 度" % [to_mouse, rad_to_deg(target_angle)])
# 运行结果: 鼠标偏移: (150, -80), 旋转角度: -28.1 度进阶用法:用 atan2 实现角色朝移动方向转身
C#
using Godot;
public partial class TopDownPlayer : CharacterBody2D
{
// 导出属性:移动速度
[Export] public float ExMoveSpeed = 200f;
// 导出属性:转身速度(弧度/秒)
[Export] public float ExTurnSpeed = 10f;
public override void _PhysicsProcess(double delta)
{
// 获取输入方向
Vector2 input = Input.GetVector("move_left", "move_right", "move_up", "move_down");
if (input.LengthSquared() > 0.01f)
{
// 计算移动方向的角度
float targetAngle = Mathf.Atan2(input.Y, input.X);
// 平滑转向目标角度
float diff = Mathf.AngleDifference(Rotation, targetAngle);
float step = Mathf.Sign(diff) * Mathf.Min(Mathf.Abs(diff), ExTurnSpeed * (float)delta);
Rotation += step;
// 移动
Velocity = input.Normalized() * ExMoveSpeed;
}
else
{
Velocity = Vector2.Zero;
}
MoveAndSlide();
GD.Print($"当前朝向: {Mathf.RadToDeg(Rotation):F1} 度");
// 运行结果: 当前朝向: 45.0 度(随输入方向平滑变化)
}
}GDScript
extends CharacterBody2D
## 导出属性:移动速度
@export var ex_move_speed: float = 200.0
## 导出属性:转身速度(弧度/秒)
@export var ex_turn_speed: float = 10.0
func _physics_process(delta):
# 获取输入方向
var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
if input.length_squared() > 0.01:
# 计算移动方向的角度
var target_angle := atan2(input.y, input.x)
# 平滑转向目标角度
var diff := angle_difference(rotation, target_angle)
var step := signf(diff) * minf(absf(diff), ex_turn_speed * delta)
rotation += step
# 移动
velocity = input.normalized() * ex_move_speed
else:
velocity = Vector2.ZERO
move_and_slide()
print("当前朝向: %.1f 度" % rad_to_deg(rotation))
# 运行结果: 当前朝向: 45.0 度(随输入方向平滑变化)注意事项
- 参数顺序:Y 在前,X 在后:
atan2(y, x)的参数顺序和直觉不同。第一个参数是纵向(Y),第二个是横向(X)。记不住的话就默念"Y-X,Y-X"。 - 覆盖完整 360 度方向:
atan2()返回 -PI 到 PI 的角度,能区分所有方向。这是它比atan()强大的地方,也是游戏开发中更常用的原因。 - 原点处的行为:当
x=0, y=0时,atan2(0, 0)返回 0。但实际上原点处没有方向,所以使用前最好检查距离是否为零。 - Godot 中的替代方案:在 Godot 中,你可以直接用
Vector2.Angle()方法获取向量的角度,它内部就是调用的atan2(y, x)。例如(mousePos - GlobalPosition).Angle()等价于atan2(toMouse.Y, toMouse.X)。 - C# 中的选择:推荐使用
Mathf.Atan2()(返回float),而不是Math.Atan2()(返回double)。
