很多新手刚开始接触 Unity 时,容易陷入一种“看懂了但不会做”的状态:几百分钟的完整教程看了一堆,菜单和窗口也认识了大半,可一旦自己动手,连让一个小球动起来都要折腾半天。问题的关键通常不是教程不够细,而是缺少一条从概念到项目的串联路径。本文会按实际做项目的顺序,把 Unity 环境搭建、C# 脚本基础、物理碰撞、收集计分、材质图集这些核心环节连起来讲,并提供完整可复制的代码与新手最容易踩的坑。无论你是零基础转游戏开发,还是有一定 C# 经验但刚接触 Unity,都能跟着做出一套可运行的小 Demo。
1. 先搞清楚:Unity 引擎到底在学什么
1.1 Unity 不是一门“编程语言”
很多初学者会把 Unity 和 C++、Python 放在一起比较,问“Unity 和 C# 哪个更难”。其实 Unity 是一款游戏引擎,它提供的是场景管理、渲染、物理模拟、输入检测、音频播放、资源加载等一整套底层能力。开发者要做的工作,是在引擎提供的框架里编写业务逻辑,告诉引擎“什么时候生成一个物体”“物体碰到障碍物后要做什么”。
换句话说,Unity 本身不是编程语言,而是一个“舞台”。场景中的角色、灯光、摄像机、UI 都由编辑器搭好,C# 则是控制这些对象的剧本。
1.2 C# 在 Unity 中承担什么角色
Unity 的官方脚本语言是 C#,这是一门由微软主导开发的现代面向对象语言。它既有类似 Java 的清晰语法,也有委托、事件、LINQ、异步编程等高级特性。Unity 里的 C# 脚本不会像传统控制台程序那样从Main方法开始运行,而是以组件的形式挂载在 GameObject 上,由引擎在特定时机调用脚本里的方法。
刚开始学习时,你不需要把 C# 的全部语法学完。掌握下面这些内容就能做出大部分入门项目:
- 变量和基础数据类型;
if、for、foreach等流程控制;- 方法定义与参数传递;
- 类、继承、接口的基本概念;
- Unity 提供的
MonoBehaviour、Transform、Rigidbody等类。
需要注意的是,Unity 开发中的 C# 和后端网站、上位机软件中的 C# 有些差异。Unity 脚本更强调“生命周期的回调”“组件之间的协作”“资源生命周期管理”。如果你之前学的是 WinForm、ASP.NET Core 等方向,转移过来的过程需要适应组件化思维。
1.3 手游开发为什么偏爱 Unity
Unity 在手游开发中占据很重要的位置,主要有几个原因:
- 跨平台能力强:一份工程可以打包到 Windows、macOS、Android、iOS,也可以通过官方适配方案导出到微信小游戏等平台;
- 编辑器可视化程度高:场景布局、资源预览、粒子效果调节都相对直观;
- 资产商店生态庞大:模型、特效、音效、插件都很容易获得;
- 中文学习资料多,社区问答覆盖度高。
所以对想进入游戏行业或者做独立游戏的同学来说,Unity + C# 是一条性价比较高的入门路线。
1.4 学习前的心理准备
引擎操作和编程都需要刻意练习。建议不要一口气看完长篇教程再动手,而是看一个小节就暂停,把示例重新做一遍。后续你会发现,编辑器里那些按钮和菜单,只有在真正做事时才有意义。
2. 开发环境搭建:从 Unity Hub 到第一个工程
2.1 安装 Unity Hub 和 Unity 编辑器
第一步是安装 Unity Hub,它相当于 Unity 版本的“启动器”,承担登录、安装运行时、创建工程、管理许可证的功能。
从 Unity 官网下载 Hub 后,按照引导安装即可。在 Hub 的“安装”页,点击“安装编辑器”,选择 LTS 版本。LTS 表示长期支持版本,稳定性较好,适合入门学习和大多数商业项目。
这里不建议把编辑器装到 C 盘系统盘。Unity 编辑器本体加上后续模块体积较大,放到空间充足的磁盘更合适。安装时可以根据目标平台勾选模块:如果只在本机制作测试,优先选择 Windows Build Support (Mono),也可以顺手勾上 Android Build Support,这样后续做手游真机调试时不用再重装。
不同版本的编译器和 API 会有差异,但入门阶段的知识是通用的。本文示例不依赖某个特定版本的新特性,只需要你有 Unity 2019.4 以上版本即可。如果遇到报错,请先检查自己的版本差异。
2.2 创建项目的关键选项
打开 Unity Hub,点击“新建项目”,会看到 2D、3D、URP、HDRP 等模板。对于刚入门 3D 游戏开发,选择内置渲染管线的 3D 模板即可。URP 模板也可以,但会额外引入渲染管线的概念,遇到 Shader 相关问题时排查难度稍高。
创建工程时,建议注意两点:
- 项目名称使用英文,路径中尽量不要出现中文和空格,避免部分插件或编译工具解析出错;
- 如果团队协作,项目存储地址建议放在磁盘某个固定目录下,方便后续接入版本管理。
创建完成后,编辑器可能需要一段时间生成项目文件,耐心等待进入主界面。
2.3 编辑器核心窗口
Unity 编辑器的主要窗口如下:
| 窗口 | 作用 |
|---|---|
| Hierarchy(层级) | 显示当前场景中的所有 GameObject,类似目录树 |
| Scene(场景) | 可视化的场景编辑视图 |
| Game(游戏) | 游戏运行时的画面预览 |
| Inspector(检查器) | 显示选中对象或资源的详细信息与组件配置 |
| Project(项目) | 浏览项目文件资源 |
| Console(控制台) | 查看日志、警告和错误 |
刚开始不必强行记住所有快捷键,先掌握最常用的几个:
- 鼠标中键拖拽平移 Scene 视图;
- 鼠标滚轮缩放;
- 右键拖拽旋转视角;
Q/W/E/R分别对应手型工具、移动工具、旋转工具、缩放工具。
2.4 推荐的项目目录结构
在 Project 窗口的Assets下,建议提前创建几个文件夹:
Assets/ Scenes/ // 存放场景文件 Scripts/ // 存放 C# 脚本 Prefabs/ // 存放预制体 Materials/ // 存放材质 Textures/ // 存放贴图 Audio/ // 存放音频 Art/ // 存放美术资源这样做的原因很简单:一个项目会随着时间不断膨胀,结构清晰能让你快速找到对应资源。养成命名规范的习惯比想象中重要。
3. C# 脚本在 Unity 中怎么运行
3.1 一切脚本都要挂在 GameObject 上
在 Unity 中,脚本被当作一种“组件”。创建脚本的方式是:
在 Project 窗口右键选择Create -> C# Script,或者点击 Assets 菜单创建,然后重命名。接着把脚本拖拽到 Hierarchy 窗口中的 GameObject 上,脚本就挂载完成了。
也可以先选中场景中的 GameObject,然后在 Inspector 中点击Add Component,搜索脚本类名添加。
脚本文件名通常必须和类名一致。例如文件名是PlayerController.cs,那么类名也必须是PlayerController。这是 C# 编译器对脚本类的要求。
3.2 MonoBehaviour 与脚本生命周期
Unity 的脚本默认继承自MonoBehaviour。这个基类提供了 Unity 组件生命周期相关的回调方法。最常用的是:
using UnityEngine; public class ExampleScript : MonoBehaviour { private void Awake() { // 对象被实例化时立即调用,适合初始化引用 Debug.Log("Awake"); } private void Start() { // Start 在第一次 Update 之前调用,适合初始化数据 Debug.Log("Start"); } private void Update() { // 每帧调用一次,适合处理输入、逻辑更新 Debug.Log("Update"); } }它们的执行顺序是:Awake -> OnEnable -> Start -> Update -> OnDisable -> OnDestroy。
新手常见误区是:认为Start里写了代码就会立刻执行。实际上,如果场景里已经有该对象,Start只会在第一帧前执行一次;如果对象后来通过Instantiate生成,Start也会在生成后的首帧前执行。
3.3 Update、FixedUpdate、LateUpdate 的区别
三个方法很容易混淆,但实际用处不同。
Update:每一帧调用一次,适合处理用户输入、游戏状态更新、动画逻辑。它的调用频率取决于设备帧率,60 FPS 和 30 FPS 的设备 Update 次数不同。FixedUpdate:按固定时间步长调用,默认是每秒 50 次左右。所有物理计算、刚体施力都应该放在这里,因为物理系统按固定步长模拟。LateUpdate:在每帧 Update 执行完之后调用,适合摄像机跟随。因为这样能确保角色已经完成本帧移动,摄像机再去跟随位置,画面更平滑。
来看一张简单的使用建议:
| 方法 | 使用场景 |
|---|---|
| Update | 键盘输入、UI 更新、每帧行为 |
| FixedUpdate | 刚体移动、物理碰撞处理、施力 |
| LateUpdate | 摄像机跟随、后置逻辑 |
3.4 GameObject、组件和 Transform
Unity 中所有出现在场景中的对象都是 GameObject,而 GameObject 本身只是一个容器。真正起作用的是它身上挂载的组件。比如一个 3D 小球由以下部分组成:
- Transform:记录位置、旋转和缩放;
- Mesh Filter:指定渲染的网格;
- Mesh Renderer:控制网格的渲染方式;
- Collider:负责物理碰撞范围;
- Rigidbody:让物体受重力影响并可受力运动;
- 自定义脚本组件。
代码中通过GetComponent<T>()获取对象上的组件。例如在脚本里获取刚体:
private Rigidbody rb; private void Start() { rb = GetComponent<Rigidbody>(); }Transform位置可以直接操作:
transform.position = new Vector3(0f, 1f, 0f); transform.rotation = Quaternion.Euler(0f, 90f, 0f);注意旋转使用四元数,但因为四元数不够直观,Unity 提供了Quaternion.Euler把欧拉角转成四元数。
4. 完整实战:一个可以运行的滚球收集小游戏
下面从创建场景开始,实现一个小球在平面上滚动,碰撞并收集方块的小游戏。做完后,你会理解输入、物理移动、摄像机跟随、触发检测、UI 显示等 Unity 开发的完整流程。
4.1 场景搭建
创建 3D 项目后,编辑器会默认生成一个包含 Main Camera 和 Directional Light 的场景。现在我们开始搭建物体。
- 在 Hierarchy 窗口右键选择
3D Object -> Plane,并把它重命名为Ground,作为地面。 - 再右键创建
3D Object -> Sphere,重命名为Player。调整它的位置到(0, 0.5, 0),让小球正好放在地面上,避免悬浮太高或一半陷入地面。 - 选中
Player,点击 Inspector 中的Add Component,搜索并添加Rigidbody。 - 为了让小球能被识别为“玩家”,在 Inspector 顶部 Tag 下拉框中选择
Add Tag...,新建一个名为Player的标签,然后把小球的 Tag 改为Player。 - 创建几个
3D Object -> Cube,放在地面上,作为收集道具,并让它们稍高于地面。
4.2 编写玩家控制脚本
在Scripts文件夹下创建PlayerController.cs,内容如下。
using UnityEngine; // RequireComponent 确保挂载本脚本时一定会有 Rigidbody [RequireComponent(typeof(Rigidbody))] public class PlayerController : MonoBehaviour { public float moveSpeed = 10f; private Rigidbody rb; private void Start() { // 获取同一个 GameObject 上的 Rigidbody 组件 rb = GetComponent<Rigidbody>(); } private void FixedUpdate() { float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector3 movement = new Vector3(horizontal, 0f, vertical); rb.AddForce(movement * moveSpeed); } }这里为什么使用AddForce而不是直接改transform.position?因为小球需要和地面发生物理交互,使用物理驱动力才能产生自然的滚动效果。直接改坐标会让物理引擎误以为物体被“传送到”某个位置,容易导致穿墙或抖动。
脚本保存后,从 Project 窗口拖拽到 Hierarchy 中的 Player 对象上。此时 Player 会自动添加 Rigidbody,因为脚本上有[RequireComponent(typeof(Rigidbody))]。
4.3 摄像机跟随
创建CameraFollow.cs,挂在 Main Camera 上,并在 Inspector 中把Target设为 Player。
using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; public Vector3 offset = new Vector3(0f, 8f, -10f); public float smoothSpeed = 5f; private void LateUpdate() { if (target == null) { return; } Vector3 desiredPosition = target.position + offset; transform.position = Vector3.Lerp( transform.position, desiredPosition, smoothSpeed * Time.deltaTime ); // 摄像机看向小球,保证画面中心一直有小球 transform.LookAt(target); } }Vector3.Lerp是线性插值,每帧从当前位置向目标位置移动一定比例。smoothSpeed * Time.deltaTime让移动与帧率多少相关,而不是每帧固定距离,因此在不同帧率设备上的表现更一致。
LateUpdate在这里的意义是:先运行完玩家移动的Update与物理回调,确保小球最终位置是最新状态,再更新摄像机位置,避免画面先于物体移动出现抖动的滞后感。
4.4 道具旋转与触发收集
每个收集道具需要两个行为:持续旋转、被玩家碰到后计数并销毁。
创建PickUp.cs,挂到每个 Cube 上。
using UnityEngine; public class PickUp : MonoBehaviour { public int scoreValue = 1; public float rotateSpeed = 60f; private void Update() { transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime); } private void OnTriggerEnter(Collider other) { // 判断碰到自己的对象是不是玩家 if (other.CompareTag("Player")) { // 调用 GameManager 累加分数 GameManager.Instance.AddScore(scoreValue); Destroy(gameObject); } } }要触发OnTriggerEnter,Cube 上必须有一个 Collider,并且要在 Inspector 中勾选Is Trigger。否则物理系统会把它当成障碍物做碰撞,而不是“穿过并检测”。
小球Player也必须携带 Collider 和 Rigidbody,否则无法产生触发事件。通常触发检测要求两个对象中至少有一个带 Rigidbody,这也是小球需要添加刚体的原因之一。
4.5 UI 计分与 GameManager
为了让分数在画面中显示,需要创建一个全局管理脚本。这里采用最简单的单例模式:通过静态属性Instance让其他脚本能直接访问 GameManager。
先创建脚本GameManager.cs:
using UnityEngine; using UnityEngine.UI; public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } // 在场景中把这引用拖到 UI Text 组件上 public Text scoreText; private int score; private void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } public void AddScore(int value) { score += value; if (scoreText != null) { scoreText.text = "得分:" + score; } } }然后在 Hierarchy 中创建一个空对象,命名为GameManager,挂上该脚本。
接着创建 UI 文本:
- 在 Hierarchy 右键选择
UI -> Text。如果项目提示使用 TextMeshPro,可以先忽略,旧版 Text 在本示例中依然能运行。 - 调整 Text 的位置,让它显示在左上角。
- 选中 GameManager,在 Inspector 中把
Score Text字段拖入刚才创建的 UI Text。
运行场景后,每次小球碰到 Cube,UI 会立刻更新。
4.6 运行验证
点击顶部工具栏的 Play 按钮,使用键盘方向键或 WASD 控制小球移动。
预期结果:
- 小球能在地面滚动,不会穿过方块;
- 摄像机始终跟随小球;
- 小球碰上 Cube 时,Cube 消失;
- 左上角分数增加,碰撞几个道具就增加几分。
如果小球不动,首先检查 Console 窗口是否报错,然后确认 PlayerController 是否已经挂在 Player 上,以及 Rigidbody 是否存在。移动输入依赖老版 Input Manager,如果项目使用了新版 Input System,需要按第 6 章的方式修改设置。
5. 容易被忽略的图形与资源细节
5.1 让物体逐渐消失的简单实现
很多新手会在聊天里问“Unity 脚本怎么控制物体逐渐消失”。这个问题涉及一个关键点:只修改颜色的 alpha 值,多数默认材质不会生效,因为默认渲染模式是不透明的。
要让物体逐渐隐藏,需要材质支持透明混合。在内置渲染管线中,把材质的 Rendering Mode 改成 Transparent;在 URP 中,要把 Shader 的 Surface Type 改成 Transparent。
下面是一个简单的渐隐脚本:
using UnityEngine; public class ObjectFader : MonoBehaviour { public float fadeDuration = 1.5f; private Renderer objectRenderer; private Material mat; private float elapsed; private void Start() { objectRenderer = GetComponent<Renderer>(); if (objectRenderer != null) { mat = objectRenderer.material; } } private void Update() { if (mat == null) { return; } elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / fadeDuration); Color color = mat.color; color.a = Mathf.Lerp(1f, 0f, t); mat.color = color; if (t >= 1f) { enabled = false; } } }代码思路并不复杂:用计时器记录已经经过的时间,用Mathf.Clamp01把比例限制在 0 到 1 之间,再通过Mathf.Lerp让透明度从 1 渐变到 0。
这段脚本直接用objectRenderer.material会创建材质实例,方便理解但会在项目中增加材质副本。真实项目里如果需要批量控制多个物体,更合适的做法是使用 MaterialPropertyBlock 或直接操作 Shader 属性,避免产生多余的材质实例。
5.2 用 Mathf.PerlinNoise 生成起伏地面
Mathf.PerlinNoise是 Unity 提供的噪声函数,它输入一个二维坐标,返回一个 0 到 1 之间的连续浮点值。因为噪声结果是连续的,适合用来生成地形高度、随机但不突兀的位移、纹理混合权重等。
下面用 Cube 网格生成起伏地形:
using UnityEngine; public class NoiseGround : MonoBehaviour { public GameObject cubePrefab; public int width = 10; public int depth = 10; public float heightScale = 3f; public float noiseScale = 0.3f; private void Start() { for (int x = 0; x < width; x++) { for (int z = 0; z < depth; z++) { float y = Mathf.PerlinNoise(x * noiseScale, z * noiseScale) * heightScale; GameObject cube = Instantiate( cubePrefab, new Vector3(x, y, z), Quaternion.identity ); } } } }脚本需要挂到一个空对象上,并指定一个cubePrefab,比如一个带有 Cube Collider 的小方块。运行时它会根据 PerlinNoise 的结果,把不同 x、z 位置的小方块摆在不同高度。
初学者可以尝试调整noiseScale,会发现数值越小,产生的噪声变化越平缓,地形也就越柔和。这个函数的参数很容易理解,但能衍生的玩法很多,比如浮动怪物的位移、草丛的摆动权重等。
5.3 2D 与 UI 资源:Sprite Atlas 图集
手游项目里 UI 图片往往非常多。如果每个图标都单独提交给 GPU,会产生大量渲染批次,增加 CPU 负担。Sprite Atlas 的作用是把多张 Sprite 合并到一张大图上,减少纹理切换和 Draw Call。
在编辑器中,Project 窗口右键选择Create -> 2D -> Sprite Atlas是常见入口。创建后把需要打包的 Sprite 或文件夹拖入Objects for Packing列表,再点击 Apply 或 Build,让图集生效。不同 Unity 版本的菜单位置和按钮名称可能有变化,但中心思想一致:多个小图集合成一张大图,避免每次绘制都切换纹理。
使用图集时注意两点:
- 不要把所有场景 UI 都塞进同一个超大图集,要按功能模块拆分;
- 图集太大可能浪费显存,而且打包更新时不方便,常见做法是“公共图标一个图集,副本页一个图集”。
如果你做 2D 游戏,Sprite Atlas 会在 Sprite 美术资源的组织上节省很多时间。
6. 常见报错与排查方向
下面是 Unity 新手最常遇到的几类问题,先给对照表,再挑几个典型场景展开。
| 问题现象 | 常见原因 | 处理思路 |
|---|---|---|
| 打开编辑器提示 No valid Unity Editor license found | 许可证未激活或过期 | 通过 Unity Hub 登录并激活许可证,检查系统时间 |
| 点击 Play 后移动脚本不生效 | 脚本没挂载、Console 报错、输入系统不对 | 检查组件挂载和 Console,确认输入模式 |
| 小球碰到道具没有触发收集 | Collider 未勾选 Is Trigger、Tag 未设置 | 检查 Collider 设置和 Tag |
| 打包后画面泛紫或渲染异常 | 渲染管线与 Graphics API 不兼容 | 在 Player Settings 中手动配置 Graphics API |
| 第三方 DLL 报 DllNotFoundException | 原生插件平台目录缺失 | 检查 Plugins 目录与目标平台 |
| 修改了 alpha 但物体不透明 | 材质不支持透明渲染 | 切换材质渲染模式 |
6.1 许可证激活问题
新手有时会碰到这样的报错:
No valid Unity Editor license found. Please activate your license.最常见的原因是你没有登录新设备,或者之前激活的许可证已经过期。处理方法是打开 Unity Hub,登录你的 Unity 账号,在“管理许可证”中激活 Personal 许可证。如果是公司或者学校提供的许可证,请联系管理员确认授权方式。
遇到这种报错先不要找非官方工具处理,安全风险和合规问题都很高,正常激活即可。
6.2 脚本没有反应:输入系统和生命周期
如果你写了移动脚本,但点击 Play 后物体完全不动,先看 Console。如果项目通过 Package Manager 安装了 Input System,且默认输入模式改成 Input System,那么Input.GetAxis("Horizontal")会直接报错,因为老版输入 API 被禁用。
解决办法是打开Edit -> Project Settings -> Player,找到Active Input Handling,把选项改成Both,或者改成Input Manager (Old)。改成Both能同时兼容新旧 API,适合做实验项目。
此外,要检查脚本是否真的挂到了 Player 上,以及在 Inspector 中PlayerController组件是否被勾选。如果脚本里出现空引用,Console 会显示红色错误,后面的脚本逻辑也会中断。
6.3 DllNotFoundException:第三方 native 库找不到
部分 Unity 插件需要 C++ 原生库参与工作,例如一些 Lua 热更新方案。错误信息类似:
DllNotFoundException: Unable to load DLL 'slua'这个报错不等于“C# 代码有问题”,而是运行时找不到对应平台的 native 库。常见原因是:
- 库文件没有放到
Assets/Plugins的正确子目录; - 只放了 Windows 的 DLL,在 Android 上运行却缺少
.so文件; - 目标平台没有勾选 Enable 或 ABI 架构不匹配。
排查时可以打开 Project 窗口中的原生插件文件,查看 Inspector 的 Platform Settings,确认它支持当前打包平台。这个问题在热更新方案中很常见,建议先阅读插件官方文档,按目标平台逐项配置。
6.4 Graphics API 与 Shader 问题
Unity 在不同平台使用不同图形 API。Windows 上通常使用 Direct3D,Android 可能使用 Vulkan 或 OpenGL ES。如果你在 Player Settings 中勾选了Auto Graphics API,Unity 会按平台自动选择 API,但这也可能导致自定义 Shader 或者某些高级渲染特性在不同平台上表现不一致。
如果打包到某个平台后出现紫色、花屏、材质全黑,可以先取消自动选择,手动添加目标平台支持的 API,并检查项目中使用的 Shader 是否支持该图形 API。渲染问题通常不是一条配置能解决的,但先排除 Graphics API 和 Shader 平台限制,能少走很多弯路。
7. 新手项目的工程化建议
7.1 脚本划分不要“一个脚本干所有事”
很多入门项目会把移动、得分、音效、动画全部写在一个 Player 脚本里。前期似乎方便,后患却不少:脚本越来越长,改一个功能容易影响其他逻辑。
更推荐的拆分思路是:
- PlayerController 只处理输入和移动;
- Health 只负责血量变化和死亡事件;
- AudioManager 负责播放音效;
- GameManager 负责游戏状态和分数。
组件之间通过公开字段或事件通信,尽量降低耦合。这样修改弹跳力不会影响受伤逻辑,新增被动技能也更可控。
7.2 用版本管理工具保护项目
Unity 项目包含大量二进制资源、场景文件、材质文件,直接复制工程备份容易遗漏或冲突。尽早使用 Git 是更稳妥的方案。
在创建工程时,如果编辑器中生成了.gitignore没有覆盖的内容,可以手动添加常见忽略规则。Library、Temp、Logs、Obj等目录都不应该进入版本库。它们可以随时由 Unity 重新生成,但体积很大且容易冲突。
另外,Unity 场景文件是文本格式,但也比较难合并。多人协作时建议每人负责不同场景,避免多人同时修改同一场景。
7.3 避免滥用 Resources 文件夹
Resources文件夹可以让脚本直接通过Resources.Load加载资源,看起来很方便。但所有放在 Resources 里的资源都会被无条件打进包体,并且不方便做热更新和增量加载。较大的项目里,更推荐使用 Addressables 或 AssetBundle,按需加载资源。
对入门项目来说,可以先通过场景引用和 Prefab 解决大部分资源连接问题,只有真正需要动态加载时再引入 Resources 或 Addressables,这样逻辑会清爽很多。
7.4 性能意识要趁早培养
不要等 Demo 已经卡顿才研究性能优化。写代码时注意几个常见习惯:
- 不要在
Update里反复调用FindGameObjectWithTag或GetComponent,把结果缓存到变量中; - 频繁创建和销毁对象会产生 GC 压力,如果对象需要大量出现,可以考虑对象池;
- 避免在每帧进行字符串拼接,UI 文本更新也要控制频率;
- 真机调试时开启
Development Build和Autoconnect Profiler,用 Profiler 查看 CPU 耗时和内存分配。
Unity 项目里的 CPU 瓶颈不一定在代码本身,也可能在 Draw Call、光影、物理、资源加载。遇到卡顿先打开 Profiler 看具体占比,不要凭感觉瞎优化。
7.5 不要过早陷入热更新等进阶技术
入门阶段经常看到各种“热更新”“Lua 热更框架”“Unity 微信小游戏打包”等内容。这些确实是手游开发中常见的技术方向,但它们建立在扎实的编辑器操作和 C# 基础之上。
过早接触热更新会让你同时面对原生平台、脚本语言互相调用、框架生命周期等多个问题,很容易产生“Unity 好难”的错觉。先专注于核心玩法循环,做出完整小游戏,再学习 Addressables、热更新、构建流程这些工程化能力。
8. 总结与后续学习路线
这篇文章已经从零讲解了 Unity 的核心概念,并用一个滚球收集小游戏串联了场景搭建、C# 脚本、物理运动、摄像机跟随、碰撞触发、UI 显示和常见报错处理。你现在应该掌握的关键点包括:
- Unity 是引擎,C# 是写在引擎上的控制脚本语言;
- MonoBehaviour 生命周期决定了脚本代码在什么时机运行;
- Update、FixedUpdate、LateUpdate 各有各的适用场景;
- 物理移动放在 FixedUpdate,视觉跟随放在 LateUpdate;
- 触发检测需要 Collider 配合 Rigidbody,并且要区分 Is Trigger;
- 渲染相关的透明、图集和 Shader 问题需要结合资源和平台来看。
接下来可以继续学习的路线大致是:
- 熟悉 Prefab 与实例化,做更多可复用的敌人、道具;
- 学习 Unity UI 的 Canvas、布局组件和交互事件;
- 掌握协程和异步加载,做延时、动画和资源加载;
- 学习动画状态机,让角色动起来;
- 理解 URP、材质和后期处理,提升画面表现;
- 试着把项目打包到 Android 真机,体验手机平台差异。
如果你准备做手游