05.Unity游戏开发初学者的第一个练手Demo——动画音效与中文适配
动画、音效与中文适配:给游戏注入生命力
Echo Orb 2D 俯视角动作解谜游戏 开发日志第 5 篇
作者:青竹丹枫 | 南京邮电大学 数字媒体技术 | 2026年8月
Tips:这是本系列的第五篇正式技术日志啦。前面的四篇写完了核心机制、敌人 AI、UI 系统和地形设计,现在我们的Echo Orb 还差一口气,这次我们加上了动画和音效,那个白色的圆点开始走动的时候会摆腿、那颗子弹打到墙上会叮的一声。这一篇讲的不是最核心的技术,但它们是让玩家觉得这东西是用心做了的关键。博主这几天天气原因拖更了呜呜。
一、引子:动画和音效为什么容易被放到最后
我相信很多初学者和我一样,在开发早期会把全部精力放在让东西动起来、让敌人会打人、让关卡能切换上。动画和音效永远是排期表上的最后一项,因为直觉上它们不是核心功能,没有它们游戏也能跑。
但事实是,当你把一段走路动画和一个走路音效加上去之后,整个游戏的完成度感觉至少翻了一倍。一个没有动画的游戏,即便逻辑再完整,手感也是飘的。一个没有音效的游戏,即便画面再好看,整个世界也是安静的。这两种东西合在一起,才是玩家潜意识里判断一个游戏是不是业余 Demo 的关键。
所以在 Echo Orb 的开发中,我在每个模块完成后都回头把动画和音效补上。这篇日志就是把这些分散在各处的动画和音效实现整理在一起,再加上中文字体适配这个让很多人卡住一整天的坑。
二、Animator 的基础用法:Bool 和 Trigger
2.1 Animator Controller 是什么
Unity 的动画系统分三层。最底层是 Animation Clip,就是一个动画片段,比如走路 4 帧。中间层是 Animator Controller,它像一个状态机,管各个 Clip 之间怎么切换。最上层是 Animator 组件,挂在 GameObject 上,持有一个 Controller 引用。
创建流程为:Window → Animation → Animation 窗口 → 选中物体 → Create 新 Clip → 帧图拖进时间轴 → Project 里 Create → Animator Controller → 把 Clip 拖进 Controller 的橙色网格 → 给物体 Add Component → Animator → 把 Controller 拖进去。
这个流程做了好几次之后就会变成肌肉记忆,但第一次接触的时候确实很容易找不到窗口在哪。
2.2 Idle 和 Walk 的切换:Bool 参数
Echo Orb 中大多数角色都有两个状态:站着和走着。用 Bool 参数 isMoving 驱动切换:
// 在角色的 FixedUpdate 里
bool moving = h != 0 || v != 0;
if (anim != null)
anim.SetBool("isMoving", moving);
Animator 里的连线规则。Idle → Walk,条件 isMoving = true。Walk → Idle,条件 isMoving = false。Has Exit Time 不勾,Transition Duration = 0。
Has Exit Time 不勾的意思是一旦条件满足立刻切换,不等当前动画播完。走路和站立的切换要求瞬间响应,不能有过渡期。Transition Duration = 0 也一样,不需要交叉淡入淡出。
2.3 一次性事件:Trigger 参数
有些动画只需要播一次,比如死亡。Bool 需要双向管理 true 和 false,多了容易忘。Trigger 播完自动失效。
野猪 Boss 的死亡动画就是用一个 die 的 Trigger 驱动的。在 Animator 里 Any State → 死亡动画,条件就是 die Trigger。代码里只需要一行:
if (anim != null) anim.SetTrigger("die");
Trigger 本身不需要在代码里手动重置,Unity 在动画播放结束后的下一帧自动把 Trigger 消耗掉。
2.4 踩坑:静态 NPC 报了 isMoving 不存在
有些 NPC 只有一个 Walk 动画,没有 Idle/Walk 切换。但 NPC 脚本里写了 anim.SetBool。Animator 里没有 isMoving 这个参数,运行时报黄色警告。
解决办法很简单:不需要参数切换的就不要在代码里调 SetBool。NPC 只有一个 Walk 动画,直接把它设为 Animator 的默认状态就行,不需要任何代码驱动。
这边建议Animator 参数不要盲目给每个角色都加上。角色有几个状态才需要几个参数,只有一个动画的就不要多此一举。
三、左右翻转:flipX 为什么比改 Scale 好
3.1 问题的起点
2D 游戏中角色往左走时需要翻转图像。最常见的做法是改 transform.localScale.x 为负值来镜像翻转。这个做法在角色单独存在时完全没问题。
但 NPC 头上有一个交互图标,它是 NPC 的子物体。父级 Scale 翻成负的了,图标也跟着翻了。结果就是那个小小的 E 提示变成了镜像的。
3.2 用 flipX 代替改 Scale
我们可以试着翻转 SpriteRenderer 的 flipX 属性,而不是翻转整个 Transform。
void FaceDirection(Vector2 dir)
{
if (sr != null && Mathf.Abs(dir.x) > 0.01f)
sr.flipX = dir.x > 0;
}
flipX 只影响渲染出来的图像,Transform 本身纹丝不动。子物体的位置、缩放、旋转全部不受影响。交互图标该怎么显示还怎么显示。
这个经验适用于任何有子物体的角色。野猪用 flipX,黄蜂用 flipX,NPC 用 flipX。从那以后我再也没用改 Scale 的方式来翻面。
四、Animator 的层级结构:多部件角色怎么处理
4.1 当角色有多个可动部件
如果角色需要身体和武器分开做动画,比如主角发射子弹时手臂有个独立动作,那就需要 Animator 的 Layer 功能。给身体一个 Layer,给武器一个 Layer,各自独立播放。
但在 Echo Orb 里,所有角色都只有单层动画。主角的发射动作是子弹的 Trail Renderer 来体现视觉效果的,角色本体不需要额外的手臂动画。这是在美术资源有限的情况下的一种取巧做法。
4.2 动画帧的制作与导入
Echo Orb 的动画帧基本上都是 AI 生成的 spritesheet。导入 Unity 后,如果是一整张 spritesheet,需要先切图:选中图片 → Inspector → Sprite Mode → Multiple → Sprite Editor → Slice → Grid By Cell Size → 填每帧的宽高 → Apply。
切完后在 Project 窗口展开图片,会看到里面切出来的子 Sprite。把这些子 Sprite 全选拖进 Animation 窗口的时间轴就行了。
五、音效系统:两个方法覆盖所有场景
5.1 AudioSource.PlayOneShot:给持久物体用
Unity 播放音效有两种主要方式。第一种是 AudioSource.PlayOneShot,适用于已经有一个 AudioSource 组件的 GameObject,比如玩家。
// PlayerController 里
if (audioSource != null && shootSound != null)
audioSource.PlayOneShot(shootSound);
发射音效和死亡音效都打在玩家自己的 AudioSource 上。好处是不需要额外创建对象,播放完自动结束。
5.2 AudioSource.PlayClipAtPoint:给临时物体用
第二种是 PlayClipAtPoint,适用于没有自己 AudioSource 的物体。Unity 在调用这个方法时自动在那个位置创建一个临时的 AudioSource,播完即销毁。
// 子弹弹射音效
if (bounceSound != null)
AudioSource.PlayClipAtPoint(bounceSound, transform.position);
子弹弹墙、敌人死亡这些事件都用的 PlayClipAtPoint。调用者不需要挂 AudioSource 组件。
注意 PlayClipAtPoint 默认 3D 音效衰减会生效,离摄像机越远声音越小。如果你的游戏是 2D 俯视角且不希望有距离衰减,可以把音量调大或者在 Audio Project Settings 里把 Spatial Blend 拉到 2D。
5.3 走路音效的循环播放
走路音效是一个短循环片段。做法是给玩家挂第二个 AudioSource,专门用于走路。把 clip 设成那个短循环音效、loop = true、playOnAwake = false。移动时 Play,停下时 Stop。
walkAudioSource = gameObject.AddComponent<AudioSource>();
walkAudioSource.clip = walkSound;
walkAudioSource.loop = true;
walkAudioSource.playOnAwake = false;
在 FixedUpdate 里:
if (moving && !walkAudioSource.isPlaying)
walkAudioSource.Play();
else if (!moving && walkAudioSource.isPlaying)
walkAudioSource.Stop();
5.4 音效素材从哪里来
Echo Orb 的音效全部从免费音效网站下载。大家选用素材的时候记得看清每一个音效的使用备注,防止需要授权或需要申明哦。
六、中文字体:TMP 怎么认识汉字
6.1 问题:英文正常,中文全空白
Echo Orb 的对话系统里用了 TextMeshPro。默认字体是 LiberationSans,包含英文字符但不含中文。直接用的话,所有中文字符渲染出来都是空白方块。
我们可以先找一个中文字体文件。Windows 自带的微软雅黑就在 C 盘的 Windows 的 Fonts 目录下,文件名 msyh.ttc。复制一份到 Unity 的 Assets 里。
6.2 Font Asset Creator 的正确参数
接着,把 ttf 或 ttc 字体转成 TMP 的 SDF 格式。打开 Window → TextMeshPro → Font Asset Creator。
Source Font File 选刚导入的微软雅黑。关键一步:Character Set 下拉选 Unicode Range (Hex),在下方出现的输入框里填入 4E00-9FFF,这是中日韩统一表意文字的基本区间,涵盖绝大多数常用汉字。
Atlas Resolution 设 2048 x 2048。如果你需要覆盖更多生僻字或同时覆盖日文假名,就加 3040-309F 和 30A0-30FF。点 Generate Font Atlas,稍等一会儿,最后 Save 到 Assets 里。
七、摄像机与地图的视觉细节
7.1 地图边缘的颜色
在动画和音效都处理好了之后,博主又在摄像机上做了一些改动,以优化视觉效果。
摄像机限制了地图边界,但边界之外如果只有 Unity 默认的蓝色背景,视觉上会显得很突兀。最简单的处理是在 Main Camera 的 Camera 组件里把 Background 颜色改成接近地图主色调的深色。这样地图之外的区域至少不会太扎眼。
7.2 Tilemap 的遮挡顺序
Tilemap 画出来之后,有时候角色会被地图覆盖住看不见。这是渲染顺序的问题。选中 Tilemap 的 Tilemap Renderer 组件,把 Order in Layer 调成比角色小的值。角色 Sprite Renderer 的 Order in Layer 设成更大的数。这一点大家在搜索Tilemap有关教程时应该着重讲到了这一点,大家可以多复习复习。
八、这一篇教会我的
经验 | 为什么重要 |
Bool 管持续状态,Trigger 管一次性事件 | 不要用 Bool 去管死亡动画,Trigger 自动失效更安全 |
flipX 代替改 Scale | 子物体的交互图标不会再跟着翻 |
PlayOneShot 给持久物体,PlayClipAtPoint 给临时事件 | 子弹弹射和敌人死亡不需要额外挂 AudioSource |
走路音效用循环 AudioSource | Play 和 Stop 控制,不用每步都创建新对象 |
TMP 中文要 Unicode Range | 4E00-9FFF 是汉字基本区间,不能用默认的 Extended ASCII |
Animator 参数不要给不需要的角色 | 只有一个 Walk 动画的 NPC 设为默认状态即可,不需要代码驱动 |
Has Exit Time 不勾才能瞬间切换 | 走路和站立的切换需要零延迟 |
再次强调!!!
本人只是一个目前在做一些自认为有意义的学习的小白,此篇只是我个人的一些开发路径和观点。发布的目的是渴望得到一些指点,同时也为想迈出游戏开发这一步的读者们一些小的方向。若有不对或不足之处恳请各位大佬指点迷津!
下一篇预告
打包发布与面试那些事 —— 怎么把 Unity 项目变成别人能玩的 zip 包、itch.io 上传的全流程、GitHub 仓库怎么打理才像一个专业的作品集、面试时怎么用三句话讲清楚你的项目。这是本系列的最后一篇,从项目到作品集,我们把它收个尾。
