换装系统很快会超过“换图”
小游戏早期的皮肤系统通常很简单:玩家选择红色角色,就把 hero_blue 换成 hero_red。如果每个皮肤都只有一整套图,这样能跑。但只要加入帽子、武器、翅膀、表情、染色、稀有特效和商店预览,单张图方案就会失控。每个组合都导出一套图,资源数量会爆炸;动画稍微改一帧,所有皮肤都要重导。
我处理过一个 Phaser 跑酷项目,角色有衣服、头饰和滑板三类装扮。第一版把每个组合都当成独立皮肤,后来活动一口气加 12 个头饰和 8 个滑板,组合数量直接失控。更麻烦的是滑板动画和角色跳跃动画不同步,玩家在商店预览里看到还好,进游戏后脚底漂移。
可维护的 Avatar 系统要分清基础骨架、可替换部件、动画帧命名、资源加载和存档表达。Phaser 没有内置复杂换装框架,但用 Container、Sprite、统一动画 key 和资源清单,完全可以做出稳定的分层 Avatar。
flowchart TD
A[玩家选择装扮] --> B[Avatar 配置]
B --> C[资源清单检查]
C --> D{资源是否已加载}
D -->|否| E[加载部件 atlas]
D -->|是| F[创建/更新分层 Sprite]
E --> F
F --> G[同步动画状态]
G --> H[商店预览/战斗角色/存档]
先定义 Avatar 槽位
换装系统的第一步不是资源加载,而是定义槽位。比如 body、hair、face、weapon、back、mount、effect。每个槽位有允许的部件类型、默认部件、显示层级、是否参与动画、是否能染色。槽位定义稳定以后,皮肤配置才有边界。
槽位不要过多。每多一个槽位,资源制作、动画同步、遮挡和性能都会增加。小型 H5 游戏常见的三到五个槽位已经足够。把“眼镜”和“头饰”是否拆开,要看玩法和商业需求,而不是看技术能不能支持。
每个部件都要声明兼容性。某些头饰只支持默认体型,某些武器只支持战斗角色,某些翅膀不显示在商店小头像里。兼容性写在配置里,UI 才能给出清楚提示,而不是运行时缺图。
动画 key 要统一
Avatar 分层最大的挑战是动画同步。身体、衣服、头发、武器都要同时播放 idle、run、jump、attack。如果每个部件有不同动画 key 和帧数,角色就会错位。最稳的方式是统一动画命名和帧序列:所有参与动画的部件都提供同样的动作名和帧数。
例如 body 有 run_0 到 run_7,衣服也必须有 run_0 到 run_7。播放时由 AvatarController 统一设置 frame,而不是每个 Sprite 自己跑独立动画。这样能保证所有层在同一帧。
如果某个部件没有某个动作,可以回退到默认部件或隐藏。不要静默显示上一帧,否则玩家会看到武器停在空中。资源校验脚本应该检查每个部件是否包含必需动作。
商店预览和实战角色要共用规则
商店预览经常另写一套小头像逻辑,结果和实战角色不一致。玩家在商店看到帽子位置正常,进游戏后跑步时帽子漂移。预览可以有不同尺寸和镜头,但 Avatar 组合和动画同步规则应该共用。
可以做一个 AvatarFactory,输入 avatarConfig 和 displayMode,输出 Phaser Container。displayMode 可以是 shopPreview、battle、profileIcon。不同模式决定缩放、可见槽位和动画,但部件解析和兼容校验一致。
预览还要支持未拥有皮肤。未拥有皮肤可以加载资源并预览,但不能写入当前装备。点击试穿时,状态应是 previewAvatar,不是 currentAvatar。购买或确认后再提交到存档。不要让预览状态污染实际装备。
资源加载要按部件分组
换装资源很容易变大。所有皮肤都进首包,会拖慢加载;按需加载又会在商店滑动时卡顿。可以把默认角色和常用部件放首包,稀有皮肤按商店页或活动分组加载。进入商店时预取当前页和下一页部件。
资源 key 要稳定。部件 id 和 atlas key 可以一致,例如 avatar_hat_bamboo。存档只保存部件 id,不保存资源 URL。资源 URL 由 manifest 映射,方便 CDN hash 更新。旧存档引用下架部件时,可以显示默认部件并提示资源不可用。
对象池复用角色时,要清理旧部件。比如上一名玩家有翅膀,下一名没有,如果不销毁或隐藏 back slot,就会串皮肤。AvatarController 的 applyConfig 必须先对齐槽位,再创建或移除 Sprite。
染色和特效要控制成本
染色可以用 tint、palette shader 或预生成贴图。小项目用 tint 很方便,但 tint 会影响整层,不适合只染衣服某个区域。palette shader 更灵活,但要考虑 WebGL 兼容和性能。预生成贴图最稳定,但资源数量增加。
特效皮肤也要限流。稀有武器拖尾、翅膀粒子、脚底光环都很吸引人,但多人同屏或低端机上会很重。皮肤配置应声明 effectBudget,低端设备可以关闭部分特效,保留主体外观。
不要让皮肤改变玩法判定。角色看起来更高,不代表碰撞盒更高;武器更长,不代表攻击范围更大,除非这是明确的付费或玩法设计。外观和规则边界必须写清楚。
一个 Avatar 配置接口
下面示例展示配置形状。实际项目可以更复杂,但存档最好只保存已装备部件 id。
type AvatarConfig = {
body: string;
hair?: string;
face?: string;
weapon?: string;
back?: string;
palette?: string;
};
function applyAvatar(container: Phaser.GameObjects.Container, config: AvatarConfig, assets: AvatarAssetMap) {
for (const slot of AVATAR_SLOTS) {
updateSlotSprite(container, slot, config[slot.key], assets);
}
}
核心是 AVATAR_SLOTS 决定层级和规则,config 只是玩家选择。这样新增一个帽子不需要改角色类,只需要加配置和资源。
存档和旧版本兼容
皮肤系统很容易遇到旧存档。早期只保存 skinId,后面改成分层 avatarConfig;早期没有 weapon slot,后面新增武器外观。迁移时要把旧 skinId 映射成新槽位组合。映射表必须保留,否则老玩家会丢外观。
如果某个皮肤下架,也不要删除配置。可以标记 hidden 或 unavailable,但旧玩家仍能显示已拥有皮肤。资源确实无法保留时,也要提供替代外观和补偿策略。外观是玩家资产,处理要谨慎。
跨端同步时,服务端可以保存拥有列表和当前装备,客户端负责表现。客户端不能只靠本地存档判断是否拥有付费皮肤。商店预览可以本地,购买和装备确认要走权威逻辑。
上线前检查清单
上线前检查:槽位是否稳定,动画 key 是否完整,商店预览是否共用 AvatarFactory,未拥有预览是否不写存档,旧 skinId 是否能迁移,下架皮肤是否有策略,低端设备是否关闭高成本特效,资源缺失是否有默认外观。
还要测试:切换皮肤后立刻进战斗、对象池复用角色、商店快速滑动、网络失败购买、页面刷新恢复、不同语言下皮肤名长度、多人同屏稀有特效。换装系统既是 UI,也是资源和资产系统。
皮肤制作要有验收工具
换装系统的生产风险很大一部分来自资源。某个帽子少了一帧,某把武器原点偏了 3 像素,某个 atlas 命名错了,代码可能完全正常,但角色看起来就会坏。人工逐个进游戏检查效率很低,最好做一个 Avatar 资源验收 Scene。
验收 Scene 可以按部件列表自动展示 idle、run、jump、attack 等动作,显示每层原点、包围盒和槽位顺序。美术提交资源后,先在这里看一遍。缺帧、错位、遮挡、颜色异常都会比进正式关卡更容易发现。
验收工具还可以导出问题截图。比如 hat_bamboo attack_04 missing 或 weapon_spear origin mismatch。这比在群里发一句“这个皮肤不对”更有效。换装系统越依赖大量资源,越需要让资源错误在进入游戏前暴露。
多角色和 NPC 复用要小心
主角 Avatar 系统稳定后,团队常想把它复用到 NPC、敌人和排行榜头像。复用是好事,但不同场景的需求不同。排行榜头像不需要完整动画,NPC 可能只需要 idle 和 talk,敌人可能有独立受击和死亡动作。不要强迫所有对象加载完整部件。
可以让 AvatarFactory 支持 profile、npc、battle 三种 profile。profile 模式只加载头像所需槽位,npc 模式加载少量动作,battle 模式加载完整动作和特效。这样既复用配置,又控制资源。
多人同屏时,还要考虑同款皮肤重复加载。资源层应该共享 atlas,实例层只创建不同 sprite。不要每个玩家都重新加载同一套图片。Phaser cache 可以复用资源,但你要保证 key 稳定、释放策略不会误删仍在使用的皮肤。
染色和审核边界
如果允许玩家自定义颜色,范围要受控。过亮、过暗、透明度过低的颜色可能影响可读性;某些颜色组合可能和敌我阵营、稀有度、危险提示冲突。染色不是完全自由画板,至少要限制色相、亮度和饱和度范围。
支持昵称头像或自定义图案时,还要考虑审核。纯本地换色风险较低,上传贴图或图案则进入内容安全范畴。H5 游戏最好先从官方部件组合开始,不要过早开放任意图片上传。换装系统的商业价值来自表达,但表达也需要边界。
换装数据要支持运营活动
皮肤系统经常和活动绑定:限时获取、签到领取、礼包购买、赛季奖励、兑换码发放。客户端 UI 需要知道皮肤来源、获取状态、剩余时间和跳转入口。不要只给皮肤一个 locked 状态,否则玩家不知道怎么解锁。
可以把皮肤状态拆成 owned、previewable、locked、expired、claimable。owned 可装备,previewable 可试穿,locked 显示获取路径,expired 不再展示入口但老玩家可继续使用,claimable 表示已满足条件待领取。状态来自服务端或活动配置,Avatar UI 只展示。
活动结束后的策略也要明确。未领取皮肤是否还能补领,试用皮肤是否自动卸下,限时皮肤过期后战斗中如何处理。最稳的做法是每次进入关键场景前校验当前装备可用性,不可用时替换为默认外观并给出提示。不要在战斗中突然让角色缺层。
皮肤性能要按同屏数量评估
单个角色换装性能通常没问题,问题出在排行榜、多人大厅、战斗召唤物和镜像角色。同屏 20 个 Avatar,每个 6 层 sprite,再加特效,很快就会增加 draw call 和更新成本。不是所有场景都需要完整 Avatar。
排行榜和好友列表可以使用预渲染头像或简化头像。战斗远处角色可以隐藏小部件,只保留身体和武器。低端设备可以关闭动态头发和背饰特效。Avatar 系统要能根据场景 profile 降级,而不是每次都创建完整组合。
性能日志也要记录 Avatar 层数和特效数量。玩家反馈大厅卡顿时,工程要知道是不是稀有皮肤特效过多,而不是只看总 FPS。
结语
Phaser 角色换装可以从简单换图开始,但要尽早把槽位、动画、资源和存档边界理清。否则每新增一批装扮,都会让组合数量、资源体积和兼容问题成倍增长。
好的 Avatar 系统让玩家自由搭配,让美术按槽位生产,让工程按配置加载和同步。皮肤预览不是只换一张图,而是一套可维护的角色表现管线。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。