feat: 接入激励视频
This commit is contained in:
@@ -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;
|
||||
|
||||
// 从紧迫态跳回安全区:停掉残留脉冲并复位 scale(updateClockLabel 会负责把颜色改回)
|
||||
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`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// ==================== 启动参数 ====================
|
||||
|
||||
Reference in New Issue
Block a user