feat: 接入激励视频

This commit is contained in:
richarjiang
2026-06-29 19:55:40 +08:00
parent 411ca8c772
commit e2fd4eeda3
16 changed files with 9215 additions and 73 deletions

View File

@@ -340,6 +340,12 @@ export class PageLevel extends BaseView {
/** 是否正在解锁提示(防止双击重复触发) */
private _isUnlocking: boolean = false;
/** 是否正在观看加时广告(防止双击重复触发) */
private _isAddingTime: boolean = false;
/** 激励视频广告期间是否暂停了倒计时 */
private _isCountdownPausedForRewardAd: boolean = false;
/** 下一个待解锁的线索序号2 或 3超过 3 表示全部已解锁 */
private _nextClueIndex: number = 2;
@@ -1237,8 +1243,11 @@ export class PageLevel extends BaseView {
/**
* 点击解锁线索顺序解锁先线索2再线索3全部解锁后切换为查看答案入口
*/
private onUnlockClue(): void {
// 全部已解锁后,点击"查看答案":自动填入正确答案并走通关流程
private async onUnlockClue(): Promise<void> {
if (this._isUnlocking) {
return;
}
if (this._nextClueIndex > 3) {
if (this._isTransitioning) return;
@@ -1251,11 +1260,24 @@ export class PageLevel extends BaseView {
this.playClickSound();
console.log('[PageLevel] 点击查看答案,自动填充答案并触发通关流程');
// 填充答案到输入格distributeInputText 内部会用 _isSyncingInputText 阻止 EditBox 事件回调)
this.distributeInputText(answer);
this._isUnlocking = true;
try {
const rewarded = await this.showRewardedVideoAd('查看答案');
if (!rewarded || this._isTransitioning) {
return;
}
// 走提交流程(答案命中 → showSuccess → 通关弹窗)
this.onSubmitAnswer();
const latestAnswer = this._currentConfig?.answer;
if (!latestAnswer) {
ToastManager.show('答案暂未配置');
return;
}
this.distributeInputText(latestAnswer);
this.onSubmitAnswer();
} finally {
this._isUnlocking = false;
}
return;
}
@@ -1269,45 +1291,116 @@ export class PageLevel extends BaseView {
}
this.playClickSound();
this.setClue(index, clueContent);
this._isUnlocking = true;
try {
const rewarded = await this.showRewardedVideoAd('查看线索');
if (!rewarded || this._isTransitioning || this._nextClueIndex !== index) {
return;
}
// 解锁线索后播放"出现"动画,让内容刷新不突兀
const tipsItem = this.getTipsItem(index);
if (tipsItem) {
this.playClueAppearAnimation(tipsItem);
const latestClueContent = index === 2 ? this._currentConfig?.clue2 : this._currentConfig?.clue3;
if (!latestClueContent) {
ToastManager.show('该提示暂未配置');
return;
}
this.setClue(index, latestClueContent);
const tipsItem = this.getTipsItem(index);
if (tipsItem) {
this.playClueAppearAnimation(tipsItem);
}
this._nextClueIndex++;
if (this._nextClueIndex > 3) {
this.setUnlockButtonText(PageLevel.UNLOCK_BUTTON_ANSWER_TEXT);
}
console.log(`[PageLevel] 解锁线索${index}`);
} finally {
this._isUnlocking = false;
}
// 推进到下一条待解锁线索
this._nextClueIndex++;
// 全部解锁完毕后不隐藏按钮,改为查看答案入口
if (this._nextClueIndex > 3) {
this.setUnlockButtonText(PageLevel.UNLOCK_BUTTON_ANSWER_TEXT);
}
console.log(`[PageLevel] 解锁线索${index}`);
}
/**
* 点击增加时间按钮(倒计时增加 60 秒)
*/
private onAddTime(): void {
private async onAddTime(): Promise<void> {
if (this._isAddingTime) {
return;
}
if (this._isTimeUp) {
ToastManager.show('时间已结束,无法增加');
return;
}
this.playClickSound();
this._isAddingTime = true;
try {
const rewarded = await this.showRewardedVideoAd('加时');
if (!rewarded) {
return;
}
if (this._isTimeUp) {
ToastManager.show('时间已结束,无法增加');
return;
}
this.addCountdownTime();
} finally {
this._isAddingTime = false;
}
}
private async showRewardedVideoAd(rewardName: string): Promise<boolean> {
this.pauseLevelForRewardedVideoAd();
try {
const rewarded = await WxSDK.showRewardedVideoAd();
if (!rewarded) {
ToastManager.show(`${rewardName}需要看完广告`);
}
return rewarded;
} finally {
this.resumeLevelAfterRewardedVideoAd();
}
}
private pauseLevelForRewardedVideoAd(): void {
this._isCountdownPausedForRewardAd = false;
if (!this._isTimeUp && !this._isTransitioning && this._countdown > 0) {
this.stopCountdown();
this._isCountdownPausedForRewardAd = true;
}
AudioManager.instance.pausePlayingAudioSources();
}
private resumeLevelAfterRewardedVideoAd(): void {
AudioManager.instance.resumePausedAudioSources();
if (!this._isCountdownPausedForRewardAd) {
return;
}
this._isCountdownPausedForRewardAd = false;
if (this.node?.isValid && !this._isTimeUp && !this._isTransitioning && this._countdown > 0) {
this.schedule(this.onCountdownTick, 1);
}
}
private addCountdownTime(): void {
const wasUrgent = this._countdown <= PageLevel.CLOCK_URGENT_THRESHOLD;
this._countdown += 60;
// 从紧迫态跳回安全区:停掉残留脉冲并复位 scaleupdateClockLabel 会负责把颜色改回)
if (wasUrgent && this._countdown > PageLevel.CLOCK_URGENT_THRESHOLD && this.clockLabel) {
Tween.stopAllByTarget(this.clockLabel.node);
this.clockLabel.node.setScale(1, 1, 1);
}
this.updateClockLabel();
this.playClickSound();
ToastManager.show('已成功增加60秒');
console.log(`[PageLevel] 增加60秒倒计时当前剩余: ${this._countdown}s`);
}

View File

@@ -10,6 +10,7 @@ export class AudioManager {
private _clickAudio: AudioClip | null = null;
private _hostNode: Node | null = null;
private _audioSource: AudioSource | null = null;
private _pausedAudioSources: AudioSource[] = [];
static get instance(): AudioManager {
if (!this._instance) {
@@ -30,6 +31,34 @@ export class AudioManager {
this._playOneShot(this._clickAudio);
}
pausePlayingAudioSources(): void {
if (!this._hostNode?.isValid) {
return;
}
this._pausedAudioSources = [];
const audioSources = this._hostNode.getComponentsInChildren(AudioSource);
audioSources.forEach((audioSource) => {
if (!audioSource?.isValid || !audioSource.playing) {
return;
}
audioSource.pause();
this._pausedAudioSources.push(audioSource);
});
}
resumePausedAudioSources(): void {
const pausedAudioSources = this._pausedAudioSources;
this._pausedAudioSources = [];
pausedAudioSources.forEach((audioSource) => {
if (audioSource?.isValid && !audioSource.playing) {
audioSource.play();
}
});
}
private _playOneShot(clip: AudioClip | null): void {
if (!clip) {
return;

View File

@@ -34,6 +34,9 @@ export interface WxPrivacySettingResult {
* 封装微信平台相关 API非微信环境下静默降级
*/
export class WxSDK {
/** 默认激励视频广告位 */
private static readonly DEFAULT_REWARDED_VIDEO_AD_UNIT_ID = 'adunit-52a53828d4b91fe2';
/** 隐私授权请求进行中时复用同一个 Promise避免启动链路重复弹窗 */
private static _privacyAuthorizePromise: Promise<boolean> | null = null;
@@ -315,6 +318,12 @@ export class WxSDK {
/** 激励视频广告实例(复用) */
private static _rewardedVideoAd: any = null;
/** 当前复用实例对应的广告位 */
private static _rewardedVideoAdUnitId: string = '';
/** 激励视频广告展示中的 Promise避免重复拉起 */
private static _rewardedVideoAdPromise: Promise<boolean> | null = null;
/**
* 展示激励视频广告
* 用户看完广告后返回 true中途退出或失败返回 false
@@ -322,68 +331,98 @@ export class WxSDK {
* @param adUnitId 广告单元 ID默认使用项目配置的 ID
* @returns Promise<boolean> 是否看完广告
*/
static showRewardedVideoAd(adUnitId: string = ''): Promise<boolean> {
return new Promise((resolve) => {
const wxApi = WxSDK.getWx();
if (!wxApi) {
console.log('[WxSDK] 非微信环境,跳过激励视频广告');
resolve(true);
return;
}
static showRewardedVideoAd(adUnitId: string = WxSDK.DEFAULT_REWARDED_VIDEO_AD_UNIT_ID): Promise<boolean> {
if (WxSDK._rewardedVideoAdPromise) {
return WxSDK._rewardedVideoAdPromise;
}
if (typeof wxApi.createRewardedVideoAd !== 'function') {
console.warn('[WxSDK] 当前微信版本不支持激励视频广告');
resolve(true);
return;
}
const wxApi = WxSDK.getWx();
if (!wxApi) {
console.log('[WxSDK] 非微信环境,跳过激励视频广告');
return Promise.resolve(true);
}
if (typeof wxApi.createRewardedVideoAd !== 'function') {
console.warn('[WxSDK] 当前微信版本不支持激励视频广告');
return Promise.resolve(true);
}
const resolvedAdUnitId = adUnitId || WxSDK.DEFAULT_REWARDED_VIDEO_AD_UNIT_ID;
if (!resolvedAdUnitId) {
console.warn('[WxSDK] 激励视频广告位未配置');
return Promise.resolve(false);
}
WxSDK._rewardedVideoAdPromise = new Promise((resolve) => {
let ad: any = null;
let finished = false;
const cleanup = () => {
if (!ad) return;
if (typeof ad.offClose === 'function') {
ad.offClose(onClose);
}
if (typeof ad.offError === 'function') {
ad.offError(onError);
}
};
const finish = (result: boolean) => {
if (finished) return;
finished = true;
cleanup();
WxSDK._rewardedVideoAdPromise = null;
resolve(result);
};
const onClose = (res: any) => {
const isEnded = res === undefined || !!res?.isEnded;
if (isEnded) {
console.log('[WxSDK] 激励视频广告观看完成');
finish(true);
} else {
console.log('[WxSDK] 激励视频广告中途退出');
finish(false);
}
};
const onError = (err: any) => {
console.error('[WxSDK] 激励视频广告错误:', err);
finish(false);
};
try {
// 复用或创建广告实例
if (!WxSDK._rewardedVideoAd) {
WxSDK._rewardedVideoAd = wxApi.createRewardedVideoAd({
adUnitId: adUnitId,
});
}
const ad = WxSDK._rewardedVideoAd;
// 定义关闭回调(一次性)
const onClose = (res: any) => {
ad.offClose(onClose);
if (res && res.isEnded) {
console.log('[WxSDK] 激励视频广告观看完成');
resolve(true);
} else {
console.log('[WxSDK] 激励视频广告中途退出');
resolve(false);
}
};
// 定义错误回调(一次性)
const onError = (err: any) => {
ad.offError(onError);
ad.offClose(onClose);
console.error('[WxSDK] 激励视频广告错误:', err);
resolve(false);
};
ad = WxSDK.getRewardedVideoAd(wxApi, resolvedAdUnitId);
ad.onClose(onClose);
ad.onError(onError);
// 先尝试 show如果广告未加载则先 load
ad.show().catch(() => {
ad.load().then(() => ad.show()).catch((loadErr: any) => {
ad.offClose(onClose);
ad.offError(onError);
Promise.resolve(ad.show()).catch(() => {
Promise.resolve(ad.load()).then(() => ad.show()).catch((loadErr: any) => {
console.error('[WxSDK] 激励视频广告加载失败:', loadErr);
resolve(false);
finish(false);
});
});
} catch (err) {
console.error('[WxSDK] 激励视频广告异常:', err);
resolve(false);
finish(false);
}
});
return WxSDK._rewardedVideoAdPromise;
}
private static getRewardedVideoAd(wxApi: any, adUnitId: string): any {
if (WxSDK._rewardedVideoAd && WxSDK._rewardedVideoAdUnitId === adUnitId) {
return WxSDK._rewardedVideoAd;
}
if (WxSDK._rewardedVideoAd && typeof WxSDK._rewardedVideoAd.destroy === 'function') {
WxSDK._rewardedVideoAd.destroy();
}
WxSDK._rewardedVideoAd = wxApi.createRewardedVideoAd({ adUnitId });
WxSDK._rewardedVideoAdUnitId = adUnitId;
return WxSDK._rewardedVideoAd;
}
// ==================== 启动参数 ====================