
1. 回望起点从AS3小游戏到职业游戏开发的拐点聊起游戏编程这十年绕不开一个如今很多新入行的朋友已经不太熟悉的名字——ActionScript 3.0。我记得很清楚2013年前后Flash还占据着网页游戏的大半壁江山。当时国内页游市场正火4399、7k7k这类小游戏平台上跑着大量基于Flash开发的作品。我接触游戏编程的方式也很草根——不是大学课程教出来的而是从网上扒源码、拆别人的小游戏、然后自己改着玩开始的。一个MovieClip在舞台上拖来拖去点一下按钮播放一段Tween动画那时候觉得能做游戏这件事本身就足够酷了。真正决定把游戏开发当成职业是在我完整做出来第一个AS3小游戏之后。那是一个竖版躲避类的小东西玩法很简单鼠标控制角色左右移动躲避从上往下掉落的障碍物分数随时间累加。大概花了两周业余时间我用纯AS3加Flash Professional画了几张粗糙的图形写完碰撞检测和计分逻辑然后把它传到了网上。想不到的事情发生了——这款游戏在一个小游戏社区里累计获得了三十多万次游玩。那段经历让我第一次意识到两件事第一一个小体量的游戏可以让这么多人获得快乐创作成就感极其强烈第二游戏编程的入门门槛远比想象中低一个能静下心来写逻辑的人完全可以从零开始做出一款能跑起来的游戏。现在回头看那段时间对我的意义不只是入行更重要的是让我建立了一种以完整作品为目标的学习方式。很多刚入门的朋友找我聊的时候最容易陷入的误区就是我要先把所有基础学完再动手做游戏。这种思路恰恰反了——游戏编程是最典型的做中学领域你做一个完整的小游戏过程中遇到的坑、解决的问题比看三个月教程都有用。AS3在今天看来技术栈已经过时但它作为第一款编程语言给我的馈赠是让我理解了事件驱动、显示列表、帧循环这批概念放在今天依然不过时。2. AS3时代的架构模式与典型坑点如果要把十年经历的细节展开AS3阶段值得好好写一段。这不仅仅是因为情怀而是因为Flash时代的游戏开发其实摸索出了一套非常完整的小游戏架构方法论这套思路至今仍然影响着我在Unity和H5游戏里的开发习惯。2.1 显示列表与事件机制理解游戏对象的生命周期AS3里最核心的概念是DisplayObject和EventDispatcher。所有看得见的东西——图形、文字、视频、位图——都是显示对象它们组织在一棵显示列表树里。这个结构和后来HTML的DOM树、Unity的Transform层级在思想上高度一致父节点管理子节点的生命周期渲染顺序由层级关系决定。新手阶段最容易踩的坑是内存泄漏。我见过不少AS3项目界面切换了十几次之后游戏越来越卡内存占用肉眼可见地往上飙。原因往往是界面销毁时没有移除事件监听。AS3的addEventListener会建立从目标对象到监听对象之间的强引用你removeChild只是把显示对象从列表里摘下去了但如果它身上还挂着一个被全局对象引用的监听函数这个对象就永远不会被垃圾回收。当年通用的几种做法一是在界面关闭时统一调用removeEventListener二是用EventDispatcher的子类重写destroy方法做清理三是用弱引用作为兜底addEventListener(type, handler, false, 0, true)的最后一个参数设为true表示这是弱引用GC在必要时可以回收掉这条监听链。第三种方式能解决大部分问题但弱引用也有副作用——如果监听函数本身没有别的强引用它可能被提前回收导致事件莫名其妙不触发。所以当时的团队规范是主动移除优先弱引用只能兜底。2.2 帧循环与时间驱动游戏逻辑的地基AS3的Event.ENTER_FRAME是很多Flash游戏的心脏。每一帧进入时触发一次然后更新所有游戏对象的位置、状态、碰撞检测。后来我学习Unity时对应的是MonoBehaviour的Update方法学习Cocos时对应的是scheduleUpdate。概念几乎一模一样。ENTER_FRAME的一个经典坑是帧率不同游戏速度就不同。你的角色每帧移动10像素60帧每秒时速度是600像素/秒30帧每秒时就变成300像素/秒。早期AS3游戏很少考虑这个问题导致同一个游戏在不同性能的电脑上速度相差一倍。正确的做法是用时间驱动记录上一帧到现在的时间差deltaTime位移量全部乘以deltaTime。这套思路在Unity里是官方API直接内置的而在AS3时代完全靠开发者自发处理。我记得后来做一款跑酷游戏的时候为了让不同帧率下游戏表现统一专门封装了一个游戏时钟类统一派发基于真实时间的Tick事件所有游戏逻辑都不直接监听ENTER_FRAME而是监听这个封装后的Tick。这样还带来一个额外的好处游戏暂停、恢复、慢动作特效都只需要控制这一个时钟不需要挨个修改每个游戏对象的状态。2.3 组件化意识的萌芽从巨型类到模块拆分AS3时代很多游戏开发者包括我写代码的风格是非常直给的——一个Hero类里塞满了移动、攻击、动画、音效、技能逻辑动辄两三千行。这么做小游戏没问题一旦项目复杂度上来修改一个功能可能牵连一片代码。我印象最深的一次重构是在一个格斗类小游戏里。基类大概有两千多行每次加一个新角色都要继承它再覆写一堆方法覆写的同时还要小心别破坏其他逻辑。后来实在受不了了我把它拆成了一套基于组件的写法角色类只负责组装组件移动组件处理位移战斗组件处理攻击判定动画组件管理帧序列切换。每个组件通过事件向外通信。这个思路对应到后来的Unity体系其实就是Component模式的原生实现。现在回头看这个阶段积累的一个很重要的经验是组件化不能过度设计。对于一个小体量游戏来说如果角色只有三五种行为强行上组件框架反而增加了间接层改一处逻辑要在多个文件之间来回跳。组件的粒度阈值大概是当某个单一职责的行为在三个以上角色中重复出现的时候把它抽成独立的东西才划算。这个判断力比具体会写多少种设计模式要宝贵得多。3. 技术栈迁移从Flash到引擎化开发的阵痛2015年之后Flash的市场份额开始肉眼可见地萎缩。Adobe官方宣布2020年停止Flash更新但实际上从移动端兴起开始Flash在游戏领域就已经走入了尾声。HTML5技术快速补位Unity也借着移动端游戏的爆发迎来了一波大发展。对我个人而言最紧要的事情是在短时间内从AS3转向新技术栈。3.1 转型期的心理博弈与技术选型当时摆在我面前有几个方向Html5 Canvas写原生JS游戏、Cocos2d-x、Unity3D、或者干脆转做服务端。选择太多反而让人焦虑。我最终选了Unity。原因有几个层面。第一它的C#语言和AS3语法相似度很高学习成本最低事件委托、类继承这些概念几乎是无缝迁移的我当时用了一周时间看完官方基础教程后基本就可以正常写逻辑了。第二Unity的跨平台发布能力在当时已经非常成熟一次开发可以发布到iOS、Android、Web多个平台不需要为每个平台单独写一套代码。第三也是最重要的一点——Unity的组件式架构和我在AS3后期摸索出来的组件化思路高度一致使用它的时候有很强的理念认同感。回头看这个选择技术选型最关键的因素往往不是技术本身的好坏而是你现有经验能不能迁移过去。身边同期转去做纯H5开发的朋友前端技术要重新学一整套阵痛期更长而转Unity的这批人因为有AS3的底子基本都能比较平滑地完成过渡。3.2 转型期最容易犯的认知错误从AS3转到Unity最大的认知冲击不是语法而是工作方式的变化。AS3时代代码几乎等于一切。你的美术资源是外部加载的SWF或者PNG序列帧你的场景是你用代码new出来的对象你的界面是用代码一行行排出来的。整款游戏某种意义上就是一堆代码的运行结果。但Unity里游戏是场景、预设体、资源、组件共同组成的一个复合体。很多逻辑不需要写代码而是通过在Inspector面板里拖拽、勾选、赋值完成的。刚开始我非常不适应这种不写代码的工作方式总觉得不踏实——对象之间的引用关系没有显式写在代码里万一漏配了怎么办万一运行时不匹配怎么办后来我意识到这是一种从命令式编程到数据驱动开发的思维转变。在Unity里创建一款游戏与其说是写程序不如说是在组装一套系统——每个预设体是一份数据描述MonoBehaviour脚本是在这份数据上的行为插件场景文件描述的是这些预设体之间的空间关系和逻辑关联。这个思路后来也渗透回我写H5游戏的方式里即使不使用Unity我也会在代码里把游戏配置数据从逻辑中抽离出来用JSON或脚本对象管理数值和关卡信息。还有一个很容易被忽视的坑资源管线的规范性。AS3时代资源就是一个个外部文件用Loader加载即可命名规范靠自觉。到了Unity时代资源会经过导入、压缩、打图集、生成AB包等一系列流程任何一个环节配置不当都会影响最终的游戏表现。比如图集拼接方式不同会影响DrawCall数量纹理压缩格式不对会导致包体翻倍Prefab引用丢失会让整个界面变成一坨粉色。我到现在还记得一次现场事故游戏发布前发现一个商店界面的按钮全部失效排查了很久才发现是一个公共Prefab的脚本引用丢了而这个问题在编辑器里能正常跑打包出来才出问题。后来学到的教训是所有资源引用尽量在编辑器里通过Inspector显式赋值不要靠代码动态查找路径打包前必须跑一遍资源检查脚本扫描是否存在缺失引用。3.3 跨平台兼容的那些事转型技术栈之后紧接着面对的是跨平台适配这个躲不开的难题。AS3时代基本只考虑桌面浏览器一种环境而移动端时代iOS、Android、不同分辨率、不同刘海屏每一个环节都可能冒问题。实践证明最实用的几个经验分辨率适配方案不要在Update里写死像素坐标而是用基于参考分辨率的布局系统。Unity的Canvas Scaler可以从三种模式中选按屏幕尺寸缩放基本上可以覆盖主流设备。而在写H5游戏时缩放适配要根据设计分辨率等比缩放超出部分裁切或留安全边距。性能预算意识移动设备的性能上限远低于桌面DrawCall、内存占用、运行时GC都需要严格预算。我当时给自己定的标准是一帧内渲染的DrawCall不超过100内存占用不超过200MB每帧的GC分配不超过2KB。超了就减特效、合并图集、缓存对象池。输入差异鼠标点击和触摸在逻辑上可以统一处理但多指操作、滑动判定、长按与右击的分辨在移动端都要重新设计交互方案。很多从PC移植到手机的游戏操作手感很别扭根源就是只做了映射没有做交互重设计。4. 十年沉淀下来的游戏编程核心心法技术会过时工具会迭代但有一些底层的东西在这些年从不曾改变。如果只总结三条最重要的心法我会选择这三条。4.1 游戏循环思维所有玩法都是时间和状态的函数不管是AS3的ENTER_FRAME、Unity的Update、还是自己写的requestAnimationFrame循环游戏的本质始终是这样一个循环感知输入、更新状态、渲染输出然后进入下一帧。想明白这件事对写任何游戏都帮助极大。拿到一个新的玩法需求先问三个问题这个玩法需要感知哪些输入每一帧要更新哪些数据更新后的结果如何呈现出来这三个问题想清楚了代码结构自然就清晰了。反过来如果直接开写很容易把逻辑散落在各种事件回调里状态同步出问题时就非常难排查。我见过太多复杂到不可维护的代码本质上都是因为突破了循环思维。举个典型例子很多新手在点击按钮暂停游戏的时候直接把Update里的逻辑用一个bool flag控制但如果游戏里有多个系统每个系统各自检查flag很容易出现有的系统暂停了、有的系统还在跑的诡异状态。我习惯的做法是设计一个全局的流程状态机——Ready、Playing、Paused、GameOver——各系统读取这个状态来约束自己的行为而不是自己各设各的布尔值。这个习惯从AS3时代一直保留到今天受益无穷。4.2 状态机是游戏逻辑的中流砥柱角色有待机、移动、攻击、受击、死亡五种状态UI有打开、关闭、切换中三种状态关卡有启动、进行中、结算、下一关四种状态——游戏里的一切几乎都可以用状态机描述。状态机的价值在于强制你梳理逻辑流动的边界。每个状态下允许哪些输入、禁止哪些输入状态切换时怎么处理进入和退出的副作用这些都写清楚后大部分逻辑不严谨导致的Bug都能在设计阶段被消灭。攻击状态下不能移动这个限制如果不通过状态机强制实施而是靠着在Update里的if判断去控制当新增一个技能状态时就很容易忘了在技能状态里也做同样的判断。我的建议是在代码里把状态和数据封装在一起用一个State对象持有状态持有者引用提供enter、exit、update三个方法。这套模式在Unity的Animator里其实已经内置了但对于游戏逻辑部分自己维护状态机会更直观、更可控。4.3 数据驱动与配置化让数值调整不再是噩梦游戏开发里有一类非常致命的场景策划跑过来说这个角色攻击力从10调到12移速从3.5降到3.0。如果你的伤害计算逻辑里硬编码了10和3.5这个需求就会变成一次代码修改重新编译重新提审如果你的数值在配置表里那只需要修改一个数字甚至可以让策划自己改。把数值、物品列表、关卡配置、剧情文本通通抽离到数据文件里是游戏开发走向工程的必经之路。AS3时代我用XML和外部加载的JSON存配置Unity时代我用ScriptableObject存预设数据写H5游戏时我用单独的js文件操作配置。核心思路始终一致代码是实现逻辑的数据是描述内容的两者不混在一起。数据驱动带来的另一个好处是热更新能力。游戏发版后如果需要调整数值在上线了配置系统的前提下服务端下推一份新配置就可以了如果数值是硬编码在代码里的那就只能走整包更新流程成本完全不在一个量级。从这个角度看数据驱动不仅是一个工程质量问题还是一个运营效率问题。4.4 调试习惯与场景复现找Bug是技术活游戏开发中相当一部分时间是花在找到那个导致问题的特定条件上。游戏是高度状态耦合的系统同一个问题往往只在特定操作顺序、特定时间节点、特定数据组合下才会触发。多年沉淀下来的调试习惯有几个第一尽早引入日志系统在关键路径上打点。不要觉得打日志是浪费时间当线上出了问题时日志就是唯一能还原现场的手段。游戏脚本里我会封一个Logger区分Debug、Info、Warn、Error四个级别并且支持按模块开关。第二做录像式的问题复现。凡是一个游戏对象的状态异常我会先尝试还原出它的完整生命周期——出生时参数是什么、经过了哪些状态、在哪个时间点被哪个方法改了属性。很多Bug本质上都是某个值的状态和预期不一致找到它最后一次被修改的位置问题就解决了一半。第三善用断言。在开发阶段给关键前提条件加上Debug.Assert比如这个列表里不应为空这个引用不能为null。断言能帮助你在开发期就发现潜在问题而不是等到线上用户反馈才来处理。这么做还有一个附带效果让你对自己写下的每一条假设保持清醒。5. 开发工具的演化与工作流规范化十年间游戏开发的工具链和工作流发生了翻天覆地的变化。从个人开发者一杆子捅到底到团队协作中版本管理、自动化构建、持续集成一个个环节补齐这套工作流的规范化是支撑游戏项目从能做出来走向能稳定地做出来的基础。5.1 版本管理从复制一份备份到分布式GitAS3时代我见过太多小团队用复制文件夹改个名来管理版本的做法。这种方式的痛点在项目规模上来之后会集中爆发——你想回退到三天前的版本发现那天的备份文件已经不知道被覆盖到哪一层了你想看看自己改了什么只能靠脑袋。后来开始用SVN再后来全面迁移到Git。现在做任何项目第一件事都是初始化Git仓库并且严格遵守分支规范main分支永远保持可发布状态开发在feature分支上进行合并前跑一遍完整的测试检查。这套纪律在我从单兵作战转向团队协作之后价值体现得尤为明显——它让所有人都能放心地改代码而不需要担心影响线上版本。5.2 自动化构建与冒烟测试让回归成本降到最低早期开发游戏每次出包都是一个需要专人盯着的体力活。打iOS包要等签名打安卓包要等编译打完还要手动装上手机点一遍主要流程确认能跑。一个人盯一套流程一小时就过去了。后来我给自己和团队搭了一套相对简单的自动化流程提交代码后CI服务器自动拉代码、跑编译、出包、装到测试机上跑一遍冒烟测试脚本——启动游戏、进入主界面、创建一局对战、正常退出。这个过程大概十分钟如果出现问题会直接把失败日志推到工作群里。这套自动化流程的长期价值不在省了打包的人工而在它给了你修改代码的信心。重构一个模块、升级一个引擎版本、改一套渲染方案都可能引入隐蔽的回归问题。有自动化冒烟测试在这些问题最多延迟几分钟就会被发现而不用等发布到线上被玩家吐槽了才意识到出了事故。5.3 调试工具与性能分析像体检一样看游戏性能游戏性能优化的一个矛盾点是如果你不去量你永远不知道瓶颈在哪。很多人做性能优化是靠感觉觉得这里卡、那里慢然后瞎猜一通改代码。这样做偶尔能蒙对方向但多数时候在浪费精力。给我带来最大帮助的习惯是在做任何优化之前先拿到可重复的性能数据基线。Unity里我常用Profiler抓CPU耗时和GC分配用Frame Debugger看DrawCall和渲染状态H5游戏里用Chrome DevTools的Performance面板记录帧耗时用Memory面板看堆内存趋势。拿到数据之后优化目标就非常清晰了——到底是脚本逻辑耗时、渲染压力大、还是GC分配过于频繁每一个都有对应的解决办法。这跟去医院体检是同一个道理没有一个医生会在没做检查的情况下就给你开刀做手术。性能优化的前提是精准定位而定位的前提是量化数据。5.4 代码评审与知识沉淀团队进步的加速器游戏开发早期我习惯一个人闷头写遇到问题自己查自己扛。后来带团队之后开始推行代码评审制度收益远超预期——不只是发现Bug更重要的是让整个团队的水平慢慢趋于一致。评审中最有价值的几个关注点状态管理是否正确——有没有出现状态机覆盖不到的分支资源管理是否有风险——加载了的东西有没有释放性能是否有隐患——有没有在Update里频繁new对象数据流是否清晰——配置数据是否还被散落硬编码。配合代码评审我们还会在每次排查完一个疑难Bug后写一份简短的事故记录包括现象、根因、修复方案、如何预防四部分。这些记录形成了一个团队的踩坑地图新同事接手老项目时看这份文档比自己翻代码高效得多。6. 给新入行者的几点实在建议写到这里上篇也差不多该收尾了。回顾这十年如果想给刚踏入游戏编程这条路的年轻人一点建议我不会说什么坚持就是胜利之类的话而会指几个最实在的方向。第一别被引擎绑架。Unity、Unreal、Godot、Cocos这些都只是工具。你真正需要建立的是游戏循环、状态管理、数据驱动、性能预算这些底层思维。三年换了三次引擎的老开发者照样能快速上手新引擎而一个只会拖拽组件的人换个引擎就等于从头再来。第二把第一个游戏做成用完就扔的小项目。不要一上来就想做开放世界大作那会让学习曲线陡峭到劝退。先做一款十分钟能通关的小游戏走完设计-开发-测试-上线完整闭环积累的信心和实际经验远超一年只看教程。第三养成写开发日志的习惯。每款游戏的关键决策——为什么选这个方案、踩了哪些坑、如何解决的——记录下来的价值会在半年后被成倍放大。等你经验积累到一定程度回头看会发现这些日志里藏着你成长的最真实轨迹。第四多逛社区、多看别人的实现。游戏开发这个领域开放程度很高Unity官方论坛、国内外的技术博客、开源项目里都有大量高质量内容。遇到问题先搜索搜不到再自己啃。带着问题去看别人的源码是最快的学习方式之一。第五保持做小Demo的节奏。无论你现在参与的项目有多复杂定期抽出时间做一个和当前工作无关的小Demo几个周末的量级就够。它可以帮助你跳出日常业务逻辑的局限保持对游戏创作新鲜感的敏感度。第六善待自己的身体和心态。游戏开发是一个长跑型行业真正的瓶颈往往不是技术而是体力和精力。规律作息、持续运动、培养一个跟写代码完全无关的爱好这些听起来跟游戏编程十年总结不搭边但能保证你再过十年还在写代码这比任何技术都要重要。下篇我会接着写这些年经历过的完整项目的复盘——从立项评估、原型验证、美术与程序协作、上线运营到长线迭代那些真正决定一款游戏成败的技术之外的东西。十五年后再回头看自己写下的这些话应该会有种照镜子般的奇妙感受。