第四章

渲染与动画:让英雄“活”起来

第三层 · 精灵/帧动画/状态机 英雄是怎么“走”起来的

一、2D 游戏渲染的本质:画图,每秒 60 张

"小陈,2D 游戏渲染的本质是什么?"老周问。

"把图片画到屏幕上?"

"对!2D 游戏 = 每秒画 60 次图——每次把场景里的所有'精灵(Sprite)'画到屏幕上。'动画'说白了,就是'快速切换图片',像翻页动画书一样。"

2D 渲染基础:
  精灵(Sprite)= 一张图片(英雄立绘、小兵、地面、特效)
  精灵渲染器(SpriteRenderer)= 负责把精灵画到屏幕上

  渲染过程(每帧):
    1. 引擎收集场景里所有要画的精灵
    2. 按"图层顺序"排序(地面最底,英雄中间,UI 最上)
    3. 一次性交给 GPU 绘制
    4. 屏幕显示画面 → 下一帧再来

  图层(Sorting Layer):
    地面层(最底)→ 单位层(英雄/小兵)→ 特效层 → UI 层(最上)
    → 英雄要站在地面上,特效要盖在英雄上,血条要在最上

"渲染顺序决定'谁盖住谁'——2D 游戏里的遮挡关系,全靠图层排序控制。"


二、帧动画:动画书原理

"英雄'走'起来,靠的是帧动画(Frame Animation)。"老周演示:

帧动画(Flipbook)原理:
  一组连续图片,快速切换 = 动画

  英雄走路动画(4 帧):
    [走1] → [走2] → [走3] → [走4] → [走1] → ...
    每秒切换 8-12 帧,看起来就是"在走"

  动画资源组织:
    一张大图(图集/雪碧图)切出多帧
    ┌────┬────┬────┬────┐
    │走1 │走2 │走3 │走4 │  ← Sprite Sheet 精灵图集
    ├────┼────┼────┼────┤
    │攻1 │攻2 │攻3 │攻4 │
    └────┴────┴────┴────┘
    动画组件按顺序播放这些帧
# 帧动画的伪代码(理解原理)
class Animation:
    def __init__(self, frames, fps=10):
        self.frames = frames      # 图片帧列表
        self.fps = fps            # 每秒播放帧数
        self.current = 0
        self.timer = 0

    def update(self, delta_time):
        self.timer += delta_time
        frame_duration = 1.0 / self.fps   # 每帧停留时间
        if self.timer >= frame_duration:
            self.timer -= frame_duration
            self.current = (self.current + 1) % len(self.frames)
            # 切换到下一帧(循环)

"帧动画 = 定时切换图片——游戏里 90% 的 2D 动画都是这么做的。每秒 10-12 帧就够'流畅',24 帧以上是'丝滑'。"


三、动画状态机:让动画"智能切换"

"但光有动画还不够——英雄要会根据情况切换动画:站着是待机、走路是移动、按技能是施法、挨打是受伤。"老周说,"这就要动画状态机(Animation State Machine)。"

动画状态机(Animator):
  ┌────────┐  按移动键   ┌────────┐
  │ 待机 Idle│ ────────▶ │ 移动 Walk │
  └────────┘            └────────┘
      ▲  停下                │ 按攻击
      │                      ▼
  ┌────────┐  技能结束   ┌────────┐
  │ 施法 Cast│ ◀──────── │ 攻击 Attack│
  └────────┘            └────────┘

  每个状态 = 一段动画
  状态之间靠"条件"跳转:
    移动输入 > 0.1  → 待机→移动
    攻击按钮按下    → 移动→攻击
    动画播完        → 攻击→待机

  好处:
    动画切换有规则、不乱跳
    (不会出现"移动中突然切到施法"这种穿帮)
# 状态机伪代码
if hero.is_moving:
    play_animation("walk")
elif hero.is_attacking:
    play_animation("attack")
else:
    play_animation("idle")
# 真实引擎用图形化的 Animator 状态机(节点+连线+条件)

"动画状态机 = 角色的'表情管理'——它决定角色在什么情况下做什么动作。MOBA 里每个英雄都有:待机/移动/普攻/技能1/技能2/大招/死亡/回城……一整套状态机。"


四、图集与渲染性能:为什么不能一张图一个文件

"小陈,假如一个场景有 200 个精灵,每个精灵一张图——引擎每帧要切换 200 次纹理,手机直接卡死。"老周说,"所以要用图集(Sprite Atlas / Texture Atlas)。"

图集(Sprite Atlas):
  把很多小图拼成一张大图
  ┌────────────────────┐
  │ 英雄1 英雄2 小兵 野怪 │
  │  技能图标  血条 特效 │  ← 一张大图(1024x1024)
  │  背景砖块 树木 地面  │
  └────────────────────┘

  为什么能提性能?
    渲染时切换纹理很贵(GPU 要重新加载)
    同一张图集里的精灵 → 一次纹理切换,一起画
    → Draw Call 大量减少(第十一章性能优化细讲)

  工具:Unity 的 Sprite Atlas、TexturePacker

  规则:同屏对象尽量放同一个图集
    (英雄 + 小兵 + 特效一个图集,UI 一个图集)

"图集 = 拼车——把要去同一个方向的乘客凑一辆车,省油(性能)。这是 2D 游戏优化第一课,第十一章还会深入。"


五、渲染管线速览:从精灵到屏幕

"最后看一眼'渲染'到底怎么发生的。"老周画:

2D 渲染管线(简化):
  场景中的精灵(位置/图片/颜色/透明度)
        │
        ▼
  排序(按图层 + 按 y 坐标:下面的一起)
        │
        ▼
  提交绘制命令(告诉 GPU:画什么、在哪、什么颜色)
        │
        ▼
  GPU 光栅化(把矢量/图片变成屏幕像素)
        │
        ▼
  显示到屏幕(垂直同步 VSync 控制帧率)
        │
        ▼
  下一帧……

  Y 排序(MOBA 特别重要):
    2D 俯视角里,站在"下方"的角色要盖住"上方"的角色
    → 按世界坐标 y 值排序(y 越小越先画 = 被盖住)
    → 英雄走到小兵下方 → 小兵被英雄盖住(符合视觉直觉)

"Y 轴排序是 2D 俯视角游戏的灵魂细节——王者荣耀里英雄走到草丛、塔下、小兵后面,遮挡关系全对,靠的就是这个。"


六、章末:老周的第三章总结

第三层:渲染与动画
├── 2D 渲染 = 每秒画 60 次图(精灵 Sprite)
├── 图层排序:地面→单位→特效→UI(谁盖谁)
├── 帧动画:快速切换图片 = 动画(动画书原理)
├── 动画状态机:待机/移动/攻击/施法按条件切换
├── 图集:小图拼大图(减少纹理切换,性能关键)
├── 渲染管线:精灵→排序→提交→GPU→屏幕
└── Y 轴排序:俯视角遮挡关系的灵魂
下一章预告:
  英雄会走了、会打了——但"为什么会走"?
  谁来控制他?怎么让他听玩家的指挥?
  —— 游戏逻辑与脚本:给英雄装上"大脑"。

"小陈,现在屏幕上的英雄会走了,会打拳了。"老周笑着说,"但你还是觉得差点什么——对,他'不听使唤'!你怎么让他跟着你的手指走?怎么按一下技能他就放技能?下一章,我们给英雄装上'大脑':游戏逻辑与脚本——从这一章开始,你要真正'编程'了。"

✌ 语言