cb/assets/Script/Sdk/MiniGameSdk.ts
2025-08-05 17:59:54 +08:00

1383 lines
52 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 小游戏平台SDK工具封装目前只支持微信和抖音平台
*/
export namespace MiniGameSdk {
interface ISize {
width: number;
height: number;
}
export interface IPosition {
top: number;
left: number;
}
export function isWechat(): boolean {
//@ts-ignore
return window.wx !== null && window.wx !== undefined;
}
export function isBytedance(): boolean {
//@ts-ignore
return window.tt !== null && window.tt !== undefined;
}
function getSysWinSize(): ISize {
let sys: any;
if (isWechat()) {
// @ts-ignore
sys = wx.getSystemInfoSync();
} else if (isBytedance()) {
// @ts-ignore
sys = tt.getSystemInfoSync();
}
let size: ISize = { width: 0, height: 0 };
if (sys) {
size.width = sys.windowWidth;
size.height = sys.windowHeight;
}
return size;
}
/**
* 插屏广告。微信抖音都支持!
*/
class ADInterstitial {
private _adUid: string;
private _interstitial: any;
get aduid() {
return this._adUid;
}
constructor(adUid: string) {
this._adUid = adUid;
}
show() {
// @ts-ignore
if (isWechat() && !wx.createInterstitialAd) {
console.warn('wechat unsupport interstitial AD!');
this._interstitial = null;
return;
}
// @ts-ignore
if (isBytedance() && !tt.createInterstitialAd) {
console.warn('bytedance unsupport interstitial AD!');
this._interstitial = null;
return;
}
if (this._interstitial) {
this._interstitial.load();
} else {
if (isWechat()) {
// @ts-ignore
this._interstitial = wx.createInterstitialAd({ adUnitId: this._adUid });
} else if (isBytedance()) {
// @ts-ignore
this._interstitial = tt.createInterstitialAd({ adUnitId: this._adUid });
} else {
this._interstitial = null;
}
this._interstitial?.onLoad(() => {
console.log('load interstitial ad success');
this._interstitial.show().catch((err: any) => {
console.log('catch interstitial ad error:', err);
});
});
this._interstitial?.onError((err: any) => {
console.log('interstitial ad on error:', err);
});
}
}
destory() {
this._interstitial?.destroy();
}
}
class ADBanner {
private _adUid: string;
private _banner: any;
get aduid() {
return this._adUid;
}
/**
* 抖音和微信都支持
* 横幅广告。预估宽度默认为300预估高度为140。如果你不确定就按默认值来。
* @param adUid 广告UID后端配置
* @param isTop 是否在屏幕顶部展示。内部会自动居中计算位置。
* @param bannerWidth 横幅广告的预估宽度。默认300
* @param autoShow 广告加载完成后是否立刻显示,默认为不显示
*/
constructor(adUid: string, param: boolean | IPosition, bannerWidth: number = 300, autoShow: boolean = false) {
this._adUid = adUid;
this.create(autoShow, bannerWidth, param); // 默认300比较合适
}
private create(autoShow: boolean, bannerWidth: number, param: boolean | IPosition) {
if (!isWechat() && !isBytedance()) {
this._banner = null;
return;
}
this.destroy();
let winSize = getSysWinSize();
let height = bannerWidth * 0.4;
let top = 0, left = 0;
if (typeof param === "boolean") {
left = (winSize.width - bannerWidth) / 2
top = param ? 5 : (winSize.height - height);
} else {
left = param.left;
top = param.top;
}
let params = {
adUnitId: this._adUid,
adIntervals: 30,// 自动刷新频率不能小于30秒
style: { left: left, top: top, width: bannerWidth }
}
if (isWechat()) {
// @ts-ignore
this._banner = wx.createBannerAd(params);
} else if (isBytedance()) {
// @ts-ignore
this._banner = tt.createBannerAd(params);
} else {
this._banner = null;
}
this._banner?.onError((err: any) => {
console.log('ad banner error:', err);
});
this._banner?.onLoad(() => {
autoShow && this._banner.show();
});
}
show() {
this._banner?.show();
}
hide() {
this._banner?.hide();
}
destroy() {
this._banner?.destroy();
}
}
class ADCustom {
private _adUid: string;
private _adCustom: any;
get aduid() {
return this._adUid;
}
/**
* 由于原生模板广告在微信服务后端可以定制宽度大小,个数,缩放比例等,所以位置调整要根据设置的宽度来定。抖音不支持!
* @param adUid 广告UID后端配置
* @param top 从左上角开始距离屏幕顶部的距离。注意这个数据为设备屏幕宽度width。如果需要获取屏幕的像素需要乘以设备像素比Pixel-Ratio例如iPhone 13 Pro的Pixel-Ratio为3像素为Width*3。
* @param left 从左上角开始距离屏幕最左边的距离。注意这个数据为设备屏幕宽度width。如果需要获取屏幕的像素需要乘以设备像素比Pixel-Ratio例如iPhone 13 Pro的Pixel-Ratio为3像素为Width*3。
* @param scale 原生模板广告的尺寸默认为1即100%。此值在微信服务后端广告中获得默认为100%目前有100%90%80%三种一般情况不用修改。若有修改记得传入值例如90%就传入0.9。
*/
constructor(adUid: string, top: number = 0, left: number = 0, scale: number = 1.0) {
this._adUid = adUid;
this.createCustomAd(top, left, scale);
}
private createCustomAd(top: number, left: number, scale: number) {
if (!isWechat()) { // only wechat support custom ad
this._adCustom = null;
console.log('Only wechat support Custom Ad');
return;
}
this.destroy();
// 原生模板5个应用宽度为375若设置了缩放比例则宽度也需要设置
// let width = 375 * this._scale;
// let newLeft = (sys.windowWidth - width) / 2;
// let newTop = sys.windowHeight / 2; // 120是预估高度
// @ts-ignore
this._adCustom = wx.createCustomAd({
adUnitId: this._adUid,
//@ts-ignore
style: { left: left, top: top, fixed: true }
});
this._adCustom?.onError((err: any) => {
console.log('ad custom error:', err);
});
}
show() {
this._adCustom?.show();
}
hide() {
this._adCustom?.hide();
}
destroy() {
this._adCustom?.destroy();
}
}
/**
* 视频广告用户点击行为结果
*/
export enum EAdVideoResult {
/**
* 用户看完了广告,游戏可发放奖励。
*/
ACCEPT,
/**
* 用户中途关闭了广告,即未看完状态。不可发放奖励。
*/
REJECT,
/**
* 广告组件内部发生了错误。不可发放奖励。
*/
ERROR,
}
class ADVideo {
private _adUid: string;
private _adVideo: any = null;
get aduid() {
return this._adUid;
}
constructor(adUid: string) {
this._adUid = adUid;
}
/**
* 由于微信和抖音视频广告机制不同微信可以看的视频广告个数只有0和1个抖音平台则可以看0~maxVideoCount
* @param onResult 两个参数第一个res是EAdVideoResult定义第二count是用户看了多少个视频广告。
* @param target onResult的拥有者
* @param maxVideoCount 可以连续看最大视频个数可最大化商业效率。默认为3个。
* @returns
*/
show(onResult: (res: EAdVideoResult, count: number) => void, target?: any, maxVideoCount: number = 3): void {
let callback = (state: EAdVideoResult, count: number) => {
onResult?.call(target, state, count);
}
if (!isWechat() && !isBytedance()) {
callback(EAdVideoResult.ACCEPT, 1);
this._adVideo = null;
return;
}
let onAdVideoClosed = (res: any) => {
this._adVideo?.offClose(onAdVideoClosed);
if (isWechat()) {
if (res && res.isEnded || res === undefined) {
callback(EAdVideoResult.ACCEPT, 1);
} else {
callback(EAdVideoResult.REJECT, 0);
}
} else if (isBytedance()) {
let resConverted = res as { isEnded: boolean, count: number };
if (resConverted && resConverted.count > 0) {
callback(EAdVideoResult.ACCEPT, resConverted.count);
} else {
callback(EAdVideoResult.REJECT, 0);
}
}
}
this._adVideo?.offClose(onAdVideoClosed);
if (isWechat()) {
// @ts-ignore
this._adVideo = wx.createRewardedVideoAd({
adUnitId: this._adUid
});
} else if (isBytedance()) {
// @ts-ignore
this._adVideo = tt.createRewardedVideoAd({
adUnitId: this._adUid,
multiton: true,
multitonRewardMsg: ['多1次奖励', '再多一次奖励', '再多一次奖励'],
multitonRewardTimes: maxVideoCount,
});
} else {
this._adVideo = null;
}
this._adVideo?.onLoad(() => {
console.log('Ad load success');
});
this._adVideo?.onError((err: { errMsg: string, errCode: number }) => {
console.log('Ad video error:', err);
callback(EAdVideoResult.ERROR, 0);
});
this._adVideo?.onClose(onAdVideoClosed);
this._adVideo?.show().catch(() => {
this._adVideo?.load().then(() =>
this._adVideo?.show()).catch((err: { errMsg: string, errCode: number }) => {
console.log('Catch video ad error:', err);
callback(EAdVideoResult.ERROR, 0);
});
});
}
destory() {
this._adVideo?.destory();
}
}
export enum EAdBannerLocation {
/**
* 屏幕顶部
*/
TOP,
/**
* 屏幕底部
*/
BOTTOM,
}
export class AdvertManager {
private static _instance: AdvertManager;
static get instance(): AdvertManager {
if (!AdvertManager._instance) {
AdvertManager._instance = new AdvertManager();
}
return AdvertManager._instance;
}
private _video: ADVideo;
private _interstitial: ADInterstitial;
private _banner: ADBanner;
private _customs: Record<string, ADCustom> = {};
private constructor() {
}
/**
* 预加载横幅广告不会显示。只有你在调用showBanner时才会显示。
* 可重复调用,但是会销毁上一次的实例。一般情况,全局有一个就行了,太多占用内存,而且没必要。
* @param adUid 广告UID
* @param location 位置有两种情况1、可以传入枚举值默认上方; 2、可以自定义位置传入IPosition注意IPosition中的top和left跟平台的top,left是一致没有乘以设备像素比ratio需要开发者自己调试位置
* @param scale 默认为跟屏幕一样的宽度,可以通过设置缩放比例来调整大小。当然,平台有规定最大或最小宽度,函数内部会自动计算。
*/
public loadBanner(adUid: string, location: EAdBannerLocation | IPosition = EAdBannerLocation.TOP, scale: number = 1.0) {
this._banner?.destroy();
let size: ISize = getSysWinSize();
// 当 style.width 小于 300 时,会取作 300。 当 style.width 大于屏幕宽度时,会取作屏幕宽度。
let width = size.width * scale;
width = width < 300 ? 300 : width; // 最小值矫正
width = width > size.width ? size.width : width; //最大值矫正
this._banner = typeof location === 'number' ? new ADBanner(adUid, location === EAdBannerLocation.TOP, width, false) : new ADBanner(adUid, location, width, false);
}
/**
* 显示横幅广告
*/
public showBanner() {
if (this._banner) {
this._banner.show();
} else {
console.warn('MiniGameSDK: banner is null, you must call loadBanner(...) first!');
}
}
/**
* 隐藏横幅广告
*/
public hideBanner() {
this._banner?.hide();
}
/**
* 弹出插屏广告
* @param adUid 广告单元id
*/
public showInterstitial(adUid: string) {
if (this._interstitial && this._interstitial.aduid === adUid) {
this._interstitial.show();
} else {
this._interstitial?.destory();
this._interstitial = new ADInterstitial(adUid);
this._interstitial.show();
}
}
/**
* 加载原生模板广告不会显示。只有你在调用showCustom时才会显示。
* 由于原生模板广告在微信服务后端可以定制宽度大小,个数,缩放比例等,所以位置调整要根据设置的宽度来定。抖音不支持本函数,会调用无效!
* @param adUid 广告ID
* @param location 位置有两种情况1、可以传入枚举值默认上方; 2、可以自定义位置传入IPosition注意IPosition中的top和left跟平台的top,left是一致没有乘以设备像素比ratio需要开发者自己调试位置
* @param scale 缩放比例默认是1即不缩放。这个缩放并不是自己填而是根据微信MP后台你配置的原生模板广告的缩放比例填目前有100%90%80%三种一般情况不用修改。若有后台修改记得传入值例如90%就传入0.9。
*/
public loadCustom(adUid: string, location: IPosition = { top: 0, left: 0 }, scale: number = 1) {
// this._custom?.destroy();
// this._custom = new ADCustom(adUid, location.top, location.left, scale);
if (this._customs[adUid]) {
console.log(`${adUid} has been loaded.`);
return;
}
this._customs[adUid] = new ADCustom(adUid, location.top, location.left, scale);
}
/**
* 显示自定义广告。
* @param adUid 广告的唯一标识符。使用此标识符来查找和显示特定的自定义广告。
*
* 此方法尝试根据提供的adUid显示一个自定义广告。如果给定的adUid对应的自定义广告已加载
* 则调用该广告的显示方法。如果广告未加载,则在控制台输出警告信息。
*/
public showCustom(adUid: string) {
if (this._customs[adUid]) {
this._customs[adUid].show();
} else {
console.warn(`You have not load ${adUid} of Custom AD, can not show!`);
}
}
/**
* 隐藏指定的自定义广告单元
*
* 此方法用于隐藏通过广告单元标识符adUid指定的自定义广告。如果指定的广告单元已加载并显示
* 则将其隐藏;如果广告单元未加载,则在控制台输出警告信息。
*
* @param adUid 广告单元标识符,用于唯一标识一个自定义广告单元。
*/
public hideCustom(adUid: string) {
if (this._customs[adUid]) {
this._customs[adUid].hide();
} else {
console.warn(`You have not load ${adUid} of Custom AD, can not hide!`);
}
}
/**
* 由于微信和抖音视频广告机制不同微信可以看的视频广告个数只有0和1个抖音平台则可以看0~maxVideoCount
* @param adUid 广告ID。如果与上一次UID不同则内部会重新创建实例。开发者完全不用关心这个细节。
* @param onVideoResult 两个参数第一个res是EAdVideoResult定义第二count是用户看了多少个视频广告。
* @param target onVideoResult的拥有者
* @param maxVideoCount 最大视频个数。默认是3仅对抖音平台生效。微信平台看完视频count的结果永远是1或0
*/
public showVideo(adUid: string, onVideoResult: (res: EAdVideoResult, count: number) => void, target?: any, maxVideoCount: number = 3) {
if (this._video && this._video.aduid === adUid) {
this._video.show(onVideoResult, target, maxVideoCount);
} else {
this._video?.destory();
this._video = new ADVideo(adUid);
this._video.show(onVideoResult, target, maxVideoCount);
}
}
/**
* 销毁内部所有实例,清空内存
*/
public destroyAll() {
this._banner?.destroy();
this._banner = null;
this._interstitial?.destory();
this._interstitial = null;
this._video?.destory();
this._video = null;
if (this._customs) {
for (let val in this._customs) {
this._customs[val]?.destroy();
}
this._customs = {};
}
}
}
export enum EGameClubIcon {
/** 绿色图标 */
GREEN = 'green',
/** 红色图标 */
WHITE = 'white',
/** 有黑色圆角背景的白色图标 */
DARK = 'dark',
/** 有白色圆角背景的绿色图标 */
LIGHT = 'light'
}
export class GameClub {
private static _instance: GameClub;
static get instance(): GameClub {
if (!this._instance) {
this._instance = new GameClub();
}
return this._instance;
}
private _club: any;
private constructor() {
}
/**
* 创建游戏圈按钮
* @param icon
* @param position
* @param size
* @param openLink
*/
create(icon: EGameClubIcon = EGameClubIcon.GREEN, position: IPosition = { top: 0, left: 0 }, size: ISize = { width: 40, height: 40 }, openLink?: string) {
if (isWechat()) {
// @ts-ignore
this._club = wx.createGameClubButton({
icon: icon,
style: {
left: position.left,
top: position.top,
width: size.width,
height: size.height
},
openlink: openLink
});
}
}
show() {
this._club?.show();
}
hide() {
this._club?.hide();
}
destory() {
this._club?.destroy();
}
}
/**
* 振动类型
*/
export enum EVirbrateType {
/**
* 短振动
*/
SHORT,
/**
* 长振动
*/
LONG
}
/**
* 抖音侧边栏专属接口
*/
export class BytedanceSidebar {
/**
* 本游戏在抖音环境下启动监控,需要放在全局环境中,保证能第一时间启动。因为可能监听抖音失败(抖音小游戏官方的说明)!
* @param onResult 包含一个boolean参数的函数
* @param target 上述函数的拥有者如果是类的成员函数需要传入this。普通或匿名函数忽略即可。
*/
static listenFromSidebar(onResult: (success: boolean) => void, target?: any) {
if (!isBytedance()) {
onResult?.call(target, false);
return;
}
// @ts-ignore
tt.onShow((res: any) => {
console.log('onShow launch res:', res);
if (res.scene === '021036') {
onResult?.call(target, true);
console.log('launch from sidebar');
} else {
onResult?.call(target, false);
console.log('NOT launch from douyin sidebar!');
}
});
// @ts-ignore
let options = tt.getLaunchOptionsSync();
if (options && options.scene === '021036') {
onResult?.call(target, true);
}
}
/**
* 检测抖音侧边栏是否存在
* @param onResult 包含一个boolean参数的函数
* @param target 上述函数的拥有者如果是类的成员函数需要传入this。普通或匿名函数忽略即可。
* @returns
*/
static checkSideBar(onResult: (success: boolean) => void, target?: any) {
if (!isBytedance()) {
onResult?.call(target, false);
return;
}
//@ts-ignore
tt.checkScene({
scene: "sidebar",
success: (res: any) => {
console.log("check scene success: ", res.isExist);
onResult?.call(target, <boolean>res.isExist);
},
fail: (res: any) => {
console.log("check scene fail:", res);
onResult?.call(target, false);
}
});
}
/**
* 跳转到抖音侧边栏
* @param onResult 包含一个boolean参数的函数
* @param target 上述函数的拥有者如果是类的成员函数需要传入this。普通或匿名函数忽略即可。
* @returns
*/
static navigateToSidebar(onResult: (success: boolean) => void, target?: any) {
if (!isBytedance()) {
console.log("not douyin platform!");
onResult?.call(target, false);
return;
}
// @ts-ignore
tt.navigateToScene({
scene: "sidebar",
success: () => {
console.log("navigate success");
onResult?.call(target, true);
},
fail: (res: any) => {
console.log("navigate failed reason:", res);
onResult?.call(target, false);
},
});
}
}
/**
* 平台常用API合集
*/
export class API {
private static _loginCode: string = null;
private static _loginAnonymousCode: string = null;
private static _hasInitWechatCloudFunction: boolean = false;
private static _userInfo: any = null;
private static _ta: any = null;
private static _ge: any = null;
/**
* 分享app给朋友微信小游戏分享是没有onSuccess回调的。
* @param title 标题
* @param description 细节描述信息
* @param imageUrl 图片地址
* @param query 查询信息
* @param onSuccess 抖音会回调,微信不会回调
*/
static shareAppToFriends(title: string, description: string = '', imageUrl?: string, query?: string, onSuccess?: () => void) {
if (isWechat()) {
try {
//@ts-ignore
wx.shareAppMessage({
title: title,
imageUrl: imageUrl,
query: query,
});
} catch (err) {
console.log(`share faild: ${err}`);
}
}
if (isBytedance()) {
//@ts-ignore
tt.shareAppMessage({
title: title,
desc: description,
imageUrl: imageUrl ?? '',
query: query ?? '',
success(res: any) {
console.log('share success:', res);
onSuccess?.();
},
fail(res: any) {
console.log('share fail:', res);
}
});
}
}
/**
* 显示提示信息
* @param title 标题
* @param duration 时长(单位:秒)
* @returns
*/
static showToast(title: string, duration: number = 2) {
if (isWechat()) {
// @ts-ignore
wx.showToast({
title: title,
icon: 'none',
duration: duration * 500
});
}
}
/**
* 设备震动效果,默认为短震动。注意:可能一些机型不会生效,具体看平台方的说明
* @param type MiniGameSdk.API.EVirbrateType
*/
static vibrate(type: EVirbrateType = EVirbrateType.SHORT) {
if (isWechat()) {
switch (type) {
case EVirbrateType.SHORT:
//@ts-ignore
wx.vibrateShort({
success(res: any) {
console.log('vibrate success:', res);
},
fail(res: any) {
console.log('vibrateShort failed', res);
},
});
break;
case EVirbrateType.LONG:
//@ts-ignore
wx.vibrateLong({
success(res: any) {
console.log('vibrate success', res);
},
fail(res: any) {
console.log(`vibrateLong failed`, res);
},
});
break;
default:
break;
}
}
if (isBytedance()) {
switch (type) {
case EVirbrateType.SHORT:
//@ts-ignore
tt.vibrateShort({
success(res: any) {
console.log('vibrate success:', res);
},
fail(res: any) {
console.log('vibrateShort failed', res);
},
});
break;
case EVirbrateType.LONG:
//@ts-ignore
tt.vibrateLong({
success(res: any) {
console.log('vibrate success', res);
},
fail(res: any) {
console.log(`vibrateLong failed`, res);
},
});
break;
default:
break;
}
}
}
/**
* 重启小游戏
*/
static reboot() {
if (isWechat()) {
//@ts-ignore
wx.restartMiniProgram({
success: () => {
console.log('restart success');
},
fail: () => {
console.log('restart failed');
}
})
}
if (isBytedance()) {
try {
// @ts-ignore
tt.restartMiniProgramSync();
} catch (error) {
console.log(`restartMiniProgramSync`, error);
}
}
}
/**
* 退出小游戏
*/
static exit() {
if (isWechat()) {
//@ts-ignore
wx.exitMiniProgram({
success: () => {
console.log('exit success');
},
fail: () => {
console.log('exit failed');
}
});
}
if (isBytedance()) {
// @ts-ignore
tt.exitMiniProgram({
success(res: any) {
console.log("exit success:", res?.data);
},
fail(res: any) {
console.log("exit fail:", res?.errMsg);
},
});
}
}
/**
* 显示转发按钮。通常在刚进入游戏的时候调用。
* 主要是打开平台“...”这个按钮里面的分享菜单,一般默认是关闭的,需要调用这个函数打开。可以让用户分享你的游戏入口。
*/
static showShareMenu() {
if (isWechat()) {
//@ts-ignore
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline'],
success: () => { },
fail: () => { },
complete: () => { }
});
}
}
/**
* 微信小游戏:跳转到另外一款小游戏
* 抖音小游戏:跳转到指定的视频界面
* @param targetId 微信小游戏appid或者视频界面
*/
static navigateTo(targetId: string, onSuccess?: () => void) {
if (isWechat()) {
// @ts-ignore
wx.navigateToMiniProgram({
appId: targetId,
extraData: {
foo: 'bar'
},
envVersion: 'develop',
success(res: any) {
onSuccess?.();
}
});
}
if (isBytedance()) {
// @ts-ignore
tt.navigateToVideoView({
videoId: targetId,
success: (res: any) => {
onSuccess?.();
},
fail: (err: any) => {
console.log("bytedance navigateToVideoView fail", err);
},
});
}
}
/**
* 小游戏平台登录功能。微信返回code抖音返回code和anonymousCode。用于登录的凭证需要把这个code传回你的服务器程序中去调用code2Session
* @param callback (code, anonymousCode) 第一个参数为code微信和抖音都支持第二个参数为匿名设备ID仅抖音支持失败都返回null
*/
static login(callback: (code: string, anonymousCode: string) => void) {
let loginPlatform = () => {
if (isWechat()) {
//@ts-ignore
wx.login({
success: (res: { code: any; errMsg: any; }) => {
if (res.code) {
API._loginCode = res.code;
API._loginAnonymousCode = null;
callback?.(API._loginCode, API._loginAnonymousCode);
} else {
console.log('login error:', res.errMsg)
}
},
fail: () => {
API._loginCode = null;
API._loginAnonymousCode = null;
callback?.(API._loginCode, API._loginAnonymousCode);
console.log('login fail')
}
});
} else if (isBytedance()) {
//@ts-ignore
tt.login({
force: true,
success(res: any) {
console.log(`login ${res.code} ${res.anonymousCode}`);
if (res.code) {
API._loginCode = res.code?.toString();
API._loginAnonymousCode = res.anonymousCode?.toString();
callback?.(API._loginCode, API._loginAnonymousCode);
} else {
console.log('login error:', res.errMsg)
}
},
fail(res: any) {
API._loginCode = null;
API._loginAnonymousCode = null;
callback?.(API._loginCode, API._loginAnonymousCode);
console.log(`login fail`, res);
},
});
} else {
API._loginCode = null;
API._loginAnonymousCode = null;
callback?.(API._loginCode, API._loginAnonymousCode);
console.log('not mini game platform, login codes are all null');
}
}
if (!API._loginCode) {
loginPlatform();
} else {
if (isWechat()) {
//@ts-ignore
wx.checkSession({
success() {
console.log(`session is valid, use current code:`, API._loginCode);
callback?.(API._loginCode, API._loginAnonymousCode);
},
fail() {
console.log(`session expired`);
loginPlatform();
}
});
} else if (isBytedance()) {
//@ts-ignore
tt.checkSession({
success() {
console.log(`session is valid, user current code: ${API._loginCode}, ${API._loginAnonymousCode}`);
callback?.(API._loginCode, API._loginAnonymousCode);
},
fail() {
console.log(`session expired`);
loginPlatform();
},
});
} else {
console.log('not mini game platform, login null');
callback?.(null, null);
}
}
}
/**
* 小游戏平台登录功能。微信返回code抖音返回code和anonymousCode。用于登录的凭证需要把这个code传回你的服务器程序中去调用code2Session
* @param callback (code, anonymousCode) 第一个参数为code微信和抖音都支持第二个参数为匿名设备ID仅抖音支持失败都返回null
*/
static getUserInfo(callback: (userInfo: any) => void) {
//@ts-ignore
tt.getUserInfo({
withCredentials: true,
success: (res: any) => {
API._userInfo = res;
callback(API._userInfo);
},
fail: (err: any) => {
callback(err);
}
});
}
/**
* 调用微信云函数。由于参数需要自定义所以为any需要自行解释。函数只完成通道和处理一场的作用
* @param callback 返回云函数调用结果。需要检查返回参数是否为空,失败的时候为空
* @param name 云函数的名字
* @param data 云函数的内容
*/
static callWechatCloudFunction(callback: (res: any) => void, name: string, data: {}) {
if (!isWechat()) {
console.log('Not wechat platform, not support callWechatCloudFunction');
return;
}
this.login((code: string, anonymousCode: string) => {
if (!API._hasInitWechatCloudFunction) {
//@ts-ignore
wx.cloud.init();
API._hasInitWechatCloudFunction = true;
}
//@ts-ignore
wx.cloud.callFunction({
name: name,
data: data,
success: (res: any) => callback?.(res),
fail: (err: any) => {
console.log('wechat cloud function error:', err);
callback?.(null);
}
});
});
}
/**
* 存储用户信息,数据量不能大。可以考虑用于分数排行榜。用户之间可共享排行数据。
* @param key
* @param value
*/
static setUserCloudStorage(key: string, value: string) {
if (isWechat()) {
// @ts-ignore
wx.setUserCloudStorage({
KVDataList: [{ key: key, value: value }],
success: () => console.log(`set cloud storage success:${key}, value:${value}`),
fail: (err: any) => console.log('set cloud storage error:', err)
});
}
if (isBytedance()) {
// @ts-ignore
tt.setUserCloudStorage({
KVDataList: [{ key: key, value: value, }],
success: () => console.log(`set cloud storage success:${key}, value:${value}`),
fail: (err: any) => console.log('set cloud storage error:', err)
});
}
}
//#region 数数平台
/*
* 数数平台初始化以及登录
*/
static shushu_Init() {
if (typeof wx !== 'undefined' && wx !== null) {
// console.log("开始接入数数平台");
//getWechatGameVersion
let appId = "121591378fc1423893deb12041413eb3";
let test = cc.fx.GameTool.getWechatGameVersion();
if (test == "正式版") {
appId = "87d18958cea145f29d3265470ecd3486";
}
const isProduction = test === '正式版'; // 假设使用 NODE_ENV 区分环境
var config = {
appId: appId,
serverUrl: "https://data.nika4fun.com/sync_data", // 上报地址
autoTrack: {
appShow: true, // 自动采集 ta_mg_show
appHide: true // 自动采集 ta_mg_hide
},
// 根据环境变量设置 debug 模式
debug: !isProduction,
enableLog: false
};
// 创建 TA 实例
API._ta = new ThinkingAnalyticsAPI(config);
// 初始化
API._ta.init();
const distinctId = MiniGameSdk.API.getShushuDistinctId();
if (distinctId) {
cc.fx.GameConfig.GM_INFO.shushu_DistinctId = distinctId;
// console.log('用户的 distinct_id 是:', distinctId);
} else {
// console.log('未获取到用户的 distinct_id');
}
const accountId = MiniGameSdk.API.getShushuAccountId();
if (accountId) {
cc.fx.GameConfig.GM_INFO.shushu_AccountId = accountId;
// console.log('用户的 account_id 是:', accountId);
} else {
// console.log('未获取到用户的 account_id');
}
}
}
static shushu_Login() {
if (typeof wx !== 'undefined' && wx !== null) {
// console.log("数数登录时获取到的openId:", cc.fx.GameConfig.GM_INFO.openid);
API._ta.login(cc.fx.GameConfig.GM_INFO.openid);
cc.fx.GameConfig.GM_INFO.shushu_AccountId = cc.fx.GameConfig.GM_INFO.openid;
const result = "success";
API.shushu_Track("login", result);
API.shushu_SetSuperProperties(null, null);
}
}
static shushu_userSet(time) {
if (typeof wx !== 'undefined' && wx !== null) {
// console.log("设置用户注册属性");
API._ta.userSet({ register_time: time });
API._ta.userSet({ uid: cc.fx.GameConfig.GM_INFO.uid.toString() });
}
}
static updateCoinAndLevel() {
if (typeof wx !== 'undefined' && wx !== null) {
// console.log("上传金币和关卡信息给数数")
API._ta.userSet({ current_level: (cc.fx.GameConfig.GM_INFO.level + 1) });
API._ta.userSet({ current_coin: cc.fx.GameConfig.GM_INFO.coin });
API._ta.userSet({ uid: cc.fx.GameConfig.GM_INFO.uid.toString() });
}
}
/*
* 数数平台设置动态公共属性
*/
static shushu_SetSuperProperties(register_time, pay_user) {
if (typeof wx !== 'undefined' && wx !== null && API._ta) {
var superProperties = {};
superProperties = {
current_level: (cc.fx.GameConfig.GM_INFO.level + 1), //当前关卡等级 number
current_health: cc.fx.GameConfig.GM_INFO.hp, //当前体力值
tmp_coin: cc.fx.GameConfig.GM_INFO.coin,//当前金币
version: cc.fx.GameConfig.GM_INFO.version.toString(),//当前版本号
uid: cc.fx.GameConfig.GM_INFO.uid.toString() //用户id
};
if (register_time != null) {
// console.log("设置用户公共属性注册:————————————", register_time);
superProperties = {
current_level: (cc.fx.GameConfig.GM_INFO.level + 1), //当前关卡等级 number
current_health: cc.fx.GameConfig.GM_INFO.hp, //当前体力值
tmp_coin: cc.fx.GameConfig.GM_INFO.coin,//当前金币
version: cc.fx.GameConfig.GM_INFO.version.toString(),
register_time: register_time,
uid: cc.fx.GameConfig.GM_INFO.uid.toString(), //用户id
pay_user: pay_user
};
}
if (pay_user != null && pay_user != false) {
// console.log("设置用户公共属性支付:————————————", pay_user);
superProperties = {
current_level: (cc.fx.GameConfig.GM_INFO.level + 1), //当前关卡等级 number
current_health: cc.fx.GameConfig.GM_INFO.hp, //当前体力值
tmp_coin: cc.fx.GameConfig.GM_INFO.coin,//当前金币
version: cc.fx.GameConfig.GM_INFO.version.toString(),
uid: cc.fx.GameConfig.GM_INFO.uid.toString(), //用户id
pay_user: pay_user
}
}
// @ts-ignore
// console.log("设置公共属性时:————————————", superProperties.uid);
API._ta.setSuperProperties(superProperties);//设置公共事件属性
API.updateCoinAndLevel();
}
}
static getWechatGameVersion(): string | null {
if (typeof wx !== 'undefined' && wx !== null) {
// @ts-ignore
const accountInfo = wx.getAccountInfoSync();
return accountInfo.miniProgram.version;
}
}
/*
* 数数平台具体埋点
*/
static shushu_Track(name, data, callback?: (success: boolean, error?: any) => void) {
if (typeof wx !== 'undefined' && wx !== null) {
if (API._ta) {
// 假设 track 方法返回一个 Promise
API._ta.track(
name, // 事件名称
data // 事件属性
)
}
}
}
/**
* 获取数数平台用户的 distinct_id
* @returns distinct_id 或 null
*/
static getShushuDistinctId(): string | null {
if (typeof wx !== 'undefined' && wx !== null && API._ta) {
// 假设 SDK 提供 getDistinctId 方法
if (API._ta.getDistinctId) {
return API._ta.getDistinctId();
}
}
return null;
}
/**
* 获取数数平台用户的 account_id
* @returns account_id 或 null
*/
static getShushuAccountId(): string | null {
if (typeof wx !== 'undefined' && wx !== null && API._ta) {
// 假设 SDK 提供 getAccountId 方法
if (API._ta.getAccountId) {
return API._ta.getAccountId();
}
}
return null;
}
//#region 引力平台-
static yinli_Init() {
if (typeof wx !== 'undefined' && wx !== null) {
const configYinli = {
accessToken: "aGws0nluotbm6Jjiv9WMuzOAbXLydxwe", // 项目通行证,在:网站后台-->设置-->应用列表中找到Access Token列 复制(首次使用可能需要先新增应用)
clientId: cc.fx.GameConfig.GM_INFO.openid, // 用户唯一标识如产品为小游戏则必须填用户openid注意不是小游戏的APPID
name: "ge", // 全局变量名称
debugMode: "none", // 是否开启测试模式,开启测试模式后,可以在 网站后台--设置--元数据--事件流中查看实时数据上报结果。测试时使用上线之后一定要关掉改成none或者删除
sendTimeout: 3000, // 网络请求超时时间,单位毫秒,默认值 3000 ms
maxRetries: 3, // 网络请求失败时的重试次数1 表示不重试。默认值是 3
enablePersistence: true, // 是否使用本地缓存,主实例默认为 true子实例默认为 false
asyncPersistence: false, // 是否使用异步存储,默认为 false
enable_sync_attribution: true, // 是否开启渠道归因,默认为 false
autoTrack: {
appLaunch: true, // 自动采集 $MPLaunch
appShow: false, // 自动采集 $MPShow
appHide: false, // 自动采集 $MPHide
},
};
API._ge = new GravityAnalyticsAPI(configYinli);
API._ge.setupAndStart();
API._ge.initialize({
name: cc.fx.GameConfig.GM_INFO.openid,
version: cc.fx.GameConfig.GM_INFO.version,
openid: cc.fx.GameConfig.GM_INFO.openid,
enable_sync_attribution: false,//渠道归因
})
.then((res) => {
// console.log("引力引擎初始化成功", res)
})
.catch((err) => {
// console.log("引力引擎初始化失败 " + err);
});
if (cc.fx.GameConfig.GM_INFO.shushu_AccountId == "") cc.fx.GameConfig.GM_INFO.shushu_AccountId =
cc.fx.GameConfig.GM_INFO.openid;
const CURRENT_USER_TA_ACCOUNT_ID = cc.fx.GameConfig.GM_INFO.shushu_AccountId; // 用户唯一标识如产品为小游戏则必须填用户openid注意不是小游戏的APPID
const CURRENT_USER_TA_DISTINCT_ID = cc.fx.GameConfig.GM_INFO.shushu_DistinctId; // 用户唯一标识如产品为小游戏则必须填用户openid注意不是小游戏的APPID
API._ge.bindTAThirdPlatform(CURRENT_USER_TA_ACCOUNT_ID, CURRENT_USER_TA_DISTINCT_ID);
}
}
static yinli_Register() {
if (typeof wx !== 'undefined' && wx !== null) {
API._ge.registerEvent();
}
}
/**
* 上报付费事件
* @param payAmount 付费金额 单位为分
* @param payType 货币类型 按照国际标准组织ISO 4217中规范的3位字母例如CNY人民币、USD美金等
* @param orderId 订单号
* @param payReason 付费原因 例如:购买钻石、办理月卡
* @param payMethod 付费方式 例如:支付宝、微信、银联等
*/
static yinli_Pay(payAmount, orderId, payReason) {
if (typeof wx !== 'undefined' && wx !== null) {
API._ge.payEvent(payAmount, "CNY", orderId, payReason, "微信");
}
}
static yinli_Login() {
if (typeof wx !== 'undefined' && wx !== null) {
API._ge.loginEvent();
}
}
//分享
static shareGame() {
if (typeof wx !== 'undefined' && wx !== null) {
let helpLevel = cc.fx.GameConfig.GM_INFO.level + 1;
if (helpLevel > 324) {
helpLevel = 324;
}
// 获取关卡信息和 UID
const level = helpLevel;
const uid = cc.fx.GameConfig.GM_INFO.uid;
// const title =
// 构建分享参数
const shareParams = {
title: '如果你突然打了个喷嚏,那一定是我在等你帮忙过关!', // 分享标题
path: `/pages/index/index?level=${level}&uid=${uid}`, // 分享路径,带上关卡信息和 UID
imageUrl: 'https://example.com/share-image.png' // 分享图片链接
};
// 调用微信分享 API
if (cc.sys.platform === cc.sys.WECHAT_GAME) {
//@ts-ignore
wx.shareAppMessage(shareParams);
let eventData = {
identity: "helped", //发起者为helped 帮助者为helper
level: level //被帮助关卡等级
}
console.log("分享给好友", eventData);
cc.fx.GameTool.shushu_Track("stage_help", eventData); //帮助通关
}
}
}
}
}