math-essentials について
Godot 4.3+開発者向けのゲーム数学スキルで、GDScriptとC#の例を掲載しています。ベクトル、変換、補間、パス、乱数に加え、実用レシピと注意点を扱います。
主な機能
- 01ベクトル演算と距離判定
- 022D・3D変換
- 03補間、イージング、移動処理
- 04Curveリソースとパス追従
- 05乱数と重み付き選択
- 06定番レシピとトラブルシューティング
活用シーン
- 01ターゲットの方向、距離、正面判定の計算
- 02ターゲットへの滑らかな追従や正確な移動
- 033D回転と向きの補間処理
- 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 | 2 点間の距離 |
distance_squared_to(b) | float | 距離の二乗(比較時に高速) |
direction_to(b) | Vector | この点から b への正規化された方向 |
angle_to(b) | float | 2 つのベクトル間の角度(ラジアン) |
angle_to_point(b) | float | この点から b への角度(2D) |
dot(b) | float | 内積 |
cross(b) | float/Vector3 | 外積(2D では float、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();
内積
内積を使うと、2 つのベクトルがどの程度同じ方向を向いているかがわかります。
# 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)
外積を求めると、入力した 2 つのベクトルに垂直なベクトルが得られます。
# 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. 乱数生成
1 回限りのランダム性には、グローバル関数(randf()、randi() % N、randf_range(a, b))を使用します。シード指定による再現可能なランダム性(プロシージャル生成、リプレイ、セーブ状態)には RandomNumberGenerator を使用します。重み付き選択には、累積和方式またはエイリアス法を使用します。
各パターンの GDScript 全文に加え、プロシージャル生成のノイズ用
FastNoiseLiteについては、references/random-numbers.md を参照してください。
6. よく使うゲーム数学のレシピ
5 つのレシピ:ターゲットを見る(2D の Vector2.angle_to_point)、点を中心に旋回する(極座標)、正弦波で揺らす(浮遊する UI 要素、宝物)、角度を折り返す(-PI..PI の正規化)、デッドゾーン付きの制限付き接近(アナログ入力と小さな入力の無視)。
各レシピですぐに使えるコードについては、references/game-math-recipes.md を参照してください。
7. よくある落とし穴
| 症状 | 原因 | 修正 |
|---|---|---|
lerp が目標に到達しない | 毎フレーム lerp(a, b, speed * delta) を使用している | 正確に到達させるには move_toward() を使用します |
| 回転が 180° の位置で跳ねる | lerp_angle ではなく lerp を使用している | 角度の補間には必ず 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()ではなくlerp_angle()を使用している - 3D の前方向は
+zではなく-transform.basis.zである - ターゲットに正確に到達する必要がある場合は、
move_toward()を使用している -
lerp(a, b, speed * delta)はフレームレートに依存する滑らかな追従であり、正確な移動ではないと理解している - 決定論的な乱数やシード付き乱数(プロシージャル生成、リプレイ)には
RandomNumberGeneratorを使用している - ノイズベースの生成には、適切な周波数とシードを指定して
FastNoiseLiteを使用している - 戦利品テーブルや確率ベースのシステムには、重み付きランダム選択を使用している
- パスの追従には、
progressまたはprogress_ratioを指定してPathFollow2D/3Dを使用している - 3D の回転補間にはオイラー角ではなく、クォータニオンの球面線形補間(slerp)を使用している