3、游戏物体与组件:GameObject概念、Transform组件、Mesh Renderer、添加/删除组件、组件生命周期

好,咱们今天聊聊Unity里最核心的两个概念——GameObject和Component。说白了,整个Unity世界就是由这两样东西搭起来的。我刚开始学Unity的时候,总觉得这俩东西有点抽象,后来做项目做多了才发现,理解透它们,你才算真正入了门。

3.1 什么是GameObject?

GameObject,翻译过来就是“游戏物体”。你可以把它想象成一个空盒子。这个盒子本身啥也干不了,没有形状,没有颜色,没有物理效果。但它有一个最重要的能力——可以装东西

装什么呢?装组件(Component)。

一个GameObject可以装很多个组件。比如你想在场景里放一个立方体,那你就创建一个GameObject,然后给它挂上一个Mesh Filter(指定模型形状)、一个Mesh Renderer(渲染出来),再挂上一个Box Collider(碰撞检测)。你看,一个“立方体”就诞生了。

我个人习惯把GameObject理解成“容器”,组件就是“功能模块”。容器本身不重要,重要的是里面装了啥。

核心理解:GameObject = 空容器,Component = 功能模块。没有组件的GameObject,在场景里就是个“幽灵”,看不见也摸不着。

3.2 Transform组件——每个物体都有的“身份证”

你创建一个新的GameObject,Unity会自动给它挂上一个Transform组件。这个组件你删不掉,也替换不了。为什么?因为每个物体都必须有位置、旋转和缩放信息。

Transform组件就管这三件事:

  • Position(位置):物体在三维空间里的坐标 (x, y, z)
  • Rotation(旋转):物体绕各轴的旋转角度
  • Scale(缩放):物体在各轴上的缩放比例

我记得有一次做AR项目,一个模型怎么放都不对位置。折腾了半天,发现是父物体的Scale不是(1,1,1),导致子物体的位置计算全乱了。嗯,这里要注意——Transform是层级传递的。父物体移动,子物体跟着动;父物体缩放,子物体也跟着缩放。

小技巧:在代码里访问Transform,直接用 gameObject.transform 或者 transform(如果脚本挂在这个物体上)。这是Unity里最常用的属性之一。

3.3 Mesh Renderer——让物体“看得见”

光有Transform还不够,物体在场景里还是透明的。你需要一个Mesh Renderer组件来把它渲染出来。

Mesh Renderer负责把模型的网格数据(顶点、三角形、UV等)和材质(Material)结合起来,最终显示在屏幕上。简单说,Mesh Renderer = 模型 + 材质 → 画面

常用的几个属性:

属性 作用
Materials 指定渲染用的材质列表
Light Probes 光照探针,用于动态物体接收烘焙光照
Reflection Probes 反射探针,让物体产生环境反射
Shadow Casting 是否投射阴影
Receive Shadows 是否接收阴影

我曾经做过一个优化项目,场景里几百个物体都开着阴影投射和接收,帧率直接掉到20帧。后来把不需要阴影的小物体关掉,帧率立马回到60。所以——阴影很贵,按需开启

注意:Mesh Renderer只负责渲染,不负责碰撞检测。如果你想检测物体之间的碰撞,还需要挂一个Collider组件(比如Box Collider、Sphere Collider)。

3.4 添加/删除组件

在Unity编辑器里添加组件很简单:选中GameObject,点击Inspector面板底部的“Add Component”按钮,搜索或选择你想要的组件就行。

删除组件更简单:在组件右上角点击三个点,选择“Remove Component”。

那在代码里怎么操作呢?

// 添加组件
Rigidbody rb = gameObject.AddComponent<Rigidbody>();

// 获取组件(如果不存在则添加)
Rigidbody rb = gameObject.GetComponent<Rigidbody>();
if (rb == null)
{
    rb = gameObject.AddComponent<Rigidbody>();
}

// 删除组件(注意:不能删除Transform)
Destroy(gameObject.GetComponent<Rigidbody>());

这里有个坑——Destroy() 并不是立即执行的。它会在当前帧结束时才真正移除组件。如果你在Destroy之后立即访问这个组件,它可能还在。我遇到过好几次这种问题,调试了半天才发现是销毁时机的问题。

建议:如果你需要立即移除组件,可以用 DestroyImmediate()。但注意,这个函数只能在编辑器里用,不能在发布的游戏里用。

3.5 组件生命周期

每个组件(其实也就是每个MonoBehaviour脚本)都有固定的生命周期。Unity会按照特定顺序调用这些方法。理解这个顺序,你才能写出靠谱的逻辑。

常用的生命周期方法:

  1. Awake():脚本实例被创建时调用。无论脚本是否启用,都会执行。适合做初始化,比如获取组件引用。
  2. OnEnable():脚本或GameObject被启用时调用。每次启用都会执行。
  3. Start():在第一次Update之前调用。适合做需要依赖其他对象初始化的逻辑。
  4. Update():每帧调用。适合处理输入、移动等持续逻辑。
  5. FixedUpdate():固定时间间隔调用(默认0.02秒)。适合处理物理相关逻辑。
  6. LateUpdate():所有Update执行完后调用。适合做相机跟随等需要最后处理的操作。
  7. OnDisable():脚本或GameObject被禁用时调用。
  8. OnDestroy():组件或GameObject被销毁时调用。

你想想看,如果你在Awake里访问另一个还没初始化的组件,那就会出问题。所以我的习惯是:Awake里只做自己的初始化,Start里做依赖其他对象的初始化

重要顺序:Awake → OnEnable → Start → Update/FixedUpdate/LateUpdate → OnDisable → OnDestroy

我曾经接手过一个项目,所有初始化逻辑都写在Awake里,结果因为脚本加载顺序不确定,经常出现空引用异常。后来我把所有需要依赖其他脚本的逻辑移到Start里,问题就解决了。

3.6 总结与避坑

好了,咱们把这一章的核心点捋一捋:

  • GameObject是容器,组件是功能。没有组件的GameObject就是个空壳。
  • Transform是必有的组件,管位置、旋转、缩放。注意层级传递。
  • Mesh Renderer让物体可见,但阴影和反射很耗性能,按需开启。
  • 添加/删除组件用 AddComponentDestroy,注意销毁时机。
  • 生命周期顺序要记牢,Awake做自初始化,Start做依赖初始化。

最后说一个我踩过的坑:我曾经在Update里用GetComponent获取组件,每帧都调用,结果性能直接崩了。GetComponent虽然快,但每帧调用还是会有开销。正确的做法是在Awake或Start里把组件引用缓存下来,后面直接用。

嗯,这一章就到这里。下一章咱们聊聊物理系统,那又是另一个有意思的话题。