消消方块阵换皮表情
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2319 lines
92 KiB

//*********************
// 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<void> {
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<void> {
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<ITableBattleCube>(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<ITableBattleCube>(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<ITableBattleCube>(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],
// ['<b><size=26><outline color=#000000 width=2><color=#ffffff>波次结束,获得</color><color=#45ff64>宝箱,解锁武器和技能</color></outline></size></b>'],
// () => {
// 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();
}
}
}
}