广告接入实现(Cocos Creator)
这篇回答什么问题: 用 Cocos Creator 做微信 / 抖音小游戏,广告从创建、预加载、展示到发奖这条链路怎么写才不漏收入、不出漏洞,以及两端 API 到底哪里不一样。
前置:广告位设计与 IPU 优化 讲摆在哪,本文只讲怎么接对。摆错位置是效率问题,接错链路是收入问题——后者更贵。
零、先认清一件事:Cocos 不封装广告
Cocos Creator 3.8 的微信小游戏发布文档明确写着,平台 SDK(用户、社交、支付等)「移植工作在现阶段还是需要开发者自己处理」。
来源:Cocos Creator 3.8 用户手册 · 发布到微信小游戏(采集:2026-10-28)
这句话的推论很实际:
- 代码里会直接出现
wx./tt.,TypeScript 不认识这两个全局对象,得自己写声明文件,否则整个项目编译不过 - 任何「装个插件就有广告 API」的教程都要留一手——插件多半只是套了一层壳,底下的坑一个不少
- 双端差异必须自己抹平,没有官方中间层兜底
声明文件最小写法(放到 assets/scripts/types/ 下):
declare const wx: any;
declare const tt: any;
不要为了「类型安全」去手写完整的 RewardedVideoAd 接口。平台接口会加字段(下面 2.5 的 count 就是例子),类型写死之后每次升级都要跟着改,成本大于收益。
一、两端广告 API 对照表
以下全部来自官方文档,采集日期 2026-10-28,逐条可查(见文末来源表)。
| 能力 | 微信 | 抖音 | 关键差异 |
|---|---|---|---|
| 激励视频 | wx.createRewardedVideoAd({adUnitId}) | tt.createRewardedVideoAd({adUnitId}) | 加载链路相反,见 2.1 |
| 插屏 | wx.createInterstitialAd({adUnitId}) | tt.createInterstitialAd({adUnitId}) | 实例生命周期完全不同,见 2.2 |
| 原生模板 / 横幅 | wx.createCustomAd({...}) | tt.createBannerAd({...}) | 微信 Banner 已停维护,见 2.4 |
| 关闭回调 | onClose(res) → res.isEnded | onClose(res) → res.isEnded + res.count | 见 2.5 |
| 激励视频最低基础库 | 2.0.4 | 1.3.0 | — |
| 插屏最低基础库 | 2.6.0 | 1.71.0 | — |
| 原生模板最低基础库 | 2.11.1 | — | — |
微信侧还支持 multiton: true(2.8.0 起,多例模式)和 disableFallbackSharePage(3.7.7 起,禁用分享页),抖音侧对应的是「激励再得」,参数名和玩法都不一样。
二、五个不对称点
这一节是本文的核心。下面每一条都是「按一个平台写、到另一个平台就废」的地方。
2.1 激励视频:加载链路是反的
微信:create → load() 预加载 → onLoad 之后才 show()。load() 返回 Promise,是官方给的预加载手段。
抖音:官方明确要求不要走这条路。原文大意是——
- 广告创建后可以在用户操作时直接调
show(),不需要再次load() - 建议直接
show,不要用load→ 触发onLoad→show的链路 - 第一次
createRewardedVideoAd()创建对象后,不会抛出任何事件,目前也不会自动加载
来源:抖音 · 激励视频广告(采集:2026-10-28)
于是最经典的翻车写法:
// ❌ 在抖音上大概率永远等不到 onLoad
const ad = tt.createRewardedVideoAd({ adUnitId });
ad.onLoad(() => ad.show());
微信上必须等加载完再 show,抖音上创建后直接 show 就对了。一套代码统一写成「等 onLoad 再 show」,结果就是微信正常、抖音零展示——而抖音的收入报表要等上线后才可见,你在测试环境根本发现不了。
正确做法是在平台层各写各的,不要抽公共的加载流程。抽象层次提到「给广告位 id,返回是否发奖」,加载细节留在各自实现里。
2.2 插屏:微信实例可复用,抖音实例用一次就死
微信:createInterstitialAd 每次调用返回全新实例。官方只写了两条约束——「每次创建都会返回一个全新的实例」和「插屏广告实例不允许跨页面使用」,没写「只能展示一次」。所以同一页面内可以缓存复用,但不能跨页面缓存。
来源:微信 · wx.createInterstitialAd / InterstitialAd 类(采集:2026-10-28)
「不允许跨页面使用」这条在 Cocos 里容易踩到:如果把它理解成「游戏只有一个场景」,就不会出问题;但只要用了多场景(director.loadScene),上一个场景缓存的实例在新场景里就是无效的,show() 不起作用也不报错。插屏实例应当按场景持有,离开场景时销毁。
抖音:每次创建返回全新实例,且实例只支持展示一次——加载出错、展示成功、点击关闭,三种情况都会自动销毁,下次必须重新创建。
来源:抖音 tt.createInterstitialAd 及其 C-API 说明页(采集:2026-10-28)
这条差异的杀伤力在缓存上。下面这种写法在微信上完全没问题,在抖音上第二次调用就静默失败:
// ❌ 抖音上只对第一次有效
let interstitial = null;
function showInterstitial(id) {
if (!interstitial) interstitial = tt.createInterstitialAd({ adUnitId: id });
interstitial.show();
}
抖音侧必须每次新建:
// ✅ 抖音插屏:一次一个实例,用完即弃
function showInterstitial(adUnitId: string): Promise<boolean> {
const ad = tt.createInterstitialAd({ adUnitId });
return new Promise((resolve) => {
ad.onClose(() => resolve(true));
ad.onError(() => resolve(false));
ad.show().catch(() => resolve(false));
});
}
2.3 抖音插屏有三道时间闸门
抖音插屏的限制分两套口径,数字不一致:
| 口径 | 启动后 | 插屏间隔 | 与激励视频间隔 | 出处 |
|---|---|---|---|---|
| 接口文档 / C-API 页 | 15 秒 | 30 秒(否则报 2002) | — | tt.createInterstitialAd |
| 小游戏行为规范 | 30 秒 | 60 秒 | 60 秒 | 运营规范 2.2.3 |
接口文档只描述技术限制,行为规范是处罚依据。技术限制拦不住你,处罚拦得住。
所以代码里按 30s / 60s / 60s 配,不要踩 15s / 30s 的线。详见 合规红线 第三节。
2002 的含义是「等待若干秒再请求」,它不是故障。这些闸门直接影响 广告位设计 里的判断:
- 「进游戏就弹插屏」在抖音上不可行
- 「连续游玩 N 局后弹」的 N 要按时间算,不能按局数算——一局 10 秒的游戏,N 局可能还不到 60 秒
- 节流必须做在客户端,不能只靠服务端下发配置。用户连续点击时,客户端是唯一能拦住的地方
把它塞进 onError 的失败率统计,你会看到一个虚高的「广告失败率」,然后去查根本不存在的网络问题。插屏要先在本地判时间,够条件才发请求。
2.4 微信的 Banner 已经停维护了
微信从基础库 3.5.5 起停止维护 wx.createBannerAd,官方要求改用 wx.createCustomAd(原生模板广告,2.11.1 起支持)。
来源:微信 · wx.createBannerAd、wx.createCustomAd(采集:2026-10-28)
两个 API 的参数不一样,照抄会报错:
| 参数 | BannerAd | CustomAd |
|---|---|---|
adUnitId | 必填 | 必填 |
adIntervals | 选填,≥30 秒,不传则不自动刷新 | 必填,≥30 秒,仅对支持自动刷新的模板生效 |
style.left / top | 必填 | 必填 |
style.width | 必填 | 仅某些模板生效(如矩阵格子) |
style.height | 必填 | 不存在 |
style.fixed | 无 | 仅小程序有效,小游戏传了无效 |
注意 style.height 在 CustomAd 里没有——原生模板的高度由模板自己决定,你控制不了。这意味着不能再用「预留 90px 高度」的方式给横幅留位置,得按模板实际返回的尺寸做布局,否则会盖住操作区。
2.5 关闭回调字段不同,抖音多一个 count
微信:onClose(res) 只保证 res.isEnded(2.1.0 起),含义是「视频是否是在用户完整观看的情况下被关闭的」。
抖音:onClose(res) 有 res.isEnded 和 res.count 两个字段。count 表示用户完整观看了几次,官方建议「在返回 count 的情况下,统一使用该字段判断广告是否观看完成」。
来源:抖音 · RewardedVideoAd.onClose(采集:2026-10-28)
所以统一封装的返回值必须能吃下「看了几次」这个概念,而不是 boolean:
interface AdResult {
/** 本次应当发几份奖励。0 表示不发 */
rewardCount: number;
/** 展示是否成功(含失败重试成功的) */
shown: boolean;
error?: string;
}
现在只用得上 0 和 1,但抖音的「激励再得」一旦开启,一次 onClose 可能返回 count: 3——按 boolean 写的代码到那天要重写发奖逻辑。这是提前留一个字段的成本,几乎为零。
抖音「激励再得」相关参数(multitonRewardMsg / multitonRewardTimes / progressTip)目前仅支持安卓,接入时要用 tt.getSystemInfo 分流。
三、发奖链路:三个必踩的坑
3.1 发奖只能在 onClose 里
show() 返回的 Promise 在广告开始播放时 resolve,而不是播放结束时。这个时机差别在桌面模拟器上看不出来,真机上第一次测就会暴露。
// ❌ 用户刚看到广告第一帧就拿到奖励,然后可以立刻关掉
await ad.show();
grantReward();
// ✅ 唯一正确的发奖时机
ad.onClose((res) => {
if (res.isEnded) grantReward();
});
3.2 单飞锁:一条广告只能发一份奖
看一次广告拿三次奖励,是 IAA 最经典的线上事故,成因有三个,而且经常同时出现:
onClose是持续监听,不是一次性回调。在show()里重复注册,回调会一路累积show()失败后重试,同一局的结算函数被挂了两遍- 用户连点按钮,两次
show()竞争同一个实例
解法只有一个:同一时刻只允许一局在播,用一个 pending 变量同时承担「当前这一局的结算函数」和「是否有局在播」两个语义。
let pending: ((r: AdResult) => void) | null = null;
function settle(r: AdResult) {
const fn = pending;
pending = null; // 先清锁,再回调 —— 顺序不能反
fn && fn(r); // fn 只可能被调用一次
}
「先清锁再回调」这个顺序很关键。反过来写,回调里如果又触发了一次播放(比如奖励到手后立刻引导看下一次),新的一局会被这把还没清掉的锁挡掉。
3.3 isEnded 不等于可信
res.isEnded 是客户端返回值。改包、跑模拟器、直接调 grantReward() 都能绕过它。
- 低价值奖励(复活、双倍金币):客户端判断够了,加个服务端频次上限就行
- 高价值奖励(稀有道具、大额货币):必须服务端校验。小游戏没有账号体系时至少做两件事——奖励发放走服务端接口、按设备与账号维度限制单日总量
这是判断,不是平台规则。但对纯 IAA 产品,超发一次稀有道具对经济系统的伤害,可能大于那一天的全部广告收入。
四、加载与失败降级
4.1 预加载时机
| 平台 | 创建 | 预加载 | 播放后 |
|---|---|---|---|
| 微信 | 首次进入游戏时 | load(),onLoad 后置 ready 标记 | 立刻重新 load(),否则下一次拿不到广告 |
| 抖音 | 首次需要时 | 不需要,直接 show() | 视实例策略而定,见下 |
微信侧「播放完要重新 load」这条,漏了不会报错,只会表现为第二次点击广告没反应。测试时连着点两次就能发现,但只点一次就上线的话永远发现不了。
抖音侧实例是否需要每次重建,官方文档未明确说明(示例代码是创建一次、监听一次)。本站暂按「复用实例」实现,同时保留 destroy() 兜底,列为待核实。
4.2 onError 要分四类,别一律当失败
| 类型 | 典型错误 | 处理 |
|---|---|---|
| 无填充 | 该地区/时段没有广告可投 | 静默降级,走免费奖励分支 |
| 网络超时 | 弱网 | 重试一次,仍失败则降级 |
| 实例失效 | 长时间切后台 | 重建实例后再试一次 |
| 参数错误 | adUnitId 填错 / 广告位未审核通过 | 必须报错到控制台并上报,禁止静默降级 |
最后一类是最贵的。adUnitId 填错、广告位没通过审核、用了测试 id,表现都是「广告一直出不来」——如果代码把它当普通失败吞掉,你会带着一个零收入的包上线,而所有页面都正常、没有任何报错。
正式包里所有 adUnitId 必须逐条核对平台后台,且开发期必须有一个开关能让错误直接抛出来。测试环境的广告失败率永远偏高,别把「测试时也出不来」当成正常现象。
4.3 降级路径:不是取舍题
广告拉不到时,用户在等一个结果。两种处理各有代价:
| 策略 | 用户感受 | 收入影响 | 适用 |
|---|---|---|---|
| 免费发奖 | 好,「系统送的」 | 这次少一份收入,但保住体验 | A 类(用户主动要的) |
| 静默跳过,流程继续 | 无感 | 保住单价 | B / C 类 |
| 差,尤其失败复活场景 | 保住单价,但用户可能直接退出 | 不要用 |
判断依据是 广告位设计 里的分档:A 类广告失败必须给免费奖励,因为用户是在最需要帮助的时刻被拒绝的;C 类插屏失败直接跳过。
抖音《广告小游戏运营规范》明确:没有对广告不展示等异常情况做兜底处理、导致用户行为中断的,属于违规。原文要求「如不支持激励视频的低版本用户、广告调用失败等情况,应直接展示结果或给予奖励」。
所以「提示稍后重试」这种让用户卡在原地的写法,在抖音上不只是转化差,是可能被判违规。详见 合规红线 3.3 节。
五、包体与首屏:广告还没露脸,人就走了
微信小游戏主包不能超过 4MB,包含所有代码和资源,额外资源必须走网络下载。
来源:Cocos Creator 3.8 用户手册 · 发布到微信小游戏(采集:2026-10-28)
分包的四条上限(来源:微信开放文档 · 分包加载,采集:2026-10-28):
| 限制项 | 上限 |
|---|---|
| 主包 | 4MB |
| 全部主包 + 分包总和 | 30MB |
| 单个普通分包 | 不限制 |
| 单个独立分包 | 4MB |
网上大量教程和部分英文版文档仍写「总大小 20M」——那是扩容前的旧数字。中文官方页现已写 30M。
这条影响的是「能不能做重度」:30MB 意味着可以做有一定美术体量的产品,20MB 则基本只能做超休闲。规划资源量时按 30MB 算,但主包 4MB 这条没变,所以分包策略仍然是第一优先级。
同页还有一条更容易被忽略的限制:小游戏包内资源不是按需加载,而是一次性加载完所有包内资源再启动页面。所以主包里每多 100KB,都在直接拖慢每一个用户的首次启动——不管这个资源这一局用不用得上。
另外两条硬约束:
- 不可以从远程服务器下载脚本文件(资源可以,脚本不行)
- 微信小游戏不支持 WebView
降低主包的三个手段,都在 Cocos 构建面板里:
| 手段 | 作用 | 注意 |
|---|---|---|
初始场景分包(startSceneAssetBundle) | 首场景及依赖进 assets/start-scene,本地加载 | 主包放远程时必须开,否则首屏要等下载 |
| 引擎原生代码分包 | 引擎的 WASM/Asm.js 进子包 | 减少主包体积 |
分离引擎(separateEngine) | 用微信小游戏引擎插件 | 仅支持 js 模式,开了 WASM 物理就不能用 |
为什么这条要写在广告文档里:广告的机会窗口是前 3 分钟(见 广告位设计 第七节)。首屏慢 2 秒,用户在那 3 分钟里能走到的关卡就少一关,A 类广告的触点直接就少一个。包体问题最后一定以 IPU 的形式体现在报表上。
六、一套代码跑两端:隔离层怎么写
6.1 目录结构
assets/scripts/
platform/
types.ts # 统一接口与返回类型
wechat.ts # wx.* 实现
douyin.ts # tt.* 实现
index.ts # 按平台导出,业务层只 import 这里
game/ # 业务逻辑,不允许出现 wx./tt.
规矩只有一条:game/ 目录下不许出现 wx. 或 tt.。 出现了就意味着以后加第三个平台时要改业务代码,那时候的改动量是现在的十倍。
6.2 平台判断
Cocos Creator 3.x 的平台枚举是 sys.Platform,字符串值,微信为 WECHAT_GAME,抖音为 BYTEDANCE_MINI_GAME。
来源:Cocos Creator API · 枚举 Platform(采集:2026-10-28,查的是 3.3 版文档,3.8 未逐项核对,接入时以所装版本的 cc.d.ts 为准)
import { sys } from 'cc';
export type PlatformTag = 'wechat' | 'douyin' | 'web';
export function platformTag(): PlatformTag {
if (sys.platform === sys.Platform.WECHAT_GAME) return 'wechat';
if (sys.platform === sys.Platform.BYTEDANCE_MINI_GAME) return 'douyin';
return 'web'; // 编辑器预览、浏览器调试
}
web 分支必须有,而且要有能跑通同一套流程的假实现(模拟 1.5 秒后返回 isEnded: true)。否则每次调广告都要真机,验证「发奖逻辑对不对」这种纯业务问题会被拖到极慢。
6.3 统一接口
export type AdType = 'rewarded' | 'interstitial' | 'native';
export interface AdResult {
rewardCount: number; // 0 = 不发奖
shown: boolean;
error?: string;
}
export interface IAdService {
/** 预热,进入可能需要的场景前调用,失败不阻塞 */
preload(type: AdType, zoneId: string): void;
showRewarded(zoneId: string): Promise<AdResult>;
showInterstitial(zoneId: string): Promise<AdResult>;
/** 原生模板/横幅的位置,单位 px,左上角原点 */
layoutNative(zoneId: string, rect: { left: number; top: number; width: number }): void;
}
zoneId 是唯一定位一个广告位的字符串,同时用于三个地方:取 adUnitId、埋点上报、后台频控配置。一个广告位只有一个 zoneId,跨平台也不复用——微信和抖音的广告位是两套后台,同一个逻辑位置有两个物理 id。
6.4 微信侧骨架
import type { AdResult } from './types';
declare const wx: any;
let rewarded: any = null;
let pending: ((r: AdResult) => void) | null = null;
function ensureRewarded(adUnitId: string) {
if (rewarded) return rewarded;
rewarded = wx.createRewardedVideoAd({ adUnitId }); // 小游戏端是全局单例
rewarded.onError((err: any) => {
settle({ rewardCount: 0, shown: false, error: `wx_${err?.errCode ?? 'unknown'}` });
});
rewarded.onClose((res: any) => {
const ended = res?.isEnded === true;
settle({ rewardCount: ended ? 1 : 0, shown: true });
rewarded.load().catch(() => {}); // 看完立刻预热下一次
});
rewarded.load().catch(() => {});
return rewarded;
}
function settle(r: AdResult) {
const fn = pending;
pending = null;
fn && fn(r);
}
export function showRewarded(adUnitId: string): Promise<AdResult> {
if (pending) return Promise.resolve({ rewardCount: 0, shown: false, error: 'busy' });
const ad = ensureRewarded(adUnitId);
return new Promise<AdResult>((resolve) => {
pending = resolve;
ad.show().catch(() => {
// show 失败:load 一次再重试,仍失败才结算
ad.load()
.then(() => ad.show())
.catch(() => settle({ rewardCount: 0, shown: false, error: 'show_failed' }));
});
});
}
注意 onError 里也调了 settle——广告拉取失败时不会触发 onClose,如果只监听 onClose,用户点了按钮之后就一直等在那里,UI 永远转圈。
6.5 抖音侧骨架
import type { AdResult } from './types';
declare const tt: any;
export function showRewarded(adUnitId: string): Promise<AdResult> {
// 抖音:不走 load -> onLoad -> show,创建后直接 show
const ad = tt.createRewardedVideoAd({ adUnitId });
return new Promise<AdResult>((resolve) => {
let settled = false;
const done = (r: AdResult) => {
if (settled) return;
settled = true;
resolve(r);
};
ad.onError((err: any) => done({ rewardCount: 0, shown: false, error: `tt_${err?.errNo}` }));
ad.onClose((res: any) => {
// 官方建议:有 count 时以 count 为准
const count = typeof res?.count === 'number' ? res.count : (res?.isEnded ? 1 : 0);
done({ rewardCount: count, shown: true });
});
ad.show().catch(() => done({ rewardCount: 0, shown: false, error: 'show_failed' }));
});
}
抖音侧每次 show 都新建实例,是否造成实例泄漏未在文档中找到说明,列为待核实。稳妥做法是在 done() 之后调用 ad.destroy && ad.destroy()。
七、埋点:把口径落成事件
术语与指标口径 里三个核心指标的「数据来源」都写着自建埋点。这一节把这句话补完。
7.1 事件表
| 事件 | 触发时机 | 必带字段 |
|---|---|---|
ad_request | 决定要展示一次广告(含预加载) | zone_id ad_type scene |
ad_show | show() 成功,广告真正露出 | zone_id ad_type |
ad_close | onClose 触发 | zone_id reward_count watch_ms |
ad_error | onError 触发 | zone_id err_code err_type |
ad_reward | 奖励发放成功 | zone_id reward_count item_id |
公共字段:uid(设备维度匿名 id)、session_id、platform、day_n(用户第几天)、ts(服务器校准后时间戳)、ver(包版本)。
7.2 三条必须守住的规矩
request 和 show 不能合并。 合并了就算不出展示率,而展示率正是 glossary 里判断「平台没广告」还是「你自己没露出来」的唯一依据。
zone_id 一个都不能少。 没有 zone 维度,加完第二个广告位之后就再也无法归因是哪个位在赚钱。这是 IAA 埋点最常见的偷懒,代价在三个月后才显现。
切后台会丢包。 小游戏被切走时网络请求可能直接中断。上报要么走本地缓冲 + 下次启动补发,要么接受 ad_close 的丢失率——但必须知道丢了,否则 ad_show - ad_close 的差值会被误读成「用户看完不回来」。
7.3 与平台后台对账
平台后台的收入 ÷ 你埋点的展示数 × 1000 = 你的实际 eCPM。这个数字和后台显示的 eCPM 一定对不上,差值通常来自三处:有效展示口径、分成前后、时区。
对账频率建议每周一次,看趋势比值而不是绝对值。比值突然跳变(比如某周实际 eCPM 掉了 30%),要么是广告位配置被改,要么是埋点少报了展示——后者会伪装成「收入下降」,实际是数据问题。
八、上线前自查表
- 所有
adUnitId已逐条核对平台后台,没有残留测试 id - 参数错误(广告位无效)会抛错并上报,不会被当成普通失败吞掉
- 发奖只发生在
onClose内,show()的 then 里没有任何发奖代码 - 有单飞锁,连点按钮不会发起第二次播放
-
onClose/onError只注册一次,没有在show()里重复注册 - 微信侧播放结束后会重新
load() - 抖音侧没有使用
load→onLoad→show链路 - 抖音插屏每次新建实例,且做了「启动 30 秒 / 间隔 60 秒 / 距激励视频 60 秒」本地节流
- 抖音
onClose用count判断,代码能处理count > 1 - 微信横幅用的是
createCustomAd,布局不依赖固定高度 - 广告失败时有明确的降级分支,A 类有免费奖励兜底
- 主包 ≤ 4MB,构建时看过实际体积
- 首次启动到第一帧的耗时在真机上量过(低端安卓机)
-
web分支有假实现,编辑器里能跑通完整发奖流程 - 每个广告位都有
zone_id,五个事件都上报了
九、常见错误清单
- 统一写「等 onLoad 再 show」。微信对,抖音错,且错得安静——抖音零展示不会报错。
- 缓存抖音插屏实例。实例展示一次就销毁,第二次调用失败没人发现。
- 把 2002 当网络错误上报。失败率虚高,然后去修一个不存在的问题。
- 在
await show()之后发奖。桌面模拟器看不出来,真机必现。 onClose注册在show()里。回调累积,看一次发三次。- 只监听
onClose不监听onError。广告拉不到时按钮永远转圈。 adUnitId填错被静默降级。带着零收入的包上线,所有页面都「正常」。- 埋点合并 request 与 show。展示率从此算不出来,排查广告问题没有依据。
- 业务代码里直接写
wx.。加第二个平台时全面返工。 - 只测「看完」不测「中途关闭」。两种路径的分支逻辑完全不同,前者正常不代表后者正常。
十、待核实
- 抖音插屏闸门的最终口径:接口文档 15s/30s 与行为规范 30s/60s 并存,本站取严按 30s/60s/60s 执行,建议向直客或工单确认
- 微信
InterstitialAd能否多次show()→ 已核实:官方未限制展示次数,但明确「实例不允许跨页面使用」。同一场景内可复用,跨场景必须重建,见 2.2 - 微信分包总大小上限 → 已核实为 30MB(主包 4MB / 普通分包不限 / 独立分包 4MB),见第五节
- 抖音小游戏的包体限制 → 已核到部分:网易 Unity 方案首包 < 60MB、Wasm 方案首包 < 30MB(接入指引)。Cocos 原生方案的具体上限官方未单列,实际以开发者工具上传报错为准
- 抖音激励视频实例是否需要每次重建,复用是否存在泄漏
- 抖音
count字段在未开启多例模式时的实际返回值(官方称「与 multiton 是否为 true 无关」,需实测) - Cocos 3.8 的
sys.Platform枚举是否与 3.3 文档完全一致(未逐项核对)
待补充
- 微信小游戏「高性能模式」对广告组件层级的影响(真机验证)
- 广告组件与 Cocos 渲染层的层级冲突处理(原生组件永远在 Canvas 之上)
- 安全区的具体实现:
wx.getSystemInfoSync().safeArea在两端字段是否一致 - 各品类实测的展示率、填充率区间(需真实数据)
来源
| 事实 | 数值 / 结论 | 来源 | 采集日期 | 等级 |
|---|---|---|---|---|
| Cocos 不封装平台 SDK | 移植工作需开发者自行处理 | Cocos 3.8 · 发布到微信小游戏 | 2026-10-28 | A |
| 微信小游戏主包上限 | 4MB | 同上 | 2026-10-28 | A(Cocos 文档口径) |
| 包内资源加载方式 | 一次性加载,非按需 | 同上 | 2026-10-28 | A |
| 不支持远程下载脚本 | 是 | 同上 | 2026-10-28 | A |
| 分离引擎限制 | 仅支持 js 模式 | 同上 | 2026-10-28 | A |
| 微信激励视频最低基础库 | 2.0.4,小游戏端全局单例 | 微信 · wx.createRewardedVideoAd | 2026-10-28 | A |
| 微信激励视频可选参数 | multiton 2.8.0、disableFallbackSharePage 3.7.7 | 同上 | 2026-10-28 | A |
| 微信 onClose.isEnded | 2.1.0 起,含义为「是否完整观看后关闭」 | 微信 · RewardedVideoAd.onClose | 2026-10-28 | A |
| 微信插屏最低基础库 | 2.6.0,每次创建返回全新实例 | 微信 · wx.createInterstitialAd | 2026-10-28 | A |
| 微信插屏实例作用域 | 实例不允许跨页面使用 | 微信 · InterstitialAd 类 | 2026-10-28 | A |
| 微信分包上限 | 主包 4MB / 总和 30MB / 普通分包不限 / 独立分包 4MB | 微信 · 分包加载 | 2026-10-28 | A |
| 微信 Banner 停维护 | 3.5.5 起,改用 wx.createCustomAd | 微信 · wx.createBannerAd | 2026-10-28 | A |
| 微信 CustomAd 参数 | 2.11.1 起;adIntervals 必填;style.width 部分模板生效;无 style.height | 微信 · wx.createCustomAd | 2026-10-28 | A |
| 抖音加载链路 | 建议直接 show,不走 load → onLoad → show;首次创建不抛事件、不自动加载 | 抖音 · 激励视频广告 | 2026-10-28 | A |
| 抖音 onClose 字段 | isEnded + count,官方建议有 count 时用 count | 抖音 · RewardedVideoAd.onClose | 2026-10-28 | A |
| 抖音激励再得参数 | multitonRewardMsg / multitonRewardTimes / progressTip,仅安卓 | 抖音 · 商业化激励再得 | 2026-10-28 | A |
| 抖音插屏基础库 | 1.71.0,每次创建新实例 | 抖音 · tt.createInterstitialAd | 2026-10-28 | A |
| 抖音插屏 2002 | 需等 30 秒后再请求 | 同上 | 2026-10-28 | A |
| 抖音插屏时间闸门(接口层) | 启动 15s 内不可展示;间隔 ≥30s;实例仅可展示一次 | 抖音 · 插屏广告 C-API | 2026-10-28 | B(C-API 页,JS 端未重复说明) |
| 抖音插屏时间闸门(运营层) | 启动 30s 内不可展示;插屏间隔 60s;距激励视频 60s | 抖音 · 小游戏行为规范 | 2026-10-28 | A(与上一行冲突,取严) |
| 广告失败须兜底 | 应直接展示结果或给予奖励 | 抖音 · 广告小游戏运营规范 | 2026-10-28 | A |
| Cocos 平台枚举 | sys.Platform.WECHAT_GAME / BYTEDANCE_MINI_GAME | Cocos API · 枚举 Platform | 2026-10-28 | B(3.3 版文档) |
show() 的 Promise resolve 时机 | 开始播放时,非播放结束 | 无官方明文,实践中确认 | — | C(判断,需真机验证) |
等级说明: A = 官方文档明确写出;B = 官方文档的旧版页或相邻能力页,需复核;C = 经验判断,未经文档证实。
变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-28 | 建立初版。确定以 Cocos Creator 为目标引擎,双端 API 差异按 2026-10-28 官方文档核对 |
| 2026-10-28 | 修正:抖音插屏闸门原按接口文档 15s/30s 写入,后发现《小游戏行为规范》要求 30s/60s/60s 且为处罚依据,全文改为取严口径;4.3 降级策略由「可选」改为「不可中断用户流程」(抖音运营规范硬要求) |
| 2026-10-28 | 核实补充:微信分包总上限确认为 30MB(原待核实);微信插屏实例补「不允许跨页面使用」约束,并给出多场景下的持有建议。两条待核实项关闭 |
相关文档: 广告位设计与 IPU 优化(摆在哪)· 合规红线(什么动作会被罚)· 术语与指标口径(埋点要对的账)· 平台与变现渠道(广告位怎么开通)