agent.md 20 KB

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 不需要)
平台适配 AdPlatformAdapter(Applovin / Unity / Playturbo-Mintegral / Google)

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    # 粒子爆破动画
    ad-platform/    # 广告平台适配层
      types.ts      # Adapter 接口、平台类型、window SDK 类型声明
      current.ts    # 默认 Google adapter;构建时由 Vite alias 替换为目标平台 adapter
      adapters/     # applovin / unity / playturbo / google 实现
  utils/
    random.ts

1.3 运行时数据流

assets/res/<id>/ → 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 已完成能力

  • 核心填色玩法:点击区域 → 识别 area id → 扩散动画上色 → group 完成爆破
  • 进度条与颜色按钮 UI(含环形进度)
  • 填色完成后撒花 + 回放动画
  • 音效(colorDone / allDone / hint)
  • 震动反馈
  • 加载动画(spinner + 最小显示时长)
  • 手势:双指缩放、拖拽、点击
  • 缩放到最适合 / focus 到下一个 area
  • 分享落地页(图片预览 + 播放填色回放)
  • 本地资源加载(assets/res/ 目录,fetch JSON + loadImage PNG)
  • 资源内嵌为 Base64(Vite ?url/?raw + vite-plugin-singlefile,构建输出单文件 HTML)
  • 宣传界面(promo-screen:coloring-page + slogan 入场动画)
  • 手指引导提示(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;Playturbo/Mintegral 只能调用 window.install()
MRAID Applovin/Unity 需支持 MRAID 2.0(mraid.js 注入)
平台生命周期 Unity 调 dapi.gameReady();Playturbo 调 gameReady/gameEnd 并暴露 gameStart/gameClose
帧率 建议 60fps,不得阻塞主线程
可玩时长 通常 15-60 秒内有 CTA
广告标识 需显示"Ad"或"Sponsored"字样(部分平台)

3. 实现路径与里程碑

阶段 0:分析与准备 ✅ 已完成

  • 通读代码,建立项目认知
  • 研读各平台规范原文(参见 README 链接)
  • 对标竞品 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-06 更新为 AdPlatformAdapter)

目标:通过统一 AdPlatformAdapter 接入各平台 CTA 与生命周期 API,按构建 profile 输出平台专用 HTML。

任务拆解

# 任务 文件 状态
2.1 新增 CTA 按钮 UI(常驻 + 完成后放大高亮) index.html / assets/css/tools.css
2.2 CTA 点击跳转逻辑 src/filler/cta.ts
2.3 平台 Adapter 层 src/filler/ad-platform/*
2.4 gameReady / gameEnd / 平台初始化事件 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 是最少侵入的方案。Playturbo 会扫描本地 HTML 并拒绝 type="module" / crossorigin / ESM import-export,因此 vite.config.js 的 finalize plugin 会在构建后把内联 script/style 改成普通标签。

D2:不引入 Vue/React

选择:UI 控制继续沿用直接 DOM 操作。

理由:playable 广告极度追求体积最小化,框架运行时会带来 ≥ 30 KB overhead,且当前 DOM 量极小无必要。

D3:MRAID 适配策略

选择:MRAID 仅作为平台 Adapter 的底层能力,不再由业务代码直接调用。

理由:不同平台对 CTA 和生命周期 API 的要求不同,单纯运行时 feature detect 容易与平台审核规则冲突。

D4:AdPlatformAdapter 平台适配架构

选择:保持一套填色玩法业务源码,平台差异集中在 src/filler/ad-platform/,通过 vite.config.js 根据 --mode 配置 Vite alias,将 ./ad-platform/current 替换为 Applovin / Unity / Playturbo-Mintegral / Google adapter。

理由:Playturbo/Mintegral 要求 window.install/gameReady/gameEnd 且禁止自跳转,与通用 MRAID / window.open 策略冲突;分平台产物比单一万能 HTML 更稳。

平台行为矩阵

平台 Ready End CTA Loading
Applovin MRAID custom close setup no-op ExitApi.exit()mraid.open(url)window.open(url) 保留自定义 loading
Unity dapi.gameReady() no-op mraid.open(url)window.open(url) 保留自定义 loading
Playturbo/Mintegral window.gameReady() window.gameEnd() 只调用 window.install() 不主动显示自定义 loading
Google no-op no-op window.open(url, "_blank") 保留自定义 loading

构建命令

npm run build           # dist/index.html
npm run build:applovin  # dist/applovin/index.html
npm run build:unity     # dist/unity/index.html
npm run build:playturbo # dist/playturbo/index.html
npm run build:mintegral # dist/mintegral/index.html
npm run build:google    # dist/google/index.html
npm run build:all       # 依次输出默认与各平台产物

约束:业务代码不得直接调用平台全局 API,只能通过 Adapter 的 init/onResourcesLoaded/onGameStart/onGameEnd/openStore


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 提交前各平台逐一测试
Playturbo 拒绝 type="module" / crossorigin 高(已命中) P1 构建后 finalize HTML,移除 script/style 上的 module/crossorigin 属性
Playturbo 拒绝自定义 loading DOM P1 Playturbo profile 不主动显示 loading;如 QA 仍拒绝,再做 HTML transform 移除 DOM
平台 SDK 注入 API 差异导致分支误判 P1 使用构建 profile 固定平台策略,避免依赖运行时猜测
平台生命周期重复触发 P2 Adapter 内部对 ready/start/end 做幂等保护

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