关于 math-essentials
面向 Godot 4.3+ 开发者的游戏数学技能,附有 GDScript 和 C# 示例。内容覆盖向量、变换、插值、路径与随机数,并提供常见游戏数学配方及易错点说明。
主要功能
- 01向量运算与距离计算
- 02二维和三维变换
- 03插值、缓动与运动逼近
- 04曲线资源与路径跟随
- 05随机数和加权选择
- 06常见数学配方与问题排查
适用场景
- 01计算目标方向、距离和视野判定
- 02平滑跟随目标或精确移动到目标
- 03处理三维旋转与朝向插值
- 04生成可复现随机结果和程序化路径
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill math-essentials安装后在 Claude Code 中输入/math-essentials确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/curves-and-paths.mdgame-math-recipes.mdrandom-numbers.md详细介绍
Godot 4.3+ 中的游戏数学
所有示例均面向 Godot 4.3+,不使用已弃用的 API。先展示 GDScript,然后展示 C#。
**相关技能:**移动物理请参阅 player-controller,寻路数学请参阅 ai-navigation,摄像机插值请参阅 camera-system,缓动曲线请参阅 tween-animation,碰撞数学请参阅 physics-system。
1. 向量运算
常用向量方法
| 方法 | 返回值 | 描述 |
|---|---|---|
length() | float | 向量的长度 |
length_squared() | float | 长度的平方(速度更快,无需计算平方根) |
normalized() | Vector | 方向不变、长度为 1 的单位向量 |
distance_to(b) | float | 两点之间的距离 |
distance_squared_to(b) | float | 距离的平方(比较时速度更快) |
direction_to(b) | Vector | 从此点指向 b 的归一化方向 |
angle_to(b) | float | 两个向量之间的弧度角 |
angle_to_point(b) | float | 从此点到 b 的角度(2D) |
dot(b) | float | 点积 |
cross(b) | float/Vector3 | 叉积(2D 返回浮点数,3D 返回向量) |
rotated(angle) | Vector2 | 按弧度旋转(2D) |
move_toward(to, delta) | Vector | 向目标移动,最多移动 delta |
clamp(min, max) | Vector | 限制各分量的范围 |
snapped(step) | Vector | 对齐到网格 |
reflect(normal) | Vector | 从表面反射 |
bounce(normal) | Vector | 从表面弹回(反向反射) |
slide(normal) | Vector | 沿表面滑动 |
方向与距离
# Get direction from A to B (normalized)
var dir: Vector2 = global_position.direction_to(target.global_position)
# Get distance
var dist: float = global_position.distance_to(target.global_position)
# Use squared distance for comparisons (faster — avoids sqrt)
if global_position.distance_squared_to(target.global_position) < detection_range * detection_range:
chase_target()
Vector2 dir = GlobalPosition.DirectionTo(target.GlobalPosition);
float dist = GlobalPosition.DistanceTo(target.GlobalPosition);
if (GlobalPosition.DistanceSquaredTo(target.GlobalPosition) < detectionRange * detectionRange)
ChaseTarget();
点积
点积可用于判断两个向量的方向有多一致。
# Is the target in front of us? (dot > 0 = in front, < 0 = behind)
var forward: Vector2 = Vector2.RIGHT.rotated(rotation)
var to_target: Vector2 = global_position.direction_to(target.global_position)
var dot: float = forward.dot(to_target)
if dot > 0.7: # roughly within ~45° cone
print("Target is ahead")
elif dot < -0.7:
print("Target is behind")
Vector2 forward = Vector2.Right.Rotated(Rotation);
Vector2 toTarget = GlobalPosition.DirectionTo(target.GlobalPosition);
float dot = forward.Dot(toTarget);
if (dot > 0.7f) GD.Print("Target is ahead");
叉积(3D)
叉积会生成一个垂直于两个输入向量的向量。
# Get the surface normal from two edge vectors
var edge1: Vector3 = vertex_b - vertex_a
var edge2: Vector3 = vertex_c - vertex_a
var normal: Vector3 = edge1.cross(edge2).normalized()
Vector3 edge1 = vertexB - vertexA;
Vector3 edge2 = vertexC - vertexA;
Vector3 normal = edge1.Cross(edge2).Normalized();
2. 变换
Transform2D
2D 变换包含位置、旋转和缩放。
# Get the global transform
var xform: Transform2D = global_transform
# Convert between local and global space
var local_point: Vector2 = to_local(global_point)
var world_point: Vector2 = to_global(local_point)
# Apply transform to a point
var transformed: Vector2 = xform * Vector2(10, 0) # point in local space → global
# Inverse transform
var local: Vector2 = xform.affine_inverse() * global_point
Transform2D xform = GlobalTransform;
Vector2 localPoint = ToLocal(globalPoint);
Vector2 worldPoint = ToGlobal(localPoint);
Vector2 transformed = xform * new Vector2(10, 0);
Vector2 local = xform.AffineInverse() * globalPoint;
Transform3D 与 Basis
# Basis holds rotation and scale as 3 column vectors
var basis: Basis = global_transform.basis
# Forward direction (looking along -Z in Godot)
var forward: Vector3 = -basis.z
var right: Vector3 = basis.x
var up: Vector3 = basis.y
# Look at a target
look_at(target.global_position, Vector3.UP)
# Rotate around an axis
rotate_y(deg_to_rad(90.0))
rotate_object_local(Vector3.UP, deg_to_rad(45.0))
# Interpolate between two transforms (smooth transition)
var a: Transform3D = $Start.global_transform
var b: Transform3D = $End.global_transform
global_transform = a.interpolate_with(b, 0.5) # halfway
Basis basis = GlobalTransform.Basis;
Vector3 forward = -basis.Z;
Vector3 right = basis.X;
Vector3 up = basis.Y;
LookAt(target.GlobalPosition, Vector3.Up);
RotateY(Mathf.DegToRad(90.0f));
Transform3D a = GetNode<Node3D>("Start").GlobalTransform;
Transform3D b = GetNode<Node3D>("End").GlobalTransform;
GlobalTransform = a.InterpolateWith(b, 0.5f);
is_orthonormal()(Godot 4.7+)
Basis.is_orthonormal()(const)在基底正交(各轴彼此垂直)且 归一化(每个轴的长度均为 1.0)时返回 true,在物理计算中尤其有用。它与 orthonormalized() 互为补充:先检查,只有累积的浮点误差导致基底不再归一化时,才重新正交归一化。
if not global_transform.basis.is_orthonormal():
global_transform.basis = global_transform.basis.orthonormalized()
// GodotSharp has no IsOrthonormal(); compare with the orthonormalized basis instead.
Basis basis = GlobalTransform.Basis;
if (!basis.IsEqualApprox(basis.Orthonormalized()))
{
GlobalTransform = new Transform3D(basis.Orthonormalized(), GlobalPosition);
}
3. 插值
lerp — 线性插值
# Interpolate between two values (t = 0.0 to 1.0)
var mid: float = lerp(0.0, 100.0, 0.5) # 50.0
var pos: Vector2 = lerp(start_pos, end_pos, 0.75) # 75% of the way
# Smooth following — lerp with delta for frame-rate independence
func _process(delta: float) -> void:
position = position.lerp(target_position, 5.0 * delta)
float mid = Mathf.Lerp(0.0f, 100.0f, 0.5f);
Vector2 pos = startPos.Lerp(endPos, 0.75f);
public override void _Process(double delta)
{
Position = Position.Lerp(targetPosition, 5.0f * (float)delta);
}
警告:
lerp(a, b, speed * delta)与帧率有关,并且永远无法完全到达目标。需要精确移动时,请改用move_toward()。
move_toward — 定速接近
# Move exactly `speed * delta` units toward target each frame
position.x = move_toward(position.x, target_x, speed * delta)
# Vector version
position = position.move_toward(target_position, speed * delta)
float newX = Mathf.MoveToward(Position.X, targetX, speed * (float)delta);
Position = Position.MoveToward(targetPosition, speed * (float)delta);
slerp — 球面插值
用于平滑旋转插值(沿弧线旋转,而非直线)。
# Quaternion slerp for smooth 3D rotation
var current_quat: Quaternion = global_transform.basis.get_rotation_quaternion()
var target_quat: Quaternion = target_transform.basis.get_rotation_quaternion()
var result: Quaternion = current_quat.slerp(target_quat, 5.0 * delta)
global_transform.basis = Basis(result)
Quaternion currentQuat = GlobalTransform.Basis.GetRotationQuaternion();
Quaternion targetQuat = targetTransform.Basis.GetRotationQuaternion();
Quaternion result = currentQuat.Slerp(targetQuat, 5.0f * (float)delta);
GlobalTransform = new Transform3D(new Basis(result), GlobalPosition);
smoothstep — S 形缓动
# Returns 0.0 when x <= from, 1.0 when x >= to, smooth curve between
var t: float = smoothstep(0.0, 10.0, distance) # 0→1 as distance goes 0→10
# Useful for soft thresholds (fog density, volume falloff)
var fog_intensity: float = smoothstep(50.0, 100.0, camera_distance)
cubic_interpolate — 平滑路径跟随
# Smooth interpolation using 4 control points (catmull-rom style)
var point: Vector2 = p1.cubic_interpolate(p2, p0, p3, t)
# p0 = before start, p1 = start, p2 = end, p3 = after end
插值对比
| 函数 | 速度 | 到达目标 | 平滑 | 用途 |
|---|---|---|---|---|
lerp(a, b, t) | 可变 | 仅在 t=1 时到达 | 是 | UI 过渡、混合 |
move_toward() | 恒定 | 是 | 否 | 移动、计时器 |
slerp() | 可变 | 仅在 t=1 时到达 | 是 | 旋转混合 |
smoothstep() | S 曲线 | 软阈值 | 是 | 雾效、体积、阈值 |
cubic_interpolate() | 可变 | 仅在 t=1 时到达 | 非常平滑 | 路径、摄像机轨道 |
4. 曲线和路径
Curve 资源用于表示随时间变化的值(例如伤害衰减曲线)。Path2D / Path3D 用于定义空间路径,可由 PathFollow2D / PathFollow3D 采样——适用于移动平台、导弹制导和摄像机轨道。
关于
Curve设置、Path 节点和 PathFollow 属性,请参阅 references/curves-and-paths.md。
5. 随机数生成
全局函数(randf()、randi() % N、randf_range(a, b))用于一次性随机取值。RandomNumberGenerator 用于可设定种子、可复现的随机取值(程序化生成、回放、存档状态)。可通过累积和法或别名法进行加权选择。
关于每种模式的完整 GDScript 代码,以及用于程序化生成噪声的
FastNoiseLite,请参阅 references/random-numbers.md。
6. 常用游戏数学示例
五个示例:朝向目标(2D Vector2.angle_to_point)、绕点旋转(极坐标)、正弦波浮动(浮动 UI 元素、宝藏)、角度归一化(-PI..PI 规范化)、带死区的钳制逼近(模拟输入 + 忽略微小输入)。
每个示例的即用代码请参阅 references/game-math-recipes.md。
7. 常见问题
| 症状 | 原因 | 修复方法 |
|---|---|---|
lerp 始终无法到达目标 | 每帧使用 lerp(a, b, speed * delta) | 使用 move_toward() 实现精确抵达 |
| 旋转在 180° 处跳变 | 使用了 lerp,而非 lerp_angle | 角度插值始终使用 lerp_angle() |
| 对象朝向错误(3D) | 忘记 Godot 使用 -Z 作为前向 | 前向为 -global_transform.basis.z |
| 距离检查太慢 | 对许多对象调用 distance_to | 使用 distance_squared_to,并与 range * range 比较 |
方向意外变成 (0, 0) | 零向量的 normalized() 会返回零 | 使用前检查 length() > 0(或 is_zero_approx()) |
| 变换插值效果不正确 | 对欧拉角进行插值,而非四元数 | 使用 Quaternion.slerp() 或 Transform3D.interpolate_with() |
| 重启后随机结果重复 | 使用了固定种子的 RandomNumberGenerator | Godot 4.x 会自动为全局 RNG 设定种子;对于 RandomNumberGenerator,请使用 randomize() 或设置 seed |
| 噪声值都约为 0 | frequency 太低 | 提高 FastNoiseLite.frequency(尝试 0.01–0.1) |
8. 实现检查清单
- 距离比较使用
distance_squared_to()以提升性能 - 角度插值使用
lerp_angle(),而非lerp() - 3D 前向方向为
-transform.basis.z,而非+z - 需要精确到达目标时使用
move_toward() - 理解
lerp(a, b, speed * delta)是依赖帧率的平滑跟随,而非精确移动 - 使用
RandomNumberGenerator实现确定性/可设定种子的随机效果(程序化生成、回放) - 基于噪声的生成使用
FastNoiseLite,并设置合适的频率和种子 - 战利品表和基于概率的系统使用加权随机选择
- 路径跟随使用
PathFollow2D/3D和progress或progress_ratio - 3D 旋转插值使用四元数球面线性插值,而非欧拉角