//********************* // create by 流云 // time: Wed Apr 15 2026 // desc: //********************* import { _decorator, Camera, Collider2D, Component, Node, find, Label, UITransform, v3, Vec3, Color, EventTouch, Layers, Tween, tween, sp, Sprite, instantiate, PhysicsSystem2D } from 'cc'; import { Auto_tetriGame } from './Auto_tetriGame'; import { GBundle, UIID } from '../../game/ConfigRes'; import { BattleCore } from '../BattleGame/BattleCore'; import { tetriCardItem } from '../../tetriCard/tetriCardItem'; import { tetriMap } from '../../tetriMap/tetriMap'; import { tetriWeaponSystem } from '../../tetriMap/tetriWeaponSystem'; import { ERigidBody2DType, PolygonCollider2D, RigidBody2D, Vec2 } from 'cc'; import { tetriNode } from '../../tetriCard/tetriNode'; import { tetriFloorNode } from '../../tetriCard/tetriFloorNode'; import { GEvent } from 'db://assets/mx/module/event/GEvent'; import { CoinFly } from '../BattleGame/CoinFly'; import { ConstNumType, TetriType } from '../../game/ConfigProjectData'; import { tetriAbility } from '../../tetriCard/tetriAbility'; import { tetriWeaponCarrier } from '../../tetriCard/tetriWeaponCarrier'; import { TetriBlockSpineState, playTetriBlockSpine } from '../../tetriCard/tetriBlockSpine'; import { MonsterSpawner } from '../BattleGame/MonsterSpawner'; import { SkillController } from '../BattleGame/WeaponSkill/SkillController'; import MTools from 'db://assets/mx/tools/MTools'; import { DiamondTreasure } from '../BattleGame/DiamondTreasure'; import { StatisticsType, StatusType } from '../../game/GameData'; import { TableNames } from '../../game/ConfigTableData'; import { TweenBreathe } from 'db://assets/mx/components/tween/TweenBreathe'; import { BattleType } from '../../manager/ChapterDataManager'; import { BattlePerformance } from '../BattleGame/BattlePerformance'; import { BattleResLoader } from '../../game/BattleResLoader'; const { ccclass, property } = _decorator; /**俄罗斯方块游戏UI */ @ccclass('UItetraGame') export class UItetraGame extends Auto_tetriGame { private CurentBattle: BattleCore = null; private _cards: tetriCardItem[] = []; /**本轮已成功放置(consume)的卡牌数量 */ private _placedCardCount: number = 0; /**第一块落稳后是否已触发 start(只触发一次) */ private _startedAfterFirstPlacedSettled: boolean = false; /**预警倒计时 UI:每秒刷新一次 */ private _warnUiAccSec: number = 0; private _warnUiLastShownSec: number = -1; /**宝箱飞行动画:起点缓存(world) */ private _boxJumpOriginWorld: Vec3 | null = null; private _dealScheduled: boolean = false; /**发牌次数(每次调用 dealCards +1) */ private _dealCardsCount: number = 0; /** 雷电副本:场上统计方块数达到该值后不再发牌(须在 clearCards 之前判断,否则会清空手牌) */ private readonly _lightningMapBlockDealCap = 1000; /** 猫头鹰飞行动画的 tween 目标(用于退出时停止) */ private _owlTweenTarget: { t: number } | null = null; pauseUpdateTime: boolean = false; private _speedupRateNormal: number = 1; private _speedupRateFast: number = 1.5; /**放置方块数量 */ private fangkuaiNum = 0; private maxCountCoin = 6 private _getOwlChild(name: string): Node | null { if (!this.btnOwl?.isValid) return null; return this.btnOwl.getChildByName(name); } /** 延迟发牌用:与 scheduleOnce 同一引用以便 unschedule */ private _pendingDealCardsIsAd = false; private readonly _deferDealCardsCb = (): void => { void this._dealCardsImpl(this._pendingDealCardsIsAd); }; /**高度尺光标的初始 y(作为 0m 起点) */ private _tagHeightStartY: number | null = null; /**刷新按钮的金币 */ lb_Goid_num = 25 isShow10Anim: boolean = false; isShow30Anim: boolean = false; isShow60Anim: boolean = false; isShow100Anim: boolean = false; chapter1FreeCZ = false chapter1FreeSQ = false aixinIndex = 0 /** 单帧物理子步上限,防止超长卡顿一帧算过多子步把主线程打满 */ private readonly _physMaxSubStepsCap = 10; private _applyPhysicsMaxSubStepsForFrame(dt: number): void { const ph = PhysicsSystem2D.instance; if (!ph.enable) return; const step = ph.fixedTimeStep > 0 ? ph.fixedTimeStep : 1 / 60; // 广告/切后台恢复后 dt 可能极大,限制参与子步计算的 dt,避免叠方块穿模 const clampedDt = Math.min(Math.max(0, dt), step * this._physMaxSubStepsCap); const need = Math.max(1, Math.ceil(clampedDt / step)); ph.maxSubSteps = Math.min(this._physMaxSubStepsCap, need); } onLoad(): void { super.onLoad(); } onEnable(): void { super.onEnable(); GEvent.Ins.on(GEvent.UpdateGameCoinNum, this.refreshCoinNum, this); GEvent.Ins.on(GEvent.UpdateCoin, this.refreshCoinNum, this); GEvent.Ins.on(GEvent.showCoinFly, this.showCoinFly, this); GEvent.Ins.on(GEvent.playerHit, this.onPlayerHit, this); GEvent.Ins.on(GEvent.UpdateWallHp, this.refreshWallHp, this); GEvent.Ins.on(GEvent.TetriGameCancelUseShovel, this.cancelUseShovel, this); GEvent.Ins.on(GEvent.GameReborn, this.gameReborn, this); GEvent.Ins.on(GEvent.UpdateDps, this.updateDps, this); GEvent.Ins.on(GEvent.TetriGameTouchStart, this.showChoosezhezhao, this); GEvent.Ins.on(GEvent.TetriGameTouchEnd, this.hideChoosezhezhao, this); GEvent.Ins.on(GEvent.PlayingGuideStoryEnd, this.onPlayingGuideStoryEnd, this); GEvent.Ins.on(GEvent.tetriGameStart, this.onTetriGameStart, this); GEvent.Ins.on(GEvent.BattleWarnTimeStart, this.onBattleWarnTimeStart, this); this.node.on(Node.EventType.TOUCH_START, this._onGameplayTouchStart, this); this.node.on(Node.EventType.TOUCH_END, this._onGameplayTouchEnd, this); GEvent.Ins.on(GEvent.BattleWaveRefresh, this.onBattleWaveRefresh, this); GEvent.Ins.on(GEvent.LightningLoveDrop, this.loveHpDrop, this); GEvent.Ins.on(GEvent.TetriStackHeightNeedRefresh, this._applyTetriStackHeightRulerUI, this); this.ui_Userchuizi.on(Node.EventType.TOUCH_START, this.click_btnUserchuziStart, this); this.ui_Userchuizi.on(Node.EventType.TOUCH_END, this.click_btnUserchuziEnd, this); } onDisable(): void { super.onDisable(); GEvent.Ins.off(GEvent.UpdateGameCoinNum, this.refreshCoinNum, this); GEvent.Ins.off(GEvent.UpdateCoin, this.refreshCoinNum, this); GEvent.Ins.off(GEvent.showCoinFly, this.showCoinFly, this); GEvent.Ins.off(GEvent.playerHit, this.onPlayerHit, this); GEvent.Ins.off(GEvent.UpdateWallHp, this.refreshWallHp, this); GEvent.Ins.off(GEvent.TetriGameCancelUseShovel, this.cancelUseShovel, this); GEvent.Ins.off(GEvent.GameReborn, this.gameReborn, this); GEvent.Ins.off(GEvent.UpdateDps, this.updateDps, this); GEvent.Ins.off(GEvent.TetriGameTouchStart, this.showChoosezhezhao, this); GEvent.Ins.off(GEvent.TetriGameTouchEnd, this.hideChoosezhezhao, this); GEvent.Ins.off(GEvent.PlayingGuideStoryEnd, this.onPlayingGuideStoryEnd, this); GEvent.Ins.off(GEvent.tetriGameStart, this.onTetriGameStart, this); GEvent.Ins.off(GEvent.BattleWarnTimeStart, this.onBattleWarnTimeStart, this); this.node.off(Node.EventType.TOUCH_START, this._onGameplayTouchStart, this); this.node.off(Node.EventType.TOUCH_END, this._onGameplayTouchEnd, this); GEvent.Ins.off(GEvent.BattleWaveRefresh, this.onBattleWaveRefresh, this); GEvent.Ins.off(GEvent.LightningLoveDrop, this.loveHpDrop, this); GEvent.Ins.off(GEvent.TetriStackHeightNeedRefresh, this._applyTetriStackHeightRulerUI, this); this.ui_Userchuizi.off(Node.EventType.TOUCH_START, this.click_btnUserchuziStart, this); this.ui_Userchuizi.off(Node.EventType.TOUCH_END, this.click_btnUserchuziEnd, this); this.unschedule(this.showOwl); // 退出/切场景时停止猫头鹰相关 tween,避免回调访问已销毁节点导致报错 if (this._owlTweenTarget) { Tween.stopAllByTarget(this._owlTweenTarget); this._owlTweenTarget = null; } if (this.btnOwl?.isValid) { Tween.stopAllByTarget(this.btnOwl); } } /** 战斗场景与 BattleCore 引用是否仍可用于 update */ private _isBattleSceneActive(): boolean { return gg.game.isBattleContextActive() && !!this.CurentBattle && this.node?.isValid; } onInit(): void { let curWaveNum = gg.game.CurentBattle.CurentWaveNum; gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-显示画面`) if(gg.game.tetriGameSpeed>1.5){ this._speedupRateFast = gg.game.tetriGameSpeed; } console.log('欢迎来到俄罗斯方块游戏'); gg.audio.playMusic({ name: "战斗背景音", path: "bgm/" }); // 地图异步加载完成前保持 loading,避免空地图黑屏,且防止 dealCards 早于 TetraMap 绑定 this.setProgressUIactive(); //给battlecore的对象赋值 //加载map1 this.CurentBattle = gg.game.CurentBattle; // 橙武单通:须先于 guideHide/dealCards 生成本局可选武器形状,供发牌与三选一展示共用 if (gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Dan) { gg.game.CurentBattle.ModechooseTetriType = MTools.getRandomElements([1, 2, 3, 4, 5, 6], 3); } void this._setupBattleScene(); } /** 异步挂载地图与战斗子系统,避免分包未就绪时 onInit 提前 return */ private async _setupBattleScene(): Promise { const battle = this.CurentBattle; if (!battle) return; const mapName = `tetriMap${battle.MapId}`; let mapNode = gg.res.getNode('prefab/', mapName, GBundle.tetriMap); if (!mapNode) { await gg.res.ensurePrefab(mapName, 'prefab/', GBundle.tetriMap); mapNode = gg.res.getNode('prefab/', mapName, GBundle.tetriMap); } if (!mapNode?.isValid || !this.node?.isValid) { console.error(`${mapName} prefab load failed`); gg.sdk?.pfLogKey?.('BATTLE:MAP_FAIL', mapName); gg.game.hideLoading(); return; } mapNode.parent = this.ui_mapParentNode this.setLayerMask(mapNode, this.ui_mapParentNode.layer); battle.MonsterSpawner = this.ui_monsterParent.getComponent(MonsterSpawner); battle.MonsterSpawner.initMonsterConfigs(); battle.SkillController = this.ui_skilllayer.getComponent(SkillController); battle.SkillController.initAll(); battle.TetraMap = mapNode.getComponent(tetriMap); battle.TetriWeaponSystem = mapNode.getComponentInChildren(tetriWeaponSystem); battle.ScoreLayer = this.ui_scoreLayer; battle.TopSpineLayer = this.ui_topSpineLayer; battle.TempMonsterParent = this.ui_tempMonsterParent; battle.ui_skillTraillayer = this.ui_skillTraillayer; battle.ui_roleLayer = this.ui_roleLayer; battle.ui_roleWeaponLayer = this.ui_roleWeaponLayer; if (!battle.TetriWeaponSystem) { console.error('tetriWeaponSystem not found'); } if (!battle.TetraMap) { console.error('tetriMap not found'); } battle.TetraMap?.refreshHighLineFromStack(); let coin = gg.data.table.getConst(ConstNumType.CoinRefreshCost); this.lb_Goid_num = coin; this.refreshCoinNum() this._startedAfterFirstPlacedSettled = false; if (this.ui_boxJump?.isValid) { this._boxJumpOriginWorld = this.ui_boxJump.worldPosition.clone(); } this.setFreeBtnCoinNum() this.refreshMagicalBtn() this.onBattleWaveRefresh() this._refreshSpeedUpBtnView(); this.hideBtn() this.refreshWallHp(); this.updateDps(); // 地图就绪后再关 loading 并发牌,保证卡牌 bindMap 能拿到 highLine / layer gg.game.hideLoading(); this.guideHide(); this._rebindDealtCardsToMap(); if (battle.BattleType == BattleType.SpeedMode || battle.BattleType == BattleType.OrangeWeaponMode_Dan) { gg.ui.openPanel(UIID.SkillSelect, {data:true}); } if (battle.BattleType == BattleType.OrangeWeaponMode_Dian && battle.loveHpNum > 0) { this.ui_aixinNodes.active = true this.showHideLoveHpUI() this.createLoveHp() } else { this.ui_aixinNodes.active = false } } /** 地图晚于卡牌就绪时补绑(防御异步竞态) */ private _rebindDealtCardsToMap() { const map = this.CurentBattle?.TetraMap; if (!map) return; for (let i = 0; i < this._cards.length; i++) { this._cards[i]?.bindMap(map); } } /**波次倒计时结束,开始战斗*/ onTetriGameStart() { if (this.ui_timeNode?.isValid && this.ui_timeNode.active) { tween(this.ui_timeNode) .to(0.1, { scale: v3(0.1, 0.1, 0.1) }) .call(() => { this.ui_timeNode.active = false; }) .start(); } } onBattleWarnTimeStart() { //增加波次银币 if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Dian){ return } if (!this.CurentBattle.IsGameOverBoxJump) { return } if (gg.game.CurentBattle.waveGetCoin > 0 && gg.game.CurentBattle.CurentWaveNum > 1) { gg.game.CurentBattle.addCoinNum(gg.game.CurentBattle.waveGetCoin); GEvent.Ins.emit(GEvent.showCoinFly, this.node.worldPosition.clone(), 15); gg.ui.showToast(gg.lang.get('波次结束,获得{1}银币', [gg.game.CurentBattle.waveGetCoin]), false, 1, 200) } if (gg.data.doc.chapter == 1) { let boci = [6, 9, 12, 15] if (boci.indexOf(gg.game.CurentBattle.CurentWaveNum) != -1) { this.showDescGuide2('方块下落时,点击左右屏幕,调整方块掉落位置') } else { this.hideDescGuide2() } } } onPlayingGuideStoryEnd() { this.guideShow() } guideHide() { if (gg.data.doc.chapter == 1) { gg.game.IsPlayingGuideStory = true this.ui_xuanxiangKuang.active = false this.ui_card.active = false this.ui_Gold.active = false this.ui_playHp.active = false this.ui_bottom.active = false const tryStartWarn = () => { if (!this.CurentBattle?.SkillController) { this.scheduleOnce(tryStartWarn, 0.2); return; } this.CurentBattle.startWarnTime(); }; this.scheduleOnce(() => { GEvent.Ins.emit(GEvent.PlayingGuideStoryEnd); tryStartWarn(); }, 4.0); } else { this.dealCards() } } guideShow() { gg.game.IsPlayingGuideStory = false const cam = gg.ui.GameCamera; const zoomInH = gg.ui.height / 2; tween(cam) .to(1, { orthoHeight: zoomInH }, { easing: 'quadInOut' }) .delay(0.1) .call(() => { //开启引导 if (this.node.isValid) { if (this.ui_xuanxiangKuang.isValid) { this.ui_xuanxiangKuang.active = false } if (this.ui_card.isValid) { this.ui_card.active = true } if (this.ui_Gold.isValid) { this.ui_Gold.active = true } if (this.ui_playHp.isValid) { this.ui_playHp.active = true } if (this.ui_bottom.isValid) { this.ui_bottom.active = true } this.dealCards() if (gg.data.getStatus(StatusType.IsShowPlayGuidePop) == 0) { this.ui_guideHuadong.active = true; this.showDescGuide("在绿色地基上搭建方块"); } } }) .start(); } showDescGuide(str: string) { this.ui_guideLab.active = true; console.log('showDescGuide', str); this.ui_guideLab.getComponent(Label).string = gg.lang.get(str); } hideDescGuide() { // this.ui_guideLab.active = false; } showDescGuide2(str: string) { this.ui_guideLab2.active = true; this.scheduleOnce(() => { this.hideDescGuide2() }, 5); // console.log('showDescGuide2', str); // this.ui_guideLab2.getComponent(Label).string = gg.lang.get(str); } hideDescGuide2() { // this.ui_guideLab2.active = false; } hideBtn() { if (gg.data.doc.chapter == 1) { this.btnShenqiBox.active = false this.btnCoin.active = false this.btnchuzi.active = false this.btnMaotouying.active = false this.btnRefreshBox.active = false this.chapter1FreeCZ = true this.chapter1FreeSQ = true this.ui_sqfkAd.active = false this.lb_sqnum.node.active = false this.lb_freeSqLab.node.active = true this.ui_sqbxshouzhi.active = true this.ui_czAd.active = false this.lb_freeCzLab.node.active = false this.ui_chuizishouzhi.active = false this.ui_mfybAd.active = false this.lb_mfybCount.node.active = false this.ui_mfybshouzhi.active = true } else { this.refreshShovelBtn() if (gg.game.CurentBattle.BattleType == BattleType.SpeedMode) { this.updateBtnActive(); } } } clearCards() { if (!this.ui_card?.isValid) { this._cards = []; return; } this.ui_card.children.forEach(uiboxBg => { if (!uiboxBg?.isValid) return; uiboxBg.children.forEach(child => { if (child?.isValid) child.destroy(); }); }); this._cards = []; } /** 三个卡槽父节点(优先用 Auto 里绑定的 ui_box1/2/3,避免 children 顺序与 destroy 中间态问题) */ private _getCardParentSlots(): Node[] { const slots = [this.ui_box1, this.ui_box2, this.ui_box3].filter((n) => !!n?.isValid); if (slots.length > 0) return slots; return (this.ui_card?.children ?? []).filter((n) => !!n?.isValid); } /**发牌 * 刷新按钮发牌 * 所有card已经放置自动发牌 */ async dealCards(isAd: boolean = false) { // 雷电副本:地图场上方块已达上限则不再发新牌;不 clearCards,保留当前手牌 const battle = this.CurentBattle; if ( battle?.BattleType === BattleType.OrangeWeaponMode_Dian && battle?.TetraMap?.getTetriNodeCount?.() >= this._lightningMapBlockDealCap && !isAd ) { this._dealScheduled = false; this.ui_maxCard.active = true; this.unschedule(this._deferDealCardsCb); return; } this.ui_maxCard.active = false; //发牌 //加载刷新方块 this._dealCardsCount++ this.clearCards() this._placedCardCount = 0 this._dealScheduled = false this._pendingDealCardsIsAd = isAd // clearCards 里 destroy 是延迟到本帧结束的:同一帧立刻 parent 新节点会触发引擎 walk 对旧子树 null.length 报错 this.unschedule(this._deferDealCardsCb) this.scheduleOnce(this._deferDealCardsCb, 0) } private async _dealCardsImpl(isAd: boolean): Promise { if (isAd) { await BattleResLoader.loadBundle(GBundle.tetriCards); } const waveNum = this.CurentBattle.CurentWaveNum let tetriConfigs = isAd ? gg.data.project.getTetriConfigsAd(waveNum, 3, this.CurentBattle.CurentConfigChapter) : gg.data.project.getTetriConfigsByWave(waveNum, 3, this.CurentBattle.CurentConfigChapter) if (this._dealCardsCount == 1 && this.CurentBattle.ChapterId == 1) { tetriConfigs = []; let ids = gg.data.table.getConstArray(ConstNumType.FirstWaveTetri); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const tetriConfig = gg.data.table.getTableData(TableNames.BattleCube, id); if (tetriConfig) { tetriConfigs.push(tetriConfig); } } } if (this._dealCardsCount == 2 && this.CurentBattle.ChapterId == 1) { tetriConfigs = []; let ids = gg.data.table.getConstArray(ConstNumType.FirstWaveTetri2); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const tetriConfig = gg.data.table.getTableData(TableNames.BattleCube, id); if (tetriConfig) { tetriConfigs.push(tetriConfig); } } } if (this._dealCardsCount == 1 && this.CurentBattle.ChapterId == 2) { let ids = gg.data.table.getConstArray(ConstNumType.SecondWaveTetri); tetriConfigs = []; for (let i = 0; i < ids.length; i++) { const id = ids[i]; const tetriConfig = gg.data.table.getTableData(TableNames.BattleCube, id); if (tetriConfig) { tetriConfigs.push(tetriConfig); } } } if (!tetriConfigs?.length) { console.error('[UItetraGame] dealCards: tetriConfigs 为空') return } const slots = this._getCardParentSlots() if (!slots.length) { console.error('[UItetraGame] dealCards: 无卡槽父节点 ui_box1/2/3 或 ui_card.children') return } for (let i = 0; i < tetriConfigs.length; i++) { const parentNode = slots[i] if (!parentNode?.isValid) { console.error(`[UItetraGame] dealCards: 卡槽 ${i} 无效`) continue } const cardItem = await gg.res.getNodeAsync(`prefab/`, 'cardItem', GBundle.tetriCards) if (!cardItem?.isValid) { console.error('[UItetraGame] dealCards: cardItem 加载失败') gg.sdk?.pfLogKey?.('BATTLE:PREFAB_FAIL', 'cardItem'); continue } if (!parentNode?.isValid) { cardItem.destroy(); continue } cardItem.setParent(parentNode, false) cardItem.setPosition(0, 0) const cardComp = cardItem.getComponent(tetriCardItem) if (cardComp) this._cards.push(cardComp) if (gg.data.getStatus(StatusType.IsShowPlayGuidePop) == 0 && cardComp && i == 1) { let liangkuangNode = cardComp.liangkuangNode; if (liangkuangNode?.isValid) { liangkuangNode.active = true; } } const tetriConfig = tetriConfigs[i] cardComp?.setConfig(tetriConfig) let cardNode: Node | null = null if (tetriConfig.type == TetriType.Tetris_Basefloor) { cardNode = await gg.res.getNodeAsync(`prefabFloor/`, tetriConfig.cubename, GBundle.tetriCards) } else { cardNode = await gg.res.getNodeAsync(`prefab/`, tetriConfig.cubename, GBundle.tetriCards) } if (!cardNode?.isValid) { console.error('[UItetraGame] dealCards: 方块预制体加载失败', tetriConfig.cubename) gg.sdk?.pfLogKey?.('BATTLE:PREFAB_FAIL', tetriConfig.cubename); continue } if (!cardItem?.isValid) { cardNode.destroy(); continue } cardNode.setParent(cardItem, false) cardNode.opacity = 255 cardNode.setPosition(0, 30) playTetriBlockSpine(cardNode, TetriBlockSpineState.Shop) this.setLayerMask(cardNode, Layers.Enum.UI_2D); if (tetriConfig.type == TetriType.Tetris_Basefloor) { cardNode.getOrAddComponent(tetriFloorNode).setConfig(tetriConfig) } else { cardNode.angle = 0 cardNode.getOrAddComponent(tetriNode).setConfig(tetriConfig) if (tetriConfig.type == TetriType.Tetris_BaseGold || tetriConfig.type == TetriType.Tetris_Baseheal) { cardNode.getOrAddComponent(tetriAbility).setConfig(tetriConfig) } if (tetriConfig.type == TetriType.Tetris_Baseattk || tetriConfig.type == TetriType.Tetris_Basevine) { cardNode.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConfig) } } cardComp?.setPieceNode(cardNode) const maybePieceNode = cardItem.children?.length > 0 ? cardItem.children[cardItem.children.length - 1] : null if (maybePieceNode?.isValid) { for (const rb of maybePieceNode.getComponentsInChildren(RigidBody2D)) { rb.enabled = true rb.linearVelocity = new Vec2(0, 0) rb.angularVelocity = 0 rb.gravityScale = 0 rb.type = ERigidBody2DType.Static rb.fixedRotation = true } } cardComp?.setChooseMask(this.ui_choosezhezhao) cardComp?.setCancleNode(this.ui_cancleNode) cardComp?.bindMap(this.CurentBattle?.TetraMap) if (!this.CurentBattle?.TetraMap) { console.warn('[UItetraGame] dealCards: TetraMap 尚未就绪,卡牌将无法正确预览/掉落') } if (cardComp) { cardComp.onConsumed = () => { this._placedCardCount++ const need = this._getCardParentSlots().length || this.ui_card?.children?.length || 3 if (this._placedCardCount >= need && !this._dealScheduled) { this._dealScheduled = true this.scheduleOnce(() => { this.dealCards() }, 0) } } } if (cardComp) { cardComp.onPlaceRequested = (self, battleNode, worldPos) => { this.fangkuaiNum++; const preview = battleNode; this.ui_guideHuadong.active = false; if (gg.data.getStatus(StatusType.IsShowPlayGuidePop) == 0) { gg.data.setStatus(StatusType.IsShowPlayGuidePop, 1); //打点 gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${gg.game.CurentBattle.CurentWaveNum}-首次拖动方块放置`); if (gg.game.CurentBattle.TetraMap.node.getChildByName('地基区域')) { gg.game.CurentBattle.TetraMap.node.getChildByName('地基区域').active = false; } this.ui_guideHuadong2.active = true; this.showDescGuide("非常棒!继续摆放方块,保护她吧"); } else { this.ui_guideHuadong2.active = false; this.hideDescGuide() } for (let i = 0; i < this._cards.length; i++) { let liangkuangNode = this._cards[i].liangkuangNode; if (liangkuangNode?.isValid) { liangkuangNode.active = false; } } if (!preview?.isValid) { self.cancelPlace() return false } const gameRoot = this.CurentBattle?.TetraMap?._gameRoot; if (!gameRoot?.isValid) { self.cancelPlace() return false } const map = this.CurentBattle?.TetraMap // 先入树前写配置/物理,避免 addChild 触发 start 时 _config 仍为空 preview.active = false this._applyBattlePieceConfig(preview, tetriConfig) this._primeBattlePieceRigidbodies(preview, map) gameRoot.addChild(preview) this.setLayerMask(preview, gameRoot.layer) this._enableBattlePieceColliders(preview) preview.setWorldPosition(worldPos) preview.active = true const floorNodes = preview.getComponentsInChildren(tetriFloorNode) ?? [] for (const f of floorNodes) { f.beginPlacementFall() } this._primeBattlePieceRigidbodies(preview, map) for (const wc of preview.getComponentsInChildren(tetriWeaponCarrier)) { wc.ensureBattleReady() } for (const ab of preview.getComponentsInChildren(tetriAbility)) { ab.ensureBattleReady() } for (const t of preview.getComponentsInChildren(tetriNode)) { t.playBlockSpine(TetriBlockSpineState.Falling); } this.CurentBattle?.TetraMap?.setLatestFallingNode(preview) // 第一块方块:落地企稳后再开始 battle(只触发一次) if (!this._startedAfterFirstPlacedSettled) { const startOnce = () => { if (this._startedAfterFirstPlacedSettled) return; this._startedAfterFirstPlacedSettled = true; if (gg.data.doc.chapter != 1 && !gg.game.noMonsterShow) this.CurentBattle?.startWarnTime(); this.showWarnTimeUI() }; const tetriNodes = preview.getComponentsInChildren(tetriNode) ?? []; const floorNodes = preview.getComponentsInChildren(tetriFloorNode) ?? []; if (tetriNodes.length > 0) { // 只需挂一个:任意一个 tetriNode 落稳即可认为整块“企稳” const t0 = tetriNodes[0]; const prev = t0.onSettled; t0.onSettled = () => { try { prev?.(); } catch (e) { console.error(e); } startOnce(); }; } else if (floorNodes.length > 0) { const f0: any = floorNodes[0] as any; const prev = f0.onSettled as (() => void) | null | undefined; f0.onSettled = () => { try { prev?.(); } catch (e: any) { console.error(e); } startOnce(); }; } else { // 兜底:没有识别到脚本时,仍然延迟一帧开始(避免卡死不开始) this.scheduleOnce(() => startOnce(), 0); } } self.consume() this.ui_choosezhezhao.active = false this.ui_cancleNode.active = false return true } } } } showWarnTimeUI() { //播放一次动画 const shouldPlayAnim = !this.ui_timeNode.active; if (shouldPlayAnim && this.CurentBattle.tetriWarnTimeFix > 0) { this.CurentBattle.TetraMap.showQiPaoEffect() console.log('showWarnTimeUI 播放一次动画'); this.ui_timeNode.active = true; if(gg.game.noMonsterShow) { this.ui_timeNode.active = false; }; let posiy = this.CurentBattle.TetraMap.getHeightLinePosition(); //this.ui_timeNode.y = posiy+100; this.ui_timeNode.scale = v3(0.1, 0.1, 0.1); Tween.stopAllByTarget(this.ui_timeNode); tween(this.ui_timeNode) .to(0.2, { scale: v3(1.1, 1.1, 1.1) }, { easing: 'quadOut' }) .to(0.2, { scale: v3(1, 1, 1) }, { easing: 'quadInOut' }) .start(); } if (this.CurentBattle.BattleType == BattleType.InfiniteMode) { this.lb_waveDesc.string = `第${this.CurentBattle.InfiniteModeCurentWaveNum}波怪物来袭`; } else { this.lb_waveDesc.string = `第${this.CurentBattle.CurentWaveNum}波怪物来袭`; } this.lb_dTime.string = MTools.formatTimeString(this.CurentBattle.tetriWarnTimeFix, "mm:ss"); } //获取gameroot的TetrisTable整体高度需要考虑缩放比例 //blockHeight 整体高度除以blockHeight就是当前的多少米了 update(dt: number) { if (!this._isBattleSceneActive()) return; this._applyPhysicsMaxSubStepsForFrame(dt); //引导中暂停一下 if (gg.game.IsPlayingGuideStory) { this.CurentBattle._dequeueWeaponLaunchEvents(this.CurentBattle.WeaponLaunchMaxPerFrame, this.CurentBattle.WeaponLaunchDispatchIntervalFrames); return } if (this.pauseUpdateTime) { return } this.CurentBattle.update(dt); this.showHpWarn(false) // 波次预警倒计时 UI(倒计时期间战斗暂停,但 UI 需要刷新) if (this.CurentBattle?.IsWarnTimeActive) { if (this.ui_timeNode?.isValid) { this.showWarnTimeUI() } // 每 1 秒刷新一次文本(或秒数变化时刷新) this._warnUiAccSec += dt; const secLeft = Math.max(0, Math.ceil(this.CurentBattle.tetriWarnTimeFix)); if (this._warnUiAccSec >= 1 || secLeft !== this._warnUiLastShownSec) { this._warnUiAccSec = 0; this._warnUiLastShownSec = secLeft; if (this.lb_dTime?.isValid) { this.lb_dTime.string = MTools.formatTimeString(secLeft, "mm:ss"); } } } else { this._warnUiAccSec = 0; this._warnUiLastShownSec = -1; } } /** * 堆叠米数由 tetriMap._updateHighLine 写入 BattleCore;此处只更新侧栏高度尺与宝箱/竞速段 UI。 * 由 GEvent.TetriStackHeightNeedRefresh 触发(落稳、白线周期重算、地面闪红后延迟一帧等)。 */ private _applyTetriStackHeightRulerUI(): void { if (this.pauseUpdateTime || !this.CurentBattle) return; const meters = this.CurentBattle.TetriStackHeightMeters; if (!this.ui_tagHeight?.isValid || !this.ui_heightNode?.isValid) return; if (this._tagHeightStartY === null) this._tagHeightStartY = this.ui_tagHeight.position.y; const y0 = this._tagHeightStartY ?? this.ui_tagHeight.position.y; const y15 = this.ui_10m?.position.y ?? 0; const y30 = this.ui_30m?.position.y ?? y15; const y60 = this.ui_60m?.position.y ?? y30; const mapMeterToY = (m: number): number => { if (m <= 15) { const k = y15 !== y0 ? (y15 - y0) / 15 : 0; return y0 + k * m; } if (y30 !== y15 && m <= 30) { return y15 + (y30 - y15) * ((m - 15) / 15); } if (y60 !== y30 && m <= 60) { return y30 + (y60 - y30) * ((m - 30) / 30); } return y60; }; const newY = mapMeterToY(meters); const p = this.ui_tagHeight.position; this.ui_tagHeight.setPosition(p.x, newY, p.z); this.showBoxUI(meters); this.updateProgressUI(); if (meters >= 30) { this.CurentBattle.pruneDetachedBattleLayers(); } } setProgressUIactive() { if (gg.game.CurentBattle.BattleType == BattleType.SpeedMode) { this.ui_fenduan2.active = true; this.ui_fenduan.active = false; this.ui_10m.active = false; this.ui_30m.active = false; this.ui_60m.active = false; this.ui_100m.active = false; this.ui_Baoxiang10m.active = false; this.ui_Baoxiang30m.active = false; this.ui_Baoxiang60m.active = false; this.ui_Baoxiang100m.active = false; } else { this.ui_fenduan2.active = false; this.ui_fenduan.active = true; this.ui_10m.active = true; this.ui_30m.active = true; this.ui_60m.active = true; this.ui_100m.active = false; this.ui_Baoxiang10m.active = true; this.ui_Baoxiang30m.active = true; this.ui_Baoxiang60m.active = true; this.ui_Baoxiang100m.active = false; } } updateProgressUI() { if (!this.ui_fenduan2?.active || !this.CurentBattle?.CurentConfigReplica) return; const nextIdx = this.CurentBattle.getNextSpeedModeHeightMilestoneIndex(); const heightArr = this.CurentBattle.CurentConfigReplica.hight; const atkArr = this.CurentBattle.CurentConfigReplica.atk; for (let i = 0; i < this.ui_fenduan2.children.length; i++) { const child = this.ui_fenduan2.children[i]; if (!child?.isValid) continue; let isActive=child.active; child.active = i === nextIdx || i == this.ui_fenduan2.children.length - 1; if(!isActive&&child.active){ GEvent.Ins.emit(GEvent.UpdateDps, 2); } if (i < heightArr.length && i < atkArr.length) { const atk = Math.floor(atkArr[i] / 10000 * 100); const lb = child.getComponentInChildren(Label); if (lb) lb.string = `${heightArr[i]}m 攻击+${atk}%`; } } } /**显示宝箱UI */ showBoxUI(meters: number) { if (!this.CurentBattle) return; const m = meters ?? 0; if(this.ui_fenduan.active){ this.ui_fenduan.getComponent(Sprite).fillRange = m / 60; } if(this.ui_fenduan2.active){ this.ui_fenduan2.getComponent(Sprite).fillRange = m / 60; } if (this.ui_Baoxiang10m?.isValid && !this.isShow10Anim) { // 历史原因:字段仍叫 10m,但该宝箱现在在 15m 解锁 let show = m >= 15 && !this.CurentBattle.is10mBoxOpen; this.ui_Baoxiang10m.children[0].active = show; this.ui_Baoxiang10m.getComponent(Sprite).grayscale = !show; this.ui_Baoxiang10m.getComponent(TweenBreathe).enabled = show; this.ui_Baoxiang10m.setScale(show ? v3(2, 2, 2) : v3(0.8, 0.8, 0.8)); if (show && !this.isShow10Anim) { this.isShow10Anim = true; this.showGetTip('获得技能宝箱') if (gg.data.doc.chapter == 1) { this.ui_10mshouzhi.active = true; this.showDescGuide("恭喜,达到15米了,获得武器技能转盘!") } } } if (this.ui_Baoxiang30m?.isValid && !this.isShow30Anim) { let show = m >= 30 && !this.CurentBattle.is30mBoxOpen; this.ui_Baoxiang30m.children[0].active = show; this.ui_Baoxiang30m.getComponent(Sprite).grayscale = !show; this.ui_Baoxiang30m.getComponent(TweenBreathe).enabled = show; this.ui_Baoxiang30m.setScale(show ? v3(2, 2, 2) : v3(0.8, 0.8, 0.8)); if (show && !this.isShow30Anim) { this.isShow30Anim = true; this.showGetTip('获得技能宝箱') } } if (this.ui_Baoxiang60m?.isValid && !this.isShow60Anim) { let show = m >= 60 && !this.CurentBattle.is60mBoxOpen; this.ui_Baoxiang60m.children[0].active = show; this.ui_Baoxiang60m.getComponent(Sprite).grayscale = !show; this.ui_Baoxiang60m.getComponent(TweenBreathe).enabled = show; this.ui_Baoxiang60m.setScale(show ? v3(2, 2, 2) : v3(0.8, 0.8, 0.8)); if (show && !this.isShow60Anim) { this.isShow60Anim = true; this.showGetTip('获得技能宝箱') } } // if (this.ui_Baoxiang100m?.isValid && !this.isShow100Anim) { // let show = m >= 100 && !this.CurentBattle.is100mBoxOpen; // this.ui_Baoxiang100m.children[0].active = show; // this.ui_Baoxiang100m.getComponent(Sprite).grayscale = !show; // this.ui_Baoxiang100m.getComponent(TweenBreathe).enabled = show; // this.ui_Baoxiang100m.setScale(show ? v3(2, 2, 2) : v3(0.8, 0.8, 0.8)); // if (show && !this.isShow100Anim) { // this.isShow100Anim = true; // this.showGetTip('获得技能宝箱') // } // } } click_btnPause() { this.CurentBattle.pause('BattlePause'); gg.ui.openPanel(UIID.BattlePause); } click_btnSpeedUp() { if (!this.CurentBattle) return; //第二章解锁 if (gg.data.doc.chapter == 1) { gg.ui.showToast(gg.lang.get('通关第{1}章解锁', [1]), false) return } //打点 const cur = this.CurentBattle.TimeScale > this._speedupRateNormal ? this._speedupRateFast : this._speedupRateNormal; this.CurentBattle.TimeScale = (cur >= this._speedupRateFast) ? this._speedupRateNormal : this._speedupRateFast; let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-加速点击` + this.CurentBattle.TimeScale) this._refreshSpeedUpBtnView(); } click_btn_tip() { const now = Date.now(); if (now - this._lastTipClickMs < 400) { return; } this._lastTipClickMs = now; if (gg.ui.panelIsShow(UIID.TetriDoc) || gg.ui.panelIsOpening(UIID.TetriDoc)) { return; } gg.ui.removeWaitPop(UIID.TetriDoc); gg.ui.openPanel(UIID.TetriDoc); } private _refreshSpeedUpBtnView() { if (!this.btnSpeedUp?.isValid || !this.CurentBattle) return; const isFast = this.CurentBattle.TimeScale > this._speedupRateNormal; const sp1 = this.btnSpeedUp.getChildByName("1"); const sp2 = this.btnSpeedUp.getChildByName("2"); if (sp1?.isValid) sp1.active = !isFast; if (sp2?.isValid) sp2.active = isFast; } useChuizi(){ this.ui_heightNode.active = false this.ui_bottom.active = false this.ui_top.active = false this.ui_playHp.active = false this.ui_Gold.active = false this.ui_card.active = false this.ui_choosezhezhao.active = false this.ui_hpWarn.active = false this.ui_Userchuizi.active = true //显示使用锤子层级 this.CurentBattle.pause('useShovel') this.CurentBattle.IsUseShovel = true; //注册使用this.ui_Userchuizi界面点击触摸事件 //发消息使用锤子 GEvent.Ins.emit(GEvent.TetriGameUseShovel, this.CurentBattle.gameShovelNum) } click_btnchuzi() { if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Dian && this.ui_czAd.active == true){ //广告获取锤子 gg.sdk.showVideoAd((res) => { if (res) { //白银宝箱 let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${this.CurentBattle.getReportDYChapterId()}_${curWaveNum}-AD-获取锤子`) this.CurentBattle.gameShovelNum++ this.ui_czAd.active = false this.useChuizi() return } else { gg.ui.showToast("观看视频失败"); } }); return } if (this.CurentBattle.GameShovelCoin > this.CurentBattle.CurentCoinNum) { gg.ui.showToast('银币不足') return } if (this.CurentBattle.GameShovelCoin <= this.CurentBattle.CurentCoinNum || (gg.data.doc.chapter == 1 && this.chapter1FreeCZ)) { //使用锤子 this.useChuizi() return } this._isClickChuiziBubble = true; this.btnchuzi.getChildByName("paopao").active = false; } click_btnShenqiBox() { if (this.CurentBattle.onRefreshMagicalNum <= 0) { gg.ui.showToast('神奇方块次数不足') return } // if (gg.data.doc.chapter == 1 && this.chapter1FreeSQ) { let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-神奇方块免费刷新`) this.lb_freeSqLab.node.active = false this.ui_sqbxshouzhi.active = false this.ui_sqfkAd.active = true this.lb_sqnum.node.active = true //刷新出三个卡牌,固定2类型的卡牌一张免费使用,剩下的还是走的随机 this.dealCards(true) this.chapter1FreeSQ = false; if (gg.data.getStatus(StatusType.IsShowPlayGuidePopNoGravity) == 0) { let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-新玩家玩法说明主动弹出`); gg.ui.openPanel(UIID.TetriDoc, { data: 3 }); gg.data.setStatus(StatusType.IsShowPlayGuidePopNoGravity, 1); } return } gg.sdk.showVideoAd((res) => { if (res) { //白银宝箱 let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-AD-神奇方块刷新`) gg.game.CurentBattle.IsWatchAd = true gg.PRR.AdClickNum.push(7) this.CurentBattle.onRefreshMagicalNum--; this.refreshMagicalBtn() //刷新出三个卡牌,固定2类型的卡牌一张免费使用,剩下的还是走的随机 this.dealCards(true) } else { gg.ui.showToast("观看视频失败"); } }); } click_btnRefreshBox() { if(this.ui_brboxAd.active){ gg.sdk.showVideoAd((res) => { if (res) { //换一批 let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-换一批`) this._isClickRefreshBubble = true; this.btnRefreshBox.getChildByName("paopao").active = false; //this.CurentBattle.subCoinNum(this.lb_Goid_num); this.dealCards() this._dealScheduled = false this._placedCardCount = 0 this.ui_choosezhezhao.active = false } else { gg.ui.showToast("观看视频失败"); } }); return } if (this.CurentBattle.CurentCoinNum < this.lb_Goid_num) { gg.ui.showToast('银币不足') return } let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-换一批`) this._isClickRefreshBubble = true; this.btnRefreshBox.getChildByName("paopao").active = false; this.CurentBattle.subCoinNum(this.lb_Goid_num); this.dealCards() this._dealScheduled = false this._placedCardCount = 0 this.ui_choosezhezhao.active = false // } click_ui_Baoxiang10m() { if (this.CurentBattle.is10mBoxOpen || this.ui_Baoxiang10m.getComponent(Sprite).grayscale) { return; } this.CurentBattle.is10mBoxOpen = true; this.ui_Baoxiang10m.active = false; this.ui_10mshouzhi.active = false; this.hideDescGuide() gg.ui.popPanel(UIID.TurnTable, { data: 0 }); } click_ui_Baoxiang30m() { if (this.CurentBattle.is30mBoxOpen || this.ui_Baoxiang30m.getComponent(Sprite).grayscale) { return; } //只有一个武器不给开宝箱 if (this.CurentBattle.curRogueWeaponTimes == 1) { gg.ui.showToast('请开启对局内武器宝箱获取第二把武器') return } this.CurentBattle.is30mBoxOpen = true; this.ui_Baoxiang30m.active = false; gg.ui.popPanel(UIID.TurnTable, { data: 1 }); } click_ui_Baoxiang60m() { if (this.CurentBattle.is60mBoxOpen || this.ui_Baoxiang60m.getComponent(Sprite).grayscale) { return; } if (this.CurentBattle.curRogueWeaponTimes == 1) { gg.ui.showToast('请开启对局内武器宝箱获取第二把武器') return } this.CurentBattle.is60mBoxOpen = true; this.ui_Baoxiang60m.active = false; gg.ui.popPanel(UIID.TurnTable, { data: 2 }); } click_ui_Baoxiang100m() { if (this.CurentBattle.is100mBoxOpen || this.ui_Baoxiang100m.getComponent(Sprite).grayscale) { return; } if (this.CurentBattle.curRogueWeaponTimes == 1) { gg.ui.showToast('请开启对局内武器宝箱获取第二把武器') return } this.CurentBattle.is100mBoxOpen = true; this.ui_Baoxiang100m.active = false; gg.ui.popPanel(UIID.TurnTable, { data: 3 }); } /** 战场方块:在 addChild 之前写入玩法配置(含武器/技能),与 dealCards 一致 */ private _applyBattlePieceConfig(preview: Node, tetriConfig: ITableBattleCube): void { if (tetriConfig.type === TetriType.Tetris_Basefloor) { preview.getOrAddComponent(tetriFloorNode).setConfig(tetriConfig) for (const c of preview.getComponentsInChildren(tetriFloorNode)) { c.setConfig(tetriConfig) } return } preview.angle = 0 preview.getOrAddComponent(tetriNode).setConfig(tetriConfig) for (const c of preview.getComponentsInChildren(tetriNode)) { c.setConfig(tetriConfig) } if (tetriConfig.type === TetriType.Tetris_BaseGold || tetriConfig.type === TetriType.Tetris_Baseheal) { preview.getOrAddComponent(tetriAbility).setConfig(tetriConfig) for (const c of preview.getComponentsInChildren(tetriAbility)) { c.setConfig(tetriConfig) } } if (tetriConfig.type === TetriType.Tetris_Baseattk || tetriConfig.type === TetriType.Tetris_Basevine) { preview.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConfig, true, true) for (const c of preview.getComponentsInChildren(tetriWeaponCarrier)) { c.setConfig(tetriConfig, true, true) } } } /** floor 子树碰撞体落稳前保持关闭,普通块全开 */ private _enableBattlePieceColliders(preview: Node): void { for (const col of preview.getComponentsInChildren(PolygonCollider2D)) { let isFloorCollider = false let cur: Node | null = col.node while (cur) { if (cur.getComponent(tetriFloorNode)) { isFloorCollider = true; break } if (cur === preview) break cur = cur.parent } col.enabled = !isFloorCollider } } /** 卡牌模板上的 Static/零重力须在进入物理世界前改回 Dynamic */ private _primeBattlePieceRigidbodies(preview: Node, map: tetriMap | null): void { const floorComps = preview.getComponentsInChildren(tetriFloorNode) ?? [] for (const rb of preview.getComponentsInChildren(RigidBody2D)) { rb.enabled = true const isFloor = floorComps.some(f => { const fn = f?.node if (!fn?.isValid) return false let cur: Node | null = rb.node while (cur) { if (cur === fn) return true cur = cur.parent } return false }) if (isFloor) continue rb.type = ERigidBody2DType.Dynamic if (map) { rb.gravityScale = map.fallGravityScale rb.linearDamping = map.fallLinearDamping } else if (rb.gravityScale === 0) { rb.gravityScale = 1 } rb.fixedRotation = false rb.enabledContactListener = true rb.allowSleep = false rb.wakeUp() } } setLayerMask(n: Node, layerMask: number) { n.layer = layerMask; for (let i = 0; i < n.children.length; i++) { if (n.children[i].isValid) { this.setLayerMask(n.children[i], layerMask); } } } //金币 refreshCoinNum() { this.lb_money.string = `${this.CurentBattle.CurentCoinNum}` this.lb_num.string = `${this.lb_Goid_num}` if (this.CurentBattle.CurentCoinNum < this.lb_Goid_num) { this.lb_num.color = new Color(255, 0, 0) } else { this.lb_num.color = new Color(255, 255, 255) } this.refreshCoinNumColor() } /**是否点击过锤子泡泡 */ private _isClickChuiziBubble: boolean = false; /**是否点击过刷新泡泡 */ private _isClickRefreshBubble: boolean = false; private _lastTipClickMs = 0; onBattleWaveRefresh() { if (gg.data.doc.chapter == 1) { if (this.CurentBattle.CurentWaveNum == 3) { } else if (this.CurentBattle.CurentWaveNum == 2) { this.updateBtnActive(); } if ([6, 9].includes(this.CurentBattle.CurentWaveNum) && !this._isClickChuiziBubble) { let paopao = this.btnchuzi.getChildByName("paopao") if (paopao?.isValid) { paopao.active = true; paopao.scale = Vec3.ZERO; tween(paopao) .to(0.5, { scale: Vec3.ONE }, { easing: 'quadInOut' }) .delay(3) .call(() => { paopao.active = false; }) .start(); } } if ([4, 7, 10].includes(this.CurentBattle.CurentWaveNum) && !this._isClickRefreshBubble) { let paopao = this.btnRefreshBox.getChildByName("paopao") if (paopao?.isValid) { paopao.active = true; paopao.scale = Vec3.ZERO; tween(paopao) .to(0.5, { scale: Vec3.ONE }, { easing: 'quadInOut' }) .delay(3) .call(() => { paopao.active = false; }) .start(); } } } //如果是无尽模式不显示后面的TotalWaveNum if (this.CurentBattle.BattleType == BattleType.InfiniteMode) { this.lb_boci.string = `第${this.CurentBattle.InfiniteModeCurentWaveNum}波` } else { this.lb_boci.string = `第${this.CurentBattle.CurentWaveNum}/${this.CurentBattle.TotalWaveNum}波` } if (this.CurentBattle.CurentWaveNum == 1 || this.CurentBattle.CurentWaveNum > 20) { return } if (this.CurentBattle.getOpenSkillSelectType() == 0) { return } this.CurentBattle.tetriSaveBoxNum++ this._playBoxJumpToBoxAnim(); this.showBoxAccumulateBubble(); } updateBtnActive() { this.btnShenqiBox.active = gg.game.CurentBattle.BattleType == BattleType.SpeedMode; this.btnCoin.active = true this.btnMaotouying.active = gg.game.CurentBattle.BattleType != BattleType.SpeedMode;; this.btnRefreshBox.active = true; this.btnchuzi.active = true } async showCoinFly(pos: Vec3, count: number = 1) { if (!this.ui_topSpineLayer?.isValid) return; if (count == 1) { const coinFly = await gg.res.getNodeAsync('prefab/', 'coinFly', GBundle.tetriGame); if (!coinFly?.isValid) return; coinFly.setParent(this.ui_topSpineLayer); coinFly.worldPosition = pos.clone(); coinFly.getComponent(CoinFly).flyAni(this.ui_coinEndPos.worldPosition.clone(), this.refreshCoinNum.bind(this)); this.scheduleOnce(() => { gg.audio.playEffect({ name: "获得金币" }); }, 1.2); return; } for (let i = 0; i < count; i++) { this.scheduleOnce(() => { void (async () => { const randomPos = new Vec3(pos.x + Math.random() * 50, pos.y + Math.random() * 50, 0); const coinFly = await gg.res.getNodeAsync('prefab/', 'coinFly', GBundle.tetriGame); if (!coinFly?.isValid || !this.ui_topSpineLayer?.isValid) return; coinFly.setParent(this.ui_topSpineLayer); coinFly.worldPosition = randomPos; coinFly.getComponent(CoinFly).flyAni(this.ui_coinEndPos.worldPosition.clone(), this.refreshCoinNum.bind(this)); })(); }, i * 0.01); } this.scheduleOnce(() => { gg.audio.playEffect({ name: "获得金币" }); }, 1.2); } onPlayerHit(target: Node) { this.showHpWarn(true) this.refreshWallHp(); } showHpWarn(bShow: boolean) { if (this.ui_hpWarn.active && MTools.beforeTimes(1500)) return; this.ui_hpWarn.active = bShow; } refreshWallHp() { let curWallHp = this.CurentBattle.CurentWallHp let maxWallHp = this.CurentBattle.WallMaxHp this.lb_hpNum.string = `${curWallHp}` //console.log('refreshWallHp', curWallHp, maxWallHp) this.sp_hpProgressTop.fillRange = curWallHp / maxWallHp this.scheduleOnce(() => { this.sp_hpProgressMid.fillRange = curWallHp / maxWallHp }, 0.1) this.checkbtnAdHp() // this.checkbtnAdHp() } checkbtnAdHp() { let limitNum = gg.data.table.getConst(ConstNumType.AdAddHpLimit); let AdAddHp = gg.data.table.getConst(ConstNumType.AdAddHp); let maxWallHp = this.CurentBattle.WallMaxHp let curWallHp = this.CurentBattle.CurentWallHp let chufaHp = maxWallHp * AdAddHp if (this.CurentBattle.curAdAddHpLeftNum >= limitNum) { this.btnRecoveyHp.active = false } else { if (curWallHp <= chufaHp) { this.btnRecoveyHp.active = true } else { this.btnRecoveyHp.active = false } } } refreshShovelBtn() { if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Dian){ if (this.CurentBattle.gameShovelNum <= 0) { this.ui_czAd.active = true } else { this.ui_czAd.active = false } } this.lb_cznum.string = `${this.CurentBattle.GameShovelCoin}`; this.lb_cznum.color = Color.WHITE; if (this.CurentBattle.CurentCoinNum < this.CurentBattle.GameShovelCoin) { this.lb_cznum.color = Color.RED } //this.ui_bottom_btnchuzi_lb_num.string = `${this.CurentBattle.gameShovelNum}` } refreshMagicalBtn() { this.lb_sqnum.string = `${this.CurentBattle.onRefreshMagicalNum}/3` } private _isTouchInNode(event: EventTouch, node: Node | null): boolean { if (!node?.isValid) return false; const ut = node.getComponent(UITransform); if (!ut) return false; const rect = ut.getBoundingBoxToWorld(); const p = event.getUILocation(); return p.x >= rect.xMin && p.x <= rect.xMax && p.y >= rect.yMin && p.y <= rect.yMax; } /** 仅当触摸在游戏区,且没有在拖拽新卡时,才推动正在下落的方块 */ private _canControlFallingByTouch(event: EventTouch): boolean { if (this.CurentBattle?.IsUseShovel) return false; // 拖拽下一张卡(预览/引导层激活)期间,不允许推动已下落块 if (this.ui_choosezhezhao?.active) return false; // 底部 UI 区域(卡槽、按钮)点击不触发推动 if (this._isTouchInNode(event, this.ui_bottom)) return false; if (this._isTouchInNode(event, this.ui_card)) return false; return true; } private _onGameplayTouchStart(event: EventTouch): void { if (!this._canControlFallingByTouch(event)) return; this.CurentBattle?.TetraMap?.handleExternalTouchStart(event); } private _onGameplayTouchEnd(event: EventTouch): void { if (!this._canControlFallingByTouch(event)) return; this.CurentBattle?.TetraMap?.handleExternalTouchEnd(event); } click_btnUserchuziStart(event: EventTouch) { if (!this.CurentBattle.IsUseShovel) { return } // 必须吞掉触摸:禁止穿透到方块节点(节点用 UI 命中,堆高镜头缩放后会点偏) event.propagationStopped = true; } /** * 锤子落点:UI 触点 → 双相机桥接到 GameCamera 世界(与画面一致)。 * 堆高后 GameCamera 会拉远/抬 Y,不能拿 UI 坐标直接对碰撞体,也不能用「以 GameCam 为中心 /zoom」的错误公式。 */ private _getShovelTouchWorldPos(event: EventTouch): Vec3 { const map = this.CurentBattle?.TetraMap; const p = event.getUILocation(); if (map?.uiWorldToGameWorld) { return map.uiWorldToGameWorld(p.x, p.y); } if (map) { return new Vec3(map.uiWorldXToGameWorldX(p.x), map.uiWorldYToGameWorldY(p.y), 0); } return new Vec3(p.x, p.y, 0); } private _shovelPointInPolygon(ptX: number, ptY: number, poly: Vec2[]): boolean { let inside = false; for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) { const xi = poly[i].x; const yi = poly[i].y; const xj = poly[j].x; const yj = poly[j].y; const intersect = ((yi > ptY) !== (yj > ptY)) && (ptX < (xj - xi) * (ptY - yi) / ((yj - yi) || 1e-6) + xi); if (intersect) inside = !inside; } return inside; } /** * 精确命中:优先 Polygon 点内判定(避免 L/T 形 AABB 空白角误点邻块); * 返回越小越好的分数(中心距² + 面积微偏置)。 */ private _shovelHitScore(root: Node | null, worldX: number, worldY: number): number { if (!root?.isValid) return Number.POSITIVE_INFINITY; const cols = root.getComponentsInChildren(Collider2D) ?? []; let best = Number.POSITIVE_INFINITY; const lv = new Vec3(); const wv = new Vec3(); for (let i = 0; i < cols.length; i++) { const c = cols[i]; if (!c?.node?.isValid || !c.enabled) continue; if (c instanceof PolygonCollider2D) { const pts = c.points ?? []; if (pts.length < 3) continue; const off = (c as any).offset as Vec2 | undefined; const ox = off?.x ?? 0; const oy = off?.y ?? 0; const m = c.node.getWorldMatrix(); const worldPts: Vec2[] = []; let cx = 0; let cy = 0; for (let k = 0; k < pts.length; k++) { lv.set(pts[k].x + ox, pts[k].y + oy, 0); Vec3.transformMat4(wv, lv, m); worldPts.push(new Vec2(wv.x, wv.y)); cx += wv.x; cy += wv.y; } if (!this._shovelPointInPolygon(worldX, worldY, worldPts)) continue; cx /= worldPts.length; cy /= worldPts.length; const dx = worldX - cx; const dy = worldY - cy; const aabb = c.worldAABB; const area = aabb ? Math.max(1, (aabb.xMax - aabb.xMin) * (aabb.yMax - aabb.yMin)) : worldPts.length; const score = dx * dx + dy * dy + area * 1e-6; if (score < best) best = score; continue; } const aabb = c.worldAABB; if (!aabb) continue; if (worldX < aabb.xMin || worldX > aabb.xMax || worldY < aabb.yMin || worldY > aabb.yMax) { continue; } const cx = (aabb.xMin + aabb.xMax) * 0.5; const cy = (aabb.yMin + aabb.yMax) * 0.5; const dx = worldX - cx; const dy = worldY - cy; const area = Math.max(1, (aabb.xMax - aabb.xMin) * (aabb.yMax - aabb.yMin)); const score = dx * dx + dy * dy + area * 1e-6; if (score < best) best = score; } return best; } private _tryUseShovelByOverlayTouch(event: EventTouch): boolean { const p = this._getShovelTouchWorldPos(event); const table = this.CurentBattle?.TetraMap?._tetrisTable; const mapNode = this.CurentBattle?.TetraMap?.node; const searchRoot = table?.isValid ? table : mapNode; if (!searchRoot?.isValid) return false; let bestClick: (() => void) | null = null; let bestScore = Number.POSITIVE_INFINITY; const floors = searchRoot.getComponentsInChildren(tetriFloorNode) ?? []; for (let i = 0; i < floors.length; i++) { const f = floors[i]; if (!f?.node?.isValid) continue; const score = this._shovelHitScore(f.node, p.x, p.y); if (score < bestScore) { bestScore = score; bestClick = () => f.click_btnEnd(); } } const normals = searchRoot.getComponentsInChildren(tetriNode) ?? []; for (let i = 0; i < normals.length; i++) { const n = normals[i]; if (!n?.node?.isValid) continue; const score = this._shovelHitScore(n.node, p.x, p.y); if (score < bestScore) { bestScore = score; bestClick = () => n.click_btnEnd(); } } if (!bestClick || !Number.isFinite(bestScore)) return false; // 命中用游戏世界坐标;特效 anim 层=GameCamera(524288),必须也用游戏世界坐标 this.showChuiZiEffect(p, bestClick); return true; } click_btnUserchuziEnd(event: EventTouch) { if (!this.CurentBattle.IsUseShovel) { return } event.propagationStopped = true; if (this._tryUseShovelByOverlayTouch(event)) { return; } if (gg.data.doc.chapter == 1 && this.chapter1FreeCZ) { this.chapter1FreeCZ = false; this.ui_czAd.active = false this.lb_freeCzLab.node.active = false this.ui_chuizishouzhi.active = false this.refreshShovelBtn() } //取消使用锤子发消息 GEvent.Ins.emit(GEvent.TetriGameCancelUseShovel) } cancelUseShovel() { this.ui_heightNode.active = true this.ui_bottom.active = true this.ui_top.active = true this.ui_playHp.active = true this.ui_Gold.active = true this.ui_card.active = true // this.ui_choosezhezhao.active = true // this.ui_hpWarn.active = true this.ui_Userchuizi.active = false //显示使用锤子层级 this.CurentBattle.resume() this.CurentBattle.IsUseShovel = false; //刷新一下锤子按钮 this.refreshShovelBtn() } gameReborn() { this.scheduleOnce(() => { this.CurentBattle.startGame() this.resetLoveHp() this.CurentBattle.resetLightningLoveState() }, 1.0) } /** * 播放锤子动画。 * pos 必须是 GameCamera 世界坐标:预制体里 anim 子节点 layer=524288,只被 GameCamera 看见; * 若误用 UI 坐标,堆高拉远后锤子会画在屏幕下方。 */ showChuiZiEffect(pos: Vec3, callBack: Function = null) { this.ui_chuiziEff.active = true; this.ui_chuiziEff.setWorldPosition(pos.x, pos.y, pos.z); // 确保 Spine 走游戏相机层(与叠塔同层) const anim = this.ui_chuiziEff.getChildByName('anim'); const gameLayer = (gg.ui?.GameCamera as Camera | null)?.visibility ?? 524288; if (anim?.isValid && gameLayer) { anim.layer = gameLayer; } this.ui_chuiziEff.getComponentInChildren(sp.Skeleton).setAnimation(0, 'animation', false); gg.audio.playEffect({ name: "锤子" }); this.scheduleOnce(() => { this.ui_chuiziEff.active = false; if (callBack) { callBack(); } }, 1.2) } private _playBoxJumpToBoxAnim(): void { //游戏结束不需要播放宝箱跳转动画 if (!this.CurentBattle.IsGameOverBoxJump) { return; } if (gg.game.CurentBattle.BattleType == BattleType.SpeedMode) { return; } const from = this.ui_boxJump; const to = this.ui_box; if (!from?.isValid || !to?.isValid) return; // 起点(world)缓存 if (!this._boxJumpOriginWorld) { this._boxJumpOriginWorld = from.worldPosition.clone(); } const start = this._boxJumpOriginWorld.clone(); const end = to.worldPosition.clone(); // 若两者几乎重合则直接切换显示 const dx = end.x - start.x; const dy = end.y - start.y; if (dx * dx + dy * dy < 1) { to.active = true; from.active = false; from.worldPosition = start; return; } // 预设:飞行中先显示 jump、隐藏 box,结束后反过来 from.active = true; //to.active = false; from.worldPosition = start; // 控制点:中点上抬(根据距离动态调整) const mid = new Vec3((start.x + end.x) * 0.5, (start.y + end.y) * 0.5, start.z); const dist = Math.sqrt(dx * dx + dy * dy); // 抛物线高度:调高一点(距离越远抛得越高) const lift = Math.max(220, Math.min(800, dist * 1)); const ctrl = new Vec3(mid.x, mid.y + lift, start.z); this.showGetTip('获得技能宝箱') const duration = Math.max(0.35, Math.min(0.8, dist / 900)); Tween.stopAllByTarget(from); tween({ t: 0 }) .to(duration, { t: 1 }, { easing: 'quadOut', onUpdate: (obj: any) => { const t = Math.max(0, Math.min(1, obj.t ?? 0)); const it = 1 - t; const x = it * it * start.x + 2 * it * t * ctrl.x + t * t * end.x; const y = it * it * start.y + 2 * it * t * ctrl.y + t * t * end.y; from.setWorldPosition(x, y, start.z); } }) .call(() => { if (!from?.isValid || !to?.isValid) return; to.active = true; to.getComponent(DiamondTreasure).refreshBoxNum() to.getComponent(DiamondTreasure).playSpine('idle') // 宝箱 UI 复用/切换显示时,确保可点击恢复 to.getComponent(DiamondTreasure).isCanClick = true; to.getComponent(DiamondTreasure).lableTip = this.ui_guideLab; from.active = false; from.worldPosition = start; this.refreshTreasureCostState(to); // if (this.CurentBattle.ChapterId == 1 && this.CurentBattle.CurentWaveNum == 3) { //开启引导 this.ui_boxShouzhi.active = true; // this.showDescGuide("波次结束,获得宝箱,解锁武器和技能"); let paopao = to.getChildByName('paopao') if (paopao?.isValid) { paopao.active = true; paopao.scale = Vec3.ZERO; tween(paopao) .to(0.5, { scale: v3(2, 2, 2) }, { easing: 'quadInOut' }) .start(); } // gg.ui.openPanel(UIID.NewGuideMask, { // data: [ // [this.ui_box], // ['波次结束,获得宝箱,解锁武器和技能'], // () => { // this.pauseUpdateTime = false // // gg.data.setStatistics(StatisticsType.firstChapterGuideIndex, 1) // }, // true, // 255 // ] // }); } else { this.ui_boxShouzhi.active = false; } }) .start(); } /**刷新银币lable颜色 */ refreshCoinNumColor() { this.refreshTreasureCostState(this.ui_box); //锤子 this.lb_cznum.string = `${this.CurentBattle.GameShovelCoin}`; this.lb_cznum.color = Color.WHITE; if (gg.game.CurentBattle.CurentCoinNum < gg.game.CurentBattle.GameShovelCoin) { this.lb_cznum.color = Color.RED; } } /**刷新宝箱免费/价格显示(含显隐、文案和颜色) */ private refreshTreasureCostState(boxNode: Node) { if (!boxNode?.isValid) return; let lbNum = boxNode.getChildByName("lbNum")?.getComponent(Label); let lbfree = boxNode.getChildByName("lbfree")?.getComponent(Label); if (!lbNum || !lbfree) return; const costArr = gg.game?.CurentBattle?.OpenSkillSelectCoinArr ?? []; if (costArr.length <= 0) { lbNum.node.active = false; lbfree.node.active = true; return; } let index = gg.game.CurentBattle.IsOpenSkillSelectTimes; if (index >= costArr.length) { index = costArr.length - 1; } let num = Number(costArr[index] ?? 0); lbNum.node.active = num > 0; lbfree.node.active = num <= 0; lbNum.string = String(num); lbNum.color = (gg.game.CurentBattle.CurentCoinNum < num) ? Color.RED : Color.WHITE; if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Dian){ lbfree.node.active = true lbNum.node.active = false } } showOwl() { if (this.btnOwl == null || !this.btnOwl.isValid) { return; } if (this.btnOwl.active) return; this.btnOwl.active = true; const anim = this._getOwlChild("anim"); const spt = this._getOwlChild("spt"); if (!anim || !spt) { // prefab 结构不匹配或节点已失效:直接隐藏,避免报错 this.btnOwl.active = false; return; } if (gg.data.getStatistics(StatisticsType.OwlFreeGetCoinCount) == 0) { this.btnOwl.getChildByName('ad').active = false } else { this.btnOwl.getChildByName('ad').active = true } anim.active = true; spt.active = false; const owlCoin = gg.game?.CurentBattle?.VideoAddCoin ?? 0; this.btnOwl.getComponentInChildren(Label).string = String(owlCoin); let spos = this.ui_owlStartPos.worldPosition.clone(); let epos = this.ui_owl.worldPosition.clone(); this.btnOwl.setWorldPosition(spos.x, spos.y, 0); Tween.stopAllByTarget(this.btnOwl); // 停掉上一段 _owlTweenTarget,否则旧 onUpdate 仍会持续改 btnOwl 世界坐标 if (this._owlTweenTarget) { Tween.stopAllByTarget(this._owlTweenTarget); this._owlTweenTarget = null; } const dx = epos.x - spos.x; const dy = epos.y - spos.y; const dist = Math.sqrt(dx * dx + dy * dy); const mid = new Vec3((spos.x + epos.x) * 0.5, (spos.y + epos.y) * 0.5, 0); const lift = Math.max(120, Math.min(420, dist * 0.45)); const ctrl = new Vec3(mid.x, mid.y + lift, 0); this._owlTweenTarget = { t: 0 }; tween(this._owlTweenTarget) .to(10, { t: 1 }, { easing: 'quadOut', onUpdate: (obj: { t: number }) => { if (!this.btnOwl?.isValid) return; const t = Math.max(0, Math.min(1, obj.t ?? 0)); const it = 1 - t; const x = it * it * spos.x + 2 * it * t * ctrl.x + t * t * epos.x; const y = it * it * spos.y + 2 * it * t * ctrl.y + t * t * epos.y; this.btnOwl.setWorldPosition(x, y, 0); } }) .call(() => { if (!this.btnOwl?.isValid) return; const anim2 = this._getOwlChild("anim"); const spt2 = this._getOwlChild("spt"); if (!anim2 || !spt2) return; anim2.active = false; spt2.active = true; if (gg.data.getStatistics(StatisticsType.OwlFreeGetCoinCount) == 0) { this.ui_guidedianjiMty.active = true } else { this.ui_guidedianjiMty.active = false } }) .start(); // .delay(18) // .call(() => { // if (!this.btnOwl?.isValid) return; // const anim3 = this._getOwlChild("anim"); // const spt3 = this._getOwlChild("spt"); // if (!anim3 || !spt3) return; // anim3.active = true; // spt3.active = false; // this.ui_guidedianjiMty.active = false // tween(this.btnOwl) // .by(1, { x: 200, y: 200 }) // .call(() => { // if (!this.btnOwl?.isValid) return; // this.btnOwl.active = false; // this.unschedule(this.showOwl); // this.schedule(this.showOwl, this.CurentBattle.AdFrequency); // }) // .start(); // }) // .start(); } click_btnOwl(): void { let ad = this.btnOwl.getChildByName('ad') if (ad.active == false) { //不需要看广告 this.btnOwl.getChildByName('ad').active = true let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-猫头鹰硬币免费`) GEvent.Ins.emit(GEvent.showCoinFly, this.btnOwl.worldPosition.clone(), 15); gg.data.setStatistics(StatisticsType.OwlFreeGetCoinCount, 1) this.ui_guidedianjiMty.active = false this.CurentBattle.addCoinNum(this.CurentBattle.VideoAddCoin); if (this._owlTweenTarget) { Tween.stopAllByTarget(this._owlTweenTarget); this._owlTweenTarget = null; } Tween.stopAllByTarget(this.btnOwl); this.btnOwl.active = false; this.unschedule(this.showOwl); this.schedule(this.showOwl, this.CurentBattle.AdFrequency); return; } gg.sdk.showVideoAd((res) => { if (res) { gg.game.CurentBattle.IsWatchAd = true let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-AD-猫头鹰硬币`) GEvent.Ins.emit(GEvent.showCoinFly, this.btnOwl.worldPosition.clone(), 15); this.CurentBattle.addCoinNum(this.CurentBattle.VideoAddCoin); if (this._owlTweenTarget) { Tween.stopAllByTarget(this._owlTweenTarget); this._owlTweenTarget = null; } Tween.stopAllByTarget(this.btnOwl); this.btnOwl.active = false; this.unschedule(this.showOwl); this.schedule(this.showOwl, this.CurentBattle.AdFrequency); } }); } /**弹提示 */ showGetTip(tip: string) { Tween.stopAllByTarget(this.ui_tip); this.ui_tip.active = true; this.lb_getTip.string = tip; this.ui_tip.x = -800; tween(this.ui_tip) .to(0.5, { x: -375 }) .delay(2.0) .to(1.0, { x: -800 }) .call(() => { this.ui_tip.active = false; }) .start(); } updateDps(delay: number = 0) { if(gg.game.noMonsterShow) return; const dpsThrottle = BattlePerformance.dpsUiMinIntervalMs(); // beforeTimes:冷却内返回 true → 应跳过;勿取反(取反会导致该刷时不刷) if (dpsThrottle > 0 && MTools.beforeTimes(dpsThrottle, 'battle_dps_ui')) { return; } let dps = this.CurentBattle.dps(); dps = Math.floor(dps); if (this.CurentBattle?.skipNextDpsFlyAnim) { this.CurentBattle.skipNextDpsFlyAnim = false; this._lastDps = dps; this.lb_dps.string = MTools.formatChineseUnit({ num: dps }); return; } if (dps == this._lastDps || dps == 0) { if (dps == 0) { this.lb_dps.string = '0'; this._lastDps = dps; } return; } // console.log('updateDps',dps,this._lastDps) //如果dps比this._lastDps大播放动画 if (dps > this._lastDps || dps < this._lastDps) { //播放动画 let addDps = 0; let isAdd = false if (dps > this._lastDps) { addDps = dps - this._lastDps; isAdd = true; } else { addDps = this._lastDps - dps; isAdd = false; } this._lastDps = dps; this.scheduleOnce(() => { console.log('播放动画', dps, this._lastDps) let copyNode = instantiate(this.ui_dpsMove); copyNode.parent = this.ui_dpsMove.parent; copyNode.active = true let posiy = this.CurentBattle.TetraMap.getHeightLinePosition(); copyNode.y = posiy - 100; let lb_moveDps = copyNode.getChildByName('lb_moveDps')?.getComponent(Label); lb_moveDps.string = isAdd ? '+' + addDps : '-' + addDps; let movePosy = copyNode.position.y + 50; let endPos = this.ui_dps.position.clone(); tween(copyNode) .to(1, { y: movePosy }) .to(0.5, { position: endPos, scale: v3(0.3, 0.3, 0.3) }) .call(() => { copyNode.destroy(); this.lb_dps.string = MTools.formatChineseUnit({ num: dps }); Tween.stopAllByTarget(this.ui_dps); tween(this.ui_dps) .to(0.2, { scale: v3(1.2, 1.2, 1.2) }) .to(0.2, { scale: v3(1, 1, 1) }) .start(); }) .start(); }, delay) } else { this._lastDps = dps; this.lb_dps.string = MTools.formatChineseUnit({ num: dps }); Tween.stopAllByTarget(this.ui_dps); tween(this.ui_dps) .to(0.2, { scale: v3(1.2, 1.2, 1.2) }) .to(0.2, { scale: v3(1, 1, 1) }) .start(); } } private _lastDps: number = -1; showChoosezhezhao() { if (gg.data.doc.chapter > 1) { return } if (this.fangkuaiNum >= 3) { return } this.ui_choosezhezhao1.active = true; this.ui_choosezhezhao2.active = true; } hideChoosezhezhao() { if (gg.data.doc.chapter > 1) { return } this.ui_choosezhezhao1.active = false; this.ui_choosezhezhao2.active = false; } click_btnCoin() { let maxCount = this.maxCountCoin if (gg.game.CurentBattle.IsReplica) { maxCount = 10000000; } if (gg.game.CurentBattle.getFreeCoinNum >= maxCount) { gg.ui.showToast('免费次数已用完') return } if (this.ui_mfybAd.active) { //看广告 gg.sdk.showVideoAd((res) => { if (res) { gg.game.CurentBattle.IsWatchAd = true gg.game.CurentBattle.getFreeCoinNum++; let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-AD-免费银币`) this.CurentBattle.CurentCoinNum += this.CurentBattle.VideoAddCoin; GEvent.Ins.emit(GEvent.showCoinFly, this.btnCoin.worldPosition.clone(), 15); this.ui_mfybAd.active = true this.lb_mfybCount.node.active = true this.ui_mfybshouzhi.active = false this.setFreeBtnCoinCount() if (gg.game.CurentBattle.getFreeCoinNum >= maxCount) { gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}-免费银币次数用完`) } } }); } else { //直接获取不用看广告 let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}_${curWaveNum}-免费银币`) this.CurentBattle.CurentCoinNum += this.CurentBattle.VideoAddCoin; GEvent.Ins.emit(GEvent.showCoinFly, this.btnCoin.worldPosition.clone(), 15); this.ui_mfybAd.active = true this.lb_mfybCount.node.active = true this.ui_mfybshouzhi.active = false this.setFreeBtnCoinCount() if (gg.game.CurentBattle.getFreeCoinNum >= maxCount) { gg.sdk.reportDY("inLevel", `章节${gg.game.CurentBattle.getReportDYChapterId()}-免费银币次数用完`) } } } setFreeBtnCoinNum() { const owlCoin = gg.game?.CurentBattle?.VideoAddCoin ?? 0; this.lb_coinNum.string = `+${owlCoin}`; this.setFreeBtnCoinCount() } setFreeBtnCoinCount() { let maxCount = this.maxCountCoin this.lb_mfybCount.string = `${maxCount - gg.game.CurentBattle.getFreeCoinNum}/${maxCount}`; this.lb_mfybCount.node.active = gg.game.CurentBattle.BattleType != BattleType.SpeedMode; if(gg.game.CurentBattle.IsReplica){ this.lb_mfybCount.node.active = false } } /**宝箱累计气泡框提示:当宝箱大于等于2 第十波次开始出现*/ showBoxAccumulateBubble() { if (!this.CurentBattle.IsGameOverBoxJump) { return } if (gg.game.CurentBattle.CurentWaveNum < 10) { return } if (gg.game.CurentBattle.tetriSaveBoxNum < 2) { return } //宝箱来了显示气泡 let paopao = this.ui_box.getChildByName('paopao') if (paopao?.isValid) { paopao.active = true; paopao.scale = Vec3.ZERO; tween(paopao) .to(0.5, { scale: v3(2, 2, 2) }, { easing: 'quadInOut' }) .delay(3.0) .call(() => { paopao.active = false; }) .start(); } } click_btnTiqian() { this.CurentBattle.tryFinishWarnTimeEarly(); this.onTetriGameStart(); } click_btnRecoveyHp(){ // 战斗暂停由 showVideoAd → _enter/_leaveVideoAdPause 对称处理,勿在此先 pause gg.sdk.showVideoAd((res) => { if (res) { let curWaveNum = gg.game.CurentBattle.CurentWaveNum gg.sdk.reportDY("inLevel", `章节${this.CurentBattle.getReportDYChapterId()}_${curWaveNum}-AD-满血`) this.CurentBattle.setCurentWallHp(this.CurentBattle.WallMaxHp) this.CurentBattle.curAdAddHpLeftNum++ this.refreshWallHp() } else { gg.ui.showToast('观看视频失败'); } }) } //雷电副本ui显示与隐藏 showHideLoveHpUI(){ this.btnShenqiBox.active = true this.btnCoin.active = false this.btnShenqiBox.position = this.btnCoin.position.clone() this.ui_czAd.active = true this.lb_cznum.node.active = false this.btnchuzi.getChildByName('jinbi').active = false this.ui_brboxAd.active = true this.lb_num.node.active = false this.btnRefreshBox.getChildByName('jinbi').active = false this.ui_Gold.active = false let posiai = this.ui_aixinNodes.position.clone() let posi = this.ui_timeNode.position.clone() this.ui_aixinNodes.setPosition(posi.x,posi.y) this.ui_timeNode.setPosition(posiai.x,posiai.y) } //雷电副本爱心重置 resetLoveHp(){ this.aixinIndex = 0 //爱心还原位置0,0;透明度设置为255,显示可见 this.ui_aixinNodes.children.forEach(child => { let aixin_liang = child.getChildByName('aixin_liang') aixin_liang.setPosition(0,0) aixin_liang.opacity = 255 aixin_liang.active = true }) } //雷电副本创建爱心血量 createLoveHp(){ let hpNum = gg.game.CurentBattle.loveHpNum //创建hpNum-1个爱心 let aixinNode_hei = this.ui_aixinNodes.getChildByName('aixinNode_hei') aixinNode_hei.active = true for(let i = 0; i < hpNum - 1; i++){ let node = instantiate(aixinNode_hei) node.parent = this.ui_aixinNodes node.setPosition(0,0) node.active = true } } //雷电副本爱心掉落 loveHpDrop(){ if (gg.game.CurentBattle.BattleType !== BattleType.OrangeWeaponMode_Dian || gg.game.CurentBattle.loveHpNum <= 0) return; if (!this.ui_aixinNodes?.isValid) return; const hpNum = gg.game.CurentBattle.loveHpNum; const idx = this.aixinIndex; if (idx >= hpNum) { gg.game.CurentBattle.failGame(); return; } const node = this.ui_aixinNodes.children[idx]; if (node) { this.aixinIndex = idx + 1; const aixin_liang = node.getChildByName('aixin_liang'); tween(aixin_liang) .to(1, { y: - 30 , opacity: 0}) .call(() => { aixin_liang.active = false; gg.game.CurentBattle.failGame(); }) .start(); } else { if(this.aixinIndex >= gg.game.CurentBattle.loveHpNum){ gg.game.CurentBattle.failGame(); } } } }