消消方块阵换皮表情
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.
 
 
 

328 lines
12 KiB

import { sys } from 'cc';
/** 小游戏宿主类型(与 window 全局 API 对象一一对应) */
export enum MiniGameKind {
None = 'none',
WeChat = 'wx',
Douyin = 'tt',
Kuaishou = 'ks',
Bilibili = 'bl',
Oppo = 'qg',
QQ = 'qq',
}
declare global {
interface Window {
wx?: MiniGameHostApi;
tt?: MiniGameHostApi;
ks?: MiniGameHostApi;
bl?: MiniGameHostApi;
qg?: MiniGameHostApi;
qq?: MiniGameHostApi;
}
}
/** 各平台 SDK 共有能力的最小接口(扩展字段均为可选) */
export interface MiniGameHostApi {
getSystemInfoSync?: () => SystemInfoLike;
createRewardedVideoAd?: (opts: { adUnitId: string }) => RewardedVideoAdLike;
createInterstitialAd?: (opts: { adUnitId: string }) => InterstitialAdLike;
onShow?: (cb: (res?: Record<string, unknown>) => void) => void;
onHide?: (cb: () => void) => void;
onMemoryWarning?: (cb: (res?: { level?: number }) => void) => void;
triggerGC?: () => void;
vibrateShort?: (opts?: object) => void;
vibrateLong?: (opts?: object) => void;
reportScene?: (opts: { sceneId: number; costTime?: number; success?: (res: unknown) => void; fail?: (res: unknown) => void }) => void;
login?: (opts: object) => void;
shareAppMessage?: (opts: object) => void;
onShareAppMessage?: (cb: () => object) => void;
showShareMenu?: (opts: object) => void;
getUpdateManager?: () => UpdateManagerLike;
showModal?: (opts: object) => void;
showToast?: (opts: object) => void;
restartMiniProgramSync?: () => void;
getUserInfo?: (opts: object) => void;
getLogManager?: () => { log: (msg: string) => void; debug: (msg: string) => void };
getLaunchOptionsSync?: () => Record<string, unknown>;
canIUse?: (api: string) => boolean;
checkFeedSubscribeStatus?: (opts: object) => void;
requestFeedSubscribe?: (opts: object) => void;
navigateToScene?: (opts: object) => void;
checkSliderBarIsAvailable?: (opts: object) => void;
addShortcut?: (opts: object) => void;
getPrivacySetting?: (opts: object) => void;
requirePrivacyAuthorize?: (opts: object) => void;
[key: string]: unknown;
}
interface UpdateManagerLike {
onUpdateReady?: (cb: (res: unknown) => void) => void;
onUpdateFailed?: (cb: (err: unknown) => void) => void;
onCheckForUpdate?: () => void;
applyUpdate?: () => void;
}
interface SystemInfoLike {
platform?: string;
system?: string;
SDKVersion?: string;
brand?: string;
model?: string;
}
interface RewardedVideoAdLike {
load?: () => Promise<void>;
show?: () => Promise<void>;
onLoad?: (cb: () => void) => void;
onError?: (cb: (err: unknown) => void) => void;
onClose?: (cb: (res?: { isEnded?: boolean }) => void) => void;
offLoad?: (cb: () => void) => void;
offError?: (cb: (err: unknown) => void) => void;
offClose?: (cb: (res?: { isEnded?: boolean }) => void) => void;
destroy?: () => void;
}
interface InterstitialAdLike {
load?: () => Promise<void>;
show?: () => Promise<void>;
onClose?: (cb: () => void) => void;
destroy?: () => void;
}
/**
* 小游戏平台识别与能力查询。
* 以 Cocos sys.platform 为准,window 全局仅作 API 绑定;避免 ks/tt/wx 优先级不一致。
*/
export class MiniGamePlatform {
private static _inited = false;
private static _kind = MiniGameKind.None;
private static _pf: MiniGameHostApi | null = null;
private static _isHarmonyOS = false;
private static _systemInfo: SystemInfoLike | null = null;
static init(): void {
if (this._inited) return;
this._inited = true;
this._kind = this._detectKind();
this._pf = this._bindPf(this._kind);
this._systemInfo = this._readSystemInfo();
this._isHarmonyOS = this._detectHarmonyOS(this._systemInfo);
console.log(`[MiniGamePlatform] kind=${this._kind} harmony=${this._isHarmonyOS}`);
}
static get kind(): MiniGameKind {
this.init();
return this._kind;
}
static get pf(): MiniGameHostApi | null {
this.init();
return this._pf;
}
static get isHarmonyOS(): boolean {
this.init();
return this._isHarmonyOS;
}
static get systemInfo(): SystemInfoLike | null {
this.init();
return this._systemInfo;
}
/** 抖音 / 快手 / B 站:全屏激励视频,平台挂起 JS,禁止 game.pause() */
static isFullScreenRewardedVideoHost(): boolean {
const k = this.kind;
return k === MiniGameKind.Douyin
|| k === MiniGameKind.Kuaishou
|| k === MiniGameKind.Bilibili;
}
/** 微信 overlay 激励视频:需 game.pause() 冻结主循环 */
static shouldPauseEngineForVideoAd(): boolean {
return this.kind === MiniGameKind.WeChat;
}
/**
* 激励视频拉取前检查网络(微信/抖音等均提供 getNetworkType,networkType=none 表示无网)。
* 无 API 时默认 true,避免误拦。
*/
static checkNetworkForRewardedVideo(): Promise<boolean> {
if (sys.isBrowser) {
const online = typeof navigator === 'undefined' ? true : navigator.onLine !== false;
return Promise.resolve(online);
}
this.init();
const pf = this._pf as MiniGameHostApi & {
getNetworkType?: (opts: {
success?: (res: { networkType?: string }) => void;
fail?: () => void;
}) => void;
};
const getNetworkType = pf?.getNetworkType;
if (typeof getNetworkType !== 'function') {
return Promise.resolve(true);
}
return new Promise((resolve) => {
getNetworkType.call(pf, {
success: (res) => {
const type = (res?.networkType ?? '').toLowerCase();
resolve(type !== 'none');
},
fail: () => resolve(false),
});
});
}
/**
* 微信 / 快手:createRewardedVideoAd 为全局单例,禁止 destroy 后频繁重建。
* @see https://developers.weixin.qq.com/minigame/dev/guide/open-ability/ad/rewarded-video-ad.html
* @see https://open.kuaishou.com/miniGameDocs/gameDev/open-function/ad.html
*/
static isRewardedVideoAdSingleton(): boolean {
const k = this.kind;
return k === MiniGameKind.WeChat || k === MiniGameKind.Kuaishou;
}
/**
* B 站激励视频展示前上报 sceneId=1007(官方示例要求)。
* @see https://miniapp.bilibili.com/small-game-doc/open/ad/IncentiveVideo
*/
static reportRewardedVideoScene(): void {
if (this.kind !== MiniGameKind.Bilibili) return;
try {
this.pf?.reportScene?.({ sceneId: 1007 });
} catch (e) {
console.warn('[MiniGamePlatform] reportScene(1007) failed', e);
}
}
/** 激励视频是否正常播完(各平台 onClose 语义) */
static isRewardedVideoCompleted(res?: { isEnded?: boolean } | null): boolean {
if (res == null) {
// 微信 <2.1.0 基础库 res 为 undefined 表示完整观看;快手无此兼容
return this.kind === MiniGameKind.WeChat;
}
return !!res.isEnded;
}
/**
* 官方兼容性:优先 canIUse,其次函数是否存在。
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/basic-library/compatibility-description/
*/
static canIUse(api: string): boolean {
this.init();
const pf = this._pf;
if (!pf) return false;
try {
if (typeof pf.canIUse === 'function') {
return !!pf.canIUse(api);
}
} catch {
/* ignore */
}
return typeof (pf as Record<string, unknown>)[api] === 'function';
}
/**
* 安全调用异步开放 API(鸿蒙/低版本不支持时走 fail,不抛同步异常阻塞业务)。
* 官方:不支持时 errNo=10302,应避免直接调用。
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/open-capabilities/general-capabilities/miniapp-harmony-adapter
*/
static invokeAsync(
api: string,
opts: Record<string, unknown> = {},
): boolean {
this.init();
const pf = this._pf;
const fail = typeof opts.fail === 'function' ? opts.fail as (err: unknown) => void : null;
if (!pf || !this.canIUse(api)) {
fail?.({
errNo: 10302,
errorCode: 88,
errMsg: `${api}:fail The feature is not available in current operating system`,
});
return false;
}
const fn = (pf as Record<string, unknown>)[api];
if (typeof fn !== 'function') {
fail?.({
errNo: 10302,
errorCode: 88,
errMsg: `${api}:fail not a function`,
});
return false;
}
try {
(fn as (o: object) => void).call(pf, opts);
return true;
} catch (e) {
fail?.(e);
return false;
}
}
/** 鸿蒙上视频挂载/录屏分享等能力常不可用,调用前用此判断做降级 */
static isDouyinVideoOpenCapabilityAvailable(api: string): boolean {
if (this.kind !== MiniGameKind.Douyin) return this.canIUse(api);
if (this.isHarmonyOS && !this.canIUse(api)) return false;
return this.canIUse(api);
}
private static _detectKind(): MiniGameKind {
switch (sys.platform) {
case sys.Platform.WECHAT_GAME:
return MiniGameKind.WeChat;
case sys.Platform.BYTEDANCE_MINI_GAME:
return MiniGameKind.Douyin;
default:
break;
}
// 非 Cocos 枚举覆盖的宿主(快手/B 站/OPPO/QQ)按 window 全局识别
if (typeof window !== 'undefined') {
if (window.ks) return MiniGameKind.Kuaishou;
if (window.bl) return MiniGameKind.Bilibili;
if (window.tt) return MiniGameKind.Douyin;
if (window.qg) return MiniGameKind.Oppo;
if (window.qq) return MiniGameKind.QQ;
if (window.wx) return MiniGameKind.WeChat;
}
return MiniGameKind.None;
}
private static _bindPf(kind: MiniGameKind): MiniGameHostApi | null {
if (typeof window === 'undefined') return null;
switch (kind) {
case MiniGameKind.WeChat: return window.wx ?? null;
case MiniGameKind.Douyin: return window.tt ?? null;
case MiniGameKind.Kuaishou: return window.ks ?? null;
case MiniGameKind.Bilibili: return window.bl ?? null;
case MiniGameKind.Oppo: return window.qg ?? null;
case MiniGameKind.QQ: return window.qq ?? null;
default: return null;
}
}
private static _readSystemInfo(): SystemInfoLike | null {
try {
return this._pf?.getSystemInfoSync?.() ?? null;
} catch {
return null;
}
}
/**
* 抖音鸿蒙识别(OpenHarmony / HarmonyOS NEXT)。
* 官方建议优先用 system.includes('openharmony')(真机+IDE 通用);
* platform==='openHarmony' 仅真机可靠。
* @see https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/tutorial/open-capabilities/general-capabilities/miniapp-harmony-adapter
*/
private static _detectHarmonyOS(info: SystemInfoLike | null): boolean {
if (!info) return false;
const system = (info.system ?? '').toLowerCase();
if (system.includes('openharmony')) return true;
return (info.platform ?? '') === 'openHarmony'
|| (info.platform ?? '').toLowerCase() === 'openharmony';
}
}