# Playable Ads — Agent 工作文档 > 面向 AI Agent 辅助开发的上下文与行动计划。人工协作时同样可用于 Sprint 规划。 --- ## 1. 项目现状快照(2026-05) ### 1.1 技术栈 | 层面 | 技术 | | ----------- | ------------------------------------------------ | | 构建 | Vite 5 + TypeScript 5 | | 渲染 | WebGL 2(无外部 GL 框架) | | 资源加载 | 本地 `assets/res/` 静态文件(fetch JSON/PNG) | | 动效/完成态 | js-confetti、Canvas 粒子爆破(自研 `Explosion`) | | 音效 | Web Audio API via HTMLAudioElement | | 外部依赖 | js-confetti(jszip 已移除,playable 不需要) | ### 1.2 文件树职责 ``` index.html # playable 广告主入口(填色玩法页) share.html # 分享落地页(回放 + Download CTA) src/ base/ # 渲染引擎层 Scene.ts # 场景根节点:图层管理、变换矩阵、手势、动画帧 Gesture.ts # 捏合/拖拽/点击手势抽象 Animator.ts # 值驱动动画(Interpolator 可替换) m4.ts # 4×4 矩阵运算(纯 TS 实现) BgLayer / BoxLayer / BorderLayer / FrameLayer / ImageLayer / TextureLayer ImageShaders.ts # GLSL shader 常量池 glsl/ # 独立 GLSL 文件(Gaussian / Bicubic 等) filler/ # 填色玩法层 index.ts # 广告主入口脚本(init、UI 事件、CSS 回调) play.ts # 分享页脚本(回放逻辑) FillerScene.ts # 继承 Scene,增加 focusToArea / hint FillerData.ts # 状态核心(coloredPercent、当前 group、getArea 命中) WorkLayer.ts # 互动层:tap 填色、progress=1 动画完成后切换 group AnimatableMask.ts# 掩码 FBO:每个 area 以扩散圆动画绘制到纹理 LineArtLayer.ts # 线稿渲染 NumberLayer.ts # 数字标注(atlas sprite) HintLayer.ts # 提示高亮层 Loader.ts # 原 zip 解压模块(playable 中不使用,线上网站保留) FillerData.ts # 数据模型:Area、AreaGroup、Data、FillerResource LoadingController.ts # Loading overlay 显示/隐藏 Audio.ts # 音效播放器 explosion.ts # 粒子爆破动画 utils/ random.ts ``` ### 1.3 运行时数据流 ``` assets/res// → fetch config.json + loadImage(page.png / map.png) ↓ loadResource() FillerResource { config(JSON), page(png), map(png), numberImage, bg, special } ↓ FillerData (状态机,管理 area.colored / currentGroup / taskList) ↓ FillerScene.addLayer(...) // BgLayer → BoxLayer → HintLayer → NumberLayer // → WorkLayer → LineArtLayer → BorderLayer ↓ requestAnimationFrame AnimatableMask.flush() // 将动画进度写入 FBO ↓ WorkLayer.draw() // 读取 FBO texture + colored texture → 渲染 ↓ CSS 回调 (cssAdjustProgress / cssColorDone / cssOnFinish) ↓ 完成:confetti + resetToResult + replay() ``` ### 1.4 已完成能力 - [x] 核心填色玩法:点击区域 → 识别 area id → 扩散动画上色 → group 完成爆破 - [x] 进度条与颜色按钮 UI(含环形进度) - [x] 填色完成后撒花 + 回放动画 - [x] 音效(colorDone / allDone / hint) - [x] 震动反馈 - [x] 加载动画(spinner + 最小显示时长) - [x] 手势:双指缩放、拖拽、点击 - [x] 缩放到最适合 / focus 到下一个 area - [x] 分享落地页(图片预览 + 播放填色回放) - [x] 本地资源加载(assets/res/ 目录,fetch JSON + loadImage PNG) - [x] 资源内嵌为 Base64(Vite `?url`/`?raw` + vite-plugin-singlefile,构建输出单文件 HTML) - [x] 宣传界面(promo-screen:coloring-page + slogan 入场动画) - [x] 手指引导提示(FingerHint DOM overlay,闲置 2s 后自动指向待填区域) ### 1.5 当前主要缺口(需要本项目完成的工作) | 缺口 | 优先级 | 说明 | | ------------------------ | ------ | --------------------------------------------------------------------------- | | **广告平台规范适配** | P0 | 单文件内联 HTML、资源 < 5 MB、无外部依赖(applovin/google/unity/mintergal) | | **CTA 按钮与跳转** | P0 | 所有平台都要求显眼 CTA,含平台 API 跳转 store | | **自动演示(引导手势)** | P1 | 游戏前 3-5 秒内有动效/提示引导用户点击上色 | | **体积优化** | P1 | 当前依赖 js-confetti(jszip 已移除),需图片压缩 + 音效压缩 | | ~~**多图片数据内嵌**~~ | ~~P1~~ | ✅ 已完成:Vite `?url` 静态导入 + vite-plugin-singlefile 全量内联 | | **广告生命周期 API** | P1 | 各平台 SDK 回调(mraid / gameReady 等) | | **横竖屏适配** | P2 | 目前 padding 硬编码,需响应式布局 | | **埋点/事件上报** | P2 | click / cta_click / complete 事件 | | **多题材模板** | P3 | 当前单张图,需参数化支持不同图包 | --- ## 2. 广告平台规范摘要(重点约束) 参考 README 中列出的竞品与规范链接,综合核心约束: | 规范 | 要求 | | ------------ | ---------------------------------------------------------- | | 文件格式 | 单个自包含 HTML(所有 JS/CSS/图片 Base64 内联) | | 文件大小 | ≤ 5 MB(Google)/ ≤ 5 MB(Unity)/ ≤ 3 MB(Applovin 建议) | | 外部请求 | **禁止**(Google/Applovin:所有资源必须内联) | | HTTPS 重定向 | CTA 点击必须跳转到 store 链接(mraid.open / window.open) | | MRAID | Applovin/Unity 需支持 MRAID 2.0(mraid.js 注入) | | 帧率 | 建议 60fps,不得阻塞主线程 | | 可玩时长 | 通常 15-60 秒内有 CTA | | 广告标识 | 需显示"Ad"或"Sponsored"字样(部分平台) | --- ## 3. 实现路径与里程碑 ### 阶段 0:分析与准备 ✅ 已完成 - [x] 通读代码,建立项目认知 - [x] 研读各平台规范原文(参见 README 链接) - [x] 对标竞品 Color asis 广告(优先级最高的对标对象) --- ### 阶段 1:打包为单文件 HTML ✅ 已完成(2026-05) **目标**:`vite build` 输出一个完全自包含的单文件 `dist/ad.html`,无外部依赖。 **任务拆解**: | # | 任务 | 文件 | 状态 | | --- | ------------------------------------------------------------------ | --------------------- | ------ | | 1.1 | 简化入口:移除 URL id 解析和 zipUrl 逻辑,始终调 `loadResource()` | `src/filler/index.ts` | ✅ | | 1.2 | 图片资源以 Vite `?url` 静态导入(构建时自动内联) | `src/filler/index.ts` | ✅ | | 1.3 | 音效 mp3 以 Vite `?url` 静态导入 | `src/filler/Audio.ts` | ✅ | | 1.4 | 配置 Vite 输出单文件(vite-plugin-singlefile + assetsInlineLimit) | `vite.config.js` | ✅ | | 1.5 | 清理 `@types/jszip` devDependency | `package.json` | ✅ | | 1.6 | 验证输出 HTML 可独立在浏览器打开无网络错误 | — | 待验收 | **验收标准**: - 单文件体积 ≤ 5 MB(超出需压缩图片) - `file://` 打开可完整运行,无 CORS 错误,无外部请求 --- ### 阶段 2:CTA 与广告生命周期 ✅ 已完成(2026-05) **目标**:符合各平台规范的 CTA 流程,点击跳转 App Store / Google Play。 **任务拆解**: | # | 任务 | 文件 | 状态 | | --- | ----------------------------------------- | ------------------------------------- | ---- | | 2.1 | 新增 CTA 按钮 UI(常驻 + 完成后放大高亮) | `index.html` / `assets/css/tools.css` | ✅ | | 2.2 | CTA 点击跳转逻辑 | `src/filler/cta.ts` | ✅ | | 2.3 | MRAID 2.0 适配层 | `src/filler/mraid.ts` | ✅ | | 2.4 | gameReady / 平台初始化事件 | `src/filler/index.ts` | ✅ | | 2.5 | 完成流程强化 CTA(宣传界面出现后高亮) | `src/filler/index.ts` | ✅ | | 2.6 | "Ad"标签(右上角半透明徽章) | `index.html` / `assets/css/tools.css` | ✅ | **验收标准**: - CTA 按钮在 5 秒内对用户可见 - 点击 CTA 可正确跳转(本地可通过 console.log 验证跳转 URL) - 完成填色后必定触发 CTA 强化展示 --- ### 阶段 3:引导手势动画 ✅ 已完成(简化方案) **实际方案**:以 DOM overlay 实现 `FingerHint` 类代替原计划的 WebGL GuidanceLayer,效果等同且更轻量。 | # | 任务 | 文件 | 状态 | | --- | ------------------------ | -------------------------- | ---- | | 3.1 | 引导手型图片 | `assets/img/finger.png` | ✅ | | 3.2 | DOM 悬浮手指层 | `src/filler/FingerHint.ts` | ✅ | | 3.3 | 800ms 后自动指向待填区域 | `src/filler/index.ts` | ✅ | | 3.4 | 用户交互后重置/停止 | `src/filler/FingerHint.ts` | ✅ | --- ### 阶段 4:横竖屏与响应式布局(P2) **目标**:广告在主流设备(iPhone/Android 各尺寸)横竖屏均有良好视觉。 **任务拆解**: | # | 任务 | 文件 | 要点 | | --- | ------------------------------ | ---------------------- | ------------------------------------------------------------ | | 4.1 | 底部 toolbar padding 自适应 | `src/filler/index.ts` | `Padding` 参数根据 `window.innerWidth/Height` 动态计算 | | 4.2 | 颜色按钮横屏时改为侧边栏 | `assets/css/tools.css` | `@media (orientation: landscape)` 布局调整 | | 4.3 | Canvas 尺寸 resize 处理 | `src/filler/index.ts` | `window.addEventListener('resize')` 已有,需同步 canvas size | | 4.4 | 安全区(iPhone 刘海/Home Bar) | `index.html` | `env(safe-area-inset-*)` CSS 变量 | **验收标准**: - Chrome DevTools 主流设备模拟器下无元素溢出或被遮挡 - 旋转屏幕后 canvas 正常重绘 --- ### 阶段 5:体积与性能优化 ✅ 已完成(2026-05) **目标**:最终产出 HTML 体积 ≤ 5 MB,60fps 无卡顿。 **任务拆解**: | # | 任务 | 状态 | 结果 | | --- | --------------------- | ---- | ---------------------------------------------------- | | 5.1 | 图片压缩 | ✅ | 当前最大图 302 KB,总资源 660 KB,无需额外压缩 | | 5.2 | 音效压缩 | ✅ | 3 个音效合计 51 KB,已达标 | | 5.3 | 评估/替换 js-confetti | ✅ | 保留,js-confetti 正常 tree-shaking 引入,体积可接受 | | 5.4 | Tree-shaking 验证 | ✅ | JS 81.7 KB,jszip 已排除,test() 已 tree-shaken | | 5.5 | GPU 内存估算 | ✅ | page + map 两张纹理约 4 MB GPU 显存,可接受 | **验收结果**: - 输出 HTML **985 KB**(gzip **641 KB**),远低于 5 MB 限制 ✅ - 无多余依赖,bundle 构成干净 ✅ --- ### 阶段 6:多平台测试与提交准备(P1)← **当前阶段** **目标**:在各平台预览工具中验证通过。 **任务拆解**: | # | 任务 | 平台 | 工具/入口 | | --- | ------------------------ | --------------------------- | --------------------------------------------------------------------------- | | 6.1 | Applovin 预览 | Applovin MAX | https://p.applov.in/playablePreview?create=1 | | 6.2 | Unity Ads 规范自检 | Unity | https://docs.unity.com/zh-cn/grow/acquire/creatives/playable/specifications | | 6.3 | Google AdMob HTML5 校验 | Google | https://support.google.com/google-ads/answer/9981650 | | 6.4 | Mintergal/Playturbo 预览 | Mintergal | https://www.playturbo.com/review/doc | | 6.5 | 真机测试 | iOS Safari / Android Chrome | 重点验证音效、手势、CTA 跳转 | **验收标准**: - 各平台预览工具无报错 - CTA 点击在真机上可正常跳转 App Store --- ## 4. 关键设计决策记录 ### D1:内嵌资源方案 **选择**:Vite build 时通过 `vite-plugin-singlefile` 将所有 JS/CSS inline,图片/音效在 TS 模块中以 Base64 字符串形式维护(构建前由脚本生成 `src/filler/assetData.ts`)。 **理由**:各平台明确要求单文件自包含,Vite 原生 asset inline 在 HTML 中仍产生多个文件,plugin-singlefile 是最少侵入的方案。 ### D2:不引入 Vue/React **选择**:UI 控制继续沿用直接 DOM 操作。 **理由**:playable 广告极度追求体积最小化,框架运行时会带来 ≥ 30 KB overhead,且当前 DOM 量极小无必要。 ### D3:MRAID 适配策略 **选择**:在 `src/filler/mraid.ts` 实现轻量适配层,detect window.mraid 存在再调用,否则 fallback `window.open`。 **理由**:广告可能运行在不同平台 WebView,统一一套代码,运行时 feature detect。 --- ## 5. 文件变更地图(按里程碑) ``` 阶段 1(单文件打包) 新增: src/filler/assetData.ts ← Base64 内嵌图片/音效常量(由构建脚本生成) 新增: scripts/embed-assets.mjs ← 构建前预处理脚本(图片/音效→base64 TS常量) 修改: src/filler/index.ts ← 移除 URL 解析/zipUrl 逻辑,始终调用 loadResource() 修改: src/filler/Audio.ts ← 音效改用 base64 data URL 修改: vite.config.js ← 引入 vite-plugin-singlefile 修改: package.json ← 移除 @types/jszip devDependency 阶段 2(CTA) 新增: src/filler/cta.ts ← CTA 跳转逻辑 新增: src/filler/mraid.ts ← MRAID 2.0 适配层 修改: index.html ← 新增 CTA 按钮 DOM 修改: assets/css/tools.css ← CTA 按钮样式 修改: src/filler/index.ts ← cssOnFinish 后接入 CTA 展示 阶段 3(引导动画) 新增: src/filler/GuidanceLayer.ts ← 手势引导 Layer 新增: assets/img/hand_hint.svg ← 手型图标 修改: src/filler/WorkLayer.ts ← 首次 tap 事件通知 修改: src/filler/index.ts ← 启动引导动画时序 阶段 4(响应式) 修改: assets/css/tools.css ← 横屏 media query 修改: src/filler/index.ts ← 动态 Padding 计算 阶段 5(体积优化) 修改: src/filler/explosion.ts ← 移除 js-confetti 依赖,自研或保留 修改: vite.config.js ← 确认 tree-shaking 资源: assets/res/*/page.png 压缩 资源: assets/sound/*.mp3 压缩 ``` --- ## 6. 风险清单 | 风险 | 可能性 | 影响 | 缓解措施 | | ----------------------------------------------- | -------------- | ---- | -------------------------------------------------- | | 图片+音效内嵌后体积超 5 MB | 高 | P0 | 提前压缩图片至 WebP,评估 map 缩小至 512px | | WebGL 2 在低端 Android WebView 不支持 | 中 | P1 | 检测 `getContext('webgl2')` 失败时展示静态降级画面 | | MRAID 版本差异导致 CTA 无效 | 中 | P1 | 实现多版本 detect + fallback | | 音效在 iOS Safari 需用户手势触发 | 高(已知问题) | P2 | 首次点击上色时解锁 AudioContext | | 各平台预览工具对 inline base64 大文件的处理差异 | 低 | P1 | 提交前各平台逐一测试 | --- ## 7. 参考资源 - 对标竞品(最高优先): `https://creative-ag-global.umcdn.cn/html/09/2d/44/092d441b80639f462fa667bb3f8964bf.html`(Color asis) - Applovin 预览工具: `https://p.applov.in/playablePreview?create=1` - Google AdMob 规范: `https://support.google.com/google-ads/answer/9981650?hl=en#_HTML` - Unity Ads 规范: `https://docs.unity.com/zh-cn/grow/acquire/creatives/playable/specifications` - Mintergal/Playturbo: `https://www.playturbo.com/review/doc` - Gemini 参考分析: `https://gemini.google.com/share/8340c20dd2d1`