You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1162 lines
39 KiB
1162 lines
39 KiB
import { assetManager, game, sys } from "cc";
|
|
import { BYTEDANCE, DEBUG, DEV, EDITOR, WECHAT } from "cc/env";
|
|
import { RewardedVideoAd } from "./functions/RewardedVideoAd";
|
|
import { sdkConfig } from "./sdkConfig";
|
|
import GEMgr from "./GEMgr";
|
|
import { InterstitialAd } from "./functions/InterstitialAd";
|
|
import TTRecording from "./functions/TTRecording";
|
|
import { Share } from "./functions/Share";
|
|
import Vibrate from "./functions/VibrateMgr";
|
|
import { TTRank } from "./functions/TTRank";
|
|
import { Singleton } from "../../mx/tools/Singleton";
|
|
import { ItemNumType, StatisticsType, StatusType, TaskType } from "../game/GameData";
|
|
import { update } from "../../../extensions/tools/source/scene";
|
|
import { PfDiagnostics } from "../diagnostics/PfDiagnostics";
|
|
import { BattlePerformance } from "../ui/BattleGame/BattlePerformance";
|
|
import { MiniGameKind, MiniGamePlatform, MiniGameHostApi } from "./platform/MiniGamePlatform";
|
|
import { Physics2DGate } from "../game/Physics2DGate";
|
|
|
|
/**
|
|
* SDK管理器
|
|
*/
|
|
export class SDKManager extends Singleton {
|
|
|
|
/**
|
|
* 抖音头条录屏相关
|
|
*/
|
|
private Recording: TTRecording = null;
|
|
/**
|
|
* 分享相关
|
|
*/
|
|
Share: Share = new Share();
|
|
/**
|
|
* 震动
|
|
*/
|
|
Vibrate = Vibrate;
|
|
|
|
/**
|
|
* 当前平台 API(运行时宿主对象,含各平台扩展能力)
|
|
*/
|
|
pf: MiniGameHostApi | null = null;
|
|
|
|
/**
|
|
* 平台获取的查询参数
|
|
*/
|
|
query = "";
|
|
|
|
showAd = true;
|
|
|
|
upTestData = false;
|
|
|
|
levelS: number[] = [];
|
|
|
|
serverTime: number = 0;
|
|
|
|
timer: number = 0;
|
|
|
|
/** 平台 triggerGC 已禁用:主动 GC 会触发卡帧尖刺,改由引擎与内存压力节流应对 */
|
|
private _lastPfGcMs = 0;
|
|
private static readonly PF_GC_MIN_LEVEL = 15;
|
|
private static readonly PF_GC_COOLDOWN_MS = 120000;
|
|
/** 是否允许调用平台 triggerGC(线上关闭) */
|
|
private static readonly PF_GC_ENABLED = false;
|
|
|
|
/** 最近一次平台 onMemoryWarning 的 level 与时间(界面打开失败时附带上报) */
|
|
private _lastMemWarnLevel: number | null = null;
|
|
private _lastMemWarnMs = 0;
|
|
|
|
/** 激励视频嵌套计数(加载失败重试等场景) */
|
|
private _videoAdDepth = 0;
|
|
/** 本次广告是否由 SDK 调用了 battle.pause('videoAd') */
|
|
private _videoAdBattlePausedBySdk = false;
|
|
|
|
/**
|
|
* 初始化
|
|
*/
|
|
async init() {
|
|
if (EDITOR || DEBUG || sys.isBrowser) {
|
|
this.upTestData = true;
|
|
return null;
|
|
}
|
|
MiniGamePlatform.init();
|
|
this.pf = MiniGamePlatform.pf;
|
|
if (!this.pf) {
|
|
return null;
|
|
}
|
|
|
|
if (MiniGamePlatform.kind !== MiniGameKind.Kuaishou) {
|
|
this.initUpdate();
|
|
}
|
|
|
|
this.pf.onShow(this.onPfShow.bind(this));
|
|
this.pf.onHide(this.onPfHide.bind(this));
|
|
this._bindGlobalErrorReporter();
|
|
|
|
this.serverTime = new Date().getTime();
|
|
|
|
RewardedVideoAd.init(sdkConfig.config.platformConf.video);
|
|
InterstitialAd.init(sdkConfig.config.platformConf.interstitialAd);
|
|
this.Share.init(sdkConfig.config.platformConf.shareData);
|
|
this.openWXShareMenu()
|
|
if (sys.platform == sys.Platform.BYTEDANCE_MINI_GAME) {
|
|
this.Recording = TTRecording.Ins;
|
|
this.Recording.init(sdkConfig.config.platformConf);
|
|
}
|
|
|
|
ZYSDK.ZYSDK.initSdk(sdkConfig.config.pointData.gid)
|
|
|
|
let serverData = null;
|
|
let uid: any = "123456";
|
|
while (serverData == null) {
|
|
gg.ui.showToast("获取游戏存档...");
|
|
uid = await ZYSDK.ZYSDK.getUserId();
|
|
console.log("whileUid", uid);
|
|
if (uid == null || uid == 1 || uid == "")
|
|
await ZYSDK.ZYSDK.initSdk(sdkConfig.config.pointData.gid);
|
|
serverData = await this.getServerData();
|
|
}
|
|
|
|
let fun = () => {
|
|
let str = serverData;
|
|
if (serverData.length > 350) {
|
|
str = serverData.substring(0, 350);
|
|
}
|
|
gg.sdk.reportDY("inLevel", `存档异常-${str}`);
|
|
}
|
|
|
|
if (serverData == "") {
|
|
gg.sdk.reportDY("inLevel", `新玩家-${uid}`);
|
|
}
|
|
else if (serverData == undefined) {
|
|
gg.sdk.reportDY("inLevel", `存档异常-undefined`);
|
|
}
|
|
else {
|
|
try {
|
|
let sd = JSON.parse(serverData);
|
|
if (!Object.hasOwnProperty.call(sd, "uid") || !Object.hasOwnProperty.call(sd, "chapter")) {
|
|
fun();
|
|
}
|
|
} catch (error) {
|
|
fun();
|
|
}
|
|
}
|
|
|
|
let v = ZYSDK.ZYSDK.getCustomValue();
|
|
|
|
if (v == 0) this.upTestData = false;
|
|
else if (v == 100) this.upTestData = true;
|
|
else {
|
|
this.upTestData = Math.random() <= v / 100;
|
|
}
|
|
|
|
let lvs = await ZYSDK.ZYSDK.getLevel();
|
|
|
|
if (lvs && lvs.length > 0) {
|
|
this.levelS = lvs;
|
|
}
|
|
|
|
// this.pf.login({
|
|
// force: true,
|
|
// success(res) {
|
|
// console.log(`login 调用成功${res.code} ${res.anonymousCode}`);
|
|
// },
|
|
// fail(res) {
|
|
// console.log(`login 调用失败`);
|
|
// },
|
|
// });
|
|
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
}
|
|
if (sdkConfig.serverTimeCheckInterval > 0) {
|
|
await this.updateServerTime();
|
|
this.timer = setInterval(() => {
|
|
this.updateServerTime();
|
|
}, sdkConfig.serverTimeCheckInterval);
|
|
}
|
|
|
|
|
|
this.checkTime((curentTime) => {
|
|
if (curentTime) {
|
|
console.error("时间异常");
|
|
this.pfLogKey('SYS:TIME_INVALID', { local: curentTime });
|
|
gg.ui.showToast("时间异常,请检查时间设置");
|
|
gg.sdk.reportDY("inLevel", `时间异常-本地时间${curentTime}`);
|
|
}
|
|
});
|
|
|
|
this.onMemoryWarning((warnRes?: { level?: number }) => {
|
|
const level = warnRes?.level;
|
|
if (level != null) {
|
|
this._lastMemWarnLevel = level;
|
|
this._lastMemWarnMs = Date.now();
|
|
}
|
|
if (level != null && level < 10) return;
|
|
// 不主动 triggerGC;改升战斗特效/音效节流档位,减轻内存与卡帧
|
|
if (level != null && level >= 10) {
|
|
BattlePerformance.onMemoryPressure(level);
|
|
}
|
|
try {
|
|
const info = this.pf.getSystemInfoSync();
|
|
const dev = [info.brand, info.model].filter(Boolean).join(' ') || 'unknown';
|
|
let detail = level != null ? `lv=${level}|dev=${dev}` : `dev=${dev}`;
|
|
if (MiniGamePlatform.isHarmonyOS) {
|
|
detail += '|harmony=1';
|
|
}
|
|
detail += '|gc=0';
|
|
if (PfDiagnostics.write('MEM', 'WARN', detail, 600000, 'warn')) {
|
|
gg.sdk.reportDY('inLevel', `MEM-warn-${detail}`);
|
|
}
|
|
} catch { /* ignore */ }
|
|
});
|
|
return serverData
|
|
|
|
}
|
|
|
|
checkTime(callback: Function = null) {
|
|
if (!gg.sdk.pf || DEV || DEBUG || sys.isBrowser) {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
return;
|
|
}
|
|
ZYSDK.ZYSDK.getTimestamp((res) => {
|
|
if (res && res.ts) {
|
|
let curentTime = new Date().getTime();
|
|
let servertime = res.ts * 1000;
|
|
console.log("servertime:", servertime);
|
|
console.log("curentTime:", curentTime);
|
|
if (Math.abs(servertime - curentTime) > 1000 * 60 * 30) {
|
|
callback(curentTime);
|
|
} else {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}
|
|
}
|
|
}, () => {
|
|
console.error("获取时间失败");
|
|
this.pfLogKey('SYS:TIME_FETCH_FAIL');
|
|
});
|
|
}
|
|
|
|
/**获取服务器时间 */
|
|
getServerTime() {
|
|
// console.log("本机时间毫秒数(UTC):", new Date().getTime());
|
|
// console.log("本机时间格式化(UTC):", new Date().toUTCString());
|
|
// console.log("本机时间格式化(本地):", new Date().toLocaleString());
|
|
// console.log("服务器时间毫秒数:", this.serverTime);
|
|
// console.log("服务器时间格式化(UTC):", new Date(this.serverTime).toUTCString());
|
|
// console.log("服务器时间格式化(本地):", new Date(this.serverTime).toLocaleString());
|
|
if (DEV || DEBUG || sys.isBrowser || sdkConfig.serverTimeCheckInterval == 0) {
|
|
let time = new Date().getTime();
|
|
//console.log("走了本地时间:", time, "serverTimeCheckInterval:", sdkConfig.serverTimeCheckInterval);
|
|
return time;
|
|
}
|
|
return this.serverTime;
|
|
}
|
|
|
|
/**更新服务器时间 */
|
|
updateServerTime() {
|
|
return new Promise((resolve, reject) => {
|
|
if (!sdkConfig.serverTimeCheckInterval) {
|
|
this.serverTime = Date.now();
|
|
resolve(null);
|
|
return;
|
|
}
|
|
ZYSDK.ZYSDK.getTimestamp((res) => {
|
|
if (res && res.ts) {
|
|
let num = res.ts * 1000;
|
|
this.serverTime = num;
|
|
resolve(null);
|
|
}
|
|
}, () => {
|
|
this.pfLogKey('SYS:SERVER_TIME_FAIL');
|
|
resolve(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
initUpdate() {
|
|
const updateManager = this.pf.getUpdateManager();
|
|
if (!updateManager) return;
|
|
|
|
if (updateManager.onUpdateReady) {
|
|
updateManager.onUpdateReady((res) => {
|
|
this.pf.showModal({
|
|
title: "更新提示",
|
|
content: "新版本已经准备好,是否重启小游戏?",
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
|
// assetManager.cacheManager.clearCache();
|
|
updateManager.applyUpdate();
|
|
}
|
|
},
|
|
});
|
|
});
|
|
}
|
|
if (updateManager.onUpdateFailed) {
|
|
updateManager.onUpdateFailed((err) => {
|
|
// 新的版本下载失败
|
|
console.log("版本下载失败原因", err);
|
|
this.pfLogKey('SYS:UPDATE_FAIL', err);
|
|
this.pf.showToast({
|
|
title: "新版本下载失败,请稍后再试",
|
|
icon: "none",
|
|
});
|
|
});
|
|
}
|
|
if (updateManager.onCheckForUpdate) {
|
|
updateManager.onCheckForUpdate();
|
|
}
|
|
}
|
|
|
|
restartMiniProgramSync() {
|
|
if (this.pf && this.pf.restartMiniProgramSync) {
|
|
this.pf.restartMiniProgramSync();
|
|
}
|
|
}
|
|
|
|
getServerData() {
|
|
if (EDITOR || DEBUG || sys.platform == sys.Platform.DESKTOP_BROWSER || sys.platform == sys.Platform.MOBILE_BROWSER) {
|
|
return new Promise<string>((resolve, reject) => {
|
|
resolve("");
|
|
})
|
|
}
|
|
return new Promise<string>((resolve, reject) => {
|
|
ZYSDK.ZYSDK.getGameData((data) => {
|
|
resolve(data);
|
|
}, async () => {
|
|
resolve(null);
|
|
})
|
|
})
|
|
}
|
|
|
|
|
|
/**
|
|
* 当前平台进入前台
|
|
* @param res
|
|
*/
|
|
onPfShow(res) {
|
|
console.log("onPfShow", res);
|
|
this.query = res.query;
|
|
this._show_time = new Date().getTime();
|
|
gg.game?.CurentBattle?.TetraMap?.stabilizeAfterViewportChange?.();
|
|
//this._restoreVideoAdRuntimeOnForeground();
|
|
if (this._onShare) {
|
|
let t = new Date().getTime() - this._share_time;
|
|
this._onShare(t);
|
|
this._onShare = null;
|
|
}
|
|
// if (res.launch_from == 'homepage' && res.location == 'sidebar_card') {
|
|
// //GEvent.Ins.emit(GEventType.CheckIsEnterFromCeBianLan, true);
|
|
// gga.data.setStatus(StatusType.DayGiftCanGet, 1);
|
|
// gga.data.saveToServer();
|
|
// }
|
|
if ((res.launch_from == 'homepage' && res.location == 'sidebar_card') || (res.launch_from == 'sidebar_miniprogram' || res.launch_from == 'sidebar_new')) {
|
|
//GEvent.Ins.emit(GEventType.CheckIsEnterFromCeBianLan, true);
|
|
gg.data.setStatus(StatusType.DayGiftCanGet, 1);
|
|
// gg.data.saveToServer();
|
|
}
|
|
|
|
}
|
|
private _show_time = 0;
|
|
|
|
|
|
/**
|
|
* 当前平台进入后台
|
|
*/
|
|
onPfHide() {
|
|
let c = new Date().getTime();
|
|
let t = c - this._show_time;
|
|
t = Math.floor(t / 1000);
|
|
console.log("onPfHide,在线时长:" + t);
|
|
}
|
|
|
|
|
|
/**
|
|
* 抖音全屏激励视频:平台会挂起小游戏,禁止再 game.pause()(叠加后易黑屏)。
|
|
* 微信 overlay 广告:需 game.pause() 冻结主循环与物理。
|
|
*/
|
|
private _shouldPauseEngineForVideoAd(): boolean {
|
|
return MiniGamePlatform.shouldPauseEngineForVideoAd();
|
|
}
|
|
|
|
/**
|
|
* 激励视频播放前:挂起物理/音频/(微信)主循环;战斗由 SDK 统一 pause('videoAd')。
|
|
* 调用方不得在 showVideoAd 前自行 battle.pause,否则 leave 无法对称恢复战斗与物理。
|
|
*/
|
|
private _enterVideoAdPause(): void {
|
|
Physics2DGate.suspend(Physics2DGate.VideoAd);
|
|
|
|
gg.audio.pause();
|
|
// if (this._shouldPauseEngineForVideoAd()) {
|
|
// }
|
|
game.pause();
|
|
|
|
this._videoAdBattlePausedBySdk = false;
|
|
const battle = gg.game?.CurentBattle;
|
|
if (battle && !battle.IsGameOver && !battle.IsGamePause) {
|
|
battle.pause('videoAd');
|
|
this._videoAdBattlePausedBySdk = true;
|
|
}
|
|
|
|
}
|
|
private _isVideoAdPaused = false;
|
|
|
|
/** 激励视频 onClose:与 enter 对称恢复;仅恢复本链路 pause 的战斗。 */
|
|
private _leaveVideoAdPause(): void {
|
|
// if (this._shouldPauseEngineForVideoAd()) {
|
|
// }
|
|
game.resume();
|
|
Physics2DGate.resume(Physics2DGate.VideoAd);
|
|
gg.audio.resume();
|
|
|
|
if (this._videoAdBattlePausedBySdk) {
|
|
gg.game?.CurentBattle?.resumeFromVideoAdSdk();
|
|
this._videoAdBattlePausedBySdk = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 全屏激励视频:从小程序广告页回到游戏画布时,onShow 往往早于 onClose。
|
|
* 须先恢复物理/音频/战斗,避免「弹道飞到怪身上却无碰撞」;onClose 仍负责 depth-- 与发奖。
|
|
*/
|
|
private _restoreVideoAdRuntimeOnForeground(): void {
|
|
if (this._videoAdDepth <= 0) return;
|
|
if (!RewardedVideoAd.hasPendingCallback()) return;
|
|
|
|
Physics2DGate.resume(Physics2DGate.VideoAd);
|
|
if (this._shouldPauseEngineForVideoAd()) {
|
|
game.resume();
|
|
}
|
|
gg.audio.resume();
|
|
if (this._videoAdBattlePausedBySdk) {
|
|
gg.game?.CurentBattle?.resumeFromVideoAdSdk();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 显示视频广告
|
|
* @param callback 回调
|
|
*/
|
|
showVideoAd(callback: Function) {
|
|
console.log('显示视频广告');
|
|
if (this._isVideoAdPaused) return;
|
|
if (gg.data.getItemNum(ItemNumType.skipAd) > 0) {
|
|
gg.data.subItemNum(ItemNumType.skipAd, 1);
|
|
callback(true, "skipAd");
|
|
return;
|
|
}
|
|
if (EDITOR || !this.showAd) {
|
|
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
|
|
callback(true, "editor");
|
|
return;
|
|
}
|
|
if (sys.isBrowser) {
|
|
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
|
|
callback(true, window.location.href);
|
|
return;
|
|
}
|
|
|
|
this._isVideoAdPaused = true;
|
|
void MiniGamePlatform.checkNetworkForRewardedVideo().then((online) => {
|
|
if (!online) {
|
|
this._isVideoAdPaused = false;
|
|
gg.ui.showToast('网络请求失败,请稍后再试');
|
|
this.pfLogKey('AD:NETWORK_OFFLINE');
|
|
callback(false);
|
|
return;
|
|
}
|
|
this._enterVideoAdPause();
|
|
RewardedVideoAd.show((res) => {
|
|
try {
|
|
this._isVideoAdPaused = false;
|
|
this._leaveVideoAdPause();
|
|
if (res) {
|
|
callback(true);
|
|
gg.data.addStatistics(StatisticsType.TodayAdCount, 1);
|
|
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
|
|
ZYSDK.ZYSDK.reportVideo(true);
|
|
} else {
|
|
this.pfLogKey('AD:REWARD_USER_FAIL');
|
|
callback(false);
|
|
ZYSDK.ZYSDK.reportVideo(false);
|
|
}
|
|
} catch (e) {
|
|
console.error('[SDKManager] showVideoAd callback error', e);
|
|
this.pfLogKey('AD:REWARD_CALLBACK_ERR', e);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 上次显示插屏广告时间戳
|
|
*/
|
|
private _interAdTimeStamp = 0;
|
|
/**
|
|
* 显示插屏广告
|
|
* @param onAdClose
|
|
* @param onFail
|
|
*/
|
|
showInterstitialAd(onAdClose?: Function, onFail?: Function, useAdLevel = false) {
|
|
if (EDITOR) {
|
|
onAdClose && onAdClose(true, "editor");
|
|
} else if (sys.isBrowser) {
|
|
onAdClose && onAdClose(true, window.location.href);
|
|
} else {
|
|
if (!useAdLevel || ZYSDK.ZYSDK.getAdLevel() == 1) {
|
|
//判断是否间隔x秒
|
|
let curTime = new Date().getTime();
|
|
if (curTime - this._interAdTimeStamp < ZYSDK.ZYSDK.getAdInterval() * 1000) {
|
|
onFail && onFail();
|
|
return;
|
|
}
|
|
this._interAdTimeStamp = curTime;
|
|
InterstitialAd.showInterstitialAd(onAdClose, onFail, () => {
|
|
this.reportDY("inLevel", `插屏-章节${gg.data.doc.chapter}`);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* 设置排行榜
|
|
* @param value 分数
|
|
* @param dataType 数据类型 0 数字类型 1 字符串类型
|
|
* @param priority 权重
|
|
* @param zoneId 排行榜分区
|
|
*/
|
|
setImRankData(value: number | string, dataType = 0, priority = 0, zoneId = "default") {
|
|
if (EDITOR) {
|
|
console.log('平台无法写入排行榜数据')
|
|
} else if (sys.isBrowser) {
|
|
console.log('平台无法写入排行榜数据')
|
|
} else {
|
|
TTRank.setImRankData(value, dataType, priority, zoneId)
|
|
}
|
|
}
|
|
/**
|
|
* 获取排行榜
|
|
* @param rankTitle 排行榜标题
|
|
* @param suffix 分数后缀补充文案 为空或不填,一般枚举类型不需要填后缀
|
|
* @param rankType 排行榜类型 可选值有:day、week、month、month、all
|
|
* @param zoneId 排行榜分区
|
|
* @param dataType 数据类型: 0 数字类型 1 字符串类型
|
|
* @param relationType 排行榜类型 可选值有:default 好友榜、总榜都展示 friend 仅展示好友榜 all 仅展示总榜
|
|
*/
|
|
getImRankList(rankTitle: string = "排行榜", suffix = "", rankType = "all", zoneId = "default", dataType = 0, relationType = "default") {
|
|
if (EDITOR) {
|
|
console.log('平台无法获取排行榜数据')
|
|
} else if (sys.isBrowser) {
|
|
console.log('平台无法获取排行榜数据')
|
|
} else {
|
|
TTRank.getImRankList(rankTitle, suffix, rankType, zoneId, dataType, relationType)
|
|
}
|
|
}
|
|
/**
|
|
* 打点
|
|
* @param event
|
|
* @param msg
|
|
*/
|
|
reportDY(event, msg) {
|
|
//添加打点打印和格式,带颜色
|
|
//console.log("%c打点==", "color:green", msg);
|
|
if (sys.platform == sys.Platform.BYTEDANCE_MINI_GAME) {
|
|
// window["tt"].reportAnalytics(event, {
|
|
// type: msg,
|
|
// });
|
|
ZYSDK.ZYSDK.reportUserAction(msg);
|
|
GEMgr.GEReportEvent(msg);
|
|
}
|
|
if (sys.platform == sys.Platform.WECHAT_GAME) {
|
|
//event = event.toLowerCase();
|
|
// window["wx"].reportEvent(event, {
|
|
// "type": msg,
|
|
// });
|
|
ZYSDK.ZYSDK.reportUserAction(msg);
|
|
GEMgr.GEReportEvent(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 进入抖音侧边栏
|
|
* @param callback
|
|
*/
|
|
enterSidebar(callback?: Function) {
|
|
if (window["tt"] && window["tt"].navigateToScene) {
|
|
window["tt"].navigateToScene({
|
|
scene: "sidebar",
|
|
success: (res) => {
|
|
console.log("navigate to scene success");
|
|
if (callback) callback(true);
|
|
},
|
|
fail: (res) => {
|
|
console.log("navigate to scene fail: ", res);
|
|
if (callback) callback(false);
|
|
},
|
|
})
|
|
} else if (window["ks"] && window["ks"].navigateToScene) {
|
|
window["ks"].navigateToScene({
|
|
scene: "sidebar",
|
|
success: (res) => {
|
|
console.log("navigate to scene success");
|
|
if (callback) callback(true);
|
|
},
|
|
fail: (res) => {
|
|
console.log("navigate to scene fail: ", res);
|
|
if (callback) callback(false);
|
|
},
|
|
})
|
|
} else {
|
|
if (callback) callback(true);
|
|
}
|
|
}
|
|
|
|
/**验证是否支持侧边栏 */
|
|
checkSidebar(callback?: Function) {
|
|
if (window["tt"] && window["tt"].navigateToScene) {
|
|
if (callback) callback(true);
|
|
} else if (window["ks"]) {
|
|
if (window["ks"].checkSliderBarIsAvailable) {
|
|
window["ks"].checkSliderBarIsAvailable({
|
|
success: (result) => {
|
|
console.log("check slider bar is available success: ", result.available);
|
|
if (result && result.available) {
|
|
if (callback) callback(true);
|
|
} else {
|
|
if (callback) callback(false);
|
|
}
|
|
},
|
|
fail: (res) => {
|
|
console.log("check slider bar is available fail: ", res);
|
|
if (callback) callback(false);
|
|
},
|
|
})
|
|
} else {
|
|
if (callback) callback(false);
|
|
}
|
|
} else {
|
|
if (callback) callback(false);
|
|
}
|
|
// if (!window["tt"]) {
|
|
// if (callback) callback(false);
|
|
// return;
|
|
// } else {
|
|
// window["tt"].checkScene({
|
|
// scene: "sidebar",
|
|
// success: (res) => {
|
|
// console.log("check scene success: ", res.isExist);
|
|
// //成功回调逻辑
|
|
// if (callback) callback(true);
|
|
// },
|
|
// fail: (res) => {
|
|
// console.log("check scene fail:", res);
|
|
// //失败回调逻辑
|
|
// if (callback) callback(false);
|
|
// }
|
|
// });
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* 验证是否需要显示添加到桌面按钮
|
|
* @param callback
|
|
*/
|
|
checkNeedShowAddShortcut(callback?: Function) {
|
|
if (!window["tt"]) callback?.(false);
|
|
else if (!window["tt"].addShortcut) callback?.(false);
|
|
else callback?.(true);
|
|
}
|
|
|
|
/**
|
|
* 分享游戏
|
|
* @param timeLimit 分享最小停留时间, 没有回调的平台通过计时判断是否分享成功
|
|
* @param cb 有回调的平台,通过回调函数判断是否分享成功
|
|
* @param data 可选参数,分享数据(需要知道参数类型)
|
|
*/
|
|
share(timeLimit, cb = null, data = null) {
|
|
this._share_time = new Date().getTime();
|
|
if (this.Share) {
|
|
let desc = ""
|
|
let url = ""
|
|
let query = ""
|
|
let id = "";
|
|
if (data && data.desc) desc = data.desc;
|
|
if (data && data.res) url = data.res;
|
|
if (data && data.query) query = data.query;
|
|
if (data && data.shareid) id = data.shareid;
|
|
if (sys.platform == sys.Platform.WECHAT_GAME
|
|
|| MiniGamePlatform.kind === MiniGameKind.Kuaishou
|
|
|| MiniGamePlatform.kind === MiniGameKind.Bilibili) {
|
|
this._onShare = (time) => {
|
|
console.log("本次分享时间:" + time);
|
|
if (time >= timeLimit) {
|
|
if (cb) cb(true);
|
|
}
|
|
else {
|
|
if (cb) cb(false);
|
|
}
|
|
}
|
|
this.Share.share(desc, url, cb, query);
|
|
} else if (sys.platform == sys.Platform.BYTEDANCE_MINI_GAME) {
|
|
this.Share.share(desc, url, cb, query, id);
|
|
} else {
|
|
|
|
cb(true);
|
|
}
|
|
}
|
|
else {
|
|
|
|
cb(true);
|
|
}
|
|
}
|
|
private _share_time = 0;
|
|
private _onShare = null;
|
|
|
|
/**
|
|
* 短时间震动
|
|
*/
|
|
vibrateShort() {
|
|
this.Vibrate.vibrateShort();
|
|
}
|
|
|
|
/**
|
|
* 长时间震动
|
|
*/
|
|
vibrateLong() {
|
|
this.Vibrate.vibrateLong();
|
|
}
|
|
|
|
/**
|
|
* 开始震动
|
|
*/
|
|
startVibrate() {
|
|
this.Vibrate.startVibrate();
|
|
}
|
|
|
|
/**
|
|
* 停止震动
|
|
*/
|
|
stopVibrate() {
|
|
this.Vibrate.stopVibrate();
|
|
}
|
|
|
|
|
|
/**
|
|
* 添加到桌面
|
|
* @param callback 回调
|
|
*/
|
|
addShortcut(callback?: Function) {
|
|
if (window["tt"] && window["tt"].addShortcut) {
|
|
window["tt"].addShortcut({
|
|
success: function (res) {
|
|
console.log("添加成功");
|
|
if (callback) callback(true);
|
|
},
|
|
fail: function (res) {
|
|
console.log("添加失败", res);
|
|
if (callback) callback(false);
|
|
}
|
|
})
|
|
} else if (window["ks"] && window["ks"].addShortcut) {
|
|
window["ks"].addShortcut({
|
|
success: function (res) {
|
|
console.log("添加成功");
|
|
if (callback) callback(true);
|
|
},
|
|
fail: function (res) {
|
|
console.log("添加失败");
|
|
if (callback) callback(false);
|
|
}
|
|
})
|
|
} else {
|
|
if (callback) callback(false);
|
|
}
|
|
}
|
|
|
|
getVideoPath() {
|
|
return this.Recording?.getVideoPath();
|
|
}
|
|
|
|
getVideoTime() {
|
|
return this.Recording?.getVideoTime();
|
|
}
|
|
|
|
getStatus() {
|
|
return this.Recording?.getStatus();
|
|
}
|
|
|
|
/**
|
|
* 开始录屏
|
|
*/
|
|
startRecord() {
|
|
this.Recording && this.Recording.startVideo();
|
|
}
|
|
|
|
/**
|
|
* 停止录屏
|
|
*/
|
|
stopRecord() {
|
|
this.Recording && this.Recording.stopVideo();
|
|
}
|
|
|
|
/**
|
|
* 分享录屏
|
|
*/
|
|
shareRecord(callback = null) {
|
|
if (this.Recording)
|
|
this.Recording.shareVideo(callback);
|
|
else
|
|
callback(true);
|
|
}
|
|
|
|
pauseVideo() {
|
|
this.Recording && this.Recording.pauseVideo();
|
|
}
|
|
|
|
resumeVideo() {
|
|
this.Recording && this.Recording.resumeVideo();
|
|
}
|
|
/**
|
|
* 微信转发菜单
|
|
*/
|
|
openWXShareMenu() {
|
|
if (WECHAT) {
|
|
window["wx"].showShareMenu({
|
|
withShareTicket: true,
|
|
menus: ['shareAppMessage', 'shareTimeline'],
|
|
success: () => {
|
|
|
|
},
|
|
fail: () => {
|
|
|
|
},
|
|
complete: () => {
|
|
|
|
}
|
|
});
|
|
window["wx"].onShareAppMessage(function () {
|
|
let share1 = {
|
|
title: sdkConfig.config.platformConf.wxOnShareMessage.title,
|
|
imageUrlId: sdkConfig.config.platformConf.wxOnShareMessage.imageUrlId,
|
|
imageUrl: sdkConfig.config.platformConf.wxOnShareMessage.imageUrl
|
|
}
|
|
// let share2 = {
|
|
// title: "看不见下雨了嘛,赶紧回笼呀!",
|
|
// imageUrlId: 'Y2PHkx0bR0Ou2a25kg+Vsg==',
|
|
// imageUrl: 'https://mmocgame.qpic.cn/wechatgame/WFTmogtfYrIwyhRVuZHbEs709mroiaaDcYkwBSBXLckR7ibQNPfrXKstvCpyV4bmyn/0'
|
|
// }
|
|
// let share3 = {
|
|
// title: "快救救这只猪",
|
|
// imageUrlId: 'qTQmv6/8SCixA6raayq2HA==',
|
|
// imageUrl: 'https://mmocgame.qpic.cn/wechatgame/qqRAxsORaczSj5Z33DB4pneEtpOBstVwzhsk8VPh02OlTGHzPHBxicaffB09ILdgl/0'
|
|
// }
|
|
// return Math.random() > 0.5 ? share1 : Math.random() > 0.5 ? share2 : share3;
|
|
return share1
|
|
});
|
|
if (gg.data.getStatistics(StatisticsType.yonghuxieyiYS) == 0) {
|
|
window["wx"].getPrivacySetting({
|
|
success: (res) => {
|
|
console.log("是否需要授权:", res.needAuthorization);
|
|
console.log("隐私协议名称:", res.privacyContractName);
|
|
if (res.needAuthorization) {
|
|
window["wx"].requirePrivacyAuthorize({
|
|
success: () => {
|
|
// 用户同意授权
|
|
// runGame() 继续游戏逻辑
|
|
console.log("用户同意授权");
|
|
gg.data.setStatistics(StatisticsType.yonghuxieyiYS, 2);
|
|
},
|
|
fail: () => {
|
|
console.log("用户拒绝授权");
|
|
gg.data.setStatistics(StatisticsType.yonghuxieyiYS, 1);
|
|
|
|
}, // 用户拒绝授权
|
|
complete: () => { }
|
|
})
|
|
} else {
|
|
|
|
}
|
|
},
|
|
fail: () => {
|
|
// 低版本基础库不支持时的处理
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
getUserInfo() {
|
|
return new Promise((resolve, reject) => {
|
|
if (this._userInfo) {
|
|
resolve(this._userInfo);
|
|
return;
|
|
}
|
|
if (!this.pf || !this.pf["getUserInfo"]) {
|
|
resolve(null);
|
|
return;
|
|
}
|
|
this.pf["getUserInfo"]({
|
|
// withCredentials: true,
|
|
// withRealNameAuthenticationInfo: true,
|
|
success(res) {
|
|
console.log(`getUserInfo 调用成功`, res.userInfo, res.encryptedData, res.iv, res.signature);
|
|
this._userInfo = res.userInfo;
|
|
resolve(res.userInfo);
|
|
},
|
|
fail(res) {
|
|
console.log(`getUserInfo 调用失败`, res.errMsg);
|
|
resolve(null);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
private _userInfo: any = null;
|
|
|
|
|
|
|
|
//推荐流订阅
|
|
PlatformFeedSubscribe() {
|
|
console.log('PlatformFeedSubscribe')
|
|
let self = this
|
|
if (this.pf && window["tt"]) {
|
|
console.log('PlatformFeedSubscribe1')
|
|
let sceneId = 1 //离线收益场景
|
|
let self = this
|
|
if (this.pf.canIUse("checkFeedSubscribeStatus")) {
|
|
|
|
console.log('PlatformFeedSubscribe2')
|
|
this.pf.checkFeedSubscribeStatus({
|
|
type: "play",
|
|
scene: 1,
|
|
success(res) {
|
|
console.log("checkFeedSubscribeStatus:" + res.status)
|
|
if (!res.status) {
|
|
// 用户没订阅
|
|
let contentIDs = sdkConfig.config.platformConf.contentIDs || [];
|
|
console.log("contentIDs:", contentIDs)
|
|
|
|
self.pf.requestFeedSubscribe({
|
|
type: "play",
|
|
scene: sceneId,
|
|
contentIDs: contentIDs,
|
|
success(res) {
|
|
console.log("requestFeedSubscribe:" + res.success)
|
|
},
|
|
fail(res) {
|
|
console.log(res.errMsg)
|
|
},
|
|
})
|
|
|
|
|
|
}
|
|
},
|
|
fail(res) {
|
|
console.log(res.errMsg)
|
|
},
|
|
})
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//推荐流上报场景
|
|
PlatformReportScene() {
|
|
if (this.pf && this.pf.reportScene) {
|
|
this.pf.reportScene({
|
|
sceneId: 7001,
|
|
costTime: 2000,
|
|
|
|
success(res) {
|
|
// 上报接口执行完成后的回调,用于检查上报数据是否符合预期
|
|
console.log("ReportScene_success:" + res)
|
|
},
|
|
fail(res) {
|
|
// 上报报错时的回调,用于查看上报错误的原因:如参数类型错误等
|
|
console.log("ReportScene_fail:" + res)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
getLaunchOptionsSync_custom() {
|
|
if (this.pf && this.pf.getLaunchOptionsSync) {
|
|
let obj = this.pf.getLaunchOptionsSync();
|
|
let str = JSON.stringify(obj);
|
|
console.log("getLaunchOptionsSync_custom: 场景值", str)
|
|
///obj.scene
|
|
return obj
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
private _bindGlobalErrorReporter(): void {
|
|
try {
|
|
const pf = this.pf as MiniGameHostApi & {
|
|
onError?: (cb: (err: unknown) => void) => void;
|
|
onUnhandledRejection?: (cb: (res: { reason?: unknown }) => void) => void;
|
|
};
|
|
pf?.onError?.((err) => {
|
|
this.pfLogKey('RUNTIME:UNCAUGHT', err, 300000);
|
|
});
|
|
pf?.onUnhandledRejection?.((res) => {
|
|
this.pfLogKey('RUNTIME:PROMISE_REJECT', res?.reason, 300000);
|
|
});
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
/** 同 tag 平台日志冷却(毫秒),避免刷爆配额 */
|
|
private _pfLogKeyLastMs: Record<string, number> = {};
|
|
|
|
/**平台记录日志 */
|
|
pfLog(msg: string) {
|
|
if (this.pf && this.pf.getLogManager) {
|
|
this.pf.getLogManager().log(msg);
|
|
} else {
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 关键行为 / 失败 / 异常 → 平台日志(getLogManager + RealtimeLogManager)。
|
|
* @param tag 短标签,如 RES:PREFAB_FAIL、AD:REWARD_SHOW_FAIL
|
|
* @param detail 附加信息(字符串、Error 或可 JSON 序列化对象)
|
|
* @param cooldownMs 同 tag 冷却,默认 60s
|
|
*/
|
|
pfLogKey(tag: string, detail?: unknown, cooldownMs = 60000): void {
|
|
if (!tag) return;
|
|
const now = Date.now();
|
|
const last = this._pfLogKeyLastMs[tag] ?? 0;
|
|
if (now - last < cooldownMs) return;
|
|
this._pfLogKeyLastMs[tag] = now;
|
|
|
|
const ctx = this._formatPfLogContext();
|
|
const detailStr = this._stringifyPfDetail(detail);
|
|
let msg = detailStr ? `[${tag}] ${ctx} ${detailStr}` : `[${tag}] ${ctx}`;
|
|
if (msg.length > 250) msg = msg.slice(0, 250);
|
|
|
|
this.pfLog(msg);
|
|
|
|
try {
|
|
const pfAny = this.pf as { getRealtimeLogManager?: (opts?: { syncToConsole?: boolean }) => { error?: (m: string) => void } } | null;
|
|
const getRt = pfAny?.getRealtimeLogManager;
|
|
if (typeof getRt === 'function') {
|
|
getRt.call(pfAny, { syncToConsole: !!DEV })?.error?.(msg);
|
|
}
|
|
} catch { /* ignore */ }
|
|
|
|
if (DEV) {
|
|
console.warn('[pfLogKey]', msg);
|
|
}
|
|
}
|
|
|
|
private _formatPfLogContext(): string {
|
|
const b = gg.game?.CurentBattle;
|
|
const ch = b?.getReportDYChapterId?.() ?? gg.data?.doc?.chapter ?? 0;
|
|
const wave = b?.CurentWaveNum ?? 0;
|
|
return `ch${ch}_w${wave}`;
|
|
}
|
|
|
|
private _stringifyPfDetail(detail: unknown): string {
|
|
if (detail == null) return '';
|
|
if (typeof detail === 'string') return detail;
|
|
if (detail instanceof Error) return `${detail.name}:${detail.message}`;
|
|
try {
|
|
const s = JSON.stringify(detail);
|
|
return s.length > 140 ? s.slice(0, 140) : s;
|
|
} catch {
|
|
return String(detail);
|
|
}
|
|
}
|
|
/**平台记录调试日志 */
|
|
pfDebug(msg: string) {
|
|
if (this.pf && this.pf.getLogManager) {
|
|
this.pf.getLogManager().debug(msg);
|
|
} else {
|
|
console.debug(msg);
|
|
}
|
|
}
|
|
|
|
ttWorker(callback) {
|
|
// if (this.pf && this.pf.createWorker) {
|
|
// let worker = this.pf.createWorker({
|
|
// scriptPath: "worker.js",
|
|
// success: (res) => {
|
|
// console.log("createWorker success", res);
|
|
// callback(res);
|
|
// },
|
|
// fail: (res) => {
|
|
// console.log("createWorker fail", res);
|
|
// },
|
|
// });
|
|
// }
|
|
}
|
|
|
|
/** 界面打开失败等场景:附带最近一次内存告警等级与距今秒数 */
|
|
getMemWarnState(): { level: number | null; agoSec: number | null } {
|
|
if (this._lastMemWarnLevel == null) {
|
|
return { level: null, agoSec: null };
|
|
}
|
|
return {
|
|
level: this._lastMemWarnLevel,
|
|
agoSec: Math.floor((Date.now() - this._lastMemWarnMs) / 1000),
|
|
};
|
|
}
|
|
|
|
formatMemWarnForPf(): string {
|
|
const s = this.getMemWarnState();
|
|
if (s.level == null) {
|
|
return 'memLv=na';
|
|
}
|
|
return `memLv=${s.level}|memAgo=${s.agoSec}s`;
|
|
}
|
|
|
|
formatMemWarnForDy(): string {
|
|
const s = this.getMemWarnState();
|
|
if (s.level == null) {
|
|
return 'memna';
|
|
}
|
|
return `mem${s.level}_ago${s.agoSec}s`;
|
|
}
|
|
|
|
/**
|
|
* 平台 triggerGC(已禁用)。
|
|
* 线上数据 8/21 卡顿率 11%→14% 与进/离局 force GC 高度相关;改由 releaseBundle + 内存压力节流。
|
|
*/
|
|
pfTriggerGC(_force = false): boolean {
|
|
if (!SDKManager.PF_GC_ENABLED) {
|
|
return false;
|
|
}
|
|
if (!this.pf?.triggerGC) {
|
|
return false;
|
|
}
|
|
const now = Date.now();
|
|
if (!_force && now - this._lastPfGcMs < SDKManager.PF_GC_COOLDOWN_MS) {
|
|
return false;
|
|
}
|
|
this._lastPfGcMs = now;
|
|
try {
|
|
this.pf.triggerGC();
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** 平台内存警告(callback 参数含 level,Android/鸿蒙等) */
|
|
onMemoryWarning(callback: ((res?: { level?: number }) => void) | null = null) {
|
|
if (this.pf && this.pf.onMemoryWarning) {
|
|
this.pf.onMemoryWarning((res?: { level?: number }) => {
|
|
callback?.(res);
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|