常用插件的用法与坑
5.1 编辑器扩展:怎么装、怎么跑、怎么调试
Creator 3.8 的编辑器扩展是一个文件夹,里面必须有 package.json 和一个入口文件。Skyline Courier 用到的批量资源整理扩展就是这种结构。两种装法:
从商店装的,在 Creator 里走「扩展 → 扩展管理器 → 项目」,找到插件点安装。手动装的,把插件整个文件夹复制到工程根目录下的 extensions/ 里(跟 assets 同级),然后在扩展管理器里刷新、启用。扩展管理器里有「全局」和「项目」两个位置,全局装在编辑器目录,所有工程都能用;项目装就在工程的 extensions 目录下,跟着 git 走。
调试的时候有个坑我踩过好几次:扩展的 package.json 里 main 字段指向的是编译后的文件,不是你写的源码。如果扩展是 TypeScript 工程,你改了 src 里的代码,Creator 不会自动重新编译,插件行为还是旧的。得在扩展目录里跑一遍 npm run build,然后重启 Creator 或者在扩展管理器里刷新。看到自己的调试输出没出来,第一反应就应该是「我忘了编译」。
再一个坑是面板和消息要配对。package.json 里 contributions.menu 定义了菜单项,指向一个 message 名;messages 字段里再把这个 message 绑到具体的 methods。少配一边,菜单点了没反应,控制台也不一定报错。检查的时候把 package.json 的这两段对着看,比在代码里瞎找快。
面板里的报错怎么定位?打开 Creator 的开发者 → 控制台看日志。扩展的输出如果不在控制台里,可以在代码里用 Editor.Message.request 或者直接 console.log,配合 Creator 开发者工具的调试窗口来看。面板进程和主进程是分开的,跨进程通信要靠消息,不要指望在一个进程里直接调另一个进程的函数。
5.2 UI 组件类:挂载与自适应
商店里的 UI 组件多数是一套预制体加上一组脚本。挂载方式一般是两种:把整个界面做成预制体,运行时 instantiate 出来;或者把脚本挂在某个已有节点上,它在 onLoad 里自己创建子节点。
我更推荐第一种。原因是生命周期清晰:预制体里有什么、什么时候释放、引用关系,全在编辑器里看得见。第二种写法在项目变大后很难排查,因为节点是运行时装出来的,出问题时你不知道层级里到底挂了什么。
自适应这块是 UI 组件最容易出问题的地方。Cocos 的 Widget 组件负责把节点锚定到父节点的不同边或角。常见的错误是只对一边用了 Widget,比如只锚右边,屏幕变宽时节点不动,文字就贴边了。稳妥做法是四条边全设,右下角这类需要固定位置的再加一条对齐。
分辨率适配看的是 UI 渲染模式,Canvas 组件上有 Fit Width 和 Fit Height 两个勾选项。横屏游戏通常只勾 Fit Width,竖屏只勾 Fit Height,两个都勾的话在极端比例屏幕上元素会被拉得很扁。这个取舍要在项目初期定死,中途改会连带改一堆布局。
尺寸再小一档的问题出在切图上。商店的 UI 包给的是 9 宫格图,如果你在 Sprite 组件上没勾 Trim,Sliced 模式会拉伸中间部分而不是九宫格拉伸,看起来就变形了。我现在的习惯是:任何从商店拿的按钮、面板底图,进工程第一件事就是检查是不是 Sliced、Trim 勾没勾。
5.3 动画与特效类:Spine、序列帧、粒子
三类资源在 3.8 里的接入方式差别不小。
Spine 走的是编辑器内置的 spine 组件。拖入 .json 或 .skel 加上图集,Creator 会自动导入成 Spine 资源。坑在于版本:Spine 官方编辑器的运行时和 3.8 内置的运行时版本必须对上,导出的 json 里带着版本号,对不上会直接不渲染或者骨骼错位。另外导出时勾不勾二进制、贴图怎么打包,都在 Spine 导出设置里定,导出后不要再用别的工具改图集,否则可能丢 .atlas 里的 rotate 和边界信息。
序列帧在 3.8 里最省心的方式是直接用 SpriteFrame 组件加 AnimationClip,或者做成图集让动画资源共用一张大图。3.8 的图集工具比 2.x 强不少,按项目分组自动合图很实用。坑是图集里的图片被 trim 之后,动画剪辑记录的坐标可能对不上,表现为动画播放时位置跳一下。这个问题要在图集生成后检查一遍。
粒子在 3.8 里分成 3D 粒子和 2D 粒子两套,不是同一个东西。2D 粒子对应 ParticleSystem2D 组件,用 2D 渲染管线,贴图是 spriteFrame;3D 粒子走普通的粒子渲染器,贴图是 texture。商店里那种「Unity 粒子特效转 Cocos Creator」的插件,转出来的是 3D 粒子资源;如果你的项目是纯 2D 俯视角,2D 粒子得自己用编辑器的粒子面板调。我做过一次转换,出来的效果在 3D 空间里很好看,落到 2D 俯视视角就完全不对味,只能手搓 2D 粒子。
特效资源还有一个通用坑:很多特效作者在 Unity 里用的是加法混合的材质,转到 Cocos 后混合模式没对上,表现为发光变成了一块不透明的亮斑。Cocos 2D 里有 2D 渲染的混合模式设置,效果不对先查这里。
5.4 广告与统计类:初始化时机与环境切换
变现 SDK 的插件大多提供 createRewardedVideoAd、createInterstitialAd 这类接口。初始化时机是最大的坑:SDK 的 init 放在 onLoad 里,如果这时候玩家已经进了主界面并触发了首屏逻辑,SDK 还没 ready,调用 show 就会直接失败或者报未初始化。
商店里广告和统计类商品不少,Google AdMob、Legend 微信小游戏 SDK、ThinkingAnalytics-Cocos SDK 都是现成的。但登录、支付、推送这几类,商店里没有独立命名的插件,它们在 Cocos SDKHub(Account&IAP、IAP、Advertising、Push 四类)里,华为侧走 HMS Core 插件。做账号体系先去 SDKHub 找,别在商店分类里瞎翻。
我的做法是把 SDK 初始化放在一个显式的启动流程里:先出隐私授权弹窗,用户同意之后才初始化 SDK 和统计,初始化完成再进主界面。这样也顺便满足了合规要求,微信小游戏的隐私授权接口必须在获取用户信息之前调用,广告 SDK 里的设备信息采集在用户同意之前触发,审核时是要出问题的。
测试环境与生产环境的切换,看插件用的是哪种机制。有些插件读构建时的宏或环境变量,有些读配置文件里的开关,还有些读你在 Cocos Dashboard 里配置的 appid。判断方法很直接:在测试包里打一条日志把当前环境和 appid 打出来,看它是不是你以为的那个。我见过一个统计插件在编辑器预览时用的是测试 appid,但导出后自动换成正式 appid,结果测试期数据全进了正式看板,污染了线上数据还没人发现。
统计插件的第二个坑是事件上报的时机和丢包。玩家在结算界面直接退出小游戏,上报请求还没发出去就断了。我在结算页加了延迟上报加上重试队列,牺牲一点点退出速度,换取不丢数据。
5.5 常用插件的接法、坑点与排查
把上面几类整理成一张表,是我给新同事上手时发的那份。
| 插件类型 | 典型用途 | 接入坑点 | 排查方式 |
|---|---|---|---|
| 编辑器扩展 | 批量改资源、加面板、自动化 | 改源码忘编译;菜单与消息没配对 | 控制台看日志;扩展管理器里刷新;确认 package.json 的 main 指向 |
| UI 组件 | 弹窗、面板、按钮、Toast | Widget 只锚一边;Sliced 没勾 Trim | 预览里拖窗口试极端比例;检查 Sprite 的 Trim 与渲染模式 |
| Spine | 角色骨骼动画 | 运行时版本对不上;图集信息被工具改坏 | 看导出 json 的版本字段;重新导出对齐版本再试 |
| 序列帧 | 特效帧动画、小图标 | 图集 trim 后坐标错位 | 关掉自动图集用散图验证;核对 AnimationClip 的帧数据 |
| 2D 粒子 | 火焰、烟、风吹尘 | 与 3D 粒子混用;混合模式不对 | 确认节点上是 ParticleSystem2D;检查 2D 渲染混合模式 |
| 广告 SDK | 激励视频、插屏、Banner | init 太晚调用会失败;隐私同意前初始化违规 | 把 init 挪到授权之后;日志打印 ready 状态;打点统计 show 与 reward 的差值 |
| 统计 SDK | 埋点、留存、漏斗 | 测试与正式 appid 混用;退出时上报丢失 | 启动时打印当前 appid 与环境;结算页加重试队列 |
| 热更新 | 版本更新、资源下发 | manifest 未更新;版本号没改导致玩家卡在旧版 | 先拉 version.manifest 比对;确认版本号每次发布都递增 |
| 云存档 | 玩家进度、设置项 | 弱网下写入丢失;跨设备数据冲突 | 写入加本地备份;冲突时以时间戳较新者为准 |
| 登录 SDK | 账号、排行榜 | 游客账号与正式账号数据没打通 | 明确区分游客态与登录态,两套存档分开 |
热更新这块我想多说两句,因为它是这些插件里最容易出线上事故的。3.8 构建产物里会生成 project.manifest 和 version.manifest 两个文件:前者是完整资源清单,每个文件带 md5;后者只有版本号。启动时先拉体积很小的 version.manifest 做比对,版本没变就直接跳过下载,变了再去拉完整的 project.manifest。这层设计是为了省流量。
版本号策略上,字符串比较有个经典陷阱:「1.0.10」按字符串比会小于「1.0.9」,因为字符 '1' 小于 '9'。要么统一用固定三段并自己写数值比较函数,要么在版本号后面固定加一位,比如 1.0.10 写成 1.0.10.0。
灰度包的做法是服务端返回不同的 manifest 地址:先给一小部分用户返回新版本地址,其余还是老版本地址,观察数据没问题再全量。回滚比灰度更重要,回滚的代价小得多,所以灰度策略一定要配套回滚方案一起设计,不然灰度出问题你只能干看着。
版本号的边界还有一条容易忽略:改的是原生层代码(Java、Objective-C、JavaScript 层的部分)时,热更新覆盖不了,必须发应用商店版本。所以我在做热更规划时,会先列一张「本次改动是否涉及原生」的清单,而不是笼统地按「资源更新」处理。
