cb/assets/Script/Sdk/MiniGameSdk.ts
2025-12-08 19:43:09 +08:00

2235 lines
92 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.

import Utils from "../module/Pay/Utils";
import { getProvinceName } from "../module/Position/GetPosition";
// import { getProvinceName } from "../module/Position/ProvinceLocator";
/**
* 小游戏平台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();
if (this._interstitial) {
this._interstitial = null;
}
}
}
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;
private _isLoaded: boolean = false;
private _isLoading: boolean = false;
get aduid() {
return this._adUid;
}
constructor(adUid: string) {
this._adUid = adUid;
}
/**
* 预加载广告内容
*/
preload(): Promise<void> {
return new Promise((resolve, reject) => {
if (!isWechat() && !isBytedance()) {
resolve();
return;
}
// 每次预加载都创建新的实例
this._cleanup();
// 标记为正在加载
this._isLoading = true;
this._isLoaded = false;
let adInstance;
// 创建广告实例
if (isWechat()) {
try {
// @ts-ignore
adInstance = wx.createRewardedVideoAd({
adUnitId: this._adUid
});
} catch (e) {
console.error('Failed to create rewarded video ad:', e);
this._isLoading = false;
reject(e);
return;
}
} else if (isBytedance()) {
// @ts-ignore
adInstance = tt.createRewardedVideoAd({
adUnitId: this._adUid,
multiton: true,
multitonRewardMsg: ['多1次奖励', '再多一次奖励', '再多一次奖励'],
multitonRewardTimes: 3,
});
} else {
adInstance = null;
}
if (!adInstance) {
this._isLoading = false;
reject(new Error('Failed to create ad video instance'));
return;
}
// 设置超时处理
let loadTimeout: any = null;
const clearTimer = () => {
if (loadTimeout) {
clearTimeout(loadTimeout);
loadTimeout = null;
}
};
loadTimeout = setTimeout(() => {
console.log('Ad preload timeout');
this._isLoaded = false;
this._isLoading = false;
try {
adInstance.offLoad?.(onLoadHandler);
adInstance.offError?.(onErrorHandler);
} catch (e) {
console.log('Error removing listeners:', e);
}
this._cleanup();
resolve();
}, 5000); // 5秒加载超时
// 设置加载监听
const onLoadHandler = () => {
clearTimer();
console.log('Ad preload success');
this._adVideo = adInstance;
this._isLoaded = true;
this._isLoading = false;
try {
adInstance.offLoad?.(onLoadHandler);
} catch (e) {
console.log('Error removing load listener:', e);
}
resolve();
};
const onErrorHandler = (err: { errMsg: string, errCode: number }) => {
clearTimer();
console.log('Ad preload error:', err);
this._isLoaded = false;
this._isLoading = false;
try {
adInstance.offError?.(onErrorHandler);
} catch (e) {
console.log('Error removing error listener:', e);
}
this._cleanup();
resolve();
};
adInstance.onLoad(onLoadHandler);
adInstance.onError(onErrorHandler);
// 开始加载广告
adInstance.load().catch((err: any) => {
clearTimer();
console.log('Ad load error:', err);
this._isLoaded = false;
this._isLoading = false;
this._cleanup();
resolve();
});
});
}
/**
* 由于微信和抖音视频广告机制不同微信可以看的视频广告个数只有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, video_data: any = null): void {
console.log("调用广告显示");
let callback = (state: EAdVideoResult, count: number) => {
// 确保广告实例被清理
onResult?.call(target, state, count);
}
if (!isWechat() && !isBytedance()) {
callback(EAdVideoResult.ACCEPT, 1);
this._adVideo = null;
return;
}
// 每次调用都创建新的广告实例以确保可以重复调用
this._cleanup();
let onAdVideoClosed = (res: any) => {
if (this._adVideo) {
try {
this._adVideo.offClose?.(onAdVideoClosed);
} catch (e) {
console.log('Error removing close listener:', e);
}
}
let result: EAdVideoResult;
let count: number;
if (isWechat()) {
if (res && res.isEnded || res === undefined) {
result = EAdVideoResult.ACCEPT;
count = 1;
} else {
result = EAdVideoResult.REJECT;
count = 0;
}
} else if (isBytedance()) {
let resConverted = res as { isEnded: boolean, count: number };
if (resConverted && resConverted.count > 0) {
result = EAdVideoResult.ACCEPT;
count = resConverted.count;
} else {
result = EAdVideoResult.REJECT;
count = 0;
}
}
// 确保在回调之前清理广告实例
this._cleanup();
callback(result, count);
}
let responseData = {
ad_type: "激励视频", //广告类型
ad_placement_name: video_data?.ad_placement_name || "", //内部广告位名称 //2000复活 道具 2001 2002 2003
ad_placement_id: "adunit-aa9a28e1631bf16f", //内部广告位ID
current_page: video_data?.current_page || "", //所在页面
error_detail_info: "", //错误信息
error_detail_code: "", //错误码
is_filled_success: false //是否完整播放
}
if (isWechat()) {
try {
// @ts-ignore
this._adVideo = wx.createRewardedVideoAd({
adUnitId: this._adUid
});
responseData.is_filled_success = true;
cc.fx.GameTool.shushu_Track("ad_response", responseData);
} catch (e) {
console.error('Failed to create rewarded video ad:', e);
responseData.is_filled_success = false;
responseData.error_detail_info = e.toString();
responseData.error_detail_code = e.errCode?.toString() || "";
cc.fx.GameTool.shushu_Track("ad_response", responseData);
this._cleanup();
callback(EAdVideoResult.ERROR, 0);
return;
}
} else if (isBytedance()) {
// @ts-ignore
this._adVideo = tt.createRewardedVideoAd({
adUnitId: this._adUid,
multiton: true,
multitonRewardMsg: ['多1次奖励', '再多一次奖励', '再多一次奖励'],
multitonRewardTimes: maxVideoCount,
});
} else {
this._adVideo = null;
}
// 确保广告对象已创建
if (!this._adVideo) {
this._cleanup();
callback(EAdVideoResult.ERROR, 0);
return;
}
this._adVideo.onLoad(() => {
console.log('Ad load success');
});
this._adVideo.onError((err: { errMsg: string, errCode: number }) => {
console.log('Ad video error:', err);
if (this._adVideo) {
try {
this._adVideo.offError?.();
} catch (e) {
console.log('Error removing error listener:', e);
}
}
responseData.is_filled_success = false;
responseData.error_detail_info = err.toString();
responseData.error_detail_code = err.errCode?.toString() || "";
cc.fx.GameTool.shushu_Track("ad_response", responseData);
if (err.errCode) {
console.log('Ad video error code:', err.errCode);
}
// 出错后清理实例
this._cleanup();
callback(EAdVideoResult.ERROR, 0);
});
this._adVideo.onClose(onAdVideoClosed);
let showData = {
ad_type: "激励视频", //广告类型
ad_placement_name: video_data?.ad_placement_name || "", //内部广告位名称 //2000复活 道具 2001 2002 2003
ad_placement_id: "adunit-aa9a28e1631bf16f", //内部广告位ID
current_page: video_data?.current_page || "", //所在页面
error_detail_info: "", //错误信息
error_detail_code: "", //错误码
is_show_success: false //是否完整播放
}
// 尝试显示广告
this._adVideo.show().catch((err: { errMsg: string, errCode: number }) => {
console.log('Show video ad error:', err);
// 尝试加载广告后再显示
if (this._adVideo) {
this._adVideo.load().then(() => {
showData.is_show_success = true;
cc.fx.GameTool.shushu_Track("ad_show", showData);
cc.fx.GameConfig.GM_INFO.videoTime = Math.floor(Date.now() / 1000);
return this._adVideo?.show();
}).catch((loadErr: { errMsg: string, errCode: number }) => {
showData.is_show_success = false;
showData.error_detail_info = loadErr.errMsg;
showData.error_detail_code = loadErr.errCode.toString();
cc.fx.GameTool.shushu_Track("ad_show", showData);
console.log('Load and show video ad error:', loadErr);
if (loadErr.errCode) {
console.log('Load error code:', loadErr.errCode);
}
// 出错后清理实例
this._cleanup();
callback(EAdVideoResult.ERROR, 0);
});
} else {
this._cleanup();
callback(EAdVideoResult.ERROR, 0);
}
});
}
private _cleanup() {
if (this._adVideo) {
try {
// 移除所有事件监听器
this._adVideo.offLoad?.();
this._adVideo.offError?.();
this._adVideo.offClose?.();
} catch (e) {
console.log('Error removing ad event listeners:', e);
}
}
this._adVideo = null;
this._isLoaded = false;
this._isLoading = false;
}
destory() {
this._cleanup();
}
}
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 _preloadPromise: Promise<void> | null = null; // 用于跟踪预加载状态
private constructor() {
}
/**
* 预加载视频广告
* @param adUid 广告ID
* @returns Promise<void>
*/
public preloadVideo(adUid: string): Promise<void> {
// 如果已有相同广告ID的实例先销毁
if (this._video && this._video.aduid !== adUid) {
this._video.destory();
this._video = null;
}
// 创建新实例(如果需要)
if (!this._video) {
this._video = new ADVideo(adUid);
}
// 执行预加载
this._preloadPromise = this._video.preload();
return this._preloadPromise;
}
/**
* 检查广告是否已预加载完成
* @returns Promise<void>
*/
public isPreloadReady(): Promise<void> {
console.log("加载广告中...", this._preloadPromise)
return this._preloadPromise || Promise.resolve();
}
/**
* 预加载横幅广告不会显示。只有你在调用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, video_data, onVideoResult: (res: EAdVideoResult, count: number) => void, target?: any, maxVideoCount: number = 3) {
// 只有在广告ID不匹配时才重新创建实例
if (this._video && this._video.aduid !== adUid) {
this._video.destory();
this._video = new ADVideo(adUid);
} else if (!this._video) {
this._video = new ADVideo(adUid);
}
this._video.show(onVideoResult, target, maxVideoCount, video_data);
}
/**
* 销毁内部所有实例,清空内存
*/
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() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) { // 判断是否在微信环境
//@ts-ignore
wx.setPreferredFramesPerSecond(60);
// // 设置转发按钮点击后的回调
let iphoneArr = [
"https://mmocgame.qpic.cn/wechatgame/Lf3SBqy9XpNkakoIZygRzXqww3HTibq6VyibazqmicwibjCS3YpgqbZtkdyABm4Y1wAr/0",
"https://mmocgame.qpic.cn/wechatgame/TWKuFxnCn7ksT3KXfhCC4yOfZeD4b0hrptDSJ2DFmwz02Yc8SppcwyPAOoS1MsMr/0",
"https://mmocgame.qpic.cn/wechatgame/dibaH2x79o1wSwBDymhyzXwfcyicaDb6R5icrFIO7251T4NgxIzXRXErHvAvn50vXFA/0",
"https://mmocgame.qpic.cn/wechatgame/Pgxad80d8ws3o69OicV3DTuTkcP81upQeJ0JBNS1xib3pzYLTF1ZqGY3niciaI7ICKlL/0",
"https://mmocgame.qpic.cn/wechatgame/SfB1vrRBIHKn9ffKFt5sib62yPLE31m2rCvk6DKlEicJNVZSoryEObD6ItwsQn4xibR/0",
"https://mmocgame.qpic.cn/wechatgame/OiaWk33I6QjgWiatrb5YVUq2p0QRmQgO6rLUWxEQDZ4ib9Ny4Pr8iaHnHI6WdxibY2nPL/0",
"https://mmocgame.qpic.cn/wechatgame/CG5xBibollws251aYD4msEPWCiafrcn4Fgtic4T2wME6sWmdfAUtfibgsWoxm59VadDD/0"
]
let randomIphone = iphoneArr[Math.floor(Math.random() * iphoneArr.length)];
let img = randomIphone;
// const title =
// 构建分享参数
const shareParams = {
title: '快来一起玩这个超有趣的小游戏吧!', // 修改为默认分享标题
imageUrl: img // 修改为默认分享图片
};
//@ts-ignore
// 仅设置分享内容,不主动触发分享
wx.onShareAppMessage(() => shareParams);
// 监听分享到朋友圈事件
//@ts-ignore
wx.onShareTimeline(() => {
return {
title: '你想玩上怎样的游戏?'
};
});
//@ts-ignore
wx.showShareMenu(() => {
return {
title: '你想玩上怎样的游戏?',
imageUrl: img,
query: ''
};
});
setTimeout(() => {
//@ts-ignore
wx.showShareMenu({
menus: ['shareAppMessage', 'shareTimeline']
})
}, 2000);
setTimeout(() => {
//@ts-ignore
wx.showShareMenu({
menus: ['shareAppMessage', 'shareTimeline']
})
}, 4000);
// 设置分享到朋友圈
//@ts-ignore
// wx.updateShareMenu({
// withShareTicket: true,
// success: (data) => {
// console.log('更新分享菜单成功', data);
// },
// fail: (data) => {
// console.log('更新分享菜单失败', data);
// },
// complete: (data) => {
// console.log('更新分享菜单完成', data);
// }
// });
//@ts-ignore
}
}
/**
* 显示提示信息
* @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);
}
});
}
/**
* 存储用户信息,数据量不能大。可以考虑用于分数排行榜。用户之间可共享排行数据。
* @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)
});
}
}
/** 获取用户授权信息 */
static getWechatUserInfo(callBack) {
//微信平台授权
if (isWechat()) {
//@ts-ignore
wx.getSetting({
success: (res: { authSetting: { [key: string]: boolean } }) => {
if (res.authSetting['scope.WxFriendInteraction']) {
console.log('已经获得好友信息授权');
cc.fx.GameConfig.GM_INFO.wxFriend = true;
} else {
console.log('没授权好友信息', res);
cc.fx.GameConfig.GM_INFO.wxFriend = false;
}
if (res.authSetting['scope.userInfo']) {
console.log('用户已授权头像昵称');
cc.fx.GameConfig.GM_INFO.wxUserInfo = true;
} else {
console.log("用户未授权头像昵称");
cc.fx.GameConfig.GM_INFO.wxUserInfo = false;
}
if (callBack) {
callBack(true);
}
},
fail: (err: any) => {
if (callBack) {
callBack(false);
}
// console.error('获取用户授权状态失败', err);
}
});
}
//抖音平台授权
if (isBytedance()) {
//@ts-ignore
tt.getSetting({
success: (res: { authSetting: { [key: string]: boolean } }) => {
if (res.authSetting['scope.WxFriendInteraction']) {
console.log('已经获得好友信息授权');
cc.fx.GameConfig.GM_INFO.wxFriend = true;
} else {
console.log('没授权好友信息', res);
cc.fx.GameConfig.GM_INFO.wxFriend = false;
}
if (res.authSetting['scope.userInfo']) {
console.log('用户已授权头像昵称');
cc.fx.GameConfig.GM_INFO.wxUserInfo = true;
} else {
console.log("用户未授权头像昵称");
cc.fx.GameConfig.GM_INFO.wxUserInfo = false;
}
if (callBack) {
callBack(true);
}
},
fail: (err: any) => {
if (callBack) {
callBack(false);
}
// console.error('获取用户授权状态失败', err);
}
});
}
}
/** 获取用户好友信息授权 */
static getWechatFriend(callBack) {
if (isWechat()) {
//@ts-ignore
wx.authorize({
scope: 'scope.WxFriendInteraction',
success: () => {
console.log('好友信息授权成功');
cc.fx.GameConfig.GM_INFO.wxFriend = true;
if (callBack) {
callBack(true);
}
},
fail: () => {
console.log('11111好友信息授权失败引导用户手动开启');
//@ts-ignore
wx.openSetting({
success: (res) => {
// 用户在设置中开启权限后,重新显示排行榜
if (res.authSetting['scope.WxFriendInteraction']) {
cc.fx.GameConfig.GM_INFO.wxFriend = true;
if (callBack) {
callBack(true);
}
}
},
fail: (err: any) => {
if (callBack) {
callBack(false);
}
console.error('打开设置界面失败', err);
}
});
}
});
}
}
/** 获取用户头像昵称授权 */
static getWechatUserInfoAuth(callBack: Function) {
if (isWechat()) {
//@ts-ignore
// 用户未授权,引导用户授权
wx.getUserProfile({
desc: '用于完善会员资料', // 声明获取用户个人信息后的用途
success: (res) => {
console.log("拿到用户头像昵称");
cc.fx.GameConfig.GM_INFO.wxUserInfo = true;
const userInfo = res.userInfo;
cc.fx.GameConfig.GM_INFO.useravatar = userInfo.avatarUrl; // 用户头像 URL
cc.fx.GameConfig.GM_INFO.username = userInfo.nickName; // 用户昵称
cc.fx.GameConfig.GM_INFO.useravatarIcon = userInfo.avatarUrl; // 用户头像框 URL
const user_Info = {
username: cc.fx.GameConfig.GM_INFO.username,
useravatar: cc.fx.GameConfig.GM_INFO.useravatar,
}
// console.log('用户头像:', cc.fx.GameConfig.GM_INFO.useravatar);
// console.log('用户昵称:', cc.fx.GameConfig.GM_INFO.username);
// console.log('用户授权拿到', res.userInfo);
cc.fx.StorageMessage.setStorage('user_Info', user_Info);
cc.fx.GameTool.setUserInfo(false, (data) => {
console.log("设置用户信息成功__________", data);
});
setTimeout(() => {
if (callBack) callBack(true);
}, 200);
},
fail: (err) => {
if (callBack) callBack(false);
MiniGameSdk.API.showToast('请先授权获取头像昵称,用于排行榜展示');
}
});
}
}
//#region 数数平台
/*
* 数数平台初始化以及登录
*/
static shushu_Init() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== 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() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== 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) {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
// console.log("设置用户注册属性");
API._ta.userSet({ register_time: time });
API._ta.userSet({ uid: cc.fx.GameConfig.GM_INFO.userId.toString() });
}
}
static updateCoinAndLevel() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== 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.userId });
}
}
/*
* 数数平台设置动态公共属性
*/
static shushu_SetSuperProperties(register_time, pay_user) {
//@ts-ignore
if (((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== 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,//当前金币
freeze_amount: cc.fx.GameConfig.GM_INFO.freeze_amount,//当前道具
hammer_amount: cc.fx.GameConfig.GM_INFO.hammer_amount,//当前道具
magic_amount: cc.fx.GameConfig.GM_INFO.magic_amount,//当前道具
version: cc.fx.GameConfig.GM_INFO.version.toString(),//当前版本号
user_id: cc.fx.GameConfig.GM_INFO.userId //用户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,//当前金币
freeze_amount: cc.fx.GameConfig.GM_INFO.freeze_amount,//当前道具
hammer_amount: cc.fx.GameConfig.GM_INFO.hammer_amount,//当前道具
magic_amount: cc.fx.GameConfig.GM_INFO.magic_amount,//当前道具
version: cc.fx.GameConfig.GM_INFO.version.toString(),
register_time: register_time,
user_id: cc.fx.GameConfig.GM_INFO.userId, //用户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,//当前金币
freeze_amount: cc.fx.GameConfig.GM_INFO.freeze_amount,//当前道具
hammer_amount: cc.fx.GameConfig.GM_INFO.hammer_amount,//当前道具
magic_amount: cc.fx.GameConfig.GM_INFO.magic_amount,//当前道具
version: cc.fx.GameConfig.GM_INFO.version.toString(),
user_id: cc.fx.GameConfig.GM_INFO.userId, //用户id
pay_user: pay_user
}
}
// @ts-ignore
// console.log("设置公共属性时:————————————", superProperties.uid);
API._ta.setSuperProperties(superProperties);//设置公共事件属性
API.updateCoinAndLevel();
}
}
static getWechatGameVersion(): string | null {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
// @ts-ignore
const accountInfo = wx.getAccountInfoSync();
return accountInfo.miniProgram.version;
}
}
/*
* 数数平台具体埋点
*/
static shushu_Track(name, data, callback?: (success: boolean, error?: any) => void) {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
if (API._ta) {
// 假设 track 方法返回一个 Promise
API._ta.track(
name, // 事件名称
data // 事件属性
)
}
}
}
/**
* 获取数数平台用户的 distinct_id
* @returns distinct_id 或 null
*/
static getShushuDistinctId(): string | null {
//@ts-ignore
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 {
//@ts-ignore
if (typeof wx !== 'undefined' && wx !== null && API._ta) {
// 假设 SDK 提供 getAccountId 方法
if (API._ta.getAccountId) {
return API._ta.getAccountId();
}
}
return null;
}
//#region 引力平台-
static yinli_Init() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== 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() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== 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) {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
let version = cc.fx.GameTool.getWechatGameVersion();
if (version == "开发版" || version == "体验版") {
}
else {
API._ge.payEvent(payAmount, "CNY", orderId, payReason, "微信");
}
console.log("版本:", version);
}
}
static yinli_Login() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
API._ge.loginEvent();
}
}
static yinli_Track(name, data, callback?: (success: boolean, error?: any) => void) {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
if (API._ge) {
// 假设 track 方法返回一个 Promise
API._ge.track(
name, // 事件名称
data // 事件属性
)
}
}
}
static yinli_FinishiStage() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
if (API._ge) {
// 假设 track 方法返回一个 Promise
API._ge.track("$CompleteSection", {
$main_section_no: (cc.fx.GameConfig.GM_INFO.level),
$section_sum: (cc.fx.GameConfig.GM_INFO.level),
$section_type: "0"
});
}
}
}
//分享
static shareGame() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
let helpLevel = cc.fx.GameConfig.GM_INFO.level + 1;
if (cc.fx.GameTool.maxLevel()) {
helpLevel -= 1;
}
// 获取关卡信息和 UID
const level = helpLevel;
const uid = cc.fx.GameConfig.GM_INFO.uid;
let iphoneArr = [
"https://mmocgame.qpic.cn/wechatgame/Lf3SBqy9XpNkakoIZygRzXqww3HTibq6VyibazqmicwibjCS3YpgqbZtkdyABm4Y1wAr/0",
"https://mmocgame.qpic.cn/wechatgame/TWKuFxnCn7ksT3KXfhCC4yOfZeD4b0hrptDSJ2DFmwz02Yc8SppcwyPAOoS1MsMr/0",
"https://mmocgame.qpic.cn/wechatgame/dibaH2x79o1wSwBDymhyzXwfcyicaDb6R5icrFIO7251T4NgxIzXRXErHvAvn50vXFA/0",
"https://mmocgame.qpic.cn/wechatgame/Pgxad80d8ws3o69OicV3DTuTkcP81upQeJ0JBNS1xib3pzYLTF1ZqGY3niciaI7ICKlL/0",
"https://mmocgame.qpic.cn/wechatgame/SfB1vrRBIHKn9ffKFt5sib62yPLE31m2rCvk6DKlEicJNVZSoryEObD6ItwsQn4xibR/0",
"https://mmocgame.qpic.cn/wechatgame/OiaWk33I6QjgWiatrb5YVUq2p0QRmQgO6rLUWxEQDZ4ib9Ny4Pr8iaHnHI6WdxibY2nPL/0",
"https://mmocgame.qpic.cn/wechatgame/CG5xBibollws251aYD4msEPWCiafrcn4Fgtic4T2wME6sWmdfAUtfibgsWoxm59VadDD/0"
]
let randomIphone = iphoneArr[Math.floor(Math.random() * iphoneArr.length)];
let img = randomIphone;
// const title =
// 构建分享参数
const shareParams = {
title: '如果你突然打了个喷嚏,那一定是我在等你帮忙过关!', // 分享标题
path: `/pages/index/index?level=${level}&uid=${uid}`, // 分享路径,带上关卡信息和 UID
imageUrl: img // 分享图片链接
};
// 调用微信分享 API
if (cc.sys.platform === cc.sys.WECHAT_GAME) {
//@ts-ignore
wx.shareAppMessage(shareParams);
let eventData = {
identity: "helped", //发起者为helped 帮助者为helper
helpedId: cc.fx.GameConfig.GM_INFO.uid, //被帮助者uid
level: level //被帮助关卡等级
}
console.log("分享给好友", eventData);
cc.fx.GameTool.shushu_Track("stage_help", eventData); //帮助通关
}
}
}
//上传好友排行数据
static setNewCloudlevel() {
//@ts-ignore
if ((typeof wx !== 'undefined' && wx !== null) || (typeof tt !== 'undefined' && tt !== null)) {
console.log("往子域上传分数");
let newKVData = { key: 'level', value: String(cc.fx.GameConfig.GM_INFO.level) }
// 设置新云托管分数(第一次游戏时,也调用该方法设置云托管分数)
//@ts-ignore
wx.setUserCloudStorage({
KVDataList: [newKVData],
success: (res) => {
console.log('更新玩家分数成功!');
},
fail: (res) => {
console.log(res);
}
});
}
}
/**
* 打开位置设置页面
*/
static openLocationSetting() {
//@ts-ignore
if (typeof wx !== 'undefined' && wx !== null) {
//@ts-ignore
wx.openSetting({
success: (res) => {
console.log('打开设置成功:', res);
},
fail: (err) => {
console.log('打开设置失败:', err);
}
});
}
}
/**
* 获取微信小游戏用户城市信息
* @param callback 回调函数,参数为城市信息对象或错误信息
* @param useReverseGeocoding 是否使用逆解析获取详细地址信息(需要网络请求)
*/
static getWechatCityInfo(callback: (success: boolean, data?: any, error?: any) => void, useReverseGeocoding: boolean = false) {
//@ts-ignore
if (typeof wx !== 'undefined' && wx !== null) {
// 检查是否已授权位置权限
//@ts-ignore
wx.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation']) {
// 已授权,获取位置信息
console.log("已授权,获取位置信息");
API._getCityInfo(callback, useReverseGeocoding);
} else {
console.log("未授权,先请求授权");
// 未授权,先请求授权
API._getCityInfo(callback, useReverseGeocoding);
//@ts-ignore
// wx.authorize({
// scope: 'scope.userLocation',
// success: () => {
// console.log("授权成功,获取位置信息");
// // 授权成功,获取城市信息
// API._getCityInfo(callback, useReverseGeocoding);
// },
// fail: (err) => {
// // 授权失败,提示用户手动授权
// console.log('位置授权失败:', err);
// callback(false, null, '位置授权失败,请手动授权');
// // 引导用户去设置页面手动授权
// //@ts-ignore
// wx.showModal({
// title: '位置权限申请',
// content: '需要获取您的位置信息以确定所在城市,请在设置中开启位置权限',
// confirmText: '去设置',
// success: (modalRes) => {
// if (modalRes.confirm) {
// console.log("用户点击去设置");
// //@ts-ignore
// wx.openSetting({
// success: (settingRes) => {
// console.log('用户设置结果:', settingRes);
// if (settingRes.authSetting['scope.userLocation']) {
// // 用户手动授权成功,重新获取城市信息
// API._getCityInfo(callback, useReverseGeocoding);
// } else {
// callback(false, null, '用户拒绝授权位置权限');
// Utils.setFailCityInfo();
// }
// },
// fail: (err) => {
// console.log('打开设置失败:', err);
// callback(false, null, '用户拒绝授权位置权限');
// Utils.setFailCityInfo();
// }
// });
// }
// }
// });
// }
// });
}
},
fail: (err) => {
console.log('获取设置失败:', err);
callback(false, null, '获取设置失败');
}
});
} else {
callback(false, null, '非微信环境');
}
}
/**
* 内部方法:实际获取城市信息
*/
private static _getCityInfo(callback: (success: boolean, data?: any, error?: any) => void, useReverseGeocoding: boolean) {
console.log("获取经纬度坐标");
//@ts-ignore
wx.getFuzzyLocation({
type: 'wgs84', // 使用国测局坐标系,更适合中国地图
success: (res) => {
console.log('111111获取位置成功:', res);
if (useReverseGeocoding) {
// 使用逆解析获取详细地址信息
cc.fx.GameConfig.GM_INFO.longitude = res.latitude.toString() + "," + res.longitude.toString();
API._reverseGeocoding(res.latitude, res.longitude, callback);
} else {
// 直接返回基本位置信息,包含经纬度
const cityInfo = "其他";
callback(true, cityInfo);
}
},
fail: (err) => {
console.log('获取位置失败:', err);
callback(false, null, '用户拒绝授权位置权限');
Utils.setFailCityInfo();
// 处理错误码102位置服务不可用
if (err.errCode === 102) {
// 错误码102的常见原因和解决方案
// 1. 设备定位服务未开启
// 2. 网络问题导致定位失败
// 3. 权限问题
// 提供更友好的错误信息
let errorMsg = '位置服务不可用';
if (err.errMsg.includes('auth deny')) {
errorMsg = '位置权限被拒绝,请检查系统设置';
} else if (err.errMsg.includes('network')) {
errorMsg = '网络连接失败,请检查网络设置';
} else {
errorMsg = '定位服务不可用,请检查设备定位功能是否开启';
}
// 提供用户友好的提示
//@ts-ignore
wx.showModal({
title: '定位失败',
content: errorMsg,
showCancel: false,
confirmText: '知道了'
});
callback(false, null, errorMsg);
} else {
callback(false, null, err.errMsg || '获取位置失败');
}
}
});
}
/**
* 逆解析经纬度获取详细地址信息
*/
private static _reverseGeocoding(latitude: number, longitude: number, callback: (success: boolean, data?: any, error?: any) => void) {
//@ts-ignore
if (typeof wx !== "undefined" && wx.getLocation) {
let lat = latitude;
let lng = longitude;
const province = getProvinceName(lat, lng);
console.log("玩家省份1", province);
let cityInfo = this.getCityChange(province);
callback(true, cityInfo);
// TODO: 这里写你的逻辑:埋点、排行榜、活动分组等
} else {
// 非微信环境(比如 Cocos 预览),用个测试坐标
const province = getProvinceName(39.9, 116.4);
console.log("测试坐标所在省份:", province);
let cityInfo = this.getCityChange(province);
callback(true, cityInfo);
}
// const key = '6PPBZ-VBEHW-XE3RU-3XNC5-TQH6H-KLBTF'; // 需要替换为实际的key
// const url = `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${key}&get_poi=0`;
// // 注意微信小游戏本身不提供逆解析API需要调用第三方服务或自己的服务器
// // 这里提供一个示例,实际使用时需要替换为你的服务器接口
// //@ts-ignore
// wx.request({
// url: url,
// method: 'GET',
// success: (res) => {
// if (res.statusCode === 200 && res.data.status === 0) {
// const result = res.data.result;
// const cityInfo = {
// latitude: latitude,
// longitude: longitude,
// province: result.address_component.province, // 省份
// city: result.address_component.city, // 城市
// district: result.address_component.district, // 区县
// address: result.address, // 详细地址
// formatted_address: result.formatted_addresses?.recommend || result.address // 格式化地址
// };
// console.log('逆解析成功:', cityInfo);
// callback(true, cityInfo);
// } else {
// console.log('逆解析失败:', res.data);
// callback(true, null, '逆解析失败');
// }
// },
// fail: (err) => {
// console.log('逆解析请求失败:', err);
// callback(true, null, '逆解析请求失败');
// }
// });
}
/**
* 使用微信城市选择器获取城市信息(用户手动选择)
* @param callback 回调函数
*/
static chooseWechatCity(callback: (success: boolean, data?: any, error?: any) => void) {
//@ts-ignore
if (typeof wx !== 'undefined' && wx !== null) {
//@ts-ignore
wx.chooseLocation({
success: (res) => {
console.log('选择位置成功:', res);
const cityInfo = {
name: res.name, // 位置名称
address: res.address, // 详细地址
latitude: res.latitude, // 纬度
longitude: res.longitude, // 经度
// 从地址中提取城市信息(简单处理)
province: API._extractProvinceFromAddress(res.address),
city: API._extractCityFromAddress(res.address)
};
callback(true, cityInfo);
},
fail: (err) => {
console.log('选择位置失败:', err);
callback(false, null, err.errMsg || '选择位置失败');
}
});
} else {
callback(false, null, '非微信环境');
}
}
/**
* 从地址字符串中提取省份信息
*/
private static _extractProvinceFromAddress(address: string): string {
// 简单的省份提取逻辑,实际使用时可能需要更复杂的处理
const provinces = ['北京', '天津', '上海', '重庆', '河北', '山西', '辽宁', '吉林', '黑龙江', '江苏', '浙江',
'安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '海南', '四川', '贵州',
'云南', '陕西', '甘肃', '青海', '台湾', '内蒙古', '广西', '西藏', '宁夏', '新疆', '香港', '澳门'];
for (const province of provinces) {
if (address.includes(province)) {
return province;
}
}
return '';
}
/**
* 从地址字符串中提取城市信息
*/
private static _extractCityFromAddress(address: string): string {
// 简单的城市提取逻辑
const cities = ['北京', '上海', '天津', '重庆', '石家庄', '太原', '呼和浩特', '沈阳', '长春', '哈尔滨',
'南京', '杭州', '合肥', '福州', '南昌', '济南', '郑州', '武汉', '长沙', '广州', '南宁',
'海口', '成都', '贵阳', '昆明', '拉萨', '西安', '兰州', '西宁', '银川', '乌鲁木齐'];
for (const city of cities) {
if (address.includes(city)) {
return city;
}
}
return '';
}
private static getCityChange(city) {
if (city == "未知") {
city = "其他";
}
else if (city == "北京市" || city == "天津市" || city == "上海市" || city == "重庆市" || city == "广西壮族自治区" || city == "西藏自治区"
|| city == "宁夏回族自治区" || city == "新疆维吾尔自治区"
) {
city = city.substring(0, 2);
}
else if (city == "内蒙古自治区") {
city = "内蒙古";
}
else if (city == "台湾省" || city == "香港特别行政区" || city == "澳门特别行政区") {
city = "港澳台";
}
else if (city == "未知或境外") {
city = "其他";
}
if (city.length >= 5) {
city = "其他";
}
return city;
}
/**
* 获取用户IP地址对应的城市信息不需要位置权限
* @param callback 回调函数
*/
static getCityByIP(callback: (success: boolean, data?: any, error?: any) => void) {
// 调用IP定位服务获取城市信息
// 示例使用第三方IP定位服务
const url = 'https://restapi.amap.com/v3/ip?key=YOUR_AMAP_KEY'; // 需要替换为实际的高德地图key
//@ts-ignore
wx.request({
url: url,
method: 'GET',
success: (res) => {
if (res.statusCode === 200 && res.data.status === '1') {
const ipInfo = res.data;
const cityInfo = {
province: ipInfo.province, // 省份
city: ipInfo.city, // 城市
adcode: ipInfo.adcode, // 区域编码
rectangle: ipInfo.rectangle // 城市范围坐标
};
console.log('IP定位成功:', cityInfo);
callback(true, cityInfo);
} else {
console.log('IP定位失败:', res.data);
callback(false, null, 'IP定位失败');
}
},
fail: (err) => {
console.log('IP定位请求失败:', err);
callback(false, null, 'IP定位请求失败');
}
});
}
}
}