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 { 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 = {}; private _preloadPromise: Promise | null = null; // 用于跟踪预加载状态 private constructor() { } /** * 预加载视频广告 * @param adUid 广告ID * @returns Promise */ public preloadVideo(adUid: string): Promise { // 如果已有相同广告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 */ public isPreloadReady(): Promise { 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, 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; // 用户昵称 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']) { // 已授权,获取位置信息 API._getCityInfo(callback, useReverseGeocoding); } else { // 未授权,先请求授权 //@ts-ignore wx.authorize({ scope: 'scope.userLocation', success: () => { // 授权成功,获取城市信息 API._getCityInfo(callback, useReverseGeocoding); }, fail: (err) => { // 授权失败,提示用户手动授权 console.log('位置授权失败:', err); callback(false, null, '位置授权失败,请手动授权'); // 引导用户去设置页面手动授权 //@ts-ignore wx.showModal({ title: '位置权限申请', content: '需要获取您的位置信息以确定所在城市,请在设置中开启位置权限', confirmText: '去设置', success: (modalRes) => { if (modalRes.confirm) { //@ts-ignore wx.openSetting({ success: (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) { //@ts-ignore wx.getFuzzyLocation({ type: 'wgs84', // 使用国测局坐标系,更适合中国地图 success: (res) => { console.log('获取位置成功:', res); if (useReverseGeocoding) { // 使用逆解析获取详细地址信息 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 = "港澳台"; } 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定位请求失败'); } }); } } }