模块 2:3D 完整基础

Day 2 ⏱ 预计阅读 3 小时

💡 本模块目标

掌握 Unity 3D 游戏开发的全部核心概念:Transform 空间变换、向量数学、旋转系统、相机、光照、材质、物理系统、资源管理。每个概念都有完整可复制的 C# 代码。

2.1 Transform 与父子层级

Unity Scene View 中的 Transform 操作

Transform 是 Unity 中最重要的组件,每个 GameObject 都有且只有一个。它存储位置(Position)、旋转(Rotation)、缩放(Scale)。

世界坐标 vs 局部坐标

世界坐标 vs 局部坐标 世界原点 (0,0,0) Parent 世界: (10, 0, 0) Child 局部: (3, 0, 0) 父子关系 Child 的世界坐标 = Parent.position + Child.localPosition Child 世界坐标 = (13, 0, 0)
// 世界坐标
transform.position = new Vector3(10f, 0f, 0f);
Vector3 worldPos = transform.position;

// 局部坐标(相对于父物体)
transform.localPosition = new Vector3(3f, 0f, 0f);
Vector3 localPos = transform.localPosition;

// 父子操作
transform.SetParent(otherTransform);            // 设为 otherTransform 的子物体
transform.SetParent(otherTransform, false);    // false = 保持局部坐标不变
transform.SetParent(null);                     // 从父物体脱离(变为根物体)

// 遍历子物体
for (int i = 0; i < transform.childCount; i++)
{
    Transform child = transform.GetChild(i);
    Debug.Log($"子物体 {i}: {child.name}");
}

// 世界 ↔ 局部坐标转换
Vector3 localPoint = transform.InverseTransformPoint(worldPoint);
Vector3 worldPoint = transform.TransformPoint(localPoint);

// 常用方向向量(相对于物体自身)
Vector3 forward = transform.forward;  // 物体前方(Z+ 方向)
Vector3 up = transform.up;            // 物体上方(Y+ 方向)
Vector3 right = transform.right;      // 物体右方(X+ 方向)

// 移动(相对于自身坐标系)
transform.Translate(Vector3.forward * 5f * Time.deltaTime); // 向前移动

农场游戏示例:构建地块网格

public class FarmGrid : MonoBehaviour
{
    public GameObject plotPrefab;
    public int rows = 5, cols = 5;
    public float spacing = 2f;

    void Start()
    {
        for (int r = 0; r < rows; r++)
        {
            for (int c = 0; c < cols; c++)
            {
                Vector3 pos = new Vector3(c * spacing, 0f, r * spacing);
                GameObject plot = Instantiate(plotPrefab, pos, Quaternion.identity, transform);
                plot.name = $"Plot_{r}_{c}";
            }
        }
    }
}

2.2 Vector3 数学运算

using UnityEngine;

// 常用静态向量
Vector3.zero       // (0, 0, 0)
Vector3.one        // (1, 1, 1)
Vector3.up         // (0, 1, 0)
Vector3.down       // (0, -1, 0)
Vector3.forward    // (0, 0, 1)  ← Unity 的前方是 Z+
Vector3.back       // (0, 0, -1)
Vector3.left       // (-1, 0, 0)
Vector3.right      // (1, 0, 0)

// 基本运算
Vector3 a = new Vector3(1, 2, 3);
Vector3 b = new Vector3(4, 5, 6);
Vector3 c = a + b;          // (5, 7, 9)
Vector3 d = b - a;          // (3, 3, 3)
Vector3 e = a * 2f;         // (2, 4, 6) 标量乘法

// 长度
float mag = a.magnitude;    // 向量长度(√(1²+2²+3²) ≈ 3.74)
float sqMag = a.sqrMagnitude; // 长度平方(更快,用于比较距离)
Vector3 n = a.normalized;    // 单位向量(长度为 1,方向不变)

// 距离
float dist = Vector3.Distance(a, b);

// 点积(Dot Product):判断方向关系
float dot = Vector3.Dot(a.normalized, b.normalized);
// dot > 0 → 同方向 | dot = 0 → 垂直 | dot < 0 → 反方向

// 叉积(Cross Product):求垂直向量/判断左右
Vector3 cross = Vector3.Cross(a, b);
// cross.y > 0 → b 在 a 的左边 | cross.y < 0 → b 在 a 的右边

// 线性插值(最常用!)
Vector3 lerp = Vector3.Lerp(startPos, endPos, t); // t ∈ [0,1]

// 移动(匀速,不会减速)
Vector3 moved = Vector3.MoveTowards(current, target, maxDistanceDelta);

// 球面插值(旋转时更平滑)
Vector3 slerp = Vector3.Slerp(from, to, t);

实用示例:向目标移动

public class MoveToTarget : MonoBehaviour
{
    public Transform target;
    public float speed = 5f;

    void Update()
    {
        // 方式1:MoveTowards(匀速到达目标)
        transform.position = Vector3.MoveTowards(
            transform.position,
            target.position,
            speed * Time.deltaTime
        );

        // 方式2:Lerp(平滑逼近,越接近越慢)
        transform.position = Vector3.Lerp(
            transform.position,
            target.position,
            speed * Time.deltaTime
        );

        // 判断是否到达
        float dist = Vector3.Distance(transform.position, target.position);
        if (dist < 0.1f)
        {
            Debug.Log("到达目标!");
        }
    }
}

2.3 旋转与四元数

欧拉角 vs 四元数

❓ 为什么 Unity 用四元数而不是欧拉角?

欧拉角(Euler Angles)用 (x, y, z) 三个角度表示旋转,直观但有万向节死锁(Gimbal Lock)问题:当某个轴旋转到 90° 时,会丢失一个旋转自由度。四元数(Quaternion)用 4 个数 (x, y, z, w) 表示旋转,数学上不会产生死锁,且插值更平滑。

实际使用时,你几乎不需要理解四元数的数学原理。只需要知道:用 Quaternion.Euler(x, y, z) 把欧拉角转为四元数即可。

// 欧拉角 → 四元数
transform.rotation = Quaternion.Euler(0f, 45f, 0f);

// 四元数 → 欧拉角(用于读取/调试)
Vector3 euler = transform.eulerAngles;
Debug.Log($"当前旋转: ({euler.x}, {euler.y}, {euler.z})");

// localRotation / localEulerAngles 同理
transform.localEulerAngles = new Vector3(0f, 90f, 0f);

常用旋转操作

// 朝向目标点(最常用!)
transform.LookAt(target.transform.position);
// 或指定哪个面朝前
transform.LookAt(target.transform, Vector3.up);

// 从当前位置看向目标的旋转值(不立即旋转)
Quaternion lookRot = Quaternion.LookRotation(targetPos - transform.position);

// 平滑旋转到目标朝向
transform.rotation = Quaternion.Slerp(
    transform.rotation,
    lookRot,
    5f * Time.deltaTime
);

// 绕轴旋转
transform.Rotate(Vector3.up, 90f * Time.deltaTime); // 绕 Y 轴旋转
transform.Rotate(0f, 90f * Time.deltaTime, 0f); // 同上

// 绕指定点旋转(如绕太阳公转)
transform.RotateAround(sun.position, Vector3.up, 30f * Time.deltaTime);

// 两个旋转之间的角度差
float angle = Quaternion.Angle(rotationA, rotationB);

实用示例:平滑看向鼠标点击位置

public class LookAtClick : MonoBehaviour
{
    public float rotateSpeed = 5f;

    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit))
            {
                Vector3 dir = hit.point - transform.position;
                dir.y = 0f; // 只在水平面旋转
                Quaternion targetRot = Quaternion.LookRotation(dir);
                transform.rotation = Quaternion.Slerp(
                    transform.rotation, targetRot, rotateSpeed * Time.deltaTime
                );
            }
        }
    }
}

2.4 相机系统

Camera 组件关键属性

属性说明典型值
Clear Flags渲染前清除什么Skybox / Solid Color / Depth Only
Background背景颜色(Solid Color 模式)天空蓝 #87CEEB
Projection透视/正交Perspective(3D)/ Orthographic(2D)
Field of View视野角度(透视模式)60°
Size视野大小(正交模式)5
Near / Far近裁剪面 / 远裁剪面0.1 / 1000
Viewport Rect渲染区域(分屏用)0,0,1,1(全屏)