mcpField
U
unity-3d-math

unity-3d-math

github.com/nice-wolf-studio开发4072 次安装6 周前更新
代码仓库

关于 unity-3d-math

unity-3d-math 是面向 Unity 空间与 3D 数学代码的参考技能,整理常见错误及正确 API 用法,涵盖变换、旋转、向量、摄像机和边界。

主要功能

  1. 01坐标空间变换指南
  2. 02四元数旋转与插值模式
  3. 03安全计算方向与角度
  4. 04浮点数比较指南
  5. 05摄像机与平面相交模式
  6. 06边界和距离检查指南

适用场景

  1. 01在不同空间转换位置、方向和向量
  2. 02避免无效四元数并让对象朝向目标
  3. 03在游戏逻辑中检查距离和角度
  4. 04将屏幕或视口坐标映射到世界空间

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-3d-math

安装后在 Claude Code 中输入/unity-3d-math确认已加载。

附带文件references/spatial-math-deep-dive.md

详细介绍

3D 数学与空间推理 —— 正确性模式

前置技能: unity-scripting(Vector3/Quaternion API)、unity-foundations(Transform、GameObjects)、unity-physics(射线检测基础)

这些模式针对 Claude 最危险的失败模式:看起来合理、能够编译,但会产生错误结果的数学计算。每种模式都会说明 Claude 默认会犯的错误,以及正确的 Unity 做法。


模式:坐标空间 —— TransformPoint 与 TransformDirection

适用场景:在本地空间与世界空间之间转换

错误(Claude 的默认做法):

// Using TransformDirection for a position offset
Vector3 worldPos = transform.TransformDirection(localOffset);

正确:

// Positions: TransformPoint (applies position + rotation + scale)
Vector3 worldPos = transform.TransformPoint(localOffset);

// Directions: TransformDirection (applies rotation only, ignores scale)
Vector3 worldDir = transform.TransformDirection(localDir);

// Vectors: TransformVector (applies rotation + scale, no position)
Vector3 worldVec = transform.TransformVector(localVec);

注意:TransformDirection 会忽略缩放——如果父对象有非均匀缩放,且你需要方向也受缩放影响,请使用 TransformVector。对于逆向操作,请使用 InverseTransformPoint、InverseTransformDirection、InverseTransformVector。


模式:Quaternion 乘法顺序

适用场景:组合旋转(例如,在世界旋转基础上应用本地旋转)

错误(Claude 的默认做法):

// "First rotate by A, then rotate by B"
transform.rotation = rotA * rotB; // Actually applies B first, then A

正确:

// Quaternion multiplication applies RIGHT operand first
// "Apply B in A's space" = A * B
// Parent-then-child: parent * child
transform.rotation = worldRotation * localRotation;

// Example: rotate 45 degrees around Y, then tilt 30 degrees around local X
Quaternion yaw = Quaternion.AngleAxis(45f, Vector3.up);
Quaternion pitch = Quaternion.AngleAxis(30f, Vector3.right);
transform.rotation = yaw * pitch; // yaw is applied in world, pitch in yaw's local space

注意:这与矩阵乘法的阅读顺序相反。如果你想的是“先执行 A,再执行 B”,就写 A * B——右侧操作数会在左侧操作数的本地空间中应用。


模式:欧拉角插值(万向锁)

适用场景:在两个朝向之间平滑旋转

错误(Claude 的默认做法):

// Interpolating euler angles directly
Vector3 currentEuler = Vector3.Lerp(startEuler, endEuler, t);
transform.eulerAngles = currentEuler;

正确:

// Always interpolate quaternions, never euler angles
transform.rotation = Quaternion.Slerp(startRot, endRot, t);

// For small angles where performance matters, Lerp is acceptable
transform.rotation = Quaternion.Lerp(startRot, endRot, t); // Slightly faster, less accurate for large arcs

注意:欧拉角在俯仰角为 90 度时会出现万向锁,也存在不连续问题(例如,从 359 度跳到 1 度时,会经过 358 度,而不是 2 度)。Quaternion.Slerp 始终沿最短路径旋转。仅当角度差较小时(< 45 度)才使用 Quaternion.Lerp。


模式:Quaternion.LookRotation 零向量

适用场景:旋转对象,使其朝向目标方向

错误(Claude 的默认做法):

Vector3 dir = target.position - transform.position;
transform.rotation = Quaternion.LookRotation(dir);

正确:

Vector3 dir = target.position - transform.position;
if (dir.sqrMagnitude > 0.0001f) // Guard against zero/near-zero vector
{
    transform.rotation = Quaternion.LookRotation(dir, Vector3.up);
}

注意:LookRotation(Vector3.zero) 会生成 NaN 四元数,悄无声息地破坏 Transform。forward 参数无需归一化(Unity 会在内部进行归一化),但必须非零。如果 forward 与 up 完全平行,也会失败——第二个参数默认为 Vector3.up,而目标正上方或正下方时会导致问题。


模式:绝不直接修改 Quaternion 分量

适用场景:尝试将某个旋转轴归零或修改其值

错误(Claude 的默认做法):

// "Remove the X rotation"
Quaternion rot = transform.rotation;
rot.x = 0f;
transform.rotation = rot;

正确:

// Extract euler, modify, rebuild
Vector3 euler = transform.eulerAngles;
euler.x = 0f;
transform.rotation = Quaternion.Euler(euler);

// Or use Quaternion factory methods
// Keep only Y rotation:
transform.rotation = Quaternion.Euler(0f, transform.eulerAngles.y, 0f);

注意:Quaternion 的 x/y/z/w 不是欧拉角,而是 4D 单位四元数的分量。设置 .x = 0 会生成非单位四元数,导致行为未定义。始终使用工厂方法:Quaternion.Euler()、Quaternion.AngleAxis()、Quaternion.LookRotation()。


模式:浮点数比较

适用场景:比较位置、距离或任何浮点值

错误(Claude 的默认做法):

if (transform.position == targetPosition) { /* arrived */ }
if (distance == 0f) { /* overlapping */ }

正确:

// For positions: use sqrMagnitude with epsilon
if ((transform.position - targetPosition).sqrMagnitude < 0.0001f) { /* arrived */ }

// For single floats: use Mathf.Approximately
if (Mathf.Approximately(distance, 0f)) { /* close enough */ }

// For custom tolerance:
const float epsilon = 0.01f;
if (Mathf.Abs(a - b) < epsilon) { /* within tolerance */ }

注意:Unity 中的 Vector3 == Vector3 确实会在内部进行近似比较(epsilon 约为 1e-5),但对于游戏逻辑而言,这个阈值往往过于严格。请使用符合游戏精度需求的明确阈值。对于累积了误差的计算结果,绝不要使用 == 比较浮点数。


模式:Vector3.Angle 始终返回正值

适用场景:判断转向方向(左转与右转、顺时针与逆时针)

错误(Claude 的默认做法):

float angle = Vector3.Angle(transform.forward, dirToTarget);
// angle is always 0-180, cannot tell left from right

正确:

// SignedAngle returns -180 to +180 relative to the specified axis
float signedAngle = Vector3.SignedAngle(transform.forward, dirToTarget, Vector3.up);
// Positive = target is to the right, Negative = target is to the left (when axis is up)

注意:正负号取决于 axis 参数。以 Vector3.up 为轴时,从上方观察,正值表示顺时针。请选择与旋转平面相符的轴。对于使用 XY 平面的 2D 游戏,请使用 Vector3.forward 作为轴。


模式:叉积顺序与坐标系手性

适用场景:计算法线、垂直向量或顶点绕序

错误(Claude 的默认做法):

// Assuming right-hand rule
Vector3 normal = Vector3.Cross(edge1, edge2);

正确:

// Unity uses a LEFT-handed coordinate system (Y-up, Z-forward)
// Cross product follows LEFT-hand rule:
// Cross(right, forward) = UP (not down)
Vector3 normal = Vector3.Cross(edge1, edge2);
// If normal points wrong way, swap operand order:
Vector3 flippedNormal = Vector3.Cross(edge2, edge1);

注意:Unity 使用左手坐标系(Y 轴朝上、X 轴朝右、Z 轴朝前)。OpenGL/Blender 使用右手坐标系。如果你要移植右手坐标系参考资料中的数学计算,需要调换叉积顺序,或将一个坐标轴取反。在 Unity 中,顺时针的三角形绕序表示正面。


模式:使用 sqrMagnitude 进行距离比较

适用场景:在性能敏感的代码中比较距离(Update 循环、涉及多个对象)

错误(Claude 默认):

// Vector3.Distance computes a square root every call
if (Vector3.Distance(a, b) < detectionRange)
{
    // detected
}

正确:

// Compare squared distances -- avoids sqrt
float sqrRange = detectionRange * detectionRange;
if ((a - b).sqrMagnitude < sqrRange)
{
    // detected
}

注意:在循环外缓存 sqrRange,不要在每次迭代时重新计算 range * range。检查每帧 N 个对象时(O(N) 次平方根运算),这项优化很重要。对于单次检查,Vector3.Distance 完全没问题,不要对一次性调用进行过早优化。


模式:Camera.ScreenToWorldPoint 的 Z 深度

适用场景:将屏幕位置(鼠标、触摸)转换为世界位置

错误(Claude 默认):

// z=0 gives a point ON the camera's near plane, not in the scene
Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);

正确:

// Set z to the desired distance from the camera
Vector3 screenPos = Input.mousePosition;
screenPos.z = desiredDistance; // Distance from camera along its forward axis
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);

注意:输入向量的 z 分量表示沿摄像机前方方向、以世界单位计量的距离。对于透视摄像机,z=0 会返回摄像机所在位置。对于正交摄像机,z 不会影响 x/y,但仍会设置深度。若要将对象放置在地面上,请改用 Camera.ScreenPointToRay 和 Physics.Raycast。


模式:Camera.ViewportToWorldPoint

适用场景:将对象放置在视口边缘(HUD 边界、屏幕边界)

错误(Claude 默认):

// Missing z depth -- returns a point at the camera
Vector3 topRight = Camera.main.ViewportToWorldPoint(new Vector3(1f, 1f, 0f));

正确:

// z = distance from camera where you want the world point
float distFromCamera = 10f;
Vector3 topRight = Camera.main.ViewportToWorldPoint(new Vector3(1f, 1f, distFromCamera));
Vector3 bottomLeft = Camera.main.ViewportToWorldPoint(new Vector3(0f, 0f, distFromCamera));

注意:视口坐标是归一化的:(0,0) = 左下角,(1,1) = 右上角。z 值不是世界坐标中的 Z 坐标,而是到摄像机的距离。对于视锥会随距离扩大而变化的透视摄像机,这一点很重要。


模式:Plane.Raycast 的语义

适用场景:查找射线与数学平面的交点

错误(Claude 默认):

Plane groundPlane = new Plane(Vector3.up, 0f);
float enter;
groundPlane.Raycast(ray, out enter);
Vector3 hitPoint = ray.GetPoint(enter); // Using enter without checking return value

正确:

Plane groundPlane = new Plane(Vector3.up, 0f); // Normal=up, distance=0 (XZ plane at origin)
float enter;
if (groundPlane.Raycast(ray, out enter))
{
    Vector3 hitPoint = ray.GetPoint(enter);
}
// If returns false, the ray points away from the plane (enter is negative)

注意:只有射线与平面的正面(法线指向的一侧)相交时,Plane.Raycast 才会返回 true。如果射线起点在平面后方或射线背向平面,则会返回 false,且 enter 为负值。Plane 构造函数 new Plane(normal, distance) 中的 distance 是原点沿法线方向的有符号距离。new Plane(Vector3.up, 5f) 创建的是 y = -5 处的平面,而不是 y = 5。要创建 y = 5 处的平面,请使用 new Plane(Vector3.up, new Vector3(0, 5, 0))。


模式:Bounds 始终是轴对齐的(AABB)

适用场景:检查旋转对象的空间重叠或包含关系

错误(Claude 默认):

// Assuming bounds rotates with the object
if (renderer.bounds.Contains(point))
{
    // This is an AABB check, not an OBB check
}

正确:

// renderer.bounds is an AXIS-ALIGNED bounding box in WORLD space
// It expands to encompass the rotated mesh, making it larger than the actual object
Bounds aabb = renderer.bounds;

// For oriented checks, use the collider or manual OBB:
// Option 1: Use a collider (accurate to shape)
Collider col = GetComponent<Collider>();
Vector3 closest = col.ClosestPoint(point);
bool inside = (closest - point).sqrMagnitude < 0.0001f;

// Option 2: Transform point to local space for local-space AABB check
Vector3 localPoint = transform.InverseTransformPoint(point);
Bounds localBounds = meshFilter.sharedMesh.bounds; // Local-space bounds
bool containsLocal = localBounds.Contains(localPoint);

注意:Renderer.bounds 返回世界空间 AABB。旋转对象会使其 AABB 变大(旋转后的立方体的 AABB 大于立方体本身)。Mesh.bounds 是局部空间的。对于 Bounds.Intersects(other),两者必须处于相同空间。Collider.bounds 也是 AABB;若要精确检查形状,请使用 Physics.ComputePenetration 或 Collider.ClosestPoint。


模式:Transform 层级的缩放继承

适用场景:处理具有非均匀缩放的父子 Transform

错误(Claude 默认):

// Assuming scale is independent
transform.localScale = Vector3.one; // Visually one-to-one? Not if parent is scaled

正确:

// lossyScale gives the approximate world-space scale (read-only)
Vector3 worldScale = transform.lossyScale;

// To set a specific world-space scale on a child:
Vector3 parentScale = transform.parent.lossyScale;
transform.localScale = new Vector3(
    desiredWorldScale.x / parentScale.x,
    desiredWorldScale.y / parentScale.y,
    desiredWorldScale.z / parentScale.z
);

注意:当层级中同时存在旋转和非均匀缩放(倾斜)时,lossyScale 只是近似值。Unity 不支持 Transform 倾斜,因此 lossyScale 可能无法准确表示视觉缩放。避免对旋转子对象的父对象使用非均匀缩放。SetParent(parent, true) 会保留世界位置/旋转/缩放;SetParent(parent, false) 会保留局部值,请根据需要选择。


常见模式速查

需求方法空间
局部坐标转世界坐标的位置TransformPoint局部 -> 世界
世界坐标转局部坐标的位置InverseTransformPoint世界 -> 局部
局部坐标转世界坐标的方向TransformDirection仅旋转
世界坐标转局部坐标的方向InverseTransformDirection仅旋转
局部坐标转世界坐标的向量TransformVector旋转 + 缩放
向量之间的夹角Vector3.Angle无符号,0-180
有符号夹角Vector3.SignedAngle-180 到 +180
点积 > 0向量朝向同一半球
点积 == 0向量相互垂直
点积 < 0向量朝向相反方向
鼠标坐标转世界射线Camera.ScreenPointToRay屏幕 -> 世界
鼠标坐标转世界坐标点ScreenToWorldPoint + z 深度屏幕 -> 世界
视口边缘转世界坐标ViewportToWorldPoint + z 深度视口 -> 世界

相关技能

  • unity-scripting -- Vector3、Quaternion、Time API 参考
  • unity-foundations -- Transform API、父子层级结构
  • unity-physics -- 射线检测、碰撞体空间查询
  • unity-physics-queries -- 物理查询的正确性模式

其他资源

评论

支持 Markdown