2235 lines
92 KiB
TypeScript
2235 lines
92 KiB
TypeScript
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定位请求失败');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
} |