MatchMaster/assets/Script/SceneManager.ts

2028 lines
80 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

// Learn TypeScript:
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
import MapConroler from "./Map";
import LoadingCatAnimation from "./LoadingCatAnimation";
import Utils from "./module/Pay/Utils";
import { starterPackRemaining } from "./module/Config/StarterPack";
import NumberToImage from "./NumberToImage";
import { MiniGameSdk } from "./Sdk/MiniGameSdk";
const { ccclass, property } = cc._decorator;
interface DynamicPopupState {
bundleName: string;
prefabPath: string;
root: cc.Node;
bundle: cc.AssetManager.Bundle;
loading: boolean;
requestId: number;
callbacks: Array<(error: Error, root?: cc.Node) => void>;
}
@ccclass
export default class SceneManager extends cc.Component {
private static activeInstance: SceneManager = null;
private isPopupRuntimeAlive(): boolean {
return cc.isValid(this, true)
&& SceneManager.activeInstance === this
&& !this.isDestroying
&& !!this.node
&& cc.isValid(this.node, true);
}
/** 旧场景的异步回调不能释放新 GameScene 正在使用的同名 Bundle。 */
private static releaseOrphanedPopupBundle(bundleName: string, bundle: cc.AssetManager.Bundle): void {
// The gift shares action_bundle with Home; its lifetime is not owned by this popup.
if (bundleName === "action_bundle") return;
if (!bundle) return;
setTimeout(() => {
if (cc.fx.GameTool && cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload
&& cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload()) return;
const active = SceneManager.activeInstance;
if (active && cc.isValid(active, true)) return;
if (cc.assetManager.getBundle(bundleName) !== bundle) return;
bundle.releaseAll();
if (cc.assetManager.getBundle(bundleName) === bundle) {
cc.assetManager.removeBundle(bundle);
}
}, 0);
}
private releaseStalePopupBundle(bundleName: string, bundle: cc.AssetManager.Bundle): void {
if (!bundle) return;
if (this.isPopupRuntimeAlive()) {
this.releaseDynamicPopupBundleIfUnused(bundleName, bundle);
} else {
SceneManager.releaseOrphanedPopupBundle(bundleName, bundle);
}
}
/** GameScene 的 Loading 始终使用引擎允许的最高层级,并保持为最后一个同级节点。 */
private ensureLoadingOnTop(): cc.Node {
const loading = this.node && this.node.getChildByName("Loading");
if (loading) {
loading.zIndex = cc.macro.MAX_ZINDEX;
if (loading.parent) {
loading.setSiblingIndex(loading.parent.childrenCount - 1);
}
}
return loading;
}
/** 动态弹窗加载期间使用;层级高于业务弹窗,低于 Loading 和失败重试层。 */
private ensureDynamicPopupIntercept(): cc.Node {
if (!this.node || !cc.isValid(this.node)) return null;
const intercept = this.node.getChildByName("intercept");
if (intercept) {
intercept.zIndex = cc.macro.MAX_ZINDEX - 1;
}
return intercept;
}
// 缓存 shop 预制体
private static cachedShopPrefab: cc.Prefab | null = null;
// 缓存 shop 节点
private shopNode: cc.Node | null = null;
private starterPackOpening = false;
// GameScene 中从商城打开 Jungle Treasure 时使用。
private static cachedJungleTreasurePrefab: cc.Prefab | null = null;
private jungleTreasureNode: cc.Node | null = null;
private jungleTreasureServerData: any = null;
private jungleTreasureDataLoading: boolean = false;
private jungleTreasureBundleLoading: boolean = false;
private jungleTreasureRequestId: number = 0;
private jungleTreasureRequestTimeout: any = null;
private jungleTreasureOpenSource: string = "shop_page";
// 缓存 reward 预制体
private static cachedRewardPrefab: cc.Prefab | null = null;
// 缓存 reward 奖励窗口 节点
private RewardNode: cc.Node | null = null;
//缓存月卡
private static cachedMonthlyCardPrefab: cc.Prefab | null = null;
// 缓存月卡节点
private monthlyCardNode: cc.Node | null = null;
static clearShopPrefabCaches(): void {
SceneManager.cachedShopPrefab = null;
SceneManager.cachedRewardPrefab = null;
SceneManager.cachedMonthlyCardPrefab = null;
}
/** releaseAll 不会同步清空 JS 静态字段;使用前先丢弃已经失效的 Prefab。 */
private static clearInvalidShopPrefabCaches(): void {
if (SceneManager.cachedShopPrefab && !cc.isValid(SceneManager.cachedShopPrefab)) {
SceneManager.cachedShopPrefab = null;
}
if (SceneManager.cachedRewardPrefab && !cc.isValid(SceneManager.cachedRewardPrefab)) {
SceneManager.cachedRewardPrefab = null;
}
if (SceneManager.cachedMonthlyCardPrefab && !cc.isValid(SceneManager.cachedMonthlyCardPrefab)) {
SceneManager.cachedMonthlyCardPrefab = null;
}
}
@property(cc.Label)
label: cc.Label = null;
@property
text: string = 'hello';
@property(cc.Node)
freeze: cc.Node = null;
@property(cc.Node)
hammer: cc.Node = null;
@property(cc.Node)
magic_wand: cc.Node = null;
@property(cc.Node)
pause: cc.Node = null;
@property(cc.Node)
level: cc.Node = null;
//time 弹窗的金币数
@property(cc.Node)
timeCoin: cc.Node = null;
//赢了win金币数
@property(cc.Node)
winCoin: cc.Node = null;
@property(cc.Node)
nextMode: cc.Node = null;
@property({ type: cc.Prefab, tooltip: "旧版 B 方案:结算后飞金币预制体,不配则使用 flyCoinSpriteFrame" })
flyCoinPrefab: cc.Prefab = null;
@property({ type: cc.SpriteFrame, tooltip: "旧版 B 方案:结算后飞金币图片,未配置预制体时使用" })
flyCoinSpriteFrame: cc.SpriteFrame = null;
@property({ tooltip: "旧版 B 方案:结算后飞金币数量" })
flyCoinCount: number = 16;
@property({ tooltip: "旧版 B 方案:结算后飞金币单个飞行时间" })
flyCoinDuration: number = 1;
@property({ tooltip: "旧版 B 方案:结算后飞金币间隔" })
flyCoinInterval: number = 0.05;
@property({ type: [cc.Prefab], tooltip: "方块数组" })
Block_Array: Array<cc.Prefab> = [];
@property({ type: [cc.Prefab], tooltip: "墙壁数组" })
Wall_Prefab: Array<cc.Prefab> = [];
@property(cc.SpriteAtlas)
avatar: cc.SpriteAtlas = null;
@property(cc.Prefab)
messageNode: cc.Prefab = null;
particleEffects: cc.ParticleAsset[];
// @property({type: [cc.ParticleSystem], tooltip:"粒子数组"})
// particleEffects : Array<cc.ParticleSystem> = [];
load1: boolean = false;
load2: boolean = false;
load3: boolean = false;
btnName: string = '';
callBack: any;
share_time: boolean = true;
private flyCoinLayer: cc.Node = null;
private isDestroying: boolean = false;
private winLevelName: number = 0;
private dynamicPopupLoads: { [popupName: string]: boolean } = {};
private dynamicPopupRetryNode: cc.Node = null;
private dynamicPopupRetryName: string = "";
private dynamicPopupRetries: { [popupName: string]: { displayName: string; retry: () => void } } = {};
private dynamicPopupStates: { [popupName: string]: DynamicPopupState } = {};
private popupBundleWarmupStarted: boolean = false;
warmupGameplayPopupBundles(
preloadInstances: boolean = false,
preloadWin?: (onComplete: (error?: Error) => void) => void,
onComplete?: (error?: Error) => void,
) {
if (!this.isPopupRuntimeAlive() || this.popupBundleWarmupStarted) return;
this.popupBundleWarmupStarted = true;
if (preloadInstances) {
const gameRoot = this.node && this.node.getChildByName("Game");
const requests = [
{ popupName: "pause", bundleName: "pause", prefabPath: "prefab/Pause", parent: this.node },
{ popupName: "win", bundleName: "win", prefabPath: "prefab/Win", parent: gameRoot },
{ popupName: "propWindow", bundleName: "propWindow", prefabPath: "prefab/PropWindow", parent: gameRoot },
{ popupName: "newMode", bundleName: "NewMode", prefabPath: "prefab/NewMode", parent: gameRoot },
{ popupName: "lose", bundleName: "lose", prefabPath: "prefab/Lose", parent: gameRoot },
];
let warmupError: Error = null;
const preloadNext = (index: number) => {
if (!this.isPopupRuntimeAlive()) return;
if (!cc.fx.GameTool.shouldPreloadGameplayPopupInstances()) {
this.popupBundleWarmupStarted = false;
if (onComplete) onComplete();
return;
}
if (index >= requests.length) {
if (onComplete) {
if (warmupError) this.popupBundleWarmupStarted = false;
onComplete(warmupError);
}
return;
}
const request = requests[index];
const startedAt = Date.now();
// console.log(`[进关耗时][弹窗预热/${request.bundleName}] 开始(不阻塞进关)`);
const logDone = (error?: Error) => {
// console.log(`[进关耗时][弹窗预热/${request.bundleName}] ${error ? "失败" : "完成"},独立耗时=${Date.now() - startedAt}ms`);
};
// 即使资源全命中内存,也不要在同一帧连续实例化五个弹窗。
const next = () => this.scheduleOnce(() => preloadNext(index + 1), 0);
if (request.popupName === "win") {
if (preloadWin) {
preloadWin(error => {
if (!this.isPopupRuntimeAlive()) return;
logDone(error);
if (error) {
warmupError = error;
cc.warn("Popup instance warmup failed: win", error.message || error);
}
next();
});
} else {
next();
}
return;
}
this.loadDynamicPopup(
request.popupName,
request.bundleName,
request.prefabPath,
request.parent,
(error: Error) => {
if (!this.isPopupRuntimeAlive()) return;
logDone(error);
if (error) {
warmupError = error;
cc.warn(`Popup instance warmup failed: ${request.bundleName}`, error.message || error);
// loadDynamicPopup 已清理失败资源。用户可能正共用此请求,
// 这里不能清除其输入拦截/重试状态。
}
next();
},
false,
);
};
preloadNext(0);
return;
}
// iOS 低内存机型只提前创建 Pause Bundle,不加载 Prefab 和图片。
const shouldWarmupPauseBundle = cc.sys.platform === cc.sys.WECHAT_GAME
&& !!cc.fx.GameTool
&& !!cc.fx.GameTool.isIOSDevice
&& cc.fx.GameTool.isIOSDevice();
if (!shouldWarmupPauseBundle) return;
const loadedPauseBundle = cc.assetManager.getBundle("pause");
if (loadedPauseBundle) {
console.log("[PopupBundle] already loaded: pause (iOS low-memory warmup)");
return;
}
cc.assetManager.loadBundle("pause", (error: Error, bundle: cc.AssetManager.Bundle) => {
if (!this.isPopupRuntimeAlive()) {
if (bundle) SceneManager.releaseOrphanedPopupBundle("pause", bundle);
return;
}
if (error) {
cc.warn("[PopupBundle] loadBundle failed: pause (iOS low-memory warmup)", error.message || error);
return;
}
console.log("[PopupBundle] loadBundle complete: pause (iOS low-memory warmup)");
});
}
beginDynamicPopupLoad(popupName: string) {
if (!popupName || !this.isPopupRuntimeAlive()) return;
if (!this.dynamicPopupLoads) this.dynamicPopupLoads = {};
this.dynamicPopupLoads[popupName] = true;
const intercept = this.ensureDynamicPopupIntercept();
if (!intercept) {
cc.error("GameScene is missing Canvas/intercept");
return;
}
intercept.active = true;
}
finishDynamicPopupLoad(popupName: string) {
if (!popupName || !this.isPopupRuntimeAlive()) return;
if (!this.dynamicPopupLoads) this.dynamicPopupLoads = {};
if (!this.dynamicPopupRetries) this.dynamicPopupRetries = {};
delete this.dynamicPopupLoads[popupName];
delete this.dynamicPopupRetries[popupName];
if (this.dynamicPopupRetryName === popupName) {
this.clearDynamicPopupRetry();
this.showNextDynamicPopupRetry();
}
if (Object.keys(this.dynamicPopupLoads).length === 0) {
this.dynamicPopupRetries = {};
const intercept = this.ensureDynamicPopupIntercept();
if (intercept) intercept.active = false;
}
}
showDynamicPopupLoadError(popupName: string, displayName: string, retry: () => void) {
if (!this.isPopupRuntimeAlive()) return;
this.beginDynamicPopupLoad(popupName);
this.dynamicPopupRetries[popupName] = { displayName, retry };
if (this.dynamicPopupRetryNode && cc.isValid(this.dynamicPopupRetryNode)
&& this.dynamicPopupRetryName !== popupName) {
return;
}
this.clearDynamicPopupRetry();
this.showDynamicPopupRetry(popupName);
}
private showNextDynamicPopupRetry() {
if (!this.isPopupRuntimeAlive() || this.dynamicPopupRetryNode || !this.dynamicPopupRetries) return;
const popupName = Object.keys(this.dynamicPopupRetries)
.find((name) => !!this.dynamicPopupLoads[name]);
if (popupName) this.showDynamicPopupRetry(popupName);
}
private showDynamicPopupRetry(popupName: string) {
if (!this.isPopupRuntimeAlive() || !this.dynamicPopupRetries) return;
const retryInfo = this.dynamicPopupRetries[popupName];
if (!retryInfo || !this.node || !cc.isValid(this.node)) return;
const displayName = retryInfo.displayName;
const overlay = new cc.Node("DynamicPopupLoadRetry");
overlay.setContentSize(this.node.getContentSize());
overlay.setPosition(0, 0);
overlay.zIndex = cc.macro.MAX_ZINDEX;
overlay.addComponent(cc.BlockInputEvents);
this.node.addChild(overlay);
overlay.setSiblingIndex(this.node.childrenCount - 1);
const background = overlay.addComponent(cc.Graphics);
background.fillColor = cc.color(0, 0, 0, 225);
background.rect(-overlay.width / 2, -overlay.height / 2, overlay.width, overlay.height);
background.fill();
const messageNode = new cc.Node("message");
messageNode.setContentSize(760, 120);
messageNode.parent = overlay;
const label = messageNode.addComponent(cc.Label);
label.string = `${displayName || "界面资源"}加载失败\n点击屏幕重试`;
label.fontSize = 32;
label.lineHeight = 48;
label.horizontalAlign = cc.Label.HorizontalAlign.CENTER;
this.dynamicPopupRetryNode = overlay;
this.dynamicPopupRetryName = popupName;
overlay.once(cc.Node.EventType.TOUCH_END, () => {
if (!this.isPopupRuntimeAlive()) return;
if (this.dynamicPopupRetryNode !== overlay) return;
if (!this.dynamicPopupLoads || !this.dynamicPopupLoads[popupName]) return;
label.string = `${displayName || "界面资源"}正在重新加载\n请稍等`;
try {
retryInfo.retry();
} catch (error) {
cc.error(`${displayName || "界面资源"} retry failed:`, error);
this.showDynamicPopupLoadError(popupName, displayName, retryInfo.retry);
}
});
}
private clearDynamicPopupRetry() {
if (this.dynamicPopupRetryNode && cc.isValid(this.dynamicPopupRetryNode)) {
this.dynamicPopupRetryNode.removeFromParent();
this.dynamicPopupRetryNode.destroy();
}
this.dynamicPopupRetryNode = null;
this.dynamicPopupRetryName = "";
}
loadDynamicPopup(
popupName: string,
bundleName: string,
prefabPath: string,
parent: cc.Node,
callback: (error: Error, root?: cc.Node) => void,
showLoading: boolean = true,
) {
if (!this.isPopupRuntimeAlive()) return;
if (!this.dynamicPopupStates) this.dynamicPopupStates = {};
if (showLoading) this.beginDynamicPopupLoad(popupName);
let state = this.dynamicPopupStates[popupName];
if (!state) {
state = this.dynamicPopupStates[popupName] = {
bundleName,
prefabPath,
root: null,
bundle: null,
loading: false,
requestId: 0,
callbacks: [],
};
}
if (state.root && cc.isValid(state.root)) {
callback(null, state.root);
return;
}
state.root = null;
if (!parent || !cc.isValid(parent)) {
callback(new Error(`${popupName} parent node is missing`));
return;
}
state.callbacks.push(callback);
if (state.loading) return;
state.loading = true;
const requestId = ++state.requestId;
const fail = (error: Error, bundle?: cc.AssetManager.Bundle) => {
if (!this.isDynamicPopupRequestCurrent(popupName, state, requestId)) {
if (bundle) this.releaseStalePopupBundle(bundleName, bundle);
return;
}
if (bundle && state.bundle === bundle) state.bundle = null;
state.loading = false;
if (bundle) this.releaseDynamicPopupBundleIfUnused(bundleName, bundle);
this.finishDynamicPopupAssetLoad(state, error);
};
const loadPrefab = (bundle: cc.AssetManager.Bundle) => {
if (!this.isDynamicPopupRequestCurrent(popupName, state, requestId)) {
this.releaseStalePopupBundle(bundleName, bundle);
return;
}
state.bundle = bundle;
bundle.load(prefabPath, cc.Prefab, (error: Error, prefab: cc.Prefab) => {
if (error) {
fail(error, bundle);
return;
}
if (!this.isDynamicPopupRequestCurrent(popupName, state, requestId)) {
this.releaseStalePopupBundle(bundleName, bundle);
return;
}
let root: cc.Node = null;
try {
root = cc.instantiate(prefab);
root.active = false;
parent.addChild(root);
root.setPosition(0, 0);
root.zIndex = cc.macro.MAX_ZINDEX - 2;
console.log(`[PopupPrefab] instantiated inactive: ${bundleName}/${prefabPath}`);
} catch (instantiateError) {
if (root && cc.isValid(root)) {
root.active = false;
root.removeFromParent();
root.destroy();
}
fail(instantiateError as Error, bundle);
return;
}
state.root = root;
this.finishDynamicPopupAssetLoad(state, null, root);
});
};
const loadedBundle = cc.assetManager.getBundle(bundleName);
if (loadedBundle) {
console.log(`[PopupBundle] already loaded: ${bundleName}`);
loadPrefab(loadedBundle);
return;
}
cc.assetManager.loadBundle(bundleName, (error: Error, bundle: cc.AssetManager.Bundle) => {
if (error) {
fail(error);
return;
}
console.log(`[PopupBundle] loadBundle complete: ${bundleName}`);
loadPrefab(bundle);
});
}
getDynamicPopup(popupName: string): cc.Node {
if (!cc.isValid(this, true) || !this.dynamicPopupStates) return null;
const state = this.dynamicPopupStates[popupName];
return state && state.root && cc.isValid(state.root) ? state.root : null;
}
releaseDynamicPopup(popupName: string, force: boolean = false, retainBundle: boolean = false) {
if (!this.dynamicPopupStates) return;
if (this.isDestroying !== true && !this.isPopupRuntimeAlive()) return;
const state = this.dynamicPopupStates[popupName];
if (state && !force && this.shouldKeepGameplayPopupInstance(popupName)
&& state.root && cc.isValid(state.root) && !state.loading) {
state.root.active = false;
this.finishDynamicPopupLoad(popupName);
return;
}
if (state) {
delete this.dynamicPopupStates[popupName];
state.requestId++;
state.callbacks.length = 0;
if (state.root && cc.isValid(state.root)) {
state.root.active = false;
state.root.removeFromParent();
state.root.destroy();
}
state.root = null;
const bundle = state.bundle || cc.assetManager.getBundle(state.bundleName);
state.bundle = null;
if (bundle && !retainBundle) this.releaseDynamicPopupBundle(state.bundleName, bundle);
}
this.finishDynamicPopupLoad(popupName);
}
private shouldKeepGameplayPopupInstance(popupName: string): boolean {
const cacheablePopups = ["pause", "lose", "propWindow", "newMode"];
return cacheablePopups.indexOf(popupName) >= 0
&& !!cc.fx.GameTool
&& !!cc.fx.GameTool.shouldPreloadGameplayPopupInstances
&& cc.fx.GameTool.shouldPreloadGameplayPopupInstances();
}
private isDynamicPopupRequestCurrent(popupName: string, state: DynamicPopupState, requestId: number) {
return this.isPopupRuntimeAlive()
&& !!this.dynamicPopupStates
&& this.dynamicPopupStates[popupName] === state
&& state.requestId === requestId;
}
private finishDynamicPopupAssetLoad(state: DynamicPopupState, error: Error, root?: cc.Node) {
if (!this.isPopupRuntimeAlive()) {
state.loading = false;
state.callbacks.length = 0;
return;
}
state.loading = false;
const callbacks = state.callbacks.splice(0);
callbacks.forEach((callback) => {
if (!this.isPopupRuntimeAlive()) return;
try {
callback(error, root);
} catch (callbackError) {
cc.error("Dynamic popup callback failed:", callbackError);
}
});
}
private releaseDynamicPopupBundle(bundleName: string, bundle: cc.AssetManager.Bundle) {
if (bundleName === "action_bundle") return;
if (cc.assetManager.getBundle(bundleName) !== bundle) return;
bundle.releaseAll();
if (cc.assetManager.getBundle(bundleName) === bundle) {
cc.assetManager.removeBundle(bundle);
}
}
private releaseDynamicPopupBundleIfUnused(bundleName: string, bundle: cc.AssetManager.Bundle) {
const states = this.dynamicPopupStates;
if (!states) {
SceneManager.releaseOrphanedPopupBundle(bundleName, bundle);
return;
}
const isInUse = Object.keys(states).some((popupName) => {
const state = states[popupName];
return state.bundle === bundle || (state.loading && state.bundleName === bundleName);
});
if (!isInUse) this.releaseDynamicPopupBundle(bundleName, bundle);
}
bindLosePanel(loseRoot: cc.Node) {
const lose = loseRoot && loseRoot.getChildByName("lose");
const time = lose && lose.getChildByName("Time");
const buyBtn = time && time.getChildByName("buyBtn");
const revive = time && time.getChildByName("Revive");
const timeCoin = buyBtn && buyBtn.getChildByName("coinNun");
const panel: any = loseRoot && loseRoot.getComponent("LosePanel");
const map = MapConroler && MapConroler._instance;
if (!lose || !revive || !timeCoin || !panel || !panel.bind || !map) {
cc.error("SceneManager.bindLosePanel failed: Lose prefab node structure changed");
this.unbindLosePanel();
return false;
}
panel.bind({
shareFriend: () => this.shareFriend(),
shareFriend2: () => this.shareFriend2(),
shareClose: () => this.shareClose(),
homeBtn: (event: cc.Event, data: string) => map.homeBtn(event, data),
reviewLevel: (event: cc.Event, data: string) => map.reviewLevel(event, data),
closeWinStreak: () => map.closeWinStreak(),
openHealth: () => map.openHealth(),
returnHome: (event: cc.Event, data: string) => map.returnHome(event, data),
againLevel: () => map.againLevel(),
});
map.revive = revive;
this.timeCoin = timeCoin;
NumberToImage.numberToImageNodes(1000, 25, 15, "button_", this.timeCoin, false);
return true;
}
unbindLosePanel() {
if (MapConroler && MapConroler._instance) {
MapConroler._instance.revive = null;
}
this.timeCoin = null;
}
releaseLosePanel(force: boolean = false) {
this.unbindLosePanel();
this.releaseDynamicPopup("lose", force);
}
private bindPropWindowPanel(propRoot: cc.Node) {
const prop = propRoot && propRoot.getChildByName("prop");
const buyBtn = prop && prop.getChildByName("buy_Btn");
const panel: any = propRoot && propRoot.getComponent("PropWindowPanel");
const map = MapConroler && MapConroler._instance;
if (!prop || !buyBtn || !buyBtn.getComponent("btnControl") || !panel || !panel.bind || !map) {
cc.error("SceneManager.bindPropWindowPanel failed: propWindow prefab node structure changed");
this.unbindPropWindowPanel();
return false;
}
panel.bind({
clickBtn: () => this.clickBtn(),
closePropBuy: (event: cc.Event) => this.closePropBuy(event),
});
map.coinPop = propRoot;
return true;
}
private unbindPropWindowPanel() {
if (MapConroler && MapConroler._instance) {
MapConroler._instance.coinPop = null;
}
}
private releasePropWindowPanel(force: boolean = false) {
this.unbindPropWindowPanel();
this.releaseDynamicPopup("propWindow", force);
}
private bindPausePanel(pauseRoot: cc.Node) {
const pauseContent = pauseRoot && pauseRoot.getChildByName("pause");
const panel: any = pauseRoot && pauseRoot.getComponent("PausePanel");
const setting = pauseRoot && pauseRoot.getComponent("setUi");
const map = MapConroler && MapConroler._instance;
if (!pauseContent || !panel || !panel.bind || !setting || !map) {
cc.error("SceneManager.bindPausePanel failed: Pause prefab node structure changed");
return false;
}
panel.bind({
closePause: () => this.closePause(),
shareFriend: () => this.shareFriend(),
openMessage: () => this.openMessage(),
returnHome: (event: cc.Event, data: string) => map.returnHome(event, data),
});
return true;
}
isPauseOpen(): boolean {
const pauseRoot = this.getDynamicPopup("pause");
return !!(this.dynamicPopupLoads && this.dynamicPopupLoads.pause)
|| (!!pauseRoot && pauseRoot.active);
}
releasePausePanel(force: boolean = false) {
const pauseControl = this.pause && this.pause.getComponent("btnControl");
if (pauseControl) pauseControl.setTouch(true);
this.releaseDynamicPopup("pause", force);
}
bindWinPanel(winRoot: cc.Node) {
const tween = winRoot && winRoot.getChildByName("tween");
const diguan = tween && tween.getChildByName("diguan");
const tiaodik = tween && tween.getChildByName("tiaodik");
this.level = diguan && diguan.getChildByName("level");
this.winCoin = tiaodik && tiaodik.getChildByName("coin");
this.nextMode = tiaodik && tiaodik.getChildByName("nextMode");
if (!this.level || !this.winCoin || !this.nextMode) {
cc.error("SceneManager.bindWinPanel failed: Win prefab node structure changed");
this.unbindWinPanel();
return false;
}
this.refreshWinPanelInfo();
return true;
}
unbindWinPanel() {
this.level = null;
this.winCoin = null;
this.nextMode = null;
this.clearFlyCoins();
}
private refreshWinPanelInfo() {
if (!this.level || !cc.isValid(this.level)) return;
const levelName = this.winLevelName || this.resolveWinLevelName();
// NumberToImage.numberToImageNodes3(levelName, 43, 15, "level_", this.level, true);
NumberToImage.numberToImageNodes(levelName, 43, 8, "button_", this.level, true);
this.level.children.forEach((child) => child.color = new cc.Color(195, 154, 91));
}
private resolveWinLevelName(): number {
if (cc.fx.GameConfig.GM_INFO.otherLevel > 0) {
return Number(cc.fx.GameConfig.GM_INFO.otherLevel) || 0;
}
if (cc.fx.GameTool.maxLevel() && cc.fx.GameConfig.GM_INFO.GameplayType == 1) {
return Number(cc.fx.GameConfig.GM_INFO.randomLevel) || 0;
}
return (Number(cc.fx.GameConfig.GM_INFO.level) || 0) + 1;
}
/**
* 为 GameScene 中所有可交互按钮统一添加轻微震动。
* 使用根节点捕获事件,以覆盖运行时动态创建的按钮。
*/
private onGameButtonTouchEnd(event: cc.Event.EventTouch) {
let target = event && event.target as cc.Node;
while (target) {
const button = target.getComponent(cc.Button);
if (button) {
// _pressed 为 false 时说明手指已移出按钮,不属于有效点击。
if (button.interactable && button.enabledInHierarchy && (button as any)._pressed !== false) {
try {
cc.fx.GameTool.setVibrate("light", 1);
} catch (error) {
try {
console.warn("GameScene 按钮轻振动调用失败:", error);
} catch (e) { }
}
}
return;
}
if (target === this.node) {
return;
}
target = target.parent;
}
}
// LIFE-CYCLE CALLBACKS:
onLoad() {
SceneManager.activeInstance = this;
this.isDestroying = false;
this.dynamicPopupLoads = {};
this.dynamicPopupRetries = {};
this.dynamicPopupStates = {};
this.popupBundleWarmupStarted = false;
this.ensureLoadingOnTop();
const intercept = this.ensureDynamicPopupIntercept();
if (intercept) intercept.active = false;
this.winLevelName = this.resolveWinLevelName();
this.node.on(cc.Node.EventType.TOUCH_END, this.onGameButtonTouchEnd, this, true);
this.share_time = true;
cc.game.setFrameRate(63);
this.changeBg();
// 预加载 shop 预制体
SceneManager.clearInvalidShopPrefabCaches();
if (!SceneManager.cachedShopPrefab
|| !SceneManager.cachedRewardPrefab
|| !SceneManager.cachedMonthlyCardPrefab) {
cc.assetManager.loadBundle('shop', (err: Error, bundle: cc.AssetManager.Bundle) => {
if (err) {
cc.error(err.message || err);
return;
}
if (!SceneManager.cachedShopPrefab) {
bundle.load('prefab/shop', cc.Prefab, (err: Error, prefab: cc.Prefab) => {
if (err) {
cc.error(err.message || err);
return;
}
SceneManager.cachedShopPrefab = prefab;
});
}
if (!SceneManager.cachedRewardPrefab) {
bundle.load('prefab/RewardWindow', cc.Prefab, (err: Error, prefab: cc.Prefab) => {
if (err) {
cc.error(err.message || err);
return;
}
SceneManager.cachedRewardPrefab = prefab;
});
}
if (!SceneManager.cachedMonthlyCardPrefab) {
bundle.load('prefab/monthlyCard', cc.Prefab, (err: Error, prefab: cc.Prefab) => {
if (err) {
cc.error(err.message || err);
return;
}
SceneManager.cachedMonthlyCardPrefab = prefab;
});
}
});
}
if (!cc.fx.GameTool.isLowMemoryIOSDevice()) {
setTimeout(() => {
cc.director.preloadScene("HomeScene", (err) => {
if (err) {
// console.error('预加载 HomeScene 场景失败:', err);
return;
}
// console.log('成功预加载 HomeScene 场景');
});
}, 5000);
}
else {
console.log("[Memory] low-memory iOS: skip HomeScene preload in SceneManager");
}
}
onDestroy() {
this.isDestroying = true;
if (SceneManager.activeInstance === this) SceneManager.activeInstance = null;
this.unbindLosePanel();
this.unbindPropWindowPanel();
const retainPopupBundles = !!cc.fx.GameTool
&& !!cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload
&& cc.fx.GameTool.shouldRetainGameplayPopupBundlesForGameSceneReload();
Object.keys(this.dynamicPopupStates || {}).forEach((popupName) =>
this.releaseDynamicPopup(popupName, true, retainPopupBundles));
if (!retainPopupBundles) {
["pause", "NewMode", "propWindow", "lose"].forEach((bundleName) => {
const bundle = cc.assetManager.getBundle(bundleName);
if (bundle) this.releaseDynamicPopupBundleIfUnused(bundleName, bundle);
});
}
else {
console.log("[PopupBundle] retained pause/NewMode/propWindow/lose for next GameScene");
}
this.dynamicPopupStates = {};
this.dynamicPopupLoads = {};
this.dynamicPopupRetries = {};
this.clearDynamicPopupRetry();
const intercept = this.node && this.node.getChildByName("intercept");
if (intercept) intercept.active = false;
if (this.node && cc.isValid(this.node, true)) {
this.node.off(cc.Node.EventType.TOUCH_END, this.onGameButtonTouchEnd, this, true);
}
this.clearFlyCoins();
this.disposeJungleTreasure(false);
}
clearFlyCoins() {
if (this.flyCoinLayer && cc.isValid(this.flyCoinLayer)) {
cc.Tween.stopAllByTarget(this.flyCoinLayer);
for (let i = 0; i < this.flyCoinLayer.childrenCount; i++) {
const child = this.flyCoinLayer.children[i];
if (child && cc.isValid(child)) {
cc.Tween.stopAllByTarget(child);
}
}
this.flyCoinLayer.removeAllChildren();
}
}
private updateGameCoinDisplay(displayCoin: number = cc.fx.GameConfig.GM_INFO.coin): void {
if (this.isDestroying) return;
const map = MapConroler && MapConroler._instance;
if (map && cc.isValid(map, true) && map.node && cc.isValid(map.node, true)) {
map.updateCoin(displayCoin);
}
}
getWinCoinFlyTarget() {
if (!this.node || !cc.isValid(this.node)) {
return null;
}
const game = this.node.getChildByName("Game");
const gameNode = game && game.getChildByName("GameNode");
const top = gameNode && (gameNode.getChildByName("top") || gameNode.getChildByName("Top"));
const shop = top && top.getChildByName("shop");
return shop && shop.getChildByName("coins");
}
//更新关卡获得金币
updateWinCoinIconState() {
if (!this.winCoin || !cc.isValid(this.winCoin) || !this.winCoin.parent) {
return;
}
const layerTwoOpen = cc.fx.GameConfig.LEGACY_LAYER_TWO_VARIANT_B_ENABLED;
const coinIcon = this.winCoin.parent.getChildByName("coinIcon");
const meijinbi = this.winCoin.parent.getChildByName("meijinbi");
if (coinIcon) {
coinIcon.active = layerTwoOpen;
}
if (meijinbi) {
meijinbi.active = !layerTwoOpen;
}
}
updateWinCoin() {
if (!this.winCoin || !cc.isValid(this.winCoin)) {
return;
}
console.log("更新关卡获得金币");
this.updateWinCoinIconState();
let winCoin = null;
winCoin = 40;
winCoin = winCoin * cc.fx.GameConfig.GM_INFO.doubleCoin;
if (winCoin > 0) {
NumberToImage.numberToImageNodes(winCoin, 50, 8, "half_black_", this.winCoin, false);
} else {
this.winCoin.active = false;
}
}
//更新下一关获得金币
playWinCoinFly(winRewardCoin: number) {
if (this.isDestroying || !this.node || !cc.isValid(this.node)) {
return;
}
if (!this.winCoin || !cc.isValid(this.winCoin) || (!this.flyCoinPrefab && !this.flyCoinSpriteFrame)) {
console.warn("playWinCoinFly skip: winCoin or fly coin asset missing");
this.updateGameCoinDisplay();
return;
}
const targetNode = this.getWinCoinFlyTarget();
if (!targetNode || !cc.isValid(targetNode)) {
console.warn("playWinCoinFly skip: target GameNode/top/shop/coins missing");
this.updateGameCoinDisplay();
return;
}
const coinParent = this.winCoin.parent && this.winCoin.parent.getChildByName("beijing");
if (!coinParent || !cc.isValid(coinParent)) {
console.warn("playWinCoinFly skip: Win/tween/tiaodik/beijing missing");
this.updateGameCoinDisplay();
return;
}
this.clearFlyCoins();
if (!this.flyCoinLayer || !cc.isValid(this.flyCoinLayer)) {
this.flyCoinLayer = coinParent.getChildByName("flyCoinLayer");
}
if (!this.flyCoinLayer || !cc.isValid(this.flyCoinLayer)) {
this.flyCoinLayer = new cc.Node("flyCoinLayer");
}
const layer = this.flyCoinLayer;
layer.parent = coinParent;
layer.setPosition(0, 0);
layer.active = true;
layer.zIndex = 9999;
layer.removeAllChildren();
const startWorld = this.winCoin.convertToWorldSpaceAR(cc.v2(0, 0));
const targetWorld = targetNode.convertToWorldSpaceAR(cc.v2(0, 0));
// 散开偏移沿用 Canvas 坐标,再转换到 beijing 下,避免父节点变换影响路径。
const startPos = this.node.convertToNodeSpaceAR(startWorld);
const targetPos = layer.convertToNodeSpaceAR(targetWorld);
const count = Math.max(1, Math.floor(Number(this.flyCoinCount) || 1));
const speed = 2;
const duration = Math.max(0.1, Number(this.flyCoinDuration) || 0.55) / speed;
const interval = Math.max(0, Number(this.flyCoinInterval) || 0) / speed;
const finalCoin = Math.max(0, Math.floor(Number(cc.fx.GameConfig.GM_INFO.coin) || 0));
const rewardCoin = Math.max(0, Math.floor(Number(winRewardCoin) || 0));
const startCoin = Math.max(0, finalCoin - rewardCoin);
let arrivedCount = 0;
this.updateGameCoinDisplay(startCoin);
console.log("playWinCoinFly start", count, startPos, targetPos);
for (let i = 0; i < count; i++) {
if (this.isDestroying || !cc.isValid(layer)) {
return;
}
const coin = this.createFlyCoinNode();
if (!coin || !cc.isValid(layer)) {
continue;
}
layer.addChild(coin);
coin.active = true;
coin.opacity = 255;
coin.zIndex = 1;
const startX = startPos.x + (Math.random() - 0.5) * 120 + 50;
const startY = startPos.y + (Math.random() - 0.5) * 70 + 150;
coin.setPosition(layer.convertToNodeSpaceAR(this.node.convertToWorldSpaceAR(cc.v2(startX, startY))));
coin.scale = 0;
const midPos = layer.convertToNodeSpaceAR(this.node.convertToWorldSpaceAR(cc.v2(
startX + (Math.random() - 0.5) * 180,
startY + 80 + Math.random() * 80
)));
cc.tween(coin)
.delay(i * interval)
.to(0.12 / speed, { scale: 1 }, { easing: "backOut" })
.to(duration * 0.35, { x: midPos.x, y: midPos.y }, { easing: "sineOut" })
.to(duration * 0.65, { x: targetPos.x, y: targetPos.y, scale: 0.35 }, { easing: "sineIn" })
.call(() => {
arrivedCount++;
const displayCoin = arrivedCount >= count
? finalCoin
: startCoin + Math.floor(rewardCoin * arrivedCount / count);
this.updateGameCoinDisplay(displayCoin);
if (coin && cc.isValid(coin)) {
coin.destroy();
}
})
.start();
}
}
createFlyCoinNode() {
if (this.flyCoinPrefab) {
return cc.instantiate(this.flyCoinPrefab);
}
if (!this.flyCoinSpriteFrame) {
return null;
}
const node = new cc.Node("flyCoin");
const sprite = node.addComponent(cc.Sprite);
sprite.spriteFrame = this.flyCoinSpriteFrame;
return node;
}
updateNextMode() {
if (!this.nextMode || !cc.isValid(this.nextMode) || !this.nextMode.parent) {
return;
}
let nextMode = -1;
this.nextMode.active = false;
this.nextMode.parent.getChildByName("nextLabel").active = false;
const currentLevel = cc.fx.GameConfig.GM_INFO.level + 1;
const newLevelConfig = cc.fx.GameConfig.NEW_LEVEL || [];
for (let i = 0; i < newLevelConfig.length; i++) {
const newLevel = Number(newLevelConfig[i].level);
if (newLevel > currentLevel) {
const diff = newLevel - currentLevel;
if (nextMode < 0 || diff < nextMode) {
nextMode = diff;
}
}
if (newLevel == currentLevel) {
nextMode = 0;
this.nextMode.active = false;
this.nextMode.parent.getChildByName("nextLabel").active = false;
}
}
if (nextMode > 0 && nextMode <= 10) {
this.nextMode.active = true;
this.nextMode.parent.getChildByName("nextLabel").active = true;
NumberToImage.numberToImageNodes(nextMode, 35, 8, "time_", this.nextMode, false);
} else {
this.nextMode.active = false;
this.nextMode.parent.getChildByName("nextLabel").active = false;
}
}
changeBg() {
let number = Math.floor(Math.random() * 8) + 1;
const path = 'bg/bg' + number;
cc.resources.load(path, cc.SpriteFrame, (err, spriteFrame: cc.SpriteFrame) => {
if (err) {
console.error('动态加载背景图失败:', err);
return;
}
this.node.getChildByName("Game").getChildByName("bg").getComponent(cc.Sprite).spriteFrame = spriteFrame;
})
//time金币数量
if (this.timeCoin && cc.isValid(this.timeCoin)) {
NumberToImage.numberToImageNodes(1000, 25, 15, "button_", this.timeCoin, false);
}
this.refreshWinPanelInfo();
}
loadParticleEffects() {
const path = 'Particle';
cc.resources.loadDir(path, cc.ParticleAsset, (err, assets: cc.ParticleAsset[]) => {
if (err) {
console.error('动态加载粒子特效失败:', err);
return;
}
this.particleEffects = assets;
this.setParticleSort();
this.load3 = true;
//console.log('粒子特效加载成功,共加载了', this.particleEffects.length, '个粒子特效');
});
}
setWallPrefabSort() {
const order = ['down', 'downLeft', 'downRight', 'left', 'right', 'up', 'upLeft', 'upRight'];
this.Wall_Prefab.sort((a, b) => {
const indexA = order.indexOf(a.name);
const indexB = order.indexOf(b.name);
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
});
}
setParticleSort() {
const order = ['top', 'bot', 'rig', 'lef'];
this.particleEffects.sort((a, b) => {
// console.log(a.name.substr(0,3),b.name.substr(0,3));
const indexA = order.indexOf(a.name.substr(0, 3));
const indexB = order.indexOf(b.name.substr(0, 3));
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
});
}
setSort() {
this.Block_Array.sort((a, b) => {
// 从名称中提取数字部分
const numberA = parseInt(a.name.match(/\d+/)?.[0] || '0', 10);
const numberB = parseInt(b.name.match(/\d+/)?.[0] || '0', 10);
return numberA - numberB;
});
}
start() {
}
startGame() {
let jg = cc.fx.GameTool.getAuthorizationAvatar();
if (!jg) {
cc.fx.GameTool.getUserAvatar(() => {
})
}
cc.director.loadScene("HomeScene", (err) => {
if (err) {
console.error('加载 HomeScene 场景失败:', err);
} else {
// console.log('成功切换到 HomeScene 场景');
cc.director.loadScene("HomeScene");
}
});
}
returnHome() {
if (!this.isPopupRuntimeAlive()) return;
const pauseRoot = this.getDynamicPopup("pause");
const pauseContent = pauseRoot && pauseRoot.getChildByName("pause");
const continueBtn = pauseContent && pauseContent.getChildByName("btn");
const continueControl = continueBtn && continueBtn.getComponent("btnControl");
if (continueControl && continueControl._touch && MapConroler && MapConroler._instance) {
continueControl.setTouch(false);
this.closePause();
if (MapConroler._instance.gameStart == true) {
// MiniGameSdk.API.showToast("体力值减少");
if (MapConroler._instance.count_Time) {
let overTime = Date.now();
let count_Time = overTime - MapConroler._instance.count_Time;
let add_Time = MapConroler._instance.add_Time;
let endLevel = false;
if (cc.fx.GameTool.maxLevel() && cc.fx.GameConfig.GM_INFO.GameplayType == 1) {
endLevel = true;
}
let data = {
time: count_Time,
add_Time: add_Time,
is_frenzy: MapConroler._instance.is_frenzy,
result: "give_up",
infinity: endLevel,
total_steps_count: MapConroler._instance.total_steps_count,
}
cc.fx.GameTool.shushu_Track("finish_stage", data);
let data2 = {
is_frenzy: MapConroler._instance.is_frenzy,
count: cc.fx.GameConfig.GM_INFO.winStreak
}
cc.fx.GameTool.shushu_Track("hammer_frenzy", data2);
}
}
cc.fx.AudioManager._instance.playEffect("zhuan1", null);
this.node.getChildByName("zhuanchang").active = true;
this.node.getChildByName("zhuanchang").getComponent(sp.Skeleton).setAnimation(1, "up", false);
cc.director.preloadScene("HomeScene", (err, asset) => {
if (err) {
console.error('动态加载 Prefab 失败:', err);
return;
}
});
setTimeout(() => {
cc.fx.GameConfig.GM_INFO.otherLevel = 0;
cc.fx.GameConfig.GM_INFO.otherUid = "";
let jg = cc.fx.GameTool.getAuthorizationAvatar();
if (!jg) {
cc.fx.GameTool.getUserAvatar(() => {
})
}
cc.director.loadScene("HomeScene");
}, 1200);
}
}
destroyNodesInFrames(nodes: cc.Node[], callback: () => void) {
const BATCH_SIZE = 10; // 每帧销毁的节点数量
let index = 0;
const destroyBatch = () => {
let count = 0;
while (index < nodes.length && count < BATCH_SIZE) {
const node = nodes[index];
if (node) {
node.active = false;
}
index++;
count++;
}
if (index < nodes.length) {
this.scheduleOnce(destroyBatch, 6);
} else {
callback();
}
};
destroyBatch();
}
// 改进后的切换场景方法
switchToEmptyScene() {
const allNodes = cc.director.getScene().children;
this.destroyNodesInFrames(allNodes, () => {
let jg = cc.fx.GameTool.getAuthorizationAvatar();
if (!jg) {
cc.fx.GameTool.getUserAvatar(() => {
})
}
cc.director.loadScene("HomeScene");
});
}
private returningHome: boolean = false;
returnHomeScene(event?: cc.Event, customEventData?: string) {
if (!this.isPopupRuntimeAlive() || this.returningHome) return;
this.returningHome = true;
cc.fx.GameConfig.locatePositionOnHome = customEventData === "ruzhi";
let started = false;
const goHome = () => {
if (started || !this.isPopupRuntimeAlive()) return;
started = true;
const accepted = cc.director.loadScene("HomeScene", (error) => {
if (error && this.isPopupRuntimeAlive()) {
this.returningHome = false;
cc.error("returnHomeScene failed:", error);
}
});
if (!accepted) this.returningHome = false;
};
try {
cc.fx.GameTool.shushu_Track("where_am_i", {});
} catch (error) {
cc.warn("where_am_i tracking failed:", error);
}
try {
if (customEventData === "ruzhi" && !cc.fx.GameTool.getAuthorizationAvatar()) {
// 授权结果不影响返回首页,也不等待头像昵称上传。
cc.fx.GameTool.getUserAvatar(goHome);
} else {
goHome();
}
} catch (error) {
cc.warn("returnHomeScene avatar request failed:", error);
goHome();
}
}
openPause() {
if (!this.isPopupRuntimeAlive()) return;
cc.fx.AudioManager._instance.playEffect("anniu_little", null);
const pauseControl = this.pause && this.pause.getComponent("btnControl");
if (!pauseControl || !pauseControl._touch) return;
pauseControl.setTouch(false);
if (MapConroler && MapConroler._instance) {
MapConroler._instance.pause = true;
MapConroler._instance.stopBoom();
}
this.loadPausePanel();
}
private loadPausePanel() {
if (!this.isPopupRuntimeAlive()) return;
this.loadDynamicPopup("pause", "pause", "prefab/Pause", this.node, (error, pauseRoot) => {
if (!this.isPopupRuntimeAlive()) return;
if (error || !pauseRoot) {
this.showPauseLoadError(error || new Error("Pause prefab is missing"));
return;
}
try {
if (!this.bindPausePanel(pauseRoot)) throw new Error("Pause runtime binding failed");
const pauseContent = pauseRoot.getChildByName("pause");
pauseRoot.active = true;
pauseContent.scale = 0.3;
cc.tween(pauseContent)
.to(0.2, { scale: 1.05 }, { easing: 'backOut' })
.to(0.15, { scale: 1.0 }, { easing: 'sineOut' })
.start();
this.finishDynamicPopupLoad("pause");
} catch (bindError) {
this.releasePausePanel(true);
this.showPauseLoadError(bindError as Error);
}
});
}
private showPauseLoadError(error: Error) {
if (!this.isPopupRuntimeAlive()) return;
cc.error("Pause cannot open:", error && (error.message || error));
this.showDynamicPopupLoadError("pause", "暂停界面资源", () => this.loadPausePanel());
}
closeShop() {
if (this.shopNode) {
this.shopNode.active = false;
if (MapConroler && MapConroler._instance) {
MapConroler._instance.shopPause = false;
var pause = MapConroler._instance.iceTrue();
if (pause == false && !this.isPauseOpen()) {
if (MapConroler && MapConroler._instance) MapConroler._instance.pause = false;
if (MapConroler._instance.gameStart == true) MapConroler._instance.startBoom();
}
}
}
}
/** GameScene 商城入口打开 Jungle Treasure。 */
openJungleTreasure(event?: cc.Event, customEventData?: string) {
let level = Number(cc.fx.GameConfig.GM_INFO.level) || 0;
if (!cc.fx.GameConfig.GM_INFO.canIos
|| cc.fx.GameConfig.GM_INFO.jungleTreasureAvailable !== true
|| level < cc.fx.GameConfig.getJungleTreasureUnlockLevel()
|| this.jungleTreasureDataLoading
|| this.jungleTreasureBundleLoading || this.jungleTreasureNode) {
return;
}
this.jungleTreasureOpenSource = customEventData === "jump_window"
? "jump_window"
: "shop_page";
this.openLoad();
this.jungleTreasureDataLoading = true;
let requestId = ++this.jungleTreasureRequestId;
this.clearJungleTreasureRequestTimeout();
this.jungleTreasureRequestTimeout = setTimeout(() => {
if (requestId !== this.jungleTreasureRequestId || !this.jungleTreasureDataLoading) {
return;
}
this.jungleTreasureRequestId++;
this.finishJungleTreasureOpenFailure("获取活动数据超时,请稍后重试");
}, 8000);
Utils.getJungleTreasure((response) => {
if (requestId !== this.jungleTreasureRequestId || this.isDestroying) {
return;
}
this.clearJungleTreasureRequestTimeout();
this.jungleTreasureDataLoading = false;
let data = response && response.code === 1 ? response.data : null;
if (data && data.available === false) {
cc.fx.GameConfig.GM_INFO.jungleTreasureAvailable = false;
this.closeLoad();
return;
}
let config = data && data.config;
let tierStates = data && data.tierStates;
if (!config || !Array.isArray(config.tiers) || !Array.isArray(tierStates)
|| tierStates.length < config.tiers.length) {
this.finishJungleTreasureOpenFailure(
(response && (response.msg || response.message)) || "获取活动数据失败",
);
return;
}
this.jungleTreasureServerData = data;
cc.fx.GameConfig.GM_INFO.jungleTreasureAvailable = true;
this.cacheJungleTreasureState(data);
this.loadJungleTreasureBundle();
});
}
closeJungleTreasure() {
this.closeLoad();
this.disposeJungleTreasure(true);
}
/** Jungle 领奖后刷新 GameScene 的金币和三种局内道具显示。 */
refreshJungleRewardDisplay(): void {
if (MapConroler && MapConroler._instance
&& MapConroler._instance.node && cc.isValid(MapConroler._instance.node)) {
MapConroler._instance.updateCoin();
MapConroler._instance.setPropNum();
}
}
private loadJungleTreasureBundle(): void {
if (SceneManager.cachedJungleTreasurePrefab) {
this.showJungleTreasure();
return;
}
this.jungleTreasureBundleLoading = true;
let existingBundle = cc.assetManager.getBundle("jungle_treasure");
if (existingBundle) {
this.loadJungleTreasurePrefab(existingBundle);
return;
}
cc.assetManager.loadBundle("jungle_treasure", (bundleError: Error, bundle: cc.AssetManager.Bundle) => {
if (this.isDestroying) {
return;
}
if (bundleError || !bundle) {
this.jungleTreasureBundleLoading = false;
this.finishJungleTreasureOpenFailure("加载活动资源失败,请稍后重试");
cc.error("GameScene 加载 jungle_treasure 分包失败:", bundleError);
return;
}
this.loadJungleTreasurePrefab(bundle);
});
}
private loadJungleTreasurePrefab(bundle: cc.AssetManager.Bundle): void {
bundle.load("prefab/jungle", cc.Prefab, (prefabError: Error, prefab: cc.Prefab) => {
if (this.isDestroying) {
return;
}
this.jungleTreasureBundleLoading = false;
if (prefabError || !prefab) {
this.finishJungleTreasureOpenFailure("加载活动界面失败,请稍后重试");
cc.error("GameScene 加载 Jungle prefab 失败:", prefabError);
return;
}
SceneManager.cachedJungleTreasurePrefab = prefab;
this.showJungleTreasure();
});
}
private showJungleTreasure(): void {
if (!SceneManager.cachedJungleTreasurePrefab || !this.jungleTreasureServerData
|| this.isDestroying) {
this.finishJungleTreasureOpenFailure("活动界面初始化失败");
return;
}
try {
let jungleNode = cc.instantiate(SceneManager.cachedJungleTreasurePrefab);
this.jungleTreasureNode = jungleNode;
this.node.addChild(jungleNode);
jungleNode.zIndex = 1001;
jungleNode.on("jungle-close", this.closeJungleTreasure, this);
jungleNode.on("jungle-state-changed", this.onJungleTreasureStateChanged, this);
this.ensureLoadingOnTop();
let manager: any = jungleNode.getComponent("jungle_Manager");
if (!manager || typeof manager.setServerData !== "function"
|| typeof manager.openActivity !== "function") {
throw new Error("Jungle prefab 缺少 jungle_Manager 初始化方法");
}
let initialized = manager.setServerData(
this.jungleTreasureServerData.config,
this.jungleTreasureServerData.tierStates,
this.jungleTreasureOpenSource,
);
if (!initialized) {
throw new Error("Jungle 服务器配置初始化失败");
}
manager.openActivity();
this.closeLoad();
}
catch (error) {
cc.error("GameScene Jungle 界面初始化异常:", error);
this.disposeJungleTreasure(true);
this.finishJungleTreasureOpenFailure("活动界面初始化失败");
}
}
private onJungleTreasureStateChanged(data: any): void {
if (!data || !Array.isArray(data.tierStates)) {
return;
}
if (!this.jungleTreasureServerData) {
this.jungleTreasureServerData = {};
}
this.jungleTreasureServerData.tierStates = data.tierStates.slice();
if (data.config) this.jungleTreasureServerData.config = data.config;
if (data.expiresAt !== undefined) this.jungleTreasureServerData.expiresAt = data.expiresAt;
if (data.serverNow !== undefined) this.jungleTreasureServerData.serverNow = data.serverNow;
if (data.revision !== undefined) this.jungleTreasureServerData.revision = data.revision;
if (data.updatedAt !== undefined) this.jungleTreasureServerData.updatedAt = data.updatedAt;
this.cacheJungleTreasureState(data);
}
private cacheJungleTreasureState(data: any): void {
if (!data || !Array.isArray(data.tierStates)) {
return;
}
let gameInfo: any = cc.fx.GameConfig.GM_INFO;
let state = gameInfo.jungleTreasureState
&& typeof gameInfo.jungleTreasureState === "object"
? gameInfo.jungleTreasureState
: {};
state.tierStates = data.tierStates.slice();
if (data.expiresAt !== undefined) state.expiresAt = data.expiresAt;
if (data.activityType !== undefined) state.activityType = data.activityType;
if (data.periodKey !== undefined) state.periodKey = data.periodKey;
if (data.startsAt !== undefined) state.startsAt = data.startsAt;
if (data.revision !== undefined) state.revision = data.revision;
if (data.updatedAt !== undefined) state.updatedAt = data.updatedAt;
gameInfo.jungleTreasureState = state;
if (data.serverNow !== undefined) {
gameInfo.jungleTreasureServerNow = data.serverNow;
}
}
private finishJungleTreasureOpenFailure(message: string): void {
this.jungleTreasureDataLoading = false;
this.jungleTreasureBundleLoading = false;
this.clearJungleTreasureRequestTimeout();
this.closeLoad();
MiniGameSdk.API.showToast(message);
}
private clearJungleTreasureRequestTimeout(): void {
if (this.jungleTreasureRequestTimeout) {
clearTimeout(this.jungleTreasureRequestTimeout);
this.jungleTreasureRequestTimeout = null;
}
}
private disposeJungleTreasure(releaseOnNextFrame: boolean): void {
this.jungleTreasureRequestId++;
this.jungleTreasureDataLoading = false;
this.jungleTreasureBundleLoading = false;
this.clearJungleTreasureRequestTimeout();
let jungleNode = this.jungleTreasureNode;
this.jungleTreasureNode = null;
this.jungleTreasureServerData = null;
SceneManager.cachedJungleTreasurePrefab = null;
if (jungleNode && cc.isValid(jungleNode)) {
jungleNode.off("jungle-close", this.closeJungleTreasure, this);
jungleNode.off("jungle-state-changed", this.onJungleTreasureStateChanged, this);
jungleNode.destroy();
}
let releaseBundle = () => {
let bundle = cc.assetManager.getBundle("jungle_treasure");
if (bundle) {
bundle.releaseAll();
cc.assetManager.removeBundle(bundle);
}
};
if (releaseOnNextFrame && !this.isDestroying) {
this.scheduleOnce(releaseBundle, 0);
}
else {
releaseBundle();
}
}
closePause() {
cc.fx.AudioManager._instance.playEffect("anniu_little", null);
this.releasePausePanel();
if (MapConroler && MapConroler._instance) {
var pause = MapConroler._instance.iceTrue();
if (pause == false && !this.isPauseOpen() && !MapConroler._instance.shopPause) {
if (MapConroler && MapConroler._instance) MapConroler._instance.pause = false;
if (MapConroler._instance.gameStart == true) MapConroler._instance.startBoom();
}
}
}
openPropBuy(name: string) {
if (!this.isPopupRuntimeAlive()) return;
cc.fx.GameTool.tryReactivateStarterPack("level_purchase");
if (MapConroler && MapConroler._instance) {
MapConroler._instance.pause = true;
MapConroler._instance.stopBoom();
}
this.btnName = name;
cc.fx.AudioManager._instance.playEffect("tanchuang", null);
const gameRoot = this.node && this.node.getChildByName("Game");
this.loadDynamicPopup("propWindow", "propWindow", "prefab/PropWindow", gameRoot, (error, propRoot) => {
if (!this.isPopupRuntimeAlive()) return;
if (error || !propRoot) {
this.showPropWindowLoadError(error || new Error("propWindow prefab is missing"), name);
return;
}
try {
if (!this.bindPropWindowPanel(propRoot)) throw new Error("propWindow runtime binding failed");
const prop = propRoot.getChildByName("prop");
const target = prop && prop.getChildByName(name);
if (!target) throw new Error(`propWindow item is missing: ${name}`);
["freeze", "hammer", "magic"].forEach((itemName) => {
prop.getChildByName(itemName).active = itemName === name;
});
prop.getChildByName("buy_Btn").getComponent("btnControl").setTouch(true);
propRoot.active = true;
prop.scale = 0.3;
cc.tween(prop)
.to(0.2, { scale: 1.05 }, { easing: 'backOut' })
.to(0.15, { scale: 1.0 }, { easing: 'sineOut' })
.start();
this.finishDynamicPopupLoad("propWindow");
} catch (bindError) {
this.releasePropWindowPanel(true);
this.showPropWindowLoadError(bindError as Error, name);
}
});
}
private showPropWindowLoadError(error: Error, name: string) {
if (!this.isPopupRuntimeAlive()) return;
cc.error("propWindow cannot open:", error && (error.message || error));
this.showDynamicPopupLoadError(
"propWindow",
"道具购买资源",
() => this.openPropBuy(name)
);
}
clickBtn() {
if (!this.isPopupRuntimeAlive()) return;
cc.fx.AudioManager._instance.playEffect("anniu_Big", null);
const propRoot = this.getDynamicPopup("propWindow");
const prop = propRoot && propRoot.getChildByName("prop");
const buyBtn = prop && prop.getChildByName("buy_Btn");
const buyControl = buyBtn && buyBtn.getComponent("btnControl");
const map = MapConroler && MapConroler._instance;
if (buyControl && buyControl._touch && map && cc.isValid(map, true)) {
buyControl.setTouch(false);
if (this.btnName == "freeze")
map.buyFreeze();
else if (this.btnName == "hammer")
map.buyHammer();
else if (this.btnName == "magic")
map.buyMagic();
}
}
resetBtn() {
if (!this.isPopupRuntimeAlive()) return;
const propRoot = this.getDynamicPopup("propWindow");
const prop = propRoot && propRoot.getChildByName("prop");
const buyBtn = prop && prop.getChildByName("buy_Btn");
const buyControl = buyBtn && buyBtn.getComponent("btnControl");
if (buyControl) buyControl.setTouch(true);
}
/** Reuse the authored gift prefab when reactivation happens inside a level. */
openStarterPack() {
if (!this.isPopupRuntimeAlive() || this.starterPackOpening || starterPackRemaining() <= 0) return;
const existing = this.getDynamicPopup("starterPack");
if (existing && existing.active) return;
const map = MapConroler._instance;
if (!map || map.gameWin) return;
const wasPaused = map.pause;
const wasFrozen = map.iceTrue();
const wasShopPaused = map.shopPause;
map.pause = true;
map.shopPause = true;
map.stopBoom();
this.starterPackOpening = true;
const restore = () => {
if (!this.isPopupRuntimeAlive() || !cc.isValid(map.node)) return;
map.shopPause = wasShopPaused || !!(this.shopNode && this.shopNode.active);
const propWindow = this.getDynamicPopup("propWindow");
if ((!wasPaused || wasFrozen) && !map.shopPause && !map.gameOver && !map.gameWin
&& !(propWindow && propWindow.active)
&& !map.iceTrue() && !this.isPauseOpen()) {
map.pause = false;
if (map.gameStart) map.startBoom();
}
};
this.loadDynamicPopup("starterPack", "action_bundle", "prefab/newbieGift", this.node, (error, root) => {
this.starterPackOpening = false;
this.finishDynamicPopupLoad("starterPack");
if (error || !root || starterPackRemaining() <= 0 || map.gameWin) { restore(); return; }
root.zIndex = cc.macro.MAX_ZINDEX - 1;
root.active = true;
root.once("starter-pack-closed", restore);
root.getComponent("NewbieGift").init(!existing);
cc.fx.GameTool.shushu_Track("interface_exposure", {
interface_id: "NewbieGift", reason: "auto", is_first_exposure: !existing,
entry_point: "reactivate_level",
});
});
}
//打开商店
openShop(event, customEventData?: string) {
if (!this.isPopupRuntimeAlive()) return;
console.log("进入sceneManager openShop");
if (!cc.fx.GameConfig.GM_INFO.canIos) {
return;
}
cc.fx.GameTool.tryReactivateStarterPack("level_purchase");
SceneManager.clearInvalidShopPrefabCaches();
if (!SceneManager.cachedShopPrefab) {
cc.error('Shop prefab is not loaded yet.');
return;
}
Utils.getShopDouble(res => {
if (!this.isPopupRuntimeAlive()) return;
if (res.code === 1) {
let eventName = "";
let btnName = ""
if (customEventData !== undefined) {
btnName = customEventData;
eventName = "click"
} else {
btnName = "shop_level";
eventName = "auto";
}
let dataInfo = {
interface_id: "shop", // 界面名称
reason: eventName, // 事件名称
is_first_exposure: false, // 是否首次曝光
entry_point: btnName, // 按钮名称
}
cc.fx.GameTool.shushu_Track("interface_exposure", dataInfo);
cc.fx.GameConfig.GM_INFO.shopDouble = res.data.shopDouble;
if (MapConroler && MapConroler._instance) {
MapConroler._instance.shopPause = true;
MapConroler._instance.pause = true;
MapConroler._instance.stopBoom();
}
if (!this.shopNode) {
// 第一次使用,创建节点
this.shopNode = cc.instantiate(SceneManager.cachedShopPrefab);
if (this.node && this.shopNode)
this.node.addChild(this.shopNode);
this.shopNode.getComponent("shop").init(res.data.shopDouble);
} else {
// 非第一次使用,直接激活节点
this.shopNode.active = true;
this.shopNode.getComponent("shop").init(res.data.shopDouble);
}
// console.log("shopNode parent:", this.shopNode.parent);
}
});
}
//打开月卡
openMonthlyCard(customEventData?: string) {
SceneManager.clearInvalidShopPrefabCaches();
if (!SceneManager.cachedMonthlyCardPrefab) {
cc.error('MonthlyCard prefab is not loaded yet.');
return;
}
let eventName = "";
let btnName = ""
if (customEventData !== undefined) {
btnName = customEventData;
eventName = "click"
} else {
btnName = "yicon_level";
eventName = "auto";
}
let dataInfo = {
interface_id: "monthlyCard", // 界面名称
reason: eventName, // 事件名称
is_first_exposure: false, // 是否首次曝光
entry_point: btnName, // 按钮名称
}
cc.fx.GameTool.shushu_Track("interface_exposure", dataInfo);
if (!this.monthlyCardNode) {
// 第一次使用,创建节点
this.monthlyCardNode = cc.instantiate(SceneManager.cachedMonthlyCardPrefab);
if (this.node && this.monthlyCardNode)
this.node.addChild(this.monthlyCardNode);
this.monthlyCardNode.getComponent("monthlyCard").init();
this.monthlyCardNode.getComponent("monthlyCard").juwai = false;
} else {
// 非第一次使用,直接激活节点
this.monthlyCardNode.active = true;
this.monthlyCardNode.getComponent("monthlyCard").init();
this.monthlyCardNode.getComponent("monthlyCard").juwai = false;
}
}
updateCoin() {
MapConroler._instance.updateCoin();
}
closePropBuy(type) {
if (!this.isPopupRuntimeAlive()) return;
if (type == true) {
}
else {
if (MapConroler && MapConroler._instance) {
var pause = MapConroler._instance.iceTrue();
if (pause == false && !this.isPauseOpen() && !MapConroler._instance.shopPause) {
if (MapConroler && MapConroler._instance) MapConroler._instance.pause = false;
if (MapConroler._instance.gameStart == true) MapConroler._instance.startBoom();
}
}
}
if (MapConroler && MapConroler._instance) {
let freezeBtn = MapConroler._instance.node.parent.getChildByName("Bottom").getChildByName("timeBtn");
let hammerBtn = MapConroler._instance.node.parent.getChildByName("Bottom").getChildByName("destroyBtn");
let magicBtn = MapConroler._instance.node.parent.getChildByName("Bottom").getChildByName("magicBtn");
if (this.btnName == "freeze") freezeBtn.getComponent("btnControl").setTouch(true);
else if (this.btnName == "hammer") hammerBtn.getComponent("btnControl").setTouch(true);
else if (this.btnName == "magic") magicBtn.getComponent("btnControl").setTouch(true);
}
this.releasePropWindowPanel();
}
openRewardWindow(data, callBack) {
// console.log("_____________________打开奖励弹窗", data);
SceneManager.clearInvalidShopPrefabCaches();
if (!SceneManager.cachedRewardPrefab) {
cc.error('Reward prefab is not loaded yet.');
return;
}
if (!this.RewardNode) {
// 第一次使用,创建节点
this.RewardNode = cc.instantiate(SceneManager.cachedRewardPrefab);
if (this.node && this.RewardNode)
this.node.addChild(this.RewardNode);
this.RewardNode.zIndex = 99;
this.RewardNode.getComponent("Reward").init(data, callBack);
}
else {
this.RewardNode.destroy();
this.RewardNode = null;
this.RewardNode = cc.instantiate(SceneManager.cachedRewardPrefab);
if (this.node && this.RewardNode)
this.node.addChild(this.RewardNode);
this.RewardNode.zIndex = 99;
this.RewardNode.getComponent("Reward").init(data, callBack);
}
this.RewardNode.zIndex = 1001;
}
shareFriend() {
// const loseRoot = this.getDynamicPopup("lose");
// if (loseRoot && loseRoot.active) {
// let shareTips = loseRoot.getChildByName("share_tips");
// // let isCheckboxChecked = cc.sys.localStorage.getItem("share_checkbox_state");
// let isCheckboxChecked = cc.fx.StorageMessage.getStorage("share_checkbox_state");
// if (isCheckboxChecked == 0 || isCheckboxChecked == "" || isCheckboxChecked == null || isCheckboxChecked == undefined) {
// shareTips.active = true;
// } else {
// this.shareFriend2();
// }
// }
// else {
// this.shareFriend2();
// }
}
shareFriend2() {
// const loseRoot = this.getDynamicPopup("lose");
// if (loseRoot && loseRoot.active) {
// let shareTips = loseRoot.getChildByName("share_tips");
// if (shareTips.active) {
// shareTips.active = false;
// let checkbox = shareTips.getChildByName("checkbox");
// let checkboxSelect = 0;
// if (checkbox.getComponent(cc.Toggle).isChecked) {
// checkboxSelect = 1;
// }
// cc.fx.StorageMessage.setStorage("share_checkbox_state", checkboxSelect);
// }
// }
// else {
// }
// if (this.share_time) {
// this.share_time = false;
// console.log("设置分享链接.......");
// let timeStamp = Date.now();
// let otherInfo = {
// timeStamp: timeStamp,
// otherLevel: (cc.fx.GameConfig.GM_INFO.level + 1),
// }
// cc.fx.StorageMessage.setStorage("otherLevel", otherInfo);
// MiniGameSdk.API.shareGame();
// setTimeout(() => {
// this.share_time = true;
// }, 5000);
// }
}
shareClose() {
// const loseRoot = this.getDynamicPopup("lose");
// const shareTips = loseRoot && loseRoot.getChildByName("share_tips");
// if (!shareTips) return;
// if (shareTips.active) {
// shareTips.active = false;
// let checkbox = shareTips.getChildByName("checkbox");
// let checkboxSelect = 0;
// if (checkbox.getComponent(cc.Toggle).isChecked) {
// checkboxSelect = 1;
// }
// cc.fx.StorageMessage.setStorage("share_checkbox_state", checkboxSelect);
// }
}
openLoad() {
const loading = this.ensureLoadingOnTop();
LoadingCatAnimation.play(loading);
}
closeLoad() {
LoadingCatAnimation.stop(this.node.getChildByName("Loading"));
}
openMessage() {
// let reddemCodeNode = cc.instantiate(this.messageNode);
// let messageScrpit = reddemCodeNode.getComponent("Message");
// let nodeType = "commentNode";
// console.log("打开留言界面", nodeType);
// let pos = 1;
// messageScrpit.showNode(nodeType, pos);
// this.node.addChild(reddemCodeNode);
}
openMessage2() {
// 检查 messageNode 是否已经分配了预制体
// if (!this.messageNode) {
// cc.error("messageNode 预制体未分配,请在编辑器中设置");
// return;
// }
// let reddemCodeNode = cc.instantiate(this.messageNode);
// let messageScrpit = reddemCodeNode.getComponent("Message");
// if (!messageScrpit) {
// cc.error("Message 组件未找到,请确保预制体中包含 Message 组件");
// return;
// }
// let nodeType = "likeComment";
// console.log("打开点赞界面", nodeType);
// let pos = 1;
// messageScrpit.showNode(nodeType, pos);
// this.node.addChild(reddemCodeNode);
}
update(dt) {
}
public setPic(url, node) {
cc.assetManager.loadRemote(url, { ext: '.png' }, (err, texture: cc.Texture2D) => {
if (texture && node) {
// node.getChildByName("pic").active = true;
var sprite = node.getComponent(cc.Sprite);
sprite.spriteFrame = new cc.SpriteFrame(texture);
}
else {
}
})
}
}