第零章:游戏开发全景概览 —— 从 Web 到 Game Dev 的思维转变
第零章:游戏开发全景概览 —— 从 Web 到 Game Dev 的思维转变
本章目标
- 了解游戏开发行业的全景生态
- 理解 Unity 是什么、为什么选择它
- 对比主流游戏引擎的优劣势
- 明确本教程将要构建的项目
- 理解游戏开发与 Web 开发的核心差异
- 掌握 Game Loop(游戏循环)概念
- 了解 ECS 与 Component 模式
- 预览整个教程系列的学习路线
预计学习时间:60 - 90 分钟
0.1 为什么写这个教程?
如果你是一位前端或全栈开发者,熟悉 JavaScript/TypeScript,用 React、Vue 或 Next.js 构建过 Web 应用,但对游戏开发完全陌生 —— 那么这个教程就是为你量身打造的。
我们不会从零讲编程基础,而是会不断地把游戏开发的概念映射到你已经熟悉的 Web 开发概念上。比如:
| Web 开发概念 | 游戏开发对应概念 |
|---|---|
| DOM Element | GameObject |
| React Component | Unity Component |
| CSS Transform | Transform Component |
requestAnimationFrame | Game Loop / Update() |
| NPM Package | Unity Package / Asset |
| Chrome DevTools | Unity Editor |
| HTML 页面 | Scene(场景) |
| SPA Router | Scene Management |
| State Management | ScriptableObject / 全局状态 |
看到了吗?你并不是从零开始。你已经拥有了大量可以迁移的知识。
0.2 游戏开发行业全景
0.2.1 游戏的分类
在开始之前,让我们先了解游戏的基本分类:
按平台分类:
- 移动端游戏(Mobile):iOS / Android,我们本教程的目标平台
- PC 游戏:Windows / Mac / Linux
- 主机游戏(Console):PlayStation / Xbox / Nintendo Switch
- Web 游戏:基于浏览器运行
- XR 游戏:VR / AR / MR
按类型分类:
- RPG(角色扮演)
- FPS(第一人称射击)
- 开放世界(Open World)—— 我们要做的类型
- 策略(Strategy)
- 模拟经营(Simulation)
- 平台跳跃(Platformer)
- 等等…
0.2.2 游戏开发的岗位
一个游戏项目通常涉及以下角色:
游戏项目团队
├── 策划(Game Designer) -- 设计玩法、关卡、数值
├── 程序(Game Programmer) -- 就是我们要做的事
│ ├── 客户端程序 -- 渲染、UI、游戏逻辑
│ ├── 服务端程序 -- 网络、数据库、匹配系统
│ └── 工具程序 -- 编辑器、自动化工具
├── 美术(Artist)
│ ├── 原画(Concept Art)
│ ├── 3D 建模(3D Modeler)
│ ├── 动画(Animator)
│ ├── 特效(VFX Artist)
│ └── UI 设计(UI Designer)
├── 音频(Audio)
│ ├── 音乐(Composer)
│ └── 音效(Sound Designer)
└── 测试(QA Tester)
作为独立开发者或小团队,你可能需要身兼多职。好消息是,Unity Asset Store 和各种免费资源可以帮你补齐美术和音频的短板。
0.3 什么是游戏引擎?
0.3.1 类比理解
如果你是 Web 开发者,可以这样理解:
Web 框架 (React / Next.js) ←→ 游戏引擎 (Unity / Unreal)
就像 React 帮你处理了 DOM diff、虚拟 DOM、事件系统一样,游戏引擎帮你处理了:
- 渲染系统:把 3D 模型画到屏幕上(类似浏览器的渲染引擎)
- 物理系统:重力、碰撞检测(Web 里没有对应概念)
- 音频系统:播放和管理声音
- 输入系统:键盘、鼠标、手柄、触屏
- 动画系统:骨骼动画、状态机
- UI 系统:游戏内的界面(类似 HTML/CSS)
- 资源管理:加载和卸载纹理、模型、音频
- 跨平台构建:一套代码,多平台发布
0.3.2 没有引擎的话……
想象一下不用 React,纯手写 document.createElement 来构建一个复杂的 SPA —— 可以做到,但效率极低。游戏引擎同理。没有引擎,你需要直接调用 OpenGL/Vulkan/Metal 来画每一个三角形。
0.4 主流游戏引擎对比
Unity
| 特性 | 说明 |
|---|---|
| 语言 | C# |
| 适合 | 移动游戏、独立游戏、XR、2D/3D |
| 学习曲线 | 中等 |
| 价格 | 个人版免费(收入 < $200K/年) |
| 市场份额 | 移动端约 50%+ |
| 代表作 | Pokemon GO、原神(移动端)、Among Us、Hollow Knight |
Unreal Engine
| 特性 | 说明 |
|---|---|
| 语言 | C++ / Blueprint(可视化脚本) |
| 适合 | 3A 大作、高质量画面 |
| 学习曲线 | 陡峭 |
| 价格 | 免费(收入超 $1M 后抽成 5%) |
| 代表作 | 堡垒之夜、最终幻想 VII 重制版 |
Godot
| 特性 | 说明 |
|---|---|
| 语言 | GDScript / C# |
| 适合 | 独立游戏、2D 游戏 |
| 学习曲线 | 较低 |
| 价格 | 完全免费开源 |
| 代表作 | Sonic Colors Ultimate、部分独立游戏 |
其他引擎
- Cocos Creator:国内移动游戏常用,使用 TypeScript(对你来说很亲切!)
- Phaser:Web 2D 游戏框架,纯 JS/TS
- Three.js / Babylon.js:Web 3D 渲染库,但不是完整引擎
- Bevy:Rust 语言的新兴引擎,纯 ECS 架构
0.4.1 为什么选 Unity?
对于我们的目标(3D 开放世界移动游戏),选择 Unity 的理由:
- 移动端王者:Unity 在移动游戏市场的占有率最高
- C# 语言:如果你会 TypeScript,学 C# 非常快(后面会详细对比)
- 学习资源丰富:全球最大的游戏开发社区之一
- Asset Store:海量免费和付费资源,适合独立开发者
- 3D + 移动端:Unity 的 URP(Universal Render Pipeline)专为移动端优化
- 一次开发,多端发布:iOS、Android、PC、主机都可以
0.5 我们要构建什么?
项目名称:BellLab Open World
这是一个 3D 开放世界移动游戏的原型,包含以下核心系统:
BellLab Open World
├── 🌍 开放世界地形系统
│ ├── 地形生成与编辑
│ ├── 植被系统
│ └── 天气与昼夜循环
├── 🚶 角色控制系统
│ ├── 第三人称相机
│ ├── 移动与跳跃
│ └── 角色动画
├── 🎒 物品与背包系统
│ ├── 物品拾取
│ ├── 背包 UI
│ └── 物品使用
├── 💬 NPC 对话系统
│ ├── 对话树
│ ├── 任务系统
│ └── NPC AI 行为
├── ⚔️ 简单战斗系统
│ ├── 近战攻击
│ ├── 远程攻击
│ └── 生命值系统
├── 📱 移动端适配
│ ├── 虚拟摇杆
│ ├── 触屏操作
│ └── 性能优化
└── 💾 存档系统
├── 本地保存
└── 数据序列化
每个系统都对应教程中的若干章节。我们会从最基础的操作开始,逐步构建出完整的游戏原型。
[截图:最终游戏效果预览 —— 一个包含地形、角色、NPC 的 3D 开放世界场景]
0.6 游戏开发 vs Web 开发:核心差异
这一节非常重要。理解这些差异将帮助你更快地适应游戏开发的思维方式。
0.6.1 渲染模型:被动 vs 主动
Web 开发(被动渲染):
// Web: 只在数据变化时重新渲染
const [count, setCount] = useState(0);
// 只有调用 setCount 时才会触发重新渲染
<button onClick={() => setCount(count + 1)}>
{count}
</button>
游戏开发(主动渲染):
// Unity: 每一帧都在执行,不管有没有变化
// 60 FPS 意味着这个函数每秒执行 60 次
void Update()
{
// 检查输入
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 移动角色
transform.Translate(new Vector3(horizontal, 0, vertical) * speed * Time.deltaTime);
}
在 Web 中,你的代码是事件驱动的 —— 用户点击、数据变化才触发更新。 在游戏中,你的代码在持续运行 —— 每一帧都在执行,哪怕什么都没发生。
0.6.2 坐标系统
Web(2D 坐标):
(0,0) ──────→ X
│
│
↓
Y
CSS: top, left, width, height
Unity(3D 坐标):
Y (上)
│
│
│
└───── X (右)
/
/
Z (前)
Unity 使用左手坐标系
Position: (x, y, z)
Rotation: (x, y, z) 欧拉角
Scale: (x, y, z)
0.6.3 状态管理
Web 开发:
// Redux / Zustand / Context
const store = create((set) => ({
player: { hp: 100, mp: 50 },
updateHP: (value) => set((state) => ({
player: { ...state.player, hp: value }
}))
}));
Unity 游戏开发:
// 状态直接存在于 Component 中
public class PlayerHealth : MonoBehaviour
{
// 状态直接作为字段
public int maxHP = 100;
public int currentHP;
void Start()
{
// 初始化(类似 constructor 或 useEffect 的初始化)
currentHP = maxHP;
}
public void TakeDamage(int damage)
{
// 直接修改状态,不需要 immutable update
currentHP -= damage;
if (currentHP <= 0)
{
Die();
}
}
}
注意:Unity 中的状态是可变的(mutable)。不像 React 要求 immutable update,Unity 中你直接修改变量。这对习惯了函数式编程的前端开发者来说需要适应。
0.6.4 生命周期对比
React 组件生命周期 Unity MonoBehaviour 生命周期
────────────────── ─────────────────────────────
constructor() → Awake()
componentDidMount() → Start()
componentDidUpdate() → Update() (每帧调用!)
componentWillUnmount() → OnDestroy()
shouldComponentUpdate → OnEnable() / OnDisable()
public class MyComponent : MonoBehaviour
{
// 类似 constructor —— 最早执行,只执行一次
void Awake()
{
Debug.Log("Awake: 对象被创建");
}
// 类似 componentDidMount —— 在第一帧之前执行
void Start()
{
Debug.Log("Start: 组件准备就绪");
}
// 类似 componentDidUpdate —— 但是每帧都执行!
void Update()
{
Debug.Log("Update: 每帧执行");
}
// 物理更新 —— Web 里没有对应概念
// 以固定时间间隔执行(默认 0.02 秒/次)
void FixedUpdate()
{
Debug.Log("FixedUpdate: 物理更新");
}
// 类似 componentWillUnmount
void OnDestroy()
{
Debug.Log("OnDestroy: 对象被销毁");
}
}
0.6.5 性能考量的差异
| 方面 | Web 开发 | 游戏开发 |
|---|---|---|
| 帧率要求 | 不太关注 | 必须 30/60 FPS |
| 内存管理 | GC 自动处理 | 需要主动管理,避免 GC 卡顿 |
| 资源大小 | 关注首屏加载 | 关注整体包体大小和加载策略 |
| 电量消耗 | 较少关注 | 移动端极其重要 |
| 发热控制 | 不关注 | 移动端核心指标 |
0.7 Game Loop(游戏循环)
这是游戏开发最核心的概念之一。
0.7.1 Web 的事件循环 vs 游戏循环
你一定熟悉 JavaScript 的 Event Loop:
JavaScript Event Loop:
┌──────────────────────────┐
│ Call Stack │
├──────────────────────────┤
│ Microtask Queue │ ← Promise.resolve()
├──────────────────────────┤
│ Macrotask Queue │ ← setTimeout, setInterval
├──────────────────────────┤
│ requestAnimationFrame │ ← 动画回调
└──────────────────────────┘
游戏循环与 requestAnimationFrame 最为接近,但更加系统化:
Unity Game Loop(简化版):
┌─────────────────────────────────────────┐
│ │
│ ┌─────────┐ ┌──────────┐ │
│ │ Physics │ │ 物理计算 │ │
│ │FixedUpdate│→ │碰撞检测 │ │
│ └────┬─────┘ └──────────┘ │
│ ↓ │
│ ┌─────────┐ ┌──────────┐ │
│ │ Input │ │ 处理输入 │ │
│ │ Update() │→ │游戏逻辑 │ │
│ └────┬─────┘ └──────────┘ │
│ ↓ │
│ ┌─────────┐ ┌──────────┐ │
│ │ Late │ │相机跟随 │ │
│ │ Update()│→ │最终调整 │ │
│ └────┬─────┘ └──────────┘ │
│ ↓ │
│ ┌─────────┐ ┌──────────┐ │
│ │ Render │ │ 渲染画面 │ │
│ │ │→ │ 绘制 UI │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ └──────── 下一帧 ────────→ 循环 │
│ │
└─────────────────────────────────────────┘
0.7.2 一帧内发生了什么?
假设游戏以 60 FPS 运行,每一帧大约 16.67 毫秒。在这 16.67ms 内:
// 阶段 1:物理更新(可能执行多次)
void FixedUpdate()
{
// 固定时间步长(默认 0.02 秒 = 50 次/秒)
// 处理物理相关的逻辑:移动刚体、施加力
rigidbody.AddForce(Vector3.forward * 10f);
}
// 阶段 2:主更新(每帧一次)
void Update()
{
// 处理输入
if (Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
// 游戏逻辑
UpdateTimer();
CheckWinCondition();
// 非物理的移动
// Time.deltaTime 是上一帧到这一帧的时间差
// 这确保了无论帧率高低,移动速度一致
transform.position += direction * speed * Time.deltaTime;
}
// 阶段 3:延迟更新(每帧一次,在 Update 之后)
void LateUpdate()
{
// 通常用于相机跟随
// 确保角色已经移动完毕后,再更新相机位置
camera.position = player.position + offset;
}
// 阶段 4:渲染(引擎自动处理)
// 你不需要手动调用渲染,Unity 会自动把场景画到屏幕上
0.7.3 Time.deltaTime —— 帧率无关的运动
这是游戏开发中最重要的概念之一:
// 错误做法:速度取决于帧率
void Update()
{
// 60 FPS 时移动 60 单位/秒
// 30 FPS 时移动 30 单位/秒
// 这是不对的!
transform.position += Vector3.forward * 1f;
}
// 正确做法:使用 Time.deltaTime
void Update()
{
// 无论帧率多少,都是 1 单位/秒
// Time.deltaTime 在 60 FPS 时约为 0.0167
// Time.deltaTime 在 30 FPS 时约为 0.0333
transform.position += Vector3.forward * 1f * Time.deltaTime;
}
在 Web 中,requestAnimationFrame 也有类似概念:
// Web 中的等价写法
let lastTime = 0;
function animate(currentTime: number) {
const deltaTime = (currentTime - lastTime) / 1000; // 转为秒
lastTime = currentTime;
// 帧率无关的移动
position.x += speed * deltaTime;
requestAnimationFrame(animate);
}
0.8 ECS vs Component 模式
0.8.1 Unity 的 Component 模式
Unity 传统上使用 Component 模式(也叫 MonoBehaviour 模式),这和 React 的组件模式非常相似:
React 组件组合:
<Player>
<HealthBar />
<Inventory />
<MovementController />
</Player>
Unity 组件组合:
Player (GameObject)
├── Transform (位置、旋转、缩放)
├── MeshRenderer (3D 模型渲染)
├── Rigidbody (物理模拟)
├── CapsuleCollider (碰撞体)
├── PlayerMovement (自定义:移动逻辑)
├── PlayerHealth (自定义:生命值)
└── PlayerInventory (自定义:背包)
核心思想是一样的:组合优于继承(Composition over Inheritance)。
// Unity Component(类似 React Component)
public class PlayerMovement : MonoBehaviour
{
// 类似 React 的 props(在 Inspector 中设置)
public float moveSpeed = 5f;
public float jumpForce = 10f;
// 类似 React 的 state(内部状态)
private bool isGrounded;
private Rigidbody rb;
// 类似 useEffect 的初始化
void Start()
{
rb = GetComponent<Rigidbody>(); // 获取同一 GameObject 上的其他组件
}
// 每帧更新
void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(h, 0, v) * moveSpeed * Time.deltaTime;
transform.Translate(movement);
}
}
0.8.2 ECS(Entity Component System)
Unity 还提供了一个更新的架构:DOTS (Data-Oriented Technology Stack),它使用纯 ECS 模式:
传统 Component 模式(OOP,面向对象):
GameObject = Entity + 一堆 Component 脚本
ECS 模式(DOD,面向数据):
Entity = 只是一个 ID(无数据、无行为)
Component = 纯数据(无行为)
System = 纯行为(无数据)
// === ECS 模式示例(DOTS)===
// Component:纯数据,没有任何方法
public struct MoveSpeed : IComponentData
{
public float Value;
}
public struct Position : IComponentData
{
public float3 Value;
}
// System:纯行为,处理所有拥有特定 Component 的 Entity
public partial struct MovementSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
// 遍历所有拥有 Position 和 MoveSpeed 的 Entity
foreach (var (position, speed) in
SystemAPI.Query<RefRW<Position>, RefRO<MoveSpeed>>())
{
position.ValueRW.Value += new float3(0, 0, 1) * speed.ValueRO.Value * deltaTime;
}
}
}
0.8.3 我们用哪个?
| 传统 Component | ECS (DOTS) | |
|---|---|---|
| 学习难度 | 较低 | 较高 |
| 性能 | 够用 | 极高(数万实体) |
| 生态成熟度 | 非常成熟 | 仍在发展中 |
| 适合场景 | 大多数游戏 | 需要大量实体的游戏 |
| 本教程 | 使用这个 | 高级章节会介绍 |
我们将使用传统 Component 模式,因为:
- 概念更接近你熟悉的 React Component
- 学习资源更丰富
- 对于我们的开放世界原型来说性能足够
- 更容易上手
0.9 C# vs TypeScript 快速对比
在正式开始之前,让我们快速对比一下两种语言:
// TypeScript
interface Player {
name: string;
hp: number;
}
class GameManager {
private players: Player[] = [];
addPlayer(player: Player): void {
this.players.push(player);
}
getPlayer(index: number): Player | undefined {
return this.players[index];
}
}
// 箭头函数
const greet = (name: string): string => `Hello, ${name}`;
// async/await
async function loadData(): Promise<Data> {
const response = await fetch('/api/data');
return response.json();
}
// C# —— 对比 TypeScript,你会发现很多相似之处
// interface(和 TS 几乎一样!)
public interface IPlayer
{
string Name { get; set; }
int HP { get; set; }
}
public class GameManager
{
// List<T> 类似 Array<T>
private List<IPlayer> players = new List<IPlayer>();
// void 方法
public void AddPlayer(IPlayer player)
{
players.Add(player);
}
// 可空返回类型(类似 T | undefined)
public IPlayer? GetPlayer(int index)
{
if (index >= 0 && index < players.Count)
return players[index];
return null;
}
}
// Lambda 表达式(类似箭头函数)
Func<string, string> greet = (name) => $"Hello, {name}";
// async/await(几乎一样!)
async Task<Data> LoadData()
{
var response = await httpClient.GetAsync("/api/data");
return await response.Content.ReadAsAsync<Data>();
}
关键差异速查:
| TypeScript | C# | 说明 |
|---|---|---|
const / let | var / 具体类型 | C# 的 var 是类型推断 |
string | string | 完全一样 |
number | int / float / double | C# 区分整数和浮点数 |
boolean | bool | 略有不同 |
null / undefined | null | C# 只有 null |
interface | interface | C# 的 interface 更严格 |
class | class | 非常相似 |
=> | => | Lambda 语法一样 |
async/await | async/await | 几乎一样 |
: 类型标注 | 类型在前面 | int x vs x: number |
export | public | 访问控制不同 |
0.10 教程系列路线图
以下是本教程系列的完整章节规划:
第一部分:基础入门(你现在在这里)
├── 第 00 章:游戏开发全景概览(本章)
├── 第 01 章:Mac 环境搭建
├── 第 02 章:Unity 编辑器深度导览
├── 第 03 章:GameObject 与 Component 系统
├── 第 04 章:C# 编程基础(面向 TS 开发者)
└── 第 05 章:第一个交互场景
第二部分:3D 世界构建
├── 第 06 章:3D 数学基础(向量、矩阵、四元数)
├── 第 07 章:地形系统
├── 第 08 章:材质与光照
├── 第 09 章:天空盒与环境
└── 第 10 章:昼夜循环系统
第三部分:角色系统
├── 第 11 章:角色控制器
├── 第 12 章:第三人称相机
├── 第 13 章:动画系统
├── 第 14 章:角色状态机
└── 第 15 章:移动端虚拟摇杆
第四部分:游戏系统
├── 第 16 章:物品与背包系统
├── 第 17 章:NPC 与对话系统
├── 第 18 章:任务系统
├── 第 19 章:简单战斗系统
└── 第 20 章:UI 系统(UGUI)
第五部分:高级主题
├── 第 21 章:性能优化
├── 第 22 章:存档系统
├── 第 23 章:音效与音乐
├── 第 24 章:打包与发布
└── 第 25 章:上架 App Store / Google Play
0.11 本章练习
练习 1:概念映射
拿出一张纸(或打开一个文档),把你在 Web 开发中熟悉的以下概念,尝试映射到游戏开发中的对应概念:
npm installpackage.json- Chrome DevTools 的 Elements 面板
- React 的
useEffect(() => {}, []) - CSS 的
position: absolute document.querySelector()addEventListener('click', handler)window.requestAnimationFrame()
练习 2:Game Loop 思考
思考以下问题:
- 如果一个游戏运行在 30 FPS,
Update()每秒被调用多少次? - 如果
FixedUpdate()的固定时间步长是 0.02 秒,FixedUpdate()每秒被调用多少次? - 为什么相机跟随逻辑要放在
LateUpdate()而不是Update()中? - 如果不使用
Time.deltaTime,在不同设备上运行游戏会有什么问题?
练习 3:C# 语法热身
如果你有 TypeScript 经验,尝试把以下 TS 代码”翻译”成 C#(不需要运行,写在纸上即可):
interface IEnemy {
name: string;
hp: number;
attack(): void;
}
class Goblin implements IEnemy {
name: string;
hp: number;
constructor(name: string, hp: number) {
this.name = name;
this.hp = hp;
}
attack(): void {
console.log(`${this.name} attacks!`);
}
}
const enemies: IEnemy[] = [];
enemies.push(new Goblin("Small Goblin", 30));
0.12 下一章预告
在下一章 《第 01 章:Mac 环境搭建》 中,我们将:
- 安装 Unity Hub 和 Unity Editor 2022 LTS
- 配置 VS Code 作为 C# 开发环境
- 创建第一个 Unity 项目(使用 URP 模板)
- 设置 Git LFS 进行版本控制
- 了解 Unity 项目的文件夹结构
准备好你的 Mac,我们要开始动手了!
本章小结
在本章中,我们建立了游戏开发的宏观认知,理解了 Unity 在游戏引擎生态中的位置,明确了游戏开发与 Web 开发的核心差异。最重要的是,我们发现了许多可以从 Web 开发迁移过来的概念 —— 你并不是从零开始。
带着这些认知,让我们进入下一章,动手搭建开发环境。