/* * @Author: mojunshou 1637302775@qq.com * @Date: 2025-03-20 15:01:09 * @LastEditors: mojunshou 1637302775@qq.com * @LastEditTime: 2025-04-02 17:55:03 * @Description: 消除游戏主场景 */ import { _decorator, Color, EventTouch, instantiate, JsonAsset, Label, Node, Prefab, randomRangeInt, Sprite, Toggle, tween, UITransform, Vec2, Vec3, Widget } from "cc"; import { RandomManager } from "db://oops-framework/core/common/random/RandomManager"; import { oops } from "db://oops-framework/core/Oops"; 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 { Button } from "cc"; const { ccclass, property } = _decorator; // 游戏状态枚举 enum GameState { READY, // 准备中 PLAYING, // 游戏中 PAUSED, // 暂停 GAME_OVER // 游戏结束 } // 游戏模式枚举 enum GameMode { MANUAL, // 手动模式 AUTO // 自动模式 } // 格子状态 enum CellState { EMPTY, // 空格子 FILLED, // 有方块 HIGHLIGHTED // 高亮(可放置) } // 新手引导步骤 enum TutorialStep { NONE, // 无引导 ROW_ELIMINATION, // 行消除引导 COLUMN_ELIMINATION, // 列消除引导 ROTATION, // 旋转引导 FINAL_ELIMINATION // 最终消除引导 } //网格数据接口 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, rotateTag: boolean, rotateFlag: boolean, gridConfig: Grids[], deg: number, brickNode: Node | null, brickInitPos: Vec3, // 方块初始位置 type: number, // 方块类型--gridColorKey } interface GridConfigData { row: number, column: number, } /** 视图层对象 */ @ccclass('EliminateViewComp') @ecs.register('EliminateView', false) export class EliminateViewComp extends CCComp { @property({ type: LabelChange, displayName: "自动提现金额" }) private amountLb: LabelChange = null!; @property({ type: LabelChange, displayName: "额外奖励" }) private awardLb: 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: Label, displayName: "第几块金砖" }) private lab_brick: Label = null!; @property({ type: Label, displayName: "第几关" }) private lab_levle: 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: Button, displayName: "自动按钮" }) private autoBtn: Button = null!; @property({ type: Button, displayName: "二倍速按钮" }) private addSpeedBtn: Button = null!; //游戏配置行列 private rows: number = 8; // 行数 private cols: number = 8; // 列数 private itemSize: number = 76.25; // 格子大小 private brickNum: number = 3; // 砖块数量 private gameState: GameState = GameState.READY; private score: number = 0; //本局分数 private money: number = 0; //左边金钱 private cash: number = 0; //右边红包钱数 private totalNum: number = 0; //累计消除次数,断了就是又从0开始 private _isDoubleSpeed: boolean = false; //是否开启二倍速 //网格列表 // gridsNode: Node | null = null; //中间网格区域 // brickNode: Node | null = null; //底部三个item块区域 // moveNode: Node | null = null; //移动层Node // rotateNode: Node | null = null; //旋转Node吧 //没使用颜色 notUseColor = new Color(255, 255, 255, 255) //可用的颜色 usableColor = new Color(0, 255, 0, 100) //不可用的颜色 unavailableColor = new Color(255, 0, 0, 100) //旋转容错 rotateFaultTolerant = 10; private _isAutoFunc: (() => void) | null = null; isAutoMode: boolean = false; autoModeInterval: number = 1 // 自动模式的间隔时间(秒) autoModeTimer: number = 0 // 自动模式计时器 //网格列表管理列表 gridList: GridData[][] = []; //砖块列表 bricksList: BrickData[] = []; //网格颜色列表 gridColorList: GridData[] = []; brickConfig: { bricks?: any } = {} //方块配置 editingFlag = false //编辑状态 editingData: EditingData = { brickData: null, gridList: [], } //旋转标记 rotateFlag = false rotateBrickData: BrickData | null = null // 添加新的属性来跟踪是否需要重置消除计数 private shouldResetEliminateCount: boolean = true; private consecutiveEliminations: boolean = false; // 添加配置属性 - 消除基础分 @property({ displayName: "消除一行/列基础分" }) private eliminateBaseScore: number = 10; //当前局数 private curGameNum: number = 0; //第一通关开始新人模式 //第二局开始马上播放新的弹窗 //往后根据每局需要弹窗就弹窗 //通关一局算一块金砖 //通关比例算金砖比例 private autoState: boolean = false; //自动状态 /** 视图层逻辑代码分离演示 */ async start() { // const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象 this.setButton(); this.initButtonState(false); await this.loadConfig(); this.initGrid(); this.initData(); this.addEventList(); } addEventList() { oops.message.on(GameEvent.RestartGame, this.restartGame, this); oops.message.on(GameEvent.DoubleSpeedOpenSuccess, this.doubleSpeedOpenSuccess, this); } //初始化网格 private initGrid() { // 清理现有网格 this.clearExistingGrids(); // 生成网格矩阵 this.createGridMatrix(); } //初始化按钮状态 private initButtonState(state: boolean) { //自动按钮默认关闭 if (this.autoBtn) { //关闭 const on = this.autoBtn.node.getChildByName("on"); on ? on.active = state : null; const off = this.autoBtn.node.getChildByName("off"); off ? off.active = !state : null; // this.autoState = state; } } //初始化数据 private initData() { // this.gameMode = GameMode.MANUAL; this.score = 0; this.money = 0; this.cash = 0; this.totalNum = 0; this.autoState = false; this.shouldResetEliminateCount = true; this.consecutiveEliminations = false; this.lab_score.string = this.score.toString(); this.amountLb.string = this.money.toString(); this.awardLb.string = this.cash.toString(); } //初始化关卡信息 private initLevelInfo() { //关卡通关服务器或者本地读出来 // const level = } //初始化网格 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.children.forEach(node => node.destroy()); } } /** * @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) { if (this.brickNode) { this.brickNode.children.forEach(node => { node.destroy() }); } } this.bricksList.length = 0 for (let i = 1; i <= this.brickNum; i++) { this.addBrick(i) } // 清除旋转数据 if (this.rotateNode) { this.rotateNode.children.forEach(node => { node.destroy() }) } } /** * @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) { //随机名字 const brickKey = Object.keys(this.brickConfig['bricks'])[randomRangeInt(0, Object.keys(this.brickConfig['bricks']).length)] const brickConfig = this.brickConfig['bricks'][brickKey]; const randomIndex = oops.random.getRandomInt(1, 5); // 生成方块 const brickData: BrickData = { index, brickKey: brickKey, rotateFlag: brickConfig['rotateFlag'], gridConfig: brickConfig['gridConfig'], deg: 0, brickNode: null, brickInitPos: new Vec3(), rotateTag: false, type: randomIndex, } 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() } this.brickAddEvent(brickData); //2508801946299286 } // 每个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 } touchStartLocation = new Vec2() touchStartFlag = false brickAddEvent(brickData: BrickData) { const brickNode = brickData.brickNode if (!brickNode) { console.error("brickNode为空,无法添加事件"); return } // 确保节点可交互 brickNode.active = true; // 移除可能存在的旧事件监听器 brickNode.off(Node.EventType.TOUCH_START); brickNode.off(Node.EventType.TOUCH_MOVE); brickNode.off(Node.EventType.TOUCH_END); brickNode.off(Node.EventType.TOUCH_CANCEL); // 记录初始位置和状态 let startPos = new Vec3(); let originalParent: Node | null = null; let isDragging = false; // 触摸开始事件 brickNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => { // 如果正在编辑其他方块,则忽略 if (this.editingFlag) return; // 设置编辑状态 this.editingFlag = true; isDragging = true; this.touchStartFlag = true; // 清空编辑中的数据 this.editingData.brickData = null; this.editingData.gridList.length = 0; // 记录触摸开始位置和方块原始信息 this.touchStartLocation.set(event.getUILocation()); originalParent = brickNode.parent; startPos = brickNode.getWorldPosition().clone(); // 将方块移到移动层并放大 brickNode.setParent(this.moveNode); brickNode.setWorldPosition(startPos); tween(brickNode) .to(0.2, { scale: new Vec3(1, 1, 1) }) .start(); // 从方块列表中移除该方块 const index = this.bricksList.findIndex(item => item === brickData); if (index > -1) { this.editingData.brickData = this.bricksList.splice(index, 1)[0]; } else { console.error("未找到方块数据:", brickData); this.editingData.brickData = brickData; } // 清除旋转数据 if (this.rotateFlag && this.rotateBrickData !== this.editingData.brickData) { this.rotateFlag = false; this.rotateBrickData = null; if (this.rotateNode) { this.rotateNode.children.forEach(node => { node.destroy() }); } } }, this); // 触摸移动事件 brickNode.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { if (!isDragging || !this.editingData.brickData) return; // 清除旋转数据 if (event.getUILocation().subtract(this.touchStartLocation).length() >= this.rotateFaultTolerant) { this.rotateFlag = false; this.rotateBrickData = null; if (this.rotateNode) { this.rotateNode.children.forEach(node => { node.destroy() }); } } // 恢复所有网格颜色 this.gridColorRecovery(); // 移动方块 brickNode.setWorldPosition(brickNode.getWorldPosition().add(event.getUIDelta().toVec3())); // 重置编辑中的网格数据 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 (!isDragging) return; isDragging = false; this.touchStartFlag = false; // 恢复网格颜色 this.gridColorRecovery(); // 防止如放回方块回弹动画时,已经触摸在另一个方块上面 if (this.editingData.brickData !== brickData) return; // 检查是否可以放置 if (this.editingData.brickData && this.editingData.gridList.length > 0) { // 计算放置的格子数量 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; } } // 放置方块到网格 this.editingData.gridList.forEach(grid => { grid.status = CellState.FILLED; grid.type = this.editingData.brickData!.type; this.generateGrid(grid); }); // 显示放置得分动画(显示一次,包含总格子数) this.showScoreAnimation(centerPos, placedGridCount); // 标记需要重置消除计数器 this.shouldResetEliminateCount = true; this.consecutiveEliminations = true; // 销毁方块节点 brickNode.destroy(); // 添加新方块到待选区 this.addBrick(this.editingData.brickData.index); // 检查消除 this.scheduleOnce(() => { this.gridEliminate().then((hasElimination) => { // 如果没有消除,确保下次消除会重置计数 if (!hasElimination) { this.shouldResetEliminateCount = true; } // 检查游戏是否结束 this.prompt(false).then(canContinue => { if (!canContinue) { this.gameOver(); } this.editingFlag = false; }); }); }, 0.1); } else if (this.rotateFlag) { // 旋转方块 const brickData = this.editingData.brickData; this.brickGridRotate(brickData); // 方块放回待选区 this.bricksList.push(brickData); if (this.brickNode && brickData.brickNode) { this.brickNode.addChild(brickData.brickNode); tween(brickData.brickNode) .to(0.2, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.6, 0.6, 0.6) }) .start(); } this.editingFlag = false; } else { // 无法放置,将方块返回原位置 const brickData = this.editingData.brickData; if (brickData) { this.bricksList.push(brickData); // 添加回弹动画 tween(brickNode) .to(0.2, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.6, 0.6, 0.6) }) .call(() => { if (originalParent) { brickNode.setParent(originalParent); brickNode.setWorldPosition(brickData.brickInitPos); } this.editingFlag = false; }) .start(); } else { this.editingFlag = false; } } // 检查是否需要处理旋转(短距离移动视为点击) if (!this.rotateFlag && this.editingData.brickData && this.editingData.brickData.rotateFlag && event.getUILocation().subtract(this.touchStartLocation).length() < this.rotateFaultTolerant) { this.rotateFlag = true; this.rotateBrickData = this.editingData.brickData; // 显示旋转提示 if (this.rotatePrefab && this.rotateNode && brickNode) { const rotateIndicator = instantiate(this.rotatePrefab); this.rotateNode.addChild(rotateIndicator); rotateIndicator.setWorldPosition(brickNode.getWorldPosition()); } } }; 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) { const next = this.nextGridRotate(brickData.gridConfig, brickData.deg) brickData.deg = next.deg brickData.gridConfig = next.gridConfig; if (brickData.brickNode) { tween(brickData.brickNode).to(0.1, { angle: next.deg }).start(); } // this.audioManager.playRotate() } //下一个旋转 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(position: Vec3, score: number) { if (!this.lab_addScore) return; // 复制得分Label const scoreLabel = instantiate(this.lab_addScore.node); scoreLabel.active = true; // 设置文本和位置 const label = scoreLabel.getComponent(Label); if (label) { label.string = `+${score}`; } // 添加到场景中 this.node.addChild(scoreLabel); scoreLabel.setWorldPosition(position); // 创建动画效果 tween(scoreLabel) .to(0.5, { position: new Vec3(scoreLabel.position.x, scoreLabel.position.y + 50, 0), scale: new Vec3(1.2, 1.2, 1.2) }) .to(0.3, { opacity: 0 }) .call(() => { scoreLabel.destroy(); }) .start(); // 更新总分 this.score += score; this.lab_score.string = this.score.toString(); //计算总分够一局没有,够就马上停止,展示界面 } // 显示消除次数动画 showEliminateCountAnimation(yPosition: number) { if (!this.lab_total) return; // 如果需要重置计数器,先将计数归零 if (this.shouldResetEliminateCount) { this.totalNum = 0; this.shouldResetEliminateCount = false; } // 增加累计消除次数 this.totalNum++; // 复制总次数Label const totalLabel = instantiate(this.lab_total.node); totalLabel.active = true; // 设置文本 const label = totalLabel.getComponent(Label); if (label) { label.string = `Combo${this.totalNum}`; } // 添加到场景中 this.node.addChild(totalLabel); // 计算网格区域的X轴中心位置,Y轴使用传入的消除行位置 const centerPos = new Vec3(); if (this.gridNode) { // 获取网格区域的X轴中心 const worldPos = this.gridNode.getWorldPosition(); centerPos.set(worldPos.x, yPosition, worldPos.z); } totalLabel.setWorldPosition(centerPos); // 创建动画效果 tween(totalLabel) .to(0.3, { scale: new Vec3(1.5, 1.5, 1.5), opacity: 255 }) .delay(0.5) // 停留更长时间 .to(0.4, { scale: new Vec3(1.2, 1.2, 1.2), position: new Vec3(totalLabel.position.x, totalLabel.position.y + 50, 0), opacity: 0 }) .call(() => { totalLabel.destroy(); }) .start(); } // 修改 gridEliminate 方法来显示消除次数 gridEliminate() { return new Promise((resolve, reject) => { const d = this.gridEliminateCheck(this.gridList) const gridEliminateList = d.gridEliminateList const eliminateRowNum = d.eliminateRowNum const eliminateColumnNum = d.eliminateColumnNum if (gridEliminateList.length < 1) { // 没有发生消除 this.shouldResetEliminateCount = true; // 下一次消除需要重置计数 this.consecutiveEliminations = false; // 不是连续消除 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; // 标记为连续消除 this.consecutiveEliminations = true; // 显示消除次数动画,传入Y轴位置 this.showEliminateCountAnimation(avgY); } gridEliminateList.forEach((gridData) => { if (gridData.gridNode?.children[0]) { const startPos = gridData.gridNode.getWorldPosition(); tween(gridData.gridNode.children[0]) .to(0.2, { scale: new Vec3(0.5, 0.5) }) .call(() => { gridData.status = CellState.EMPTY this.generateGrid(gridData) }) .start() if (this.coinPrefab && this.amountLb?.node) { this.createCoinFlyAnimation( this.coinPrefab, startPos, this.amountLb.node.getWorldPosition(), 5 ); } //增加红包 this.createCoinFlyAnimation( this.redPacketPrefab, startPos, this.awardLb.node.getWorldPosition(), 5 ); } }) this.scheduleOnce(() => { let score = 0 for (let i = 1; i <= eliminateRowNum; i++) { score += this.cols * i } for (let i = 1; i <= eliminateColumnNum; i++) { score += this.rows * i } this.score += score this.lab_score.string = this.score.toString(); const lastGrid = gridEliminateList[gridEliminateList.length - 1]; if (lastGrid?.gridNode && this.coinPrefab && this.amountLb?.node) { const lastPos = lastGrid.gridNode.getWorldPosition(); // 添加回调函数,在金币动画完成后显示微信分数增加 this.createCoinFlyAnimation( this.coinPrefab, lastPos, this.amountLb.node.getWorldPosition(), score, () => { // 显示微信分数增加动画 this.showWechatScoreAnimation(); } ); // 添加回调函数,在红包动画完成后显示红包分数增加 this.createCoinFlyAnimation( this.redPacketPrefab, lastPos, this.awardLb.node.getWorldPosition(), 5, () => { // 显示红包分数增加动画 this.showRedPacketScoreAnimation(); } ); } // 告诉调用者有消除发生 resolve(true) // 检查是否需要继续消除 this.scheduleOnce(() => { // 递归调用,检查并处理连锁消除 this.gridEliminate().then(() => { // 连锁消除结束,不做额外处理 }); }, 0.2); }, 0.2) }) } 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, } } // 修改 createCoinFlyAnimation 方法,添加回调 private createCoinFlyAnimation(prefab: Prefab, startPos: Vec3, endPos: Vec3, count: number, callback?: Function) { if (!prefab) return; let completedCount = 0; const totalCoins = Math.min(count, 10); // 限制最大数量 for (let i = 0; i < totalCoins; i++) { const coin = instantiate(prefab); this.node.addChild(coin); coin.setWorldPosition(startPos); // 随机偏移起始位置 const randomOffset = new Vec3( (Math.random() - 0.5) * 50, (Math.random() - 0.5) * 50, 0 ); // 创建曲线动画 tween(coin) .to(0.2, { position: new Vec3( coin.position.x + randomOffset.x, coin.position.y + randomOffset.y, 0 ) }) .to(0.5, { worldPosition: endPos }) .call(() => { coin.destroy(); completedCount++; // 所有金币动画完成后执行回调 if (completedCount === totalCoins && callback) { callback(); } }) .start(); } } // 显示微信分数增加动画 private showWechatScoreAnimation() { if (!this.tweenWechatNode) return; // 生成随机小数(小于1,保留2位小数) const randomValue = Math.random() * 0.99; const formattedValue = randomValue.toFixed(2); // 获取并设置分数标签 const scoreLabel = this.tweenWechatNode.getChildByName("lab_num")?.getComponent(Label); if (scoreLabel) { scoreLabel.string = "+" + formattedValue; } // 保存原始位置 const originalPosition = this.tweenWechatNode.position.clone(); // 显示节点 this.tweenWechatNode.active = true; // 创建向上移动的动画 tween(this.tweenWechatNode) .to(0.8, { position: new Vec3( originalPosition.x, originalPosition.y + 80, originalPosition.z ), opacity: 255 }) .to(0.2, { opacity: 0 }) .call(() => { // 重置位置 this.tweenWechatNode.setPosition(originalPosition); this.tweenWechatNode.active = false; // 更新总金额 if (this.amountLb) { const currentAmount = parseFloat(this.amountLb.string); const newAmount = currentAmount + parseFloat(formattedValue); this.money = newAmount; this.amountLb.string = newAmount.toFixed(2); } }) .start(); } // 显示红包分数增加动画 private showRedPacketScoreAnimation() { if (!this.tweenRedNode) return; // 生成随机小数(小于1,保留2位小数) const randomValue = Math.random() * 0.99; const formattedValue = randomValue.toFixed(2); // 获取并设置分数标签 const scoreLabel = this.tweenRedNode.getChildByName("lab_num")?.getComponent(Label); if (scoreLabel) { scoreLabel.string = "+" + formattedValue; } // 保存原始位置 const originalPosition = this.tweenRedNode.position.clone(); // 显示节点 this.tweenRedNode.active = true; // 创建向上移动的动画 tween(this.tweenRedNode) .to(0.8, { position: new Vec3( originalPosition.x, originalPosition.y + 80, originalPosition.z ), opacity: 255 }) .to(0.2, { opacity: 0 }) .call(() => { // 重置位置 this.tweenRedNode.setPosition(originalPosition); this.tweenRedNode.active = false; // 更新总红包金额 if (this.awardLb) { const currentAmount = parseFloat(this.awardLb.string); const newAmount = currentAmount + parseFloat(formattedValue); this.cash = newAmount; this.awardLb.string = newAmount.toFixed(2); } }) .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 this.setGameState(GameState.GAME_OVER) console.log("游戏结束") oops.gui.open(UIID.GameOver); } /** * 清理网格的子节点 */ private clearGridChildren(gridNode: Node): void { const children = gridNode.children.slice(); // 创建副本避免遍历时修改问题 children.forEach(node => node.destroy()); } private setGameState(state: GameState) { this.gameState = state; switch (state) { case GameState.READY: break; case GameState.PLAYING: break; case GameState.PAUSED: break; case GameState.GAME_OVER: //打开游戏结束界面 break; } } //========================打开其他界面和按钮逻辑======================= /** 视图对象通过 ecs.Entity.remove(eliminateViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { this.node.destroy(); } //设置按钮 private btn_setting() { oops.gui.open(UIID.Setting); } //左边微信按钮 private btn_withdraw() { oops.gui.open(UIID.WechatWithdraw); } //顶部红包按钮 private btn_award() { oops.gui.open(UIID.RedPacketWithdraw); } //二倍速按钮 private btn_double() { oops.gui.open(UIID.DoubleSpeed); } //自动按钮 private btn_auto() { //自动放置 this.autoState = !this.autoState; this.initButtonState(this.autoState); if (this.autoState) { this.gameState = GameState.PLAYING; this.executeAutoPlace() } } executeAutoPlace() { if (!this.autoState || this.gameState !== GameState.PLAYING || this.editingFlag) { return } console.log("开始自动放置") const bestPlacement = this.findBestPlacement() if (!bestPlacement) { // 所有方块都无法放置,游戏结束 this.autoState = false this.gameOver() return } // 执行放置 this.placeBrickAtPosition(bestPlacement) } 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].gridColorKey = brickData.gridColorKey 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) { // 获取方块数据 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 validGridCount = 0; // 计算所有网格的平均位置 this.editingData.gridList.forEach(grid => { if (grid.gridNode) { const worldPos = grid.gridNode.getWorldPosition(); centerPos.add(worldPos); validGridCount++; } }); // 计算平均位置 if (validGridCount > 0) { centerPos.x /= validGridCount; centerPos.y /= validGridCount; centerPos.z /= validGridCount; } else { // 如果没有有效网格,使用第一个网格的位置作为后备 const targetGridData = this.editingData.gridList[0]; if (targetGridData && targetGridData.gridNode) { centerPos = targetGridData.gridNode.getWorldPosition(); } } // 添加到移动节点里进行移动 const originPos = brickData.brickNode.getWorldPosition() brickData.brickNode.setParent(this.moveNode) brickData.brickNode.setWorldPosition(originPos) // 使用动画移动到目标位置--这里的最终放置有问题 tween(brickData.brickNode) .to(2, { worldPosition: centerPos }) .call(() => { // 动画完成后修改格子状态 this.editingData.gridList.forEach((gridData) => { gridData.status = CellState.FILLED gridData.type = this.editingData.brickData.type this.generateGrid(gridData) }) // 销毁方块 this.editingData.brickData.brickNode.destroy() // 添加新方块 this.addBrick(this.editingData.brickData.index) // 检查消除和游戏状态 this.gridEliminate().then(() => { this.prompt(false).then((value: unknown) => { const promptFlag = value as boolean; if (!promptFlag) { this.autoState = false this.gameOver() } else if (this.autoState) { // 继续自动放置 this.scheduleOnce(() => { this.executeAutoPlace() }, 0.3) } this.editingFlag = false }) }) }) .start() } doubleSpeedOpenSuccess() { this._isDoubleSpeed = true; //3分钟后关闭 this.scheduleOnce(() => { this._isDoubleSpeed = false; //关闭会自动触发二倍速弹窗,这要带参数 // oops.gui.open(UIID.DoubleSpeed, { isAutoOpen: true }); }, 180); } //重新开始 private restartGame() { if (this.gameState === GameState.READY) return; this.initData(); this.reopenGrid(); this.initButtonState(this.autoState); } //重新清除网格 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.children.forEach(node => { node.destroy() }); } } this.bricksList.length = 0 for (let i = 1; i <= this.brickNum; i++) { this.addBrick(i) } // 清除旋转数据 if (this.rotateNode) { this.rotateNode.children.forEach(node => { node.destroy() }) } } }