Unity

第零章:游戏开发全景概览 —— 从 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 ElementGameObject
React ComponentUnity Component
CSS TransformTransform Component
requestAnimationFrameGame Loop / Update()
NPM PackageUnity Package / Asset
Chrome DevToolsUnity Editor
HTML 页面Scene(场景)
SPA RouterScene Management
State ManagementScriptableObject / 全局状态

看到了吗?你并不是从零开始。你已经拥有了大量可以迁移的知识。


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 的理由:

  1. 移动端王者:Unity 在移动游戏市场的占有率最高
  2. C# 语言:如果你会 TypeScript,学 C# 非常快(后面会详细对比)
  3. 学习资源丰富:全球最大的游戏开发社区之一
  4. Asset Store:海量免费和付费资源,适合独立开发者
  5. 3D + 移动端:Unity 的 URP(Universal Render Pipeline)专为移动端优化
  6. 一次开发,多端发布: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 我们用哪个?

传统 ComponentECS (DOTS)
学习难度较低较高
性能够用极高(数万实体)
生态成熟度非常成熟仍在发展中
适合场景大多数游戏需要大量实体的游戏
本教程使用这个高级章节会介绍

我们将使用传统 Component 模式,因为:

  1. 概念更接近你熟悉的 React Component
  2. 学习资源更丰富
  3. 对于我们的开放世界原型来说性能足够
  4. 更容易上手

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>();
}

关键差异速查:

TypeScriptC#说明
const / letvar / 具体类型C# 的 var 是类型推断
stringstring完全一样
numberint / float / doubleC# 区分整数和浮点数
booleanbool略有不同
null / undefinednullC# 只有 null
interfaceinterfaceC# 的 interface 更严格
classclass非常相似
=>=>Lambda 语法一样
async/awaitasync/await几乎一样
: 类型标注类型在前面int x vs x: number
exportpublic访问控制不同

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 开发中熟悉的以下概念,尝试映射到游戏开发中的对应概念:

  1. npm install
  2. package.json
  3. Chrome DevTools 的 Elements 面板
  4. React 的 useEffect(() => {}, [])
  5. CSS 的 position: absolute
  6. document.querySelector()
  7. addEventListener('click', handler)
  8. window.requestAnimationFrame()

练习 2:Game Loop 思考

思考以下问题:

  1. 如果一个游戏运行在 30 FPS,Update() 每秒被调用多少次?
  2. 如果 FixedUpdate() 的固定时间步长是 0.02 秒,FixedUpdate() 每秒被调用多少次?
  3. 为什么相机跟随逻辑要放在 LateUpdate() 而不是 Update() 中?
  4. 如果不使用 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 开发迁移过来的概念 —— 你并不是从零开始。

带着这些认知,让我们进入下一章,动手搭建开发环境。