/* * @Author: mojunshou 1637302775@qq.com * @Date: 2025-03-20 15:01:09 * @LastEditors: mojunshou 1637302775@qq.com * @LastEditTime: 2025-03-27 17:49:05 * @Description: 消除游戏主场景 */ import { _decorator } from "cc"; import { oops } from "db://oops-framework/core/Oops"; import { ecs } from "db://oops-framework/libs/ecs/ECS"; import { CCComp } from "db://oops-framework/module/common/CCComp"; import { UIID } from "../../common/config/GameUIConfig"; import { Node } from "cc"; import { Toggle } from "cc"; import { ToggleContainer } from "cc"; import { Prefab } from "cc"; import { LabelChange } from "db://oops-framework/libs/gui/label/LabelChange"; import { Label } from "cc"; import { Vec3 } from "cc"; import { BlockItemView } from "./BlockItemView"; import { UITransform } from "cc"; import { instantiate } from "cc"; import { Sprite } from "cc"; import { Color } from "cc"; import { RandomManager } from "db://oops-framework/core/common/random/RandomManager"; import { Widget } from "cc"; import { JsonAsset } from "cc"; import { randomRangeInt } from "cc"; import { EventTouch } from "cc"; import { Vec2 } from "cc"; import { tween } 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, // 列 gridColorKey: string | null, 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, gridColorKey: string | 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: 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: 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!; private lab_speed: Label = null!; private winthdrawNode: Node = null!; private awardNode: Node = null!; private autoOnToggle: Toggle = null!; //游戏配置行列 private rows: number = 8; // 行数 private cols: number = 8; // 列数 private itemSize: number = 82; // 格子大小 private brickNum: number = 3; // 砖块数量 //按钮状态 private autoClick: boolean = false; private gameState: GameState = GameState.READY; private gameMode: GameMode = GameMode.MANUAL; private rotateTag: boolean = false; // 旋转标志 private score: number = 0; //本局分数 private money: number = 0; //左边金钱 private cash: number = 0; //右边红包钱数 //网格列表 // 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 grid: CellState[][] = []; //数组管理网格状态 private gridBlocks: (BlockItemView | null)[][] = []; // 存储网格中的方块脚本引用 private gridCells: Node[][] = []; // 方块数据保存数组,生成后就保存起来 private brickList: BrickData[] = []; /** 视图层逻辑代码分离演示 */ async start() { // const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象 this.setButton(); this.initComponents(); this.initButtonState(); await this.loadConfig(); this.initGrid(); this.initData(); } private initComponents() { // this.winthdrawNode = this.node.getChildByPath("Scene/Top/btn_withdraw")!; // this.awardNode = this.node.getChildByPath("Scene/Top/btn_award")!; const toggle = this.node.getChildByPath("Scene/Bottom/btn_auto/auto_Toggle/Off")!; this.autoOnToggle = toggle.getComponent(Toggle)!; this.lab_speed = this.node.getChildByPath("Scene/Bottom/btn_double/lab_time")!.uiLabel; this.lab_speed.string = "9999999" } //初始化网格 private initGrid() { // 清理现有网格 this.clearExistingGrids(); // 生成网格矩阵 this.createGridMatrix(); } //初始化按钮状态 private initButtonState() { //自动按钮默认关闭 if (this.autoOnToggle) { console.log("自动按钮状态:", this.autoOnToggle.isChecked); this.autoOnToggle.isChecked = true; } } //初始化数据 private initData() { this.gameMode = GameMode.MANUAL; this.score = 0; this.money = 0; this.cash = 0; this.lab_score.string = this.score.toString(); this.amountLb.string = this.money.toString(); this.awardLb.string = this.cash.toString(); } //初始化网格 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, gridColorKey: null, 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.gridColorKey) { console.log("gridData.type>>>>>", 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)] // // console.log("brickKey>>>>>", brickKey) // //随机颜色-一个颜色出来 // const gridColorKey = Object.keys(this.brickConfig['GridType'])[randomRangeInt(0, Object.keys(this.brickConfig['GridType']).length)] // // console.log("gridColorKey>>>>>", gridColorKey) // //随机配置-通过名字拿到那个配置 const brickConfig = this.brickConfig['bricks'][brickKey]; // console.log("brickConfig>>>>>", brickConfig) const randomIndex = RandomManager.instance.getRandomInt(1, 5, 2); console.log("随机颜色>>>>>", randomIndex) // 生成方块 const brickData: BrickData = { index, brickKey: brickKey, rotateFlag: brickConfig['rotateFlag'], gridConfig: brickConfig['gridConfig'], deg: 0, brickNode: null, gridColorKey: randomIndex.toString(), 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) { let width = 0; if (this.brickNode) { width = this.brickNode.getComponent(UITransform)!.width; } 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); } } // 每个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.on(Node.EventType.TOUCH_END, () => { console.log("结束") }) // 设置位置 node.setPosition(Vec3.ZERO) }) return brickNode } touchStartLocation = new Vec2() touchStartFlag = false brickAddEvent(brickData: BrickData) { const brickNode = brickData.brickNode if (!brickNode) { console.log("打印进这里来了") return } brickNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => { // if (this.adShowingFlag) return // 未操作完时不能操作下一个方块 if (this.editingFlag) return this.touchStartFlag = true this.editingFlag = true this.editingData.brickData = null this.editingData.gridList.length = 0 // 隐藏游戏提示 // this.gameTipNode.active = false // 记录开始触摸的位置,用于结束触摸时,判断位置是否是单击 this.touchStartLocation.set(event.getUILocation()) // 添加到移动节点里进行移动 const pos = brickNode.getWorldPosition() brickNode.setParent(this.moveNode) brickNode.setWorldPosition(pos) // 添加放大动画 tween(brickNode) .to(0.2, { scale: new Vec3(1, 1, 1) }) .start() const index = this.bricksList.findIndex(data => data === brickData) if (index > -1) { this.editingData.brickData = this.bricksList.splice(index, 1)[0] } else { console.error("bricksList not find 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() }) } } }) // 触摸移动时 brickNode.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => { // 防止如放回方块回弹动画时,已经触摸在另一个方块上面,从而导致异常错误 if (this.editingData.brickData !== 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[] = [] brickNode.children.forEach((brickGridNode) => { const brickGridPos = brickGridNode.getWorldPosition() let gridData: GridData | null = null for (let rowIndex = 0; rowIndex < this.rows && gridData === null; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols && gridData === null; columnIndex++) { const nowGridData = this.gridList[rowIndex][columnIndex]; if (nowGridData && nowGridData.gridNode) { const gridPos = nowGridData.gridNode.getWorldPosition() if (Vec3.distance(gridPos, brickGridPos) <= (this.itemSize / 2)) { gridData = nowGridData } } } } if (gridData === null) { return; } tempGridList.push(gridData) }) // 检查整体情况 let checkFlag = false if ( tempGridList.length === brickData.gridConfig.length && tempGridList.filter(d => d.status === CellState.EMPTY).length === brickData.gridConfig.length ) { checkFlag = true tempGridList.forEach((gridData) => { this.editingData.gridList.push(gridData) }) } // 格子给用户提示 tempGridList.forEach((gridData) => { if (gridData.status !== CellState.EMPTY) { return; } if (gridData.gridNode) { const children = gridData.gridNode.children[0]; if (children) { children.getComponent(Sprite)!.color = checkFlag ? this.usableColor : this.unavailableColor } } // 用于恢复格子 this.gridColorList.push(gridData) }) }) // 触摸结束 brickNode.on(Node.EventType.TOUCH_END, (event: EventTouch) => { console.log("移动结束数据查看", this.editingData.gridList.length); console.log("brickData数据查看", this.editingData.brickData); // 当连击很快时,会出现1次start,2次end情况,为了避免所以通过标志位表示是一个连贯操作 if (!this.touchStartFlag) return this.touchStartFlag = false // console.log("TOUCH_END", this.editingFlag, this.editingData.brickData.index) // 防止如放回方块回弹动画时,已经触摸在另一个方块上面,从而导致异常错误 if (this.editingData.brickData !== brickData) return // 第二次单击,旋转 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) // brickData.brickNode.setWorldPosition(brickData.brickInitPos) tween(brickData.brickNode) .to(0.2, { worldPosition: brickData.brickInitPos, scale: new Vec3(0.8, 0.8, 0.8) }) .start() } this.editingFlag = false } // 方块到格子 else if (this.editingData.brickData && this.editingData.gridList.length > 0) { // 修改格子 this.editingData.gridList.forEach((gridData) => { gridData.status = CellState.FILLED if (this.editingData.brickData) { gridData.gridColorKey = this.editingData.brickData.gridColorKey; gridData.type = this.editingData.brickData.type; } this.generateGrid(gridData) }) // 销毁方块--这增加动画 if (this.editingData.brickData && this.editingData.brickData.brickNode) { this.editingData.brickData.brickNode.destroy(); } // 新增方块 this.addBrick(this.editingData.brickData.index); this.scheduleOnce(() => { // 格子消除 this.gridEliminate().then(() => { // 检查方块是否还能消除格子 this.prompt(false).then((promptFlag) => { if (!promptFlag) { this.gameOver() } this.editingFlag = false }) }) }) } // 方块回到待选区 else { console.log("返回带待选区域") const brickData = this.editingData.brickData this.bricksList.push(brickData) // this.audioManager.playMoveFail() // 回弹动画 if (brickData.brickNode) { tween(brickData.brickNode) .to(0.15, { worldPosition: this.editingData.brickData.brickInitPos, scale: new Vec3(0.8, 0.8, 0.8) }) .call(() => { if (this.brickNode && brickData.brickNode) { this.brickNode.addChild(brickData.brickNode); brickData.brickNode.setWorldPosition(brickData.brickInitPos); } this.editingFlag = false; }) .start(); } } // 旋转标志位 if ( !this.rotateFlag && this.editingData.brickData.rotateFlag && event.getUILocation().subtract(this.touchStartLocation).length() < this.rotateFaultTolerant ) { console.log("方块旋转啦") this.rotateFlag = true this.rotateBrickData = this.editingData.brickData; if (this.rotateBrickData.brickNode) { const prosition = this.rotateBrickData.brickNode.getWorldPosition() const rotateNode = instantiate(this.rotatePrefab) if (this.rotateNode && rotateNode) { const instantiatedRotateNode = instantiate(rotateNode); this.rotateNode.addChild(instantiatedRotateNode); instantiatedRotateNode.setWorldPosition(prosition) } } } // 格子颜色恢复 this.gridColorRecovery(); }) } //格子颜色恢复 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 } } // 消除,这里增加飞的动画 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) { resolve(false) return } 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 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 ); } resolve(true) }, 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 方法为公共方法,扩展参数列表 public createCoinFlyAnimation(prefab: Prefab, startPos: Vec3, endPos: Vec3, score: number, parent: Node = this.node) { const coinNum = Math.min(Math.max(Math.floor(score / 10), 1), 10); for (let i = 0; i < coinNum; i++) { if (!prefab) { console.warn('金币预制体未加载'); return; } const coin = instantiate(prefab); parent.addChild(coin); const randomOffset = new Vec3( (Math.random() - 0.5) * 50, (Math.random() - 0.5) * 50, 0 ); const startPosition = startPos.clone().add(randomOffset); coin.setWorldPosition(startPosition); const controlPoint = new Vec3( (startPosition.x + endPos.x) / 2 + (Math.random() - 0.5) * 100, (startPosition.y + endPos.y) / 2 + Math.random() * 100, 0 ); this.scheduleOnce(() => { tween(coin) .parallel( tween().to(0.2, { scale: new Vec3(1.2, 1.2, 1.2) }) .to(0.1, { scale: new Vec3(1, 1, 1) }), tween().by(0.3, { angle: 360 }) ) .start(); const bezierCurve = (target: Node | undefined, ratio?: number) => { if (!target || ratio === undefined) return; const t = ratio; const pos = new Vec3(); Vec3.multiplyScalar(pos, startPosition, (1 - t) * (1 - t)); Vec3.scaleAndAdd(pos, pos, controlPoint, 2 * (1 - t) * t); Vec3.scaleAndAdd(pos, pos, endPos, t * t); target.setWorldPosition(pos); }; tween(coin) .to(0.8, { worldPosition: endPos }, { easing: 'cubicIn', onUpdate: bezierCurve }) .call(() => { tween(coin) .to(0.1, { scale: new Vec3(0.7, 0.7, 0.7) }) .call(() => { coin.destroy(); }) .start(); }) .start(); }, i * 0.1); } } // 提示 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, gridColorKey: gridData.gridColorKey, type: gridData.type, }) } } return gridList } gameOver() { this.gameState = GameState.GAME_OVER this.setGameState(GameState.GAME_OVER) } /** * 清理网格的子节点 */ 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; } } // ... 修改 autoPlaceBrick 方法 async autoPlaceBrick() { if (!this.isAutoMode) return; if (this.bricksList.length === 0) return // 遍历所有方块,找到一个可以放置的方块 let bestMove = null; let selectedBrickIndex = -1; for (let i = 0; i < this.bricksList.length; i++) { const move = this.findBestPosition(this.bricksList[i]); if (move) { // 优先选择可以消除的位置 if (move.score > 0) { bestMove = move; selectedBrickIndex = i; break; } else if (bestMove === null) { // 如果还没有找到任何可放置的位置,保存这个位置 bestMove = move; selectedBrickIndex = i; } } } // 如果所有方块都没有可放置的位置,游戏结束 if (!bestMove || selectedBrickIndex === -1) { console.log('所有方块都没有可放置的位置,游戏结束'); this.isAutoMode = false; if (this._isAutoFunc) { this.unschedule(this._isAutoFunc); this._isAutoFunc = null; } this.gameOver(); return; } // 获取选中的方块 const brickData = this.bricksList[selectedBrickIndex]; // 从列表中移除方块 this.bricksList.splice(selectedBrickIndex, 1); // 保存方块的初始位置(从待选区开始) const startPos = brickData.brickInitPos.clone(); // 设置方块到移动层并确保位置精确 if (brickData.brickNode) { brickData.brickNode.setParent(this.moveNode); brickData.brickNode.setWorldPosition(startPos); } // this.guideNode.active = true; // this.guideNode.setWorldPosition( // startPos.x, // startPos.y + brickData.brickNode.getComponent(UITransform).height / 2, // startPos.z // ); // 如果需要旋转,先旋转到正确的角度 if (brickData.rotateFlag && bestMove.rotation && bestMove.rotation.deg !== brickData.deg) { await new Promise((resolve) => { if (brickData.brickNode) { tween(brickData.brickNode) .to(0.2, { angle: bestMove.rotation ? bestMove.rotation.deg : 0 }) .call(() => { if (bestMove.rotation) { brickData.gridConfig = bestMove.rotation.gridConfig; brickData.deg = bestMove.rotation.deg; } resolve(); }) .start(); } }); } // 获取目标位置,有些目标位置获取有问题需要修改 const targetGrid = this.gridList[bestMove.position.row][bestMove.position.column]; if (!targetGrid.gridNode) { console.error('targetGrid.gridNode is null'); return; } const targetWorldPos = targetGrid.gridNode.getWorldPosition().clone(); // 创建平滑的移动动画 await new Promise((resolve) => { if (brickData.brickNode) { tween(brickData.brickNode) .to(0.3, { worldPosition: targetWorldPos, }, { easing: 'cubicOut' }) .call(() => { if (brickData.brickNode) { brickData.brickNode.setWorldPosition(targetWorldPos); // 放置方块 if (bestMove.rotation) { bestMove.rotation.gridConfig.forEach((gridConfigData) => { const gridData = this.gridList[bestMove.position.row + gridConfigData.row][bestMove.position.column + gridConfigData.column]; gridData.status = CellState.FILLED; gridData.gridColorKey = brickData.gridColorKey; gridData.type = brickData.type; this.generateGrid(gridData); }); } // 销毁方块 brickData.brickNode.destroy(); // 添加新方块 this.addBrick(brickData.index); } resolve(); }) .start(); } }); // 检查并执行消除 await this.gridEliminate(); // 检查是否还有任何方块可以放置 let hasValidMove = false; for (const brick of this.bricksList) { if (this.findBestPosition(brick)) { hasValidMove = true; break; } } if (!hasValidMove) { console.log('没有方块可以放置,游戏结束'); this.isAutoMode = false; if (this._isAutoFunc) { this.unschedule(this._isAutoFunc); this._isAutoFunc = null; } this.gameOver(); } } // ... 修改 findBestPosition 方法,这个方法有问题,需要修改 findBestPosition(brickData: BrickData) { let bestScore = -1 let bestPosition = null let bestRotation = null let fallbackPosition = null let fallbackRotation = null // 遍历所有可能的位置 for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { // 检查当前位置的所有可能旋转 let currentGridConfig = brickData.gridConfig let currentDeg = brickData.deg // 最多旋转4次(0°, 90°, 180°, 270°) for (let rotation = 0; rotation < 4; rotation++) { if (this.moveIf(rowIndex, columnIndex, currentGridConfig)) { // 如果还没有找到后备位置,保存第一个可放置的位置 if (fallbackPosition === null) { fallbackPosition = { row: rowIndex, column: columnIndex } fallbackRotation = { gridConfig: currentGridConfig, deg: currentDeg } } // 模拟放置并计算得分 const gridList = this.copyGridList() currentGridConfig.forEach((gridConfigData) => { gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex].status = CellState.FILLED }) // 计算这个位置的得分 const eliminateCheck = this.gridEliminateCheck(gridList) let positionScore = eliminateCheck.gridEliminateList.length // 如果这个位置比之前找到的更好 if (positionScore > bestScore) { bestScore = positionScore bestPosition = { row: rowIndex, column: columnIndex } bestRotation = { gridConfig: currentGridConfig, deg: currentDeg } } } // 如果方块可以旋转,计算下一个旋转状态 if (brickData.rotateFlag && rotation < 3) { const next = this.nextGridRotate(currentGridConfig, currentDeg) currentGridConfig = next.gridConfig currentDeg = next.deg } else { break } } } } // 如果找到了最佳得分位置,返回它 if (bestPosition !== null) { return { position: bestPosition, rotation: bestRotation, score: bestScore } } // 如果没有找到可消除的位置但有可放置的位置,返回第一个可放置的位置 if (fallbackPosition !== null) { return { position: fallbackPosition, rotation: fallbackRotation, score: 0 } } // 如果真的没有任何可放置的位置,返回null return null } //========================打开其他界面和按钮逻辑======================= /** 视图对象通过 ecs.Entity.remove(eliminateViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { this.node.destroy(); } private onToggleContainerAutoClick(toggle: Toggle) { switch (toggle.node.name) { case "On": this.autoClick = true; break; case "Off": this.autoClick = false; break; } this.startAutoMode(); } private startAutoMode() { if ( this.gameState !== GameState.READY || this.editingFlag ) return // 切换自动模式状态 this.isAutoMode = this.autoClick; if (this.isAutoMode) { this._isAutoFunc = () => { this.autoPlaceBrick() } this.schedule(this._isAutoFunc, this.autoModeInterval) } else { if (this._isAutoFunc) { this.unschedule(this._isAutoFunc) this._isAutoFunc = null } } } 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 restartGame() { if (this.gameState === GameState.READY) return; } private reopenGrid() { for (let rowIndex = 0; rowIndex < this.rows; rowIndex++) { for (let columnIndex = 0; columnIndex < this.cols; columnIndex++) { this.gridList[rowIndex][columnIndex].status = CellState.FILLED 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() }) } } }