/* * @Author: mojunshou 1637302775@qq.com * @Date: 2025-03-20 15:01:09 * @LastEditors: mojunshou 1637302775@qq.com * @LastEditTime: 2025-04-30 10:32:04 * @Description: 消除游戏主场景 */ import { _decorator, Button, Color, EventTouch, instantiate, JsonAsset, Animation, v3, Label, Node, Prefab, randomRangeInt, Sprite, tween, UITransform, Vec2, Vec3, Widget } from "cc"; import { oops } from "db://oops-framework/core/Oops"; import { DeviceUtil } from "db://oops-framework/core/utils/DeviceUtil"; import { ecs } from "db://oops-framework/libs/ecs/ECS"; import { LabelChange } from "db://oops-framework/libs/gui/label/LabelChange"; import { CCComp } from "db://oops-framework/module/common/CCComp"; import { GameEvent } from "../common/config/GameEvent"; import { UIID } from "../common/config/GameUIConfig"; import { ServerHandler } from "../common/manager/ServerHandler"; import { smc } from "../common/SingletonModuleComp"; import { Format } from "../utils/Format"; import { DCHandler } from "../common/manager/DCHandler"; import { Tween } from "cc"; import { AD_TYPE } from "../common/config/GameDefine"; import { ADHandler } from "../common/manager/ADHandler"; import { director } from "cc"; import { Game } from "cc"; const { ccclass, property } = _decorator; // 游戏状态枚举 enum GameState { READY, // 准备中 PLAYING, // 游戏中 PAUSED, // 暂停 --进其他界面,广告暂停 GAME_OVER, // 游戏结束 GAME_PASS } // 格子状态 enum CellState { EMPTY, // 空格子 FILLED, // 有方块 HIGHLIGHTED // 高亮(可放置) } //网格数据接口 interface GridData { name: string, // 名称 status: CellState, // 状态 gridNode: Node | null // 网格节点 row: number, // 行 col: number, // 列 type: number, // 类型--gridColorKey } interface Grids { row: number, column: number, } interface EditingData { brickData: BrickData | null, gridList: GridData[], } //底部方块数据定义 interface BrickData { index: number, brickKey: string | null, rotateFlag: boolean, gridConfig: Grids[], deg: number, brickNode: Node | null, brickInitPos: Vec3, // 方块初始位置 type: number, // 方块类型--gridColorKey rotateNode: Node | null, } interface GridConfigData { row: number, column: number, } /** 视图层对象 */ @ccclass('EliminateViewComp') @ecs.register('EliminateView', false) export class EliminateViewComp extends CCComp { @property({ type: LabelChange, displayName: "自动提现金额" }) private lab_wxCoin: LabelChange = null!; @property({ type: LabelChange, displayName: "额外奖励" }) private lab_hbCoin: LabelChange = null!; @property({ type: Node, displayName: "tween微信钱Node" }) private tweenWechatNode: Node = null!; @property({ type: Node, displayName: "tween红包Node" }) private tweenRedNode: Node = null!; @property({ type: Prefab, displayName: "金币预制体" }) private coinPrefab: Prefab = null! @property({ type: Prefab, displayName: "红包预制体" }) private redPacketPrefab: Prefab = null!; @property({ type: Label, displayName: "本局分数" }) private lab_score: Label = null!; @property({ type: Label, displayName: "目标分数" }) private lab_taget: Label = null!; @property({ type: Prefab, displayName: "item预制体列表" }) private itemPrefabs: Prefab[] = []; @property({ type: Node, displayName: "网格Node" }) private gridNode: Node = null!; @property({ type: Node, displayName: "移动Node" }) private moveNode: Node = null!; @property({ type: Node, displayName: "旋转Node" }) private rotateNode: Node = null!; @property({ type: Node, displayName: "方块Node" }) private brickNode: Node = null!; @property({ type: Prefab, displayName: "旋转预制体" }) private rotatePrefab: Node = null!; @property({ type: Label, displayName: "累计消除次数" }) private lab_total: Label = null!; @property({ type: Label, displayName: "每次放置添加的分数" }) private lab_addScore: Label = null!; @property({ type: Node, displayName: "自动按钮节点" }) private autoBtnNode: Node = null!; //二倍速按钮 @property({ type: Button, displayName: "二倍速按钮" }) private doubleSpeedBtn: Button = null!; @property({ type: Label, displayName: "第几块金砖" }) private lab_goldNum: Label = null!; @property({ type: Label, displayName: "二倍速时间" }) private lab_doubleTime: Label = null!; @property({ type: Node, displayName: "引导层" }) private guideNode: Node = null!; @property({ type: Node, displayName: "引导item1" }) private guideItem1: Node = null!; @property({ type: Node, displayName: "引导item2" }) private guideItem2: Node = null!; @property({ type: Node, displayName: "引导item3" }) private guideItem3: Node = null!; //引导手指 @property({ type: Node, displayName: "引导手指" }) private guideFinger: Node = null!; @property({ type: Node, displayName: "ComboNode" }) private comboNode: Node = null!; @property({ type: Node, displayName: "自动放置Node拦截事件" }) private autoNode: Node = null!; //游戏配置行列 private rows: number = 8; // 行数 private cols: number = 8; // 列数 private itemSize: number = 76.25; // 格子大小 private brickNum: number = 3; // 砖块数量 private yOffset = 100; private aniBrickRotate = 0 private operateFlag: boolean = false //是否可以操作 private touchStartPos = new Vec2() private gameState: GameState = GameState.READY; private score: number = 0; //本局分数 private money: number = 0; //左边金钱 private cash: number = 0; //右边红包钱数 private targetScore: number = 0; //目标分数 private isDoubleSpeed: boolean = false; //是否开启二倍速 private doubleNum: number = 1.5; //除2就是开启了2倍速,除1就是关闭了2倍速 private callback: Function | null = null; //回调函数 //tween时间控制变量 private autoMoveTime: number = 0.4; //自动移动时间 //再次自动放置间隔时间 private autoPlaceInterval: number = 0.4; //没使用颜色 notUseColor = new Color(255, 255, 255, 255) //可用的颜色 usableColor = new Color(0, 255, 0, 100) //不可用的颜色 unavailableColor = new Color(255, 0, 0, 100) //旋转容错 rotateFaultTolerant = 50; //游戏暂停状态 private gamePause: boolean = false; isAutoMode: boolean = false; autoModeInterval: number = 1 // 自动模式的间隔时间(秒) autoModeTimer: number = 0 // 自动模式计时器 adShowingFlag: boolean = false; // 广告展示标记 //网格列表管理列表 gridList: GridData[][] = []; //砖块列表 bricksList: BrickData[] = []; //网格颜色列表 gridColorList: GridData[] = []; brickConfig: { bricks?: any } = {} //方块配置 editingData: EditingData = { brickData: null, gridList: [], } //消除区间数组,到时候去这两个中间值,然后消除次数到了就展示翻倍广告 private eliminateInterval: number[] = []; // 添加新的属性来跟踪是否需要重置消除计数 private shouldResetEliminateCount: boolean = true; private autoState: boolean = false; //自动状态 private eliminateBaseScore: number = 10; //每行得多少分 private extraGridScore: number = 1; //每个格子占用几分 private placementBaseScore: number = 1; //每个格子占用几分 private currentCombo: number = 0; // 当前连击次数 //这里要记录连击次数就是每次移动能消除就增加,如果不能就归零 //消除总数 private eliminateTotal: number = 0; //2倍速时间 private doubleSpeedTime: number = 0; //新手引导 private isGuideMode = false; private guideStep = 0; //自动间隔时间 private autoInterval: number = 1.5; //福利弹窗 private popupType: string = ""; //新人引导提示语 private guideTips: string[] = [ "拖动方块,填满整行可以进行消除", "当行与列被砖块同时填满,砖块会被消除", "点击方块可以旋转90°,不限旋转次数哦", "放置&消除方块得分达成目标获得金砖" ]; private randomList: number[] = []; private autoFunction: Function | null = null; /** 视图层逻辑代码分离演示 */ async start() { // const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象 this.setButton(); this.initButtonState(false); await this.loadConfig(); this.initData(); this.setData(); this.addEventList(); if (this.guideStep === 0 && smc.account.AccountModel.curLevel == 1) { this.startGuideMode() } else { this.guideNode.active = false; this.initGrid(); } this.updateWelfarePoint(); DCHandler.inst.reportData(3000005); oops.audio.playMusicLoop("common/audios/bgm"); DCHandler.inst.reportData(3000504, smc.account.AccountModel.curLevel); } addEventList() { oops.message.on(GameEvent.RestartGame, this.restartGame, this); oops.message.on(GameEvent.DoubleSpeedOpenSuccess, this.doubleSpeedOpenSuccess, this); oops.message.on(GameEvent.openView, this.openView, this); oops.message.on(GameEvent.showCoinAnimation, this.showCoinAnimation, this); oops.message.on(GameEvent.updateHbAndWxCoin, this.updateCoin, this); oops.message.on(GameEvent.StartAutoGame, this.startAutoGame, this); oops.message.on(GameEvent.updateGameState, this.updateGameState, this); oops.message.on(GameEvent.updateGameScore, this.updateGameScore, this); oops.message.on(GameEvent.pauseGame, this.pauseGame, this); oops.message.on(GameEvent.resumeGame, this.resumeGame, this); } updateGameState(event: string, args: string) { switch (args) { case "paused": this.gameState = GameState.PAUSED; break; case "playing": if (this.gameState == GameState.PAUSED || this.autoState && this.gameState == GameState.GAME_OVER || this.autoState && this.gameState == GameState.GAME_PASS) { this.gameState = GameState.PLAYING; } break; } } pauseGame() { this.gamePause = true; director.pause(); } resumeGame() { this.gamePause = false; director.resume(); } //初始化网格 private initGrid() { // 清理现有网格 this.clearExistingGrids(); // 生成网格矩阵 this.createGridMatrix(); } //初始化按钮状态 private initButtonState(state: boolean) { //自动按钮默认关闭 if (this.autoBtnNode) { //关闭 const on = this.autoBtnNode.getChildByName("on"); on ? on.active = state : null; const off = this.autoBtnNode.getChildByName("off"); off ? off.active = !state : null; } //第一关隐藏二倍速和自动放置按钮 const curLevel = smc.account.AccountModel.curLevel; this.autoBtnNode.active = curLevel > 1; this.doubleSpeedBtn.node.active = curLevel > 1; } //初始化数据 private initData() { // this.gameMode = GameMode.MANUAL; this.score = 0; this.targetScore = 0; this.money = 0; this.cash = 0; this.operateFlag = true this.shouldResetEliminateCount = true; this.currentCombo = 0; this.autoNode.active = false; smc.game.GameModel.smallAdCount = 0; smc.game.GameModel.allAdCount = 0; smc.game.GameModel.directAdCount = 0; smc.game.GameModel.doubleSpeedAdCount = 0; } //设置数据 setData() { if (DeviceUtil.isAndroid && DeviceUtil.isNative) { if (smc.game.GameModel.curLevelConfig.eliminateScope) { this.eliminateInterval = smc.game.GameModel.curLevelConfig.eliminateScope || []; } } this.gameState = GameState.PLAYING; this.score = smc.game.GameModel.curScore || 0; this.money = smc.account.AccountModel.wxCoin || 0; this.cash = smc.account.AccountModel.hbCoin || 0; this.targetScore = smc.game.GameModel.targetScore || 0; this.popupType = smc.game.GameModel.popupType; this.lab_score.string = this.score.toString(); this.lab_wxCoin.string = Format.formatWxCoin(this.money); this.lab_hbCoin.string = Format.formatRedPacketCoin(this.cash); this.lab_taget.string = this.targetScore.toString(); //金砖数量要加1 const num = smc.account.AccountModel.goldCoin this.lab_goldNum.string = `${num + 1}`; this.randomList = this.getRandomUniqueInts(); const curLevel = smc.account.AccountModel.curLevel; DCHandler.inst.reportData(3000500, curLevel); } updateCoin() { this.money = smc.account.AccountModel.wxCoin; this.cash = smc.account.AccountModel.hbCoin; this.lab_wxCoin.string = Format.formatWxCoin(this.money); this.lab_hbCoin.string = Format.formatRedPacketCoin(this.cash); } //开始自动游戏 startAutoGame() { if (!this.autoState) { this.autoGame(); } } openView(event: string, args: string) { oops.log.logView(args, "<<<<<<<打开的弹窗类型"); switch (args) { case "openRedBagView": oops.gui.open(UIID.RedPacketWithdraw); break; case "openPassView": oops.gui.open(UIID.GamePass); break; case "openDoubleSurprise": oops.gui.open(UIID.DoubleRewards); break; case "openRebateView": oops.gui.open(UIID.CashRebate); break; case "openCashWithdrawalView": oops.gui.open(UIID.WithSussce); //提现成功 break; case "openWechatWithdrawalView": oops.gui.open(UIID.WechatWithdraw); //微信提现页 break; case "openDoubleSpeedView": oops.gui.open(UIID.DoubleSpeed); break; case "openRecordView": oops.gui.open(UIID.WithdrawRecord); break; } } startGuideMode() { this.isGuideMode = true this.guideStep = 1 this.guideNode.active = true; for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { const currentRow: GridData[] = []; this.gridList.push(currentRow); for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { const gridData = this.createGridData(rowIndex, columnIndex); currentRow.push(gridData); this.createGridNode(gridData); } } // 初始化格子状态 for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { this.gridList[rowIndex][columnIndex].status = CellState.EMPTY this.generateGrid(this.gridList[rowIndex][columnIndex]) } } // 清除旋转数据 if (this.rotateNode) { this.rotateNode.destroyAllChildren(); } this.touchStartPos.set(Vec2.ZERO) this.setupGuideStep(this.guideStep) } setupGuideStep(step: number) { this.gameState = GameState.PLAYING; this.operateFlag = true this.bricksList.length = 0 this.brickNode.destroyAllChildren(); this.clearAllGuideGrids() this.guideStep = step const tips_node = this.guideNode.getChildByPath("tips/lab_tips"); if (tips_node) { const tips = tips_node.getComponent(Label); if (tips) { tips.string = this.guideTips[step - 1]; } } //tween移动 const guideTipsNode = this.guideNode.getChildByPath("tips"); if (guideTipsNode) { const guideTipsAni = guideTipsNode.getComponent(Animation); if (guideTipsAni) { guideTipsAni.play(); } } if (step === 1) { this.guideStep1() } else if (step === 2) { this.guideStep2() } else if (step === 3) { this.guideStep3() } else if (step === 4) { this.guideStep4() } } clearAllGuideGrids() { for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { const grid = this.gridList[row][col] grid.status = 0; grid.type = 0; this.generateGrid(grid) } } } // 引导砖块保持底部中间生成 createGuideBrick(gridConfig: GridConfigData[], brickKey: string, colorIndex: number, index: number, rotateFlag = false) { const brickData = { index, brickKey: brickKey, rotateFlag, gridConfig, deg: 0, brickNode: new Node(), gridColorKey: "colorKey", brickInitPos: new Vec3(), type: colorIndex, rotateNode: new Node(), } const node = this.generateBrick(brickKey, colorIndex); this.brickNode.addChild(node); const transform = this.brickNode.getComponent(UITransform); if (transform) { const midX = transform.width / 2; node.setPosition(0, 0); brickData.brickNode = node brickData.brickNode.scale_x = 0.8; brickData.brickNode.scale_y = 0.8; brickData.brickInitPos = node.getWorldPosition() this.bricksList.push(brickData) this.brickAddEvent(brickData) } if (brickData.rotateFlag) { brickData.rotateNode = instantiate(this.rotatePrefab) this.rotateNode.addChild(brickData.rotateNode); //先隐藏 brickData.rotateNode.active = false; brickData.rotateNode.setWorldPosition(this.brickNode.getWorldPosition()) } } //引导步骤1 private guideStep1() { // this.guideFinger.setPosition(v3(55, -461)); const emptyIndex = Math.floor(this.cols / 2) for (let c = 0; c < this.cols; c++) { if (c !== emptyIndex) { const g = this.gridList[4][c] g.status = 1 g.type = 1; this.generateGrid(g) } } this.createGuideBrick([ { row: 0, column: 0 }, ], "Brick1", 1, 1) //手指显示,方块显示 this.guideFinger.active = true; this.guideItem1.active = true; //拿出this.guideFinger.x y //展示tween动画 tween(this.guideFinger) .delay(0.2) .by(1, { position: v3(45, 500, 0) }) .delay(0.2) .to(0.01, { position: v3(60, -450, 0) }) //原点 .union() .repeatForever() .start() //方块 tween(this.guideItem1) .delay(0.2) .by(1, { position: v3(37, 515, 0) }) .delay(0.2) .to(0.01, { position: v3(0, -410, 0) }) //原点 .union() .repeatForever() .start() } //引导步骤2 private guideStep2() { for (let r = 0; r < this.rows; r++) { if (r !== 4) { const g = this.gridList[r][4] g.status = 1 g.type = 1; this.generateGrid(g) } } this.createGuideBrick([{ row: 0, column: 0 }], "Brick1", 1, 1) } //引导步骤3 private guideStep3() { // 填满前两列,除去 [2][0] 这个位置,制造一个L型缺口 for (let r = 0; r < this.rows; r++) { for (let c = 3; c < 5; c++) { if (r === 3 && c === 3) continue if (r === 4 && c === 3) continue if (r === 5 && c === 3) continue if (r === 3 && c === 4) continue if (r === 4 && c === 4) continue if (r === 5 && c === 4) continue const g = this.gridList[r][c] g.status = 1 g.type = 1; this.generateGrid(g) } } // 创建一个L型方块,引导玩家旋转后放置 this.createGuideBrick([ { row: 0, column: 0 }, { row: 0, column: 1 }, { row: 0, column: 2 }, { row: 1, column: 0 }, { row: 1, column: 1 }, { row: 1, column: 2 } ], "BrickII", 1, 1, true) //这要双II //引导三 this.guideItem1.active = false; this.guideItem2.active = true; //停止之前的tween this.guideFinger.setPosition(v3(55, -461)); Tween.stopAllByTarget(this.guideFinger); Tween.stopAllByTarget(this.guideItem1); tween(this.guideFinger) .delay(0.2) .by(1, { position: v3(0, 500, 0) }) .delay(0.2) .to(0.01, { position: v3(55, -450, 0) }) //原点 .union() .repeatForever() .start() //方块 tween(this.guideItem2) .delay(0.2) .by(1, { position: v3(0, 515, 0) }) .delay(0.2) .to(0.01, { position: v3(0, -410, 0) }) //原点 .union() .repeatForever() .start() } //引导步骤4 private guideStep4() { const centerRow = 3; const centerCol = 3; // 遍历整个网格 for (let r = 0; r < this.rows; r++) { for (let c = 0; c < this.cols; c++) { const inCenter = r >= centerRow && r <= centerRow + 1 && c >= centerCol && c <= centerCol + 1; const isCross = r === centerRow || r === centerRow + 1 || // 中间两行 c === centerCol || c === centerCol + 1; // 中间两列 if (isCross && !inCenter) { const g = this.gridList[r][c]; g.status = 1; g.type = 1; this.generateGrid(g); } } } this.createGuideBrick([ { row: 0, column: 0 }, { row: 0, column: 1 }, { row: 1, column: 0 }, { row: 1, column: 1 } ], "BrickO", 1, 1) this.guideItem2.active = false; Tween.stopAllByTarget(this.guideFinger); Tween.stopAllByTarget(this.guideItem2); this.guideFinger.setPosition(v3(55, -461)); this.guideItem3.active = true; tween(this.guideFinger) .delay(0.2) .by(1, { position: v3(0, 440, 0) }) .delay(0.2) .to(0.01, { position: v3(55, -450, 0) }) //原点 .union() .repeatForever() .start() //方块 tween(this.guideItem3) .delay(0.2) .by(1, { position: v3(0, 440, 0) }) .delay(0.2) .to(0.01, { position: v3(0, -410, 0) }) //原点 .union() .repeatForever() .start() } //显示金币动画 --是否只计算红包 showCoinAnimation(event: string, args: string) { //计算中间的坐标就好 const lastPos = this.moveNode.getWorldPosition(); const score = this.score; if (smc.game.GameModel.changeHbCoin > 0) { this.createCoinFlyAnimation( this.redPacketPrefab, lastPos, this.lab_hbCoin.node.getWorldPosition(), 2, () => { // 显示红包分数增加动画 this.showRedPacketScoreAnimation(); } ); } if (smc.game.GameModel.changeWxCoin > 0) { // 添加回调函数,在金币动画完成后显示微信分数增加 this.createCoinFlyAnimation( this.coinPrefab, lastPos, this.lab_wxCoin.node.getWorldPosition(), 2, () => { // 显示微信分数增加动画 this.showWechatScoreAnimation(); } ); } } //初始化网格 private async loadConfig() { let json_name: string = "gui/eliminate/config/GridConfig"; return new Promise((resolve, reject) => { oops.res.load(json_name, JsonAsset, (err: Error | null, res: any) => { if (res) { this.brickConfig = res.json; resolve(); } else { console.log("JSON数据加载失,请检查文件"); reject(err); } }); }); } /** * @description: 清理现有的网格数据 * @return {*} */ private clearExistingGrids(): void { this.gridList = []; if (this.gridNode) { this.gridNode.destroyAllChildren(); } } /** * @description: 设置网格容器大小 * @return {*} */ private createGridMatrix(): void { for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { const currentRow: GridData[] = []; this.gridList.push(currentRow); for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { const gridData = this.createGridData(rowIndex, columnIndex); currentRow.push(gridData); this.createGridNode(gridData); } } // 初始化格子状态 for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { this.gridList[rowIndex][columnIndex].status = CellState.EMPTY this.generateGrid(this.gridList[rowIndex][columnIndex]) } } // 初始化方块 if (this.brickNode) { this.brickNode.destroyAllChildren(); } this.bricksList.length = 0 for (let i = 1; i <= this.brickNum; i++) { this.addBrick(i) } // 清除旋转数据 if (this.rotateNode) { this.rotateNode.destroyAllChildren(); } this.touchStartPos.set(Vec2.ZERO) } /** * @description: 创建格子数据 * @param {number} row * @param {number} column * @return {*} */ private createGridData(row: number, column: number): GridData { return { name: `Grid-${row}-${column}`, status: CellState.EMPTY, gridNode: null, row: row, col: column, type: 0 }; } /** * @description: 创建网格Node * @param {GridData} gridData * @return {*} */ private createGridNode(gridData: GridData): void { const gridNode = new Node(gridData.name); if (this.gridNode) { this.gridNode.addChild(gridNode); } gridData.gridNode = gridNode; // 设置网格大小 gridNode.addComponent(UITransform).setContentSize(this.itemSize, this.itemSize); } /** * 生成或更新网格 * @param gridData 网格数据 */ private generateGrid(gridData: GridData) { if (!gridData || !gridData.gridNode) { console.warn('无效的网格数据'); return; } // 清理现有子节点 this.clearGridChildren(gridData.gridNode); // 获取对应的预制体 const prefab = this.getGridPrefab(gridData); if (!prefab) { console.warn('无法获取网格预制体'); return; } // 创建并配置新节点 const node = this.createNewGridNode(prefab, gridData); // 设置节点属性 this.setupGridNode(node, gridData); } /** * 获取对应状态的预制体 */ private getGridPrefab(gridData: GridData): Prefab { if (gridData.status === CellState.EMPTY) { if (this.itemPrefabs[0]) { return this.itemPrefabs[0]; } else { throw new Error('Grid prefab is not loaded'); } } if (gridData.status === CellState.FILLED && gridData.type) { const type = gridData.type; return this.itemPrefabs[type]; } throw new Error('Invalid grid status or missing gridColorKey'); } /** * 创建网格节点 */ private createNewGridNode(prefab: Prefab, gridData: GridData): Node { const node = instantiate(prefab); if (gridData && gridData.gridNode) { gridData.gridNode.addChild(node); return node; } return node } /** * 设置网格节点的属性 */ private setupGridNode(node: Node, gridData: GridData): void { // 设置未使用状态的颜色 if (gridData.status === CellState.EMPTY) { const sprite = node.getComponent(Sprite); if (sprite) { sprite.color = this.notUseColor; } } // 设置节点大小 const transform = node.getComponent(UITransform); if (transform) { transform.setContentSize( this.itemSize, this.itemSize ); } // 设置位置 node.setPosition(Vec3.ZERO); } addBrick(index: number) { if (this.isGuideMode) { return; } const brickKey = Object.keys(this.brickConfig['bricks'])[randomRangeInt(0, Object.keys(this.brickConfig['bricks']).length)]; const brickConfig = this.brickConfig['bricks'][brickKey]; const num = Math.floor(Math.random() * this.randomList.length); const randomIndex = this.randomList[num]; // const randomIndex = this.randomList[0]; // 生成方块 const brickData: BrickData = { index, brickKey: brickKey, rotateFlag: brickConfig['rotateFlag'], gridConfig: brickConfig['gridConfig'], deg: 0, brickNode: null, brickInitPos: new Vec3(), type: randomIndex, rotateNode: null, } this.bricksList.push(brickData) // 生成方块 const brickNode = this.generateBrick(brickKey, randomIndex); if (this.brickNode) { this.brickNode.addChild(brickNode) } brickData.brickNode = brickNode; let offset = 220 if (this.brickNum % 2 === 1) { const middleNum = Math.floor(this.brickNum / 2) + 1 if (index < middleNum) { offset = - offset } else if (index === middleNum) { offset = 0 } } if (brickData && brickData.brickNode) { brickData.brickNode.setPosition(offset, 0) brickData.brickNode.scale_x = 0.6; brickData.brickNode.scale_y = 0.6; brickData.brickInitPos = brickData.brickNode.getWorldPosition() } if (brickData.rotateFlag) { brickData.rotateNode = instantiate(this.rotatePrefab) this.rotateNode.addChild(brickData.rotateNode); //先隐藏 brickData.rotateNode.active = false; brickData.rotateNode.setWorldPosition(brickNode.getWorldPosition()) } this.brickAddEvent(brickData); } // 每个item生成独立的方块节点 generateBrick(brickKey: string, randomIndex: number) { const brickConfig = this.brickConfig['bricks'][brickKey] let rowMin = 0 let rowMax = 0 let columnMin = 0 let columnMax = 0 brickConfig['gridConfig'].forEach((gridConfigData: GridConfigData) => { if (gridConfigData.row < rowMin) { rowMin = gridConfigData.row } else if (gridConfigData.row > rowMax) { rowMax = gridConfigData.row } if (gridConfigData.column < columnMin) { columnMin = gridConfigData.column } else if (gridConfigData.column > columnMax) { columnMax = gridConfigData.column } }) const rowNum = (rowMax - rowMin + 1) const columnNum = (columnMax - columnMin + 1) // 生成独立的方块节点 const brickNode = new Node() brickNode.name = brickKey // 设置方块大小 const transformCom: UITransform = brickNode.addComponent(UITransform) transformCom.setContentSize( this.itemSize * columnNum, this.itemSize * rowNum ) transformCom.setAnchorPoint(0.5, 0.5) // 设置方块位置 const gridPrefab = this.itemPrefabs[randomIndex] //生成对应的配置方块设置地址 brickConfig['gridConfig'].forEach((gridConfigData: GridConfigData) => { const gridNode = new Node() gridNode.name = 'grid' brickNode.addChild(gridNode) gridNode.addComponent(UITransform).setContentSize(this.itemSize, this.itemSize) const gridWidget: Widget = gridNode.addComponent(Widget) gridWidget.isAlignLeft = true gridWidget.left = this.itemSize * gridConfigData.column - this.itemSize * columnMin gridWidget.isAlignBottom = true gridWidget.bottom = this.itemSize * gridConfigData.row - this.itemSize * rowMin const node = instantiate(gridPrefab) gridNode.addChild(node) const uiTransform = node.getComponent(UITransform); if (uiTransform) { uiTransform.setContentSize( this.itemSize, this.itemSize, ) } node.setPosition(Vec3.ZERO) node.on(Node.EventType.TOUCH_START, (event: EventTouch) => { event.preventSwallow = true; }, this); node.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { event.preventSwallow = true; }, this); node.on(Node.EventType.TOUCH_END, (event: EventTouch) => { event.preventSwallow = true; }, this); }) return brickNode } brickAddEvent(brickData: BrickData) { const brickNode = brickData.brickNode if (!brickNode) { console.error("brickNode为空,无法添加事件"); return } // 记录初始位置和状态 let startPos = new Vec3(); let originalParent: Node | null = null; // 触摸开始事件 brickNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => { // 如果正在编辑其他方块,则忽略 if (!this.operateFlag) return; // 清空编辑中的数据 this.editingData.brickData = null; this.editingData.gridList.length = 0; // 记录触摸开始位置和方块原始信息 this.touchStartPos.set(event.getUILocation()); originalParent = brickNode.parent; startPos = brickNode.getWorldPosition().clone(); // 将方块移到移动层并放大 brickNode.setParent(this.moveNode); brickNode.setWorldPosition(startPos); // 从方块列表中移除该方块 const index = this.bricksList.findIndex(item => item === brickData); if (index > -1) { this.editingData.brickData = this.bricksList.splice(index, 1)[0]; } else { //返回原位置 console.error(">>this.bricksList>>>>>>", JSON.stringify(this.bricksList)); console.error("未找到方块数据:", JSON.stringify(brickData)); this.bricksList.push(brickData) if (brickData.brickNode) { tween(brickData.brickNode) .to(0.15, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.8, 0.8, 0.8) }) .call(() => { if (brickData.brickNode) { this.operateFlag = true this.brickNode.addChild(brickData.brickNode) brickData.brickNode.setWorldPosition(brickData.brickInitPos) } }) .start() } } }, this); // 触摸移动事件 brickNode.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { if (!this.operateFlag) return; const movePos: Vec2 = event.getUILocation().subtract(this.touchStartPos) // 挪动很小时,不移动方块组合 if (movePos.length() <= this.rotateFaultTolerant) { return } // 隐藏旋转节点 if (this.editingData.brickData && this.editingData.brickData.rotateFlag) { if (this.editingData.brickData.rotateNode) { this.editingData.brickData.rotateNode.active = false } } // 恢复所有网格颜色 this.gridColorRecovery(); // 移动方块 brickNode.setWorldPosition(event.getUILocation().toVec3().add3f(0, this.yOffset, 0)) brickNode.scale_x = 1; brickNode.scale_y = 1; // 重置编辑中的网格数据 this.editingData.gridList.length = 0; // 检查方块每个子网格是否与游戏网格重叠 const tempGridList: GridData[] = []; let allEmptyGrids: boolean = true; // 标记是否所有网格都为空 brickNode.children.forEach((childNode) => { const childWorldPos = childNode.getWorldPosition(); // 查找与子网格重叠的游戏网格 let matchedGrid: GridData | null = null; for (let row = 0; row < this.rows && !matchedGrid; row++) { for (let col = 0; col < this.cols && !matchedGrid; col++) { const grid = this.gridList[row][col]; if (!grid || !grid.gridNode) continue; const gridPos = grid.gridNode.getWorldPosition(); if (Vec3.distance(gridPos, childWorldPos) <= (this.itemSize / 2)) { matchedGrid = grid; } } } if (matchedGrid) { tempGridList.push(matchedGrid); // 检查是否有非空网格 if (matchedGrid.status !== CellState.EMPTY) { allEmptyGrids = false; } } }); // 检查是否所有子网格都有对应的游戏网格,且都是空的 let canPlace = tempGridList.length === brickData.gridConfig.length && allEmptyGrids; // 更新编辑中的网格列表 if (canPlace) { this.editingData.gridList = [...tempGridList]; } // 更新网格颜色提示 - 只改变空网格的颜色 tempGridList.forEach(grid => { // 只处理空网格 if (grid.status === CellState.EMPTY) { if (grid.gridNode && grid.gridNode.children[0]) { const sprite = grid.gridNode.children[0].getComponent(Sprite); if (sprite) { sprite.color = canPlace ? this.usableColor : this.unavailableColor; } } // 用于后续恢复颜色 this.gridColorList.push(grid); } }); }, this); // 触摸结束或取消事件 const touchEndHandler = (event: EventTouch) => { if (!this.operateFlag) return this.operateFlag = false // 单击旋转 if (!this.editingData.brickData) { console.log("没有数据") return } if ( this.editingData.brickData.rotateFlag && event.getUILocation().subtract(this.touchStartPos).length() <= this.rotateFaultTolerant ) { //旋转 const brickData = this.editingData.brickData if (brickData && brickData.brickNode) { this.bricksList.push(brickData) this.brickNode.addChild(brickData.brickNode) //显示旋转节点 if (brickData.rotateNode) { brickData.rotateNode.active = true; } brickData.brickNode.setWorldPosition(brickData.brickInitPos) // 旋转 this.brickGridRotate(brickData).then(() => { this.operateFlag = true }) // 隐藏旋转节点 this.scheduleOnce(() => { if (brickData.rotateNode) { brickData.rotateNode.active = false; } }, 0.4); //打点 DCHandler.inst.reportData(3000600); } } else if (this.editingData.brickData && this.editingData.gridList.length > 0) { if (this.isGuideMode && this.guideStep > 0) { const brickData = this.editingData.brickData const targetGrids = this.editingData.gridList // 如果格子数量不足,直接视为无效放置 if (!brickData || !targetGrids || targetGrids.length !== brickData.gridConfig.length) { this.bricksList.push(brickData) if (brickData.brickNode) { tween(brickData.brickNode) .to(0.15, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.8, 0.8, 0.8) }) .call(() => { if (brickData.brickNode) { this.operateFlag = true this.brickNode.addChild(brickData.brickNode) brickData.brickNode.setWorldPosition(brickData.brickInitPos) } }) .start() } return } // 模拟砖块落下后的状态 const tempGridList = this.copyGridList() for (let i = 0; i < targetGrids.length; i++) { const g = targetGrids[i] tempGridList[g.row][g.col].status = CellState.FILLED } const simulateResult = this.gridEliminateCheck(tempGridList) const canEliminate = simulateResult.gridEliminateList.length > 0 //如果不能消除,则将方块返回原位置 if (!canEliminate) { this.bricksList.push(brickData); if (brickData.brickNode) { tween(brickData.brickNode) .to(0.15, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.8, 0.8, 0.8) }) .call(() => { if (brickData.brickNode) { this.operateFlag = true this.brickNode.addChild(brickData.brickNode) brickData.brickNode.setWorldPosition(brickData.brickInitPos) } }) .start() } return } } // 显示放置得分动画(显示一次,包含总格子数) this.showScoreAnimation(); // 放置方块到网格 this.editingData.gridList.forEach(grid => { grid.status = CellState.FILLED; grid.type = this.editingData.brickData!.type; this.generateGrid(grid); }); // 标记需要重置消除计数器 this.shouldResetEliminateCount = true; brickNode.destroy(); this.addBrick(this.editingData.brickData.index); // 检查消除 this.scheduleOnce(() => { this.gridEliminate().then((hasElimination) => { // 如果没有消除,确保下次消除会重置计数 if (!hasElimination) { this.shouldResetEliminateCount = true; } this.operateFlag = true; //新手引导 if (this.isGuideMode) { this.scheduleOnce(() => { if (this.guideStep < 4) { this.setupGuideStep(this.guideStep + 1) } else { this.isGuideMode = false; this.guideNode.active = false; Tween.stopAllByTarget(this.guideFinger); Tween.stopAllByTarget(this.guideItem3); this.initGrid(); } }, 0.3) } else { // 检查游戏是否结束 this.prompt(false).then(canContinue => { if (!canContinue) { this.gameOver(); } }); } }); }, 0.1); } else { // 无法放置,将方块返回原位置 const brickData = this.editingData.brickData; if (brickData) { this.bricksList.push(brickData); this.operateFlag = false // 添加回弹动画 tween(brickNode) .to(0.2, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.6, 0.6, 0.6) }) .call(() => { if (originalParent) { this.operateFlag = true brickNode.setParent(originalParent); brickNode.setWorldPosition(brickData.brickInitPos); } }) .start(); } } // 格子颜色恢复 this.gridColorRecovery() }; brickNode.on(Node.EventType.TOUCH_END, touchEndHandler, this); brickNode.on(Node.EventType.TOUCH_CANCEL, touchEndHandler, this); } //格子颜色恢复 gridColorRecovery() { while (this.gridColorList.length > 0) { const gridData = this.gridColorList.pop(); if (gridData) { if (gridData.status === CellState.EMPTY) { if (gridData.gridNode) { gridData.gridNode.children[0].getComponent(Sprite)!.color = this.notUseColor; } } } } } brickGridRotate(brickData: BrickData) { return new Promise((resolve, reject) => { const next = this.nextGridRotate(brickData.gridConfig, brickData.deg) brickData.deg = next.deg brickData.gridConfig = next.gridConfig if (brickData.brickNode) { tween(brickData.brickNode).to(this.aniBrickRotate, { angle: next.deg }).call(() => { resolve(true) }).start() } }) } //下一个旋转 nextGridRotate(gridConfig: GridConfigData[], deg: number) { const newGridConfig: GridConfigData[] = [] // 顺时针旋转 let newDeg = deg - 90 gridConfig.forEach((gridConfigData) => { // 例如(1,2) => (-2,1),可以画图分析 newGridConfig.push({ row: -gridConfigData.column, column: gridConfigData.row }) }) return { gridConfig: newGridConfig, deg: newDeg } } // 显示分数增加动画 showScoreAnimation() { if (!this.lab_addScore) return; // 计算放置的格子数量 const placedGridCount = this.editingData.gridList.length; // 获取中心位置用于显示分数 let centerPos = new Vec3(0, 0, 0); if (this.editingData.gridList.length > 0 && this.editingData.gridList[0].gridNode) { centerPos = this.editingData.gridList[0].gridNode.getWorldPosition().clone(); if (this.editingData.gridList.length > 1) { // 计算所有格子的平均位置作为中心点 for (let i = 1; i < this.editingData.gridList.length; i++) { const gridNode = this.editingData.gridList[i].gridNode; if (gridNode) { centerPos.add(gridNode.getWorldPosition()); } } const validGridCount = this.editingData.gridList.filter(grid => grid.gridNode).length; centerPos.x /= validGridCount; centerPos.y /= validGridCount; } } let score = placedGridCount; let position = centerPos // 计算得分(未消除时的得分) const calculatedScore = score * this.placementBaseScore; // 复制得分Label const scoreLabel = instantiate(this.lab_addScore.node); scoreLabel.active = true; // 设置文本和位置 const label = scoreLabel.getComponent(Label); if (label) { label.string = `+${calculatedScore}`; } // 添加到场景中 this.node.addChild(scoreLabel); scoreLabel.setWorldPosition(position); const num = this.isDoubleSpeed ? this.doubleNum : 1; // 创建动画效果 tween(scoreLabel) .to(0.5 / num, { position: new Vec3(scoreLabel.position.x, scoreLabel.position.y + 50, 0), scale: new Vec3(1.2, 1.2, 1.2) }) .to(0.3 / num, { opacity: 0 }) .call(() => { scoreLabel.destroy(); }) .start(); // 更新总分 this.score += calculatedScore; } // 显示消除次数动画 showEliminateCountAnimation(yPosition: number, eliminationCount: number) { if (!this.comboNode) return; // 如果需要重置计数器,先将计数归零 if (this.shouldResetEliminateCount) { this.currentCombo = 0; this.shouldResetEliminateCount = false; } // 增加累计消除次数 this.currentCombo += eliminationCount; // 复制总次数Label const comboNode = instantiate(this.comboNode); comboNode.active = true; // 设置文本 const label = comboNode.getChildByName("lab_combo"); if (label) { const labelComponent = label.getComponent(Label); if (labelComponent) { labelComponent.string = `${this.currentCombo}`; } } // 添加到场景中 this.node.addChild(comboNode); // 计算网格区域的X轴中心位置,Y轴使用传入的消除行位置 const centerPos = new Vec3(); if (this.gridNode) { // 获取网格区域的X轴中心 const worldPos = this.gridNode.getWorldPosition(); centerPos.set(worldPos.x, yPosition, worldPos.z); } comboNode.setWorldPosition(centerPos); // 创建动画效果 const num = this.isDoubleSpeed ? this.doubleNum : 1; tween(comboNode) .to(0.3 / num, { scale: new Vec3(1.5, 1.5, 1.5), opacity: 255 }) .delay(0.5 / num) // 停留更长时间 .to(0.4 / num, { scale: new Vec3(1.2, 1.2, 1.2), position: new Vec3(comboNode.position.x, comboNode.position.y + 50, 0), opacity: 0 }) .call(() => { comboNode.destroy(); }) .start(); } // gridEliminate() { return new Promise((resolve, reject) => { const d = this.gridEliminateCheck(this.gridList) const gridEliminateList = d.gridEliminateList const eliminateRowNum = d.eliminateRowNum const eliminateColumnNum = d.eliminateColumnNum const totalEliminationsInThisRound = eliminateRowNum + eliminateColumnNum this.eliminateTotal += totalEliminationsInThisRound; // console.log("消除总数", this.eliminateTotal) if (gridEliminateList.length < 1) { // 没有发生消除 this.updateGameScore(); this.shouldResetEliminateCount = true; // 下一次消除需要重置计数 resolve(false); return; } // 如果有消除,计算消除行的平均Y轴位置 if (gridEliminateList.length > 0) { // 计算所有被消除格子的平均Y轴位置 let totalY = 0; let validGrids = 0; for (const grid of gridEliminateList) { if (grid.gridNode) { totalY += grid.gridNode.getWorldPosition().y; validGrids++; } } // 计算平均Y位置 const avgY = validGrids > 0 ? totalY / validGrids : 0; // 显示消除次数动画,传入Y轴位置和本次消除的行列总数 this.showEliminateCountAnimation(avgY, totalEliminationsInThisRound); } const num = this.isDoubleSpeed ? this.doubleNum : 1; gridEliminateList.forEach((gridData) => { if (gridData.gridNode?.children[0]) { tween(gridData.gridNode.children[0]) .to(0.2 / num, { scale: new Vec3(0.5, 0.5) }) .call(() => { gridData.status = CellState.EMPTY this.generateGrid(gridData) }) .start() } }) oops.audio.playEffect("common/audios/eliminate") this.scheduleOnce(() => { // 计算分数:每行/列的基础分 + 每个额外格子的分数 let score = 0; // 行消除基础分 for (let i = 1; i <= eliminateRowNum; i++) { score += this.eliminateBaseScore; // 每行基础分 score += (this.cols - 1) * this.extraGridScore; // 额外格子分 } // 列消除基础分 for (let i = 1; i <= eliminateColumnNum; i++) { score += this.eliminateBaseScore; // 每列基础分 score += (this.rows - 1) * this.extraGridScore; // 额外格子分 } this.score += score; if (DeviceUtil.isNative && DeviceUtil.isAndroid) { ServerHandler.inst.updateEliminationReward({ count: totalEliminationsInThisRound, score: this.score, level: smc.account.AccountModel.curLevel, }) } if (DeviceUtil.isAndroid && DeviceUtil.isNative) { //需要欧知道是否通关了,当前通关了就不要弹了 if (this.score < this.targetScore) { if (this.eliminateInterval.length > 0) { const minNum = this.eliminateInterval[0] const maxNum = this.eliminateInterval[1] if (minNum && maxNum) { const randomNum = randomRangeInt(minNum, maxNum + 1); //这个数可以被总消除次数整除那就可以弹广告 //console.log("最小随机数>>>>>>>>>", minNum); //console.log("最大随机数>>>>>>>>>", maxNum); //console.log("消除总数>>>>>>>>>>>>>", this.eliminateTotal); //console.log("randomNum随机数>>>>>>>>>>", randomNum); ////打印少量领取总数 //console.log("少量领取总数>>>>>>>>>>>>>>>", smc.game.GameModel.smallAdCount); //console.log("skipAdCount>>>>>>>>>>>>>>", smc.game.GameModel.skipAdCount) //console.log("skipAdConfig>>>>>>>>>>>>>>", smc.game.GameModel.skipAdConfig); ////打印全都要总数 //console.log("全都要总数>>>>>>>>>>>>>>>", smc.game.GameModel.allAdCount); ////打印直接弹出总数 //console.log("直接弹出总数>>>>>>>>>>>>>>>", smc.game.GameModel.directAdCount); if (this.eliminateTotal % randomNum === 0) { //如果有自动就暂停自动 if (smc.game.GameModel.skipAdConfig != -1) { if (smc.game.GameModel.skipAdCount == 0 || smc.game.GameModel.skipAdCount % smc.game.GameModel.skipAdConfig != 0) { ServerHandler.inst.getDoubleSurprise(); } else { smc.game.GameModel.viewType = "double_reward"; smc.game.GameModel.directAdCount++; //测试看 ADHandler.inst.showAd(AD_TYPE.Double_Receive); } } else { ServerHandler.inst.getDoubleSurprise(); } } } } } } resolve(true) }, 0.1) }) } //消除检查 gridEliminateCheck(gridList: GridData[][]) { const gridEliminateList: GridData[] = [] let eliminateRowNum = 0 let eliminateColumnNum = 0 // 行检查 for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { const rowData = gridList[rowIndex] if (rowData.every(gridData => gridData.status === CellState.FILLED)) { rowData.forEach(gridData => { if (gridEliminateList.findIndex(data => data === gridData) < 0) { gridEliminateList.push(gridData) } }) eliminateRowNum += 1 } } // 列检查 for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { if (gridList.every(rowData => rowData[columnIndex].status === CellState.FILLED)) { gridList.forEach(rowData => { const gridData = rowData[columnIndex] if (gridEliminateList.findIndex(data => data === gridData) < 0) { gridEliminateList.push(gridData) } }) eliminateColumnNum += 1 } } return { gridEliminateList, eliminateRowNum, eliminateColumnNum, } } private createCoinFlyAnimation(prefab: Prefab, startPos: Vec3, endPos: Vec3, count: number, callback?: Function) { if (!prefab) return; let completedCount = 0; const totalCoins = Math.min(count, 5); // 限制最大数量 const delayBetweenCoins = 0.1; // 每个金币之间的延迟时间 for (let i = 0; i < totalCoins; i++) { const coin = instantiate(prefab); this.node.addChild(coin); //设置位置,取舞台中间位置世界坐标 coin.setWorldPosition(startPos); // 创建曲线动画 const num = this.isDoubleSpeed ? this.doubleNum : 1; // 添加延迟,使金币一个接一个飞出 tween(coin) .delay(i * delayBetweenCoins / num) // 每个金币有不同的延迟 .to(0.2 / num, { position: new Vec3( coin.position.x, coin.position.y, 0 ) }) .to(0.5 / num, { worldPosition: endPos }) .call(() => { coin.destroy(); completedCount++; // 所有金币动画完成后执行回调 if (completedCount === totalCoins && callback) { callback(); } }) .start(); } } // 显示微信分数增加动画 private showWechatScoreAnimation() { if (!this.tweenWechatNode) return; // 生成随机小数(小于1,保留2位小数) const changeNum = smc.game.GameModel.changeWxCoin; this.money = smc.account.AccountModel.wxCoin; const formattedValue = changeNum; //console.log("微信币增加数值>>>>>>>>>", changeNum); // 获取并设置分数标签 const scoreLabel = this.tweenWechatNode.getChildByName("lab_num")?.getComponent(Label); if (scoreLabel) { scoreLabel.string = "+" + Format.formatWxCoinModify(formattedValue); } // 保存原始位置 const originalPosition = this.lab_wxCoin.node.worldPosition; //设置原始位置 this.tweenWechatNode.setWorldPosition(originalPosition); // 显示节点 this.tweenWechatNode.active = true; // 创建向上移动的动画 tween(this.tweenWechatNode) .to(0.8, { worldPosition: new Vec3( originalPosition.x, originalPosition.y + 50, originalPosition.z ), opacity: 255 }) .to(0.2, { opacity: 0 }) .call(() => { //设置位置y-100 this.tweenWechatNode.setWorldPosition(new Vec3( originalPosition.x, originalPosition.y, originalPosition.z )) this.tweenWechatNode.active = false; // 更新总金额 if (this.lab_wxCoin) { this.lab_wxCoin.string = Format.formatWxCoin(this.money); } }) .start(); } // 显示红包分数增加动画 private showRedPacketScoreAnimation() { if (!this.tweenRedNode) return; //如果有值就是要那个,没有就是取随机 const changeNum = smc.game.GameModel.changeHbCoin; // console.log("红包分数增加动画", changeNum) this.cash = smc.account.AccountModel.hbCoin; if (!changeNum) return; const formattedValue = changeNum; // 获取并设置分数标签 const scoreLabel = this.tweenRedNode.getChildByName("lab_num")?.getComponent(Label); if (scoreLabel) { scoreLabel.string = "+" + Format.formatRedPacketCoin(formattedValue); } // 保存原始位置 const originalPosition = this.lab_hbCoin.node.worldPosition; //设置原始位置 this.tweenRedNode.setWorldPosition(originalPosition); // 显示节点 this.tweenRedNode.active = true; // 创建向上移动的动画 const num = this.isDoubleSpeed ? this.doubleNum : 1; tween(this.tweenRedNode) .to(0.8 / num, { worldPosition: new Vec3( originalPosition.x, originalPosition.y + 50, originalPosition.z ), opacity: 255 }) .to(0.2 / num, { opacity: 0 }) .call(() => { this.tweenRedNode.setWorldPosition(new Vec3( originalPosition.x, originalPosition.y, originalPosition.z )) this.tweenRedNode.active = false; // 更新总红包金额 if (this.lab_hbCoin) { // this.lab_hbCoin.changeTo(0.5, newAmount, () => { // }) this.lab_hbCoin.string = Format.formatRedPacketCoin(this.cash); } }) .start(); } // 提示 prompt(tipFlag = true) { return new Promise((resolve, reject) => { const gridPromptList: GridData[] = [] let moveFlag = false // 找方块可消除位置 for (let rowIndex = 0; rowIndex < this.rows && !moveFlag; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols && !moveFlag; columnIndex++) { const gridData = this.gridList[rowIndex][columnIndex] if (gridData.status !== CellState.EMPTY) continue // 方块不旋转检测是否能放 for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) { const brickData = this.bricksList[brickI] if (this.moveIf(rowIndex, columnIndex, brickData.gridConfig)) { // 复制整体网格,以方块设置网格状态 const gridList = this.copyGridList() brickData.gridConfig.forEach((gridConfigData) => { gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex].status = CellState.FILLED }) // 检查复制的整体网格是否有可消除 if (this.gridEliminateCheck(gridList).gridEliminateList.length > 0) { moveFlag = true brickData.gridConfig.forEach((gridConfigData) => { gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex]) }) } } } // 方块旋转检测是否能放 for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) { const brickData = this.bricksList[brickI] if (!brickData.rotateFlag) continue let gridConfig = brickData.gridConfig let deg = brickData.deg // 获得旋转的方块网格配置 for (let count = 1; count <= 3 && !moveFlag; count++) { const next = this.nextGridRotate(gridConfig, deg) if (this.moveIf(rowIndex, columnIndex, next.gridConfig)) { // 复制整体网格,以方块设置网格状态 const gridList = this.copyGridList() next.gridConfig.forEach((gridConfigData) => { gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex].status = CellState.FILLED }) // 检查复制的整体网格是否有可消除 if (this.gridEliminateCheck(gridList).gridEliminateList.length > 0) { moveFlag = true next.gridConfig.forEach((gridConfigData) => { gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex]) }) } } gridConfig = next.gridConfig deg = next.deg } } } } // 找方块可放置位置 for (let rowIndex = 0; rowIndex < this.rows && !moveFlag; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols && !moveFlag; columnIndex++) { const gridData = this.gridList[rowIndex][columnIndex] if (gridData.status !== CellState.EMPTY) continue // 方块不旋转检测是否能放 for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) { const brickData = this.bricksList[brickI] if (this.moveIf(rowIndex, columnIndex, brickData.gridConfig)) { moveFlag = true brickData.gridConfig.forEach((gridConfigData) => { gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex]) }) } } // 方块旋转检测是否能放 for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) { const brickData = this.bricksList[brickI] if (!brickData.rotateFlag) continue let gridConfig = brickData.gridConfig let deg = brickData.deg for (let count = 1; count <= 3 && !moveFlag; count++) { const next = this.nextGridRotate(gridConfig, deg) if (this.moveIf(rowIndex, columnIndex, next.gridConfig)) { moveFlag = true next.gridConfig.forEach((gridConfigData) => { gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex]) }) } gridConfig = next.gridConfig deg = next.deg } } } } if (gridPromptList.length < 1) { resolve(false) return } if (!tipFlag) { resolve(true) return } // 提示用户(网格变绿) gridPromptList.forEach((gridData) => { if (gridData.gridNode && gridData.gridNode.children[0]) { const sprite = gridData.gridNode.children[0].getComponent(Sprite); if (sprite) { sprite.color = this.usableColor; } } // 用于恢复格子 this.gridColorList.push(gridData) }) resolve(true) }) } moveIf(row: number, column: number, gridConfig: GridConfigData[]) { let moveFlag = true for (let i = 0; i < gridConfig.length; i++) { const gridConfigData = gridConfig[i] const gridI = row + gridConfigData.row const gridJ = column + gridConfigData.column // 边界判断 if ( gridI < 0 || gridI > this.rows - 1 || gridJ < 0 || gridJ > this.cols - 1 ) { moveFlag = false break } // 已用 else if (this.gridList[gridI][gridJ].status === CellState.FILLED) { moveFlag = false break } } return moveFlag } // 复制整体网格 copyGridList() { const gridList: GridData[][] = [] for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { gridList.push([]) for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { const gridData = this.gridList[rowIndex][columnIndex] gridList[rowIndex].push({ name: gridData.name, status: gridData.status, gridNode: null, row: gridData.row, col: gridData.col, type: gridData.type, }) } } return gridList } gameOver() { this.gameState = GameState.GAME_OVER smc.game.GameModel.curScore = this.score; oops.gui.open(UIID.GameOver); const curLevel = smc.account.AccountModel.curLevel; DCHandler.inst.reportData(3000501, curLevel); } //更新游戏分数 updateGameScore() { this.lab_score.string = this.score.toString(); smc.game.GameModel.curScore = this.score; if (this.score >= this.targetScore) { this.gameState = GameState.GAME_PASS; //防止第一关的时候出问题 if (this.guideNode.active) { this.guideNode.active = false; } //弹出通关奖励界面 DCHandler.inst.reportData(3000505, smc.account.AccountModel.curLevel); if (DeviceUtil.isNative && DeviceUtil.isAndroid) { ServerHandler.inst.getGameAwardInfo(); //如果 } else { oops.gui.open(UIID.GamePass); } } } /** * 清理网格的子节点 */ private clearGridChildren(gridNode: Node): void { const children = gridNode.children.slice(); // 创建副本避免遍历时修改问题 children.forEach(node => node.destroy()); } //========================打开其他界面和按钮逻辑======================= /** 视图对象通过 ecs.Entity.remove(eliminateViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { this.node.destroy(); } //设置按钮 private btn_setting() { oops.gui.open(UIID.Setting); this.gameState = GameState.PAUSED; DCHandler.inst.reportData(3000011); } //左边微信按钮 private btn_withdraw() { if (DeviceUtil.isNative && DeviceUtil.isAndroid) { this.gameState = GameState.PAUSED; ServerHandler.inst.getWechatTxInfo(); DCHandler.inst.reportData(3000006); } else { oops.gui.open(UIID.WechatWithdraw); } } //顶部红包按钮 private btn_award() { if (DeviceUtil.isNative && DeviceUtil.isAndroid) { this.gameState = GameState.PAUSED; ServerHandler.inst.getHbTxInfo(); DCHandler.inst.reportData(3000007); } else { oops.gui.open(UIID.RedPacketWithdraw); } } //二倍速按钮继续 private btn_double() { // oops.gui.open(UIID.DoubleSpeed); if (DeviceUtil.isNative && DeviceUtil.isAndroid) { this.gameState = GameState.PAUSED; ServerHandler.inst.getDoubleSpeedTime(); DCHandler.inst.reportData(3000010); } else { oops.gui.open(UIID.DoubleSpeed); } // console.log("二倍速看视频统计>>>>>>>>>>>>>>>", smc.game.GameModel.doubleSpeedAdCount); } //自动放置,不让点击方块 private btn_auto(event: EventTouch) { this.autoGame(); } private autoGame() { this.autoState = !this.autoState; this.initButtonState(this.autoState); if (this.autoState) { DCHandler.inst.reportData(3000008); this.executeAutoPlace(); this.autoFunction = this.executeAutoPlace; this.schedule(this.autoFunction, this.autoInterval); this.autoNode.active = true; } else { //关闭 if (this.autoFunction) { DCHandler.inst.reportData(3000009); this.operateFlag = true; this.unschedule(this.autoFunction); this.autoFunction = null; this.initButtonState(false); this.autoState = false; this.autoNode.active = false; } } } // 执行自动放置 executeAutoPlace() { //console.log("自动放置状态", this.autoState); // console.log("当前游戏状态", this.gameState); if (!this.autoState || this.gameState !== GameState.PLAYING) { return } const bestPlacement = this.findBestPlacement() if (!bestPlacement) { // 所有方块都无法放置,游戏结束 this.gameState = GameState.GAME_OVER; this.gameOver() return } // 执行放置 this.operateFlag = false; this.placeBrickAtPosition(bestPlacement) oops.log.logView } //寻找最佳位置 findBestPlacement() { const placements = [] // 对每个方块计算所有可能的放置位置和分数 for (let brickIndex = 0; brickIndex < this.bricksList.length; brickIndex++) { const brickData = this.bricksList[brickIndex] // 检查不同旋转状态 let gridConfigs = [brickData.gridConfig] let degrees = [brickData.deg] // 如果可旋转,计算所有旋转状态 if (brickData.rotateFlag) { for (let i = 1; i <= 3; i++) { const next = this.nextGridRotate( i === 1 ? brickData.gridConfig : gridConfigs[i - 1], i === 1 ? brickData.deg : degrees[i - 1] ) gridConfigs.push(next.gridConfig) degrees.push(next.deg) } } // 遍历所有网格位置 for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { // 对每个旋转状态检查 for (let rotateIndex = 0; rotateIndex < gridConfigs.length; rotateIndex++) { const currentGridConfig = gridConfigs[rotateIndex] const currentDeg = degrees[rotateIndex] // 检查是否可以放置 if (this.moveIf(rowIndex, columnIndex, currentGridConfig)) { // 复制网格并模拟放置 const gridList = this.copyGridList() currentGridConfig.forEach((gridConfigData) => { const r = gridConfigData.row + rowIndex const c = gridConfigData.column + columnIndex gridList[r][c].status = CellState.FILLED gridList[r][c].type = brickData.type; }) // 检查是否可以消除,计算分数 const elimination = this.gridEliminateCheck(gridList) let score = 0 if (elimination.gridEliminateList.length > 0) { // 计算消除得分 for (let i = 1; i <= elimination.eliminateRowNum; i++) { score += this.cols * i } for (let i = 1; i <= elimination.eliminateColumnNum; i++) { score += this.rows * i } } // 记录此放置选项 placements.push({ brickIndex, brickData, rowIndex, columnIndex, gridConfig: currentGridConfig, //这里有问题,拿到的和显示的不对 deg: currentDeg, score, canEliminate: elimination.gridEliminateList.length > 0 }) } } } } } // 按优先级排序:最高分 > 有分数 > 没有分数但可放置 placements.sort((a, b) => { // 首先按分数排序 if (a.score !== b.score) { return b.score - a.score } // 其次按是否可消除排序 if (a.canEliminate !== b.canEliminate) { return a.canEliminate ? -1 : 1 } // 最后按照方块优先级排序(底部的方块优先) return a.brickIndex - b.brickIndex }) return placements.length > 0 ? placements[0] : null } // ... existing code ... placeBrickAtPosition(placement: any) { const brickData = placement.brickData const index = this.bricksList.findIndex(data => data === brickData) if (index === -1) { console.error("无法找到要放置的方块:", brickData) return } this.editingData.brickData = this.bricksList.splice(index, 1)[0]; // 应用旋转 if (brickData.deg !== placement.deg) { brickData.gridConfig = placement.gridConfig brickData.deg = placement.deg tween(brickData.brickNode).to(0.1, { angle: placement.deg }).start() } // 构建对应网格列表--这里就有问题了,这算位置,有问题 this.editingData.gridList = []; placement.gridConfig.forEach((gridConfigData: GridConfigData) => { const r = gridConfigData.row + placement.rowIndex const c = gridConfigData.column + placement.columnIndex this.editingData.gridList.push(this.gridList[r][c]) }) // 计算移动位置(中点) let centerPos = new Vec3(0, 0, 0) let count = 0 for (const grid of this.editingData.gridList) { if (grid.gridNode) { const pos = grid.gridNode.getWorldPosition() centerPos.add(pos) count++ } } if (count > 0) { centerPos.x /= count centerPos.y /= count centerPos.z /= count } const originPos = brickData.brickNode.getWorldPosition() brickData.brickNode.setParent(this.moveNode) brickData.brickNode.setWorldPosition(originPos); //显示增加分数 // 动画放置方块 tween(brickData.brickNode) .to(this.autoMoveTime, { worldPosition: centerPos }) .call(() => { // ✅ 更新格子状态(像手动放置那样) this.showScoreAnimation(); this.editingData.gridList.forEach(gridData => { gridData.status = CellState.FILLED gridData.type = brickData.type; this.generateGrid(gridData) }) // ✅ 销毁方块节点 brickData.brickNode.destroy() // ✅ 补充新的砖块 this.addBrick(brickData.index) // ✅ 消除检查逻辑保持和手动一致 this.scheduleOnce(() => { this.gridEliminate().then(() => { this.prompt(false).then((promptFlag) => { if (!promptFlag) { this.gameOver() } }) }) }) }) .start() } doubleSpeedOpenSuccess() { //2倍速广告展示,页面展示,都会暂停游戏, if (this.gameState !== GameState.PLAYING) { return } if (this.callback) { this.unschedule(this.callback); } oops.gui.toast("二倍速时长已增加3分钟~") this.isDoubleSpeed = true; this.doubleNum = 1.5; //改变按钮时间 // let time = smc.game.GameModel.doubleSpeedTime; this.doubleSpeedTime = this.doubleSpeedTime + smc.game.GameModel.doubleSpeedTime; // this.doubleSpeedTime = 60; if (this.doubleSpeedTime <= 0) { return } if (!this.autoState) { this.autoGame(); //改按钮状态 this.autoState = true; this.initButtonState(this.autoState); } this.callback = function () { if (this.gameState === GameState.PLAYING && this.autoState) { this.doubleSpeedTime-- this.autoMoveTime = 0.25; this.autoInterval = 1.1; //修改剩余时间,秒格式化成时分10:59 const minutes = Math.floor(this.doubleSpeedTime / 60); const seconds = this.doubleSpeedTime % 60; const formattedTime = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; this.lab_doubleTime.string = formattedTime; //时间到了 if (this.doubleSpeedTime <= 0) { this.autoMoveTime = 0.4; this.autoInterval = 1.5; this.lab_doubleTime.string = "二倍速"; this.isDoubleSpeed = false; this.doubleNum = 1; //再打开继续二倍速页面 //自动按钮关闭 this.autoState = false; this.initButtonState(this.autoState); oops.gui.open(UIID.KeepSpeed); this.unschedule(this.callback); } } if (!this.autoState && this.doubleSpeedTime) { this.lab_doubleTime.string = "二倍速"; } } this.schedule(this.callback, 1); } //重新开始 private restartGame() { if (this.gameState === GameState.READY) return; this.initData(); this.setData(); this.reopenGrid(); this.initButtonState(this.autoState); this.updateWelfarePoint(); } //重新清除网格 private reopenGrid() { for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { this.gridList[rowIndex][columnIndex].status = CellState.EMPTY this.generateGrid(this.gridList[rowIndex][columnIndex]) } } // 初始化方块 if (this.brickNode) { if (this.brickNode) { this.brickNode.destroyAllChildren(); } } this.bricksList.length = 0 for (let i = 1; i <= this.brickNum; i++) { this.addBrick(i) } // 清除旋转数据 if (this.rotateNode) { this.rotateNode.destroyAllChildren(); } } //===============初始化检查是否要打开福利界面==== private updateWelfarePoint() { const showState = smc.game.GameModel.popupShow if (!showState) { //展示这些的时候,游戏要暂停 // if (this.popupType != "") { // this.gameState = GameState.PAUSED; // } switch (this.popupType) { case "weal_1": oops.gui.open(UIID.WarmReminder); ServerHandler.inst.getGuideInfo(); break; case "weal_2": //打开福利二 oops.gui.open(UIID.WelfareTwo); ServerHandler.inst.getGuideInfo(); break; case "weal_3": //打开福利三 oops.gui.open(UIID.WelfareThree); break; case "sign": //打开福利三 oops.gui.open(UIID.ReservePopup); break; case "handlingCharge": oops.gui.open(UIID.WechatTransfer); break; } smc.game.GameModel.popupShow = true; } } protected onDestroy(): void { oops.message.off(GameEvent.RestartGame, this.restartGame, this); oops.message.off(GameEvent.DoubleSpeedOpenSuccess, this.doubleSpeedOpenSuccess, this); oops.message.off(GameEvent.openView, this.openView, this); oops.message.off(GameEvent.showCoinAnimation, this.showCoinAnimation, this); oops.message.off(GameEvent.updateHbAndWxCoin, this.updateCoin, this); oops.message.off(GameEvent.StartAutoGame, this.startAutoGame, this); oops.message.off(GameEvent.updateGameState, this.updateGameState, this); oops.message.off(GameEvent.updateGameScore, this.updateGameScore, this); oops.message.off(GameEvent.pauseGame, this.pauseGame, this); oops.message.off(GameEvent.resumeGame, this.resumeGame, this); } btn_text() { // const param = { // "code": 0, // "data": { // "ssid": "9201d75d019f46f98b8663adfd515a58", // "props": { // "1004": 7077279, // "1005": 2143634, // "1006": 12, // "1007": 186, // "1008": 22, // "1009": 299980, // "2001": 2, // "8001": 530, // "9001": 2 // }, // "changes": { // "1004": 1, // "1007": 1 // }, // "tipThreshold": true // } // } // let str = JSON.stringify(param); // ServerHandler.inst.onGetVideorReward(str); // ServerHandler.inst.GetGuideInfo(); oops.gui.open(UIID.WechatTransfer); } getRandomUniqueInts(count: number = 3): number[] { let min: number = 0; let max: number = 0; const curLevel = smc.account.AccountModel.curLevel || 1; switch (curLevel) { case 1: return [1] break; case 2: case 3: min = 1; max = 3; break; case 4: case 5: case 6: min = 4; max = 9; break; case 7: case 8: min = 10; max = 18; break; case 9: case 10: case 11: case 12: min = 19; max = 27; break; default: min = 28; max = 42; break; } if (max - min + 1 < count) { throw new Error(`区间 [${min}, ${max}] 无法提供 ${count} 个不重复整数`); } const pool: number[] = []; for (let i = min; i <= max; i++) { pool.push(i); } this.shuffle(pool); return pool.slice(0, count); } // 洗牌算法(Fisher-Yates) shuffle(arr: number[]) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } } }