/* * @Author: mojunshou 1637302775@qq.com * @Date: 2025-03-11 18:05:45 * @LastEditors: mojunshou 1637302775@qq.com * @LastEditTime: 2025-03-17 14:54:40 * @Description: */ import { _decorator, Color, EventTouch, instantiate, JsonAsset, Node, Prefab, Sprite, UITransform, Vec3, Widget, tween } from "cc"; 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 { GameConfig, GameStatus, GridStatus } from "../../common/config/GameConfig"; import { UIID } from "../../common/config/GameUIConfig"; import { randomRangeInt } from "cc"; import { Vec2 } from "cc"; import { log } from "console"; const { ccclass, property } = _decorator; // 网格数据接口 interface GridData { name: string, status: GridStatus, gridNode: Node | null, row: number, column: number, gridColorKey: string | null, } //网格接口 interface Grids { row: number, column: number, } //底部方块数据定义 interface BrickData { index: number, brickKey: string, rotateFlag: boolean, gridConfig: Grids[], deg: number, brickNode: Node | null, gridColorKey: string, brickInitPos: Vec3, } //可修改的数据 interface EditingData { brickData: BrickData | null, gridList: GridData[], } 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!; //游戏状态 game_status: number = GameStatus.None; max_row: number = 0; max_col: number = 0; itemSize: number = 0; itemSpacing: number = 0; //方块间距 brickNum: number = 0; //没使用颜色 notUseColor = new Color(255, 255, 255, 255) //可用的颜色 usableColor = new Color(0, 255, 0, 100) //不可用的颜色 unavailableColor = new Color(255, 0, 0, 100) //旋转容错 rotateFaultTolerant = 10; //游戏变量定义 _score: number = 0; //分数 rotateTag: boolean = false; //旋转标记 gameConfig: { GridType?: any, bricks?: any } = {} //配置数据 editingFlag = false //编辑状态 editingData: EditingData = { brickData: null, gridList: [], } //旋转标记 rotateFlag = false rotateBrickData: BrickData | null = null; //网格列表 gridList: GridData[][] = []; //砖块列表 bricksList: BrickData[] = []; //网格颜色列表 gridColorList: GridData[] = []; gridPrefab: Prefab | null = null; rotatePrefab: Prefab | null = null; prefabUrlMap: { [key: string]: Prefab } = {}; //预制体url映射 private _isAutoFunc: (() => void) | null = null private readonly GRID_GRAY_COLOR = new Color(128, 128, 128, 255) private readonly GRID_GRAY_DELAY = 0.01 isAutoMode: boolean = false; autoModeInterval: number = 1 // 自动模式的间隔时间(秒) autoModeTimer: number = 0 // 自动模式计时器 //网格列表 gridsNode: Node | null = null; //中间网格区域 itemNode: Node | null = null; //底部三个item块区域 moveNode: Node | null = null; //移动层Node rotateNode: Node | null = null; //旋转Node hammerNode: Node | null = null; //锤子节点 //锤子功能 hammerMode = false; // 是否在锤子模式 hammerRange = 2; // 锤子影响范围(以点击位置为中心的方形区域边长) hammerPreviewNodes: Node[] = []; // 预览区域的节点列表 //分数管理 get score() { return this._score } set score(v: number) { this._score = v } /** 视图层逻辑代码分离演示 */ async start() { // const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象 this.setButton(); this.initBindNode(); await this.loadBrickConfig(); // 等待 loadBrickConfig 执行完毕 this.loadPrefabsAsset().then(() => { this.scheduleOnce(() => { this.initGameData(); this.initGrids(); }, 0.1) }); // 然后再执行 loadPrefabsAsset } /** 视图对象通过 ecs.Entity.remove(GameViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { this.node.destroy(); } private initBindNode() { this.gridsNode = this.node.getChildByPath("Scene/center/GridNode"); this.itemNode = this.node.getChildByPath("Scene/itemNode"); this.moveNode = this.node.getChildByPath("Scene/MoveNode"); this.rotateNode = this.node.getChildByPath("Scene/rotateNode"); this.hammerNode = this.node.getChildByPath("Scene/hammerNode") } private btn_setting(event: EventTouch) { console.log("点击了设置按钮"); oops.gui.open(UIID.Setting); } //初始化游戏数据 private initGameData() { this.max_row = GameConfig.MaxRow; this.max_col = GameConfig.MaxCol; this.itemSize = GameConfig.ItemSize; this.itemSpacing = GameConfig.Spacing; this.brickNum = GameConfig.BlockNum; this.rotateFlag = false; this.rotateBrickData = null; this.setGameStatus(GameStatus.Start); } // private loadBrickConfig() { // let json_name: string = "gui/eliminate/config/GridConfig"; // oops.res.load(json_name, JsonAsset, (err: Error | null, res: any) => { // if (res) { // this.gameConfig = res.json; // console.log("???????????????????", this.gameConfig); // } else { // console.log("JON数据加载失,请检查文件") // } // }) // } // //加载预制体资源 // private loadPrefabsAsset() { // let loadList = []; // const loadMap = { // gridPrefab: "gui/eliminate/prefabs/Grid", // rotatePrefab: "gui/eliminate/prefabs/Rotation" // } // Object.keys(loadMap).forEach(key => { // let path = loadMap[key as keyof typeof loadMap]; // const keyTyped = key as 'gridPrefab' | 'rotatePrefab'; // loadList.push( // new Promise((resolve, reject) => { // oops.res.load(path, Prefab, (err: Error | null, res: any) => { // if (err) { // reject(err) // return; // } // if (res) { // this[keyTyped] = res; // resolve(0) // } // }) // }) // ) // }) // //加载类型预制体 // console.log("this.gameConfig", this.gameConfig) // const prefabUrls: string[] = [] // const typeList = this.gameConfig['GridType']; // Object.keys(typeList).forEach(key => { // let path = typeList[key]['prefabUrl']; // prefabUrls.push(path); // }) // prefabUrls.forEach(path => { // loadList.push( // new Promise((resolve, reject) => { // oops.res.load(path, Prefab, (err: Error | null, res: any) => { // if (err) { // reject(err) // return; // } // if (res) { // this.prefabUrlMap[path] = res // resolve(0) // } // }) // }) // ) // }) // console.log(">>>>>>>>>>>>>>>>loadList", loadList) // } //初始化网格 private async loadBrickConfig() { 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.gameConfig = res.json; resolve(); } else { console.log("JSON数据加载失,请检查文件"); reject(err); } }); }); } //加载预制体资源 private async loadPrefabsAsset() { const loadMap = { gridPrefab: "gui/eliminate/prefabs/Grid", rotatePrefab: "gui/eliminate/prefabs/Rotation", coinPrefab: "gui/eliminate/prefabs/Coin", // 添加金币预制体路径 redPacketPrefab: "gui/eliminate/prefabs/RedPacket" // 添加红包预制体路径 }; Object.keys(loadMap).forEach(key => { let path = loadMap[key as keyof typeof loadMap]; const keyTyped = key as 'gridPrefab' | 'rotatePrefab' | 'coinPrefab' | 'redPacketPrefab'; new Promise((resolve, reject) => { oops.res.load(path, Prefab, (err: Error | null, res: any) => { if (err) { reject(err); return; } if (res) { this[keyTyped] = res; resolve(0); } }); }) }); const prefabUrls: string[] = []; const typeList = this.gameConfig['GridType']; Object.keys(typeList).forEach(key => { let path = typeList[key]['prefabUrl']; prefabUrls.push(path); }); prefabUrls.forEach(path => { new Promise((resolve, reject) => { oops.res.load(path, Prefab, (err: Error | null, res: any) => { if (err) { reject(err); return; } if (res) { this.prefabUrlMap[path] = res; resolve(0); } }); }) }); // console.log("this.prefabUrlMap", this.prefabUrlMap) } private initGrids() { // 清理现有网格 this.clearExistingGrids(); // 设置网格容器大小 this.setupGridsContainer(); // 生成网格矩阵 this.createGridMatrix(); } //设置游戏状态 /** * @description: 设置游戏状态 * @param {GameStatus} status * @return {*} */ private setGameStatus(status: GameStatus) { this.game_status = status; switch (status) { case GameStatus.None: break; case GameStatus.Start: break; case GameStatus.Over: break; default: break; } } /** * @description: 清理现有的网格数据 * @return {*} */ private clearExistingGrids(): void { this.gridList = []; if (this.gridsNode) { this.gridsNode.children.forEach(node => node.destroy()); } } /** * @description: 设置网格大小 * @return {*} */ private setupGridsContainer(): void { const containerSize = { width: this.itemSize * this.max_col + this.itemSpacing * 2, height: this.itemSize * this.max_row + this.itemSpacing * 2 }; if (this.gridsNode) { const uiTransform = this.gridsNode.getComponent(UITransform); if (uiTransform) { uiTransform.setContentSize( containerSize.width, containerSize.height ); } } } /** * @description: 设置网格容器大小 * @return {*} */ private createGridMatrix(): void { for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) { const currentRow: GridData[] = []; this.gridList.push(currentRow); for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) { const gridData = this.createGridData(rowIndex, columnIndex); currentRow.push(gridData); this.createGridNode(gridData); } } // 初始化格子状态 for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) { for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) { this.gridList[rowIndex][columnIndex].status = GridStatus.NotUse this.generateGrid(this.gridList[rowIndex][columnIndex]) } } // 初始化方块 if (this.itemNode) { if (this.itemNode) { this.itemNode.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: GridStatus.NotUse, gridNode: null, row: row, column: column, gridColorKey: null, }; } /** * @description: 创建网格Node * @param {GridData} gridData * @return {*} */ private createGridNode(gridData: GridData): void { const gridNode = new Node(gridData.name); if (this.gridsNode) { this.gridsNode.addChild(gridNode); } gridData.gridNode = gridNode; // 设置网格大小 gridNode.addComponent(UITransform).setContentSize(this.itemSize, this.itemSize); // 设置网格位置约束 this.setupGridWidget(gridNode, gridData.row, gridData.column); } /** * @description: 设置网格约束 * @param {Node} gridNode * @param {number} row * @param {number} column * @return {*} */ private setupGridWidget(gridNode: Node, row: number, column: number): void { const gridWidget = gridNode.addComponent(Widget); gridWidget.isAlignLeft = true; gridWidget.left = this.itemSize * column + this.itemSpacing; gridWidget.isAlignBottom = true; gridWidget.bottom = this.itemSize * row + this.itemSpacing; } /** * 生成或更新网格 * @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 clearGridChildren(gridNode: Node): void { const children = gridNode.children.slice(); // 创建副本避免遍历时修改问题 children.forEach(node => node.destroy()); } /** * 获取对应状态的预制体 */ private getGridPrefab(gridData: GridData): Prefab { if (gridData.status === GridStatus.NotUse) { if (this.gridPrefab) { return this.gridPrefab; } else { throw new Error('Grid prefab is not loaded'); } } if (gridData.status === GridStatus.Used && gridData.gridColorKey) { const prefabUrl = this.gameConfig['GridType']?.[gridData.gridColorKey]?.['prefabUrl']; return this.prefabUrlMap[prefabUrl]; } 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 === GridStatus.NotUse) { const sprite = node.getComponent(Sprite); if (sprite) { sprite.color = this.notUseColor; } } // 设置节点大小 const transform = node.getComponent(UITransform); if (transform) { transform.setContentSize( this.itemSize - this.itemSpacing * 2, this.itemSize - this.itemSpacing * 2 ); } // 设置位置 node.setPosition(Vec3.ZERO); } //底部增加方块 addBrick(index: number) { const brickKey = Object.keys(this.gameConfig['bricks'])[randomRangeInt(0, Object.keys(this.gameConfig['bricks']).length)] const gridColorKey = Object.keys(this.gameConfig['GridType'])[randomRangeInt(0, Object.keys(this.gameConfig['GridType']).length)] const brickConfig = this.gameConfig['bricks'][brickKey] const brickData: BrickData = { index, brickKey, rotateFlag: brickConfig['rotateFlag'], gridConfig: brickConfig['gridConfig'], deg: 0, brickNode: null, gridColorKey, brickInitPos: new Vec3(), } this.bricksList.push(brickData) // 生成方块 const brickNode = this.generateBrick(brickKey, gridColorKey); if (this.itemNode) { this.itemNode.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.itemNode) { width = this.itemNode.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); } } 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.max_row && gridData === null; rowIndex++) { for (let columnIndex = 0; columnIndex < this.max_col && 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 - this.itemSpacing)) { gridData = nowGridData } } } } if (gridData === null) return tempGridList.push(gridData) }) // 检查整体情况 let checkFlag = false if ( tempGridList.length === brickData.gridConfig.length && tempGridList.filter(d => d.status === GridStatus.NotUse).length === brickData.gridConfig.length ) { checkFlag = true tempGridList.forEach((gridData) => { this.editingData.gridList.push(gridData) }) } // 格子给用户提示 tempGridList.forEach((gridData) => { if (gridData.status !== GridStatus.NotUse) 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) => { // 当连击很快时,会出现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.itemNode && brickData.brickNode) { this.itemNode.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 = GridStatus.Used if (this.editingData.brickData) { gridData.gridColorKey = this.editingData.brickData.gridColorKey; } 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.itemNode && brickData.brickNode) { this.itemNode.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(); }) } // 提示 prompt(tipFlag = true) { console.log("点击了提示按钮") return new Promise((resolve, reject) => { const gridPromptList: GridData[] = [] let moveFlag = false // 找方块可消除位置 for (let rowIndex = 0; rowIndex < this.max_row && !moveFlag; rowIndex++) { for (let columnIndex = 0; columnIndex < this.max_col && !moveFlag; columnIndex++) { const gridData = this.gridList[rowIndex][columnIndex] if (gridData.status !== GridStatus.NotUse) 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 = GridStatus.Used }) // 检查复制的整体网格是否有可消除 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 = GridStatus.Used }) // 检查复制的整体网格是否有可消除 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.max_row && !moveFlag; rowIndex++) { for (let columnIndex = 0; columnIndex < this.max_col && !moveFlag; columnIndex++) { const gridData = this.gridList[rowIndex][columnIndex] if (gridData.status !== GridStatus.NotUse) 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) }) } // 复制整体网格 copyGridList() { const gridList: GridData[][] = [] for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) { gridList.push([]) for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) { const gridData = this.gridList[rowIndex][columnIndex] gridList[rowIndex].push({ name: gridData.name, status: gridData.status, gridNode: null, row: gridData.row, column: gridData.column, gridColorKey: gridData.gridColorKey, }) } } return gridList } // 检查是否能放置方块 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.max_row - 1 || gridJ < 0 || gridJ > this.max_col - 1 ) { moveFlag = false break } // 已用 else if (this.gridList[gridI][gridJ].status === GridStatus.Used) { moveFlag = false break } } return moveFlag } 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 } } //格子颜色恢复 gridColorRecovery() { while (this.gridColorList.length > 0) { const gridData = this.gridColorList.pop(); if (gridData) { if (gridData.status === GridStatus.NotUse) { if (gridData.gridNode) { gridData.gridNode.children[0].getComponent(Sprite)!.color = this.notUseColor; } } } } } // 生成独立的方块节点 generateBrick(brickKey: string, gridColorKey: string) { const brickConfig = this.gameConfig['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.prefabUrlMap[this.gameConfig['GridType'][gridColorKey]['prefabUrl']] //生成对应的配置方块 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.itemSpacing * 2, this.itemSize - this.itemSpacing * 2, ) } node.on(Node.EventType.TOUCH_START, (event: EventTouch) => { event.propagationStopped = false }) node.setPosition(Vec3.ZERO) }) return brickNode } // 消除,这里增加飞的动画 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 = GridStatus.NotUse 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.max_col * i } for (let i = 1; i <= eliminateColumnNum; i++) { score += this.max_row * 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.max_row; rowIndex++) { const rowData = gridList[rowIndex] if (rowData.every(gridData => gridData.status === GridStatus.Used)) { rowData.forEach(gridData => { if (gridEliminateList.findIndex(data => data === gridData) < 0) { gridEliminateList.push(gridData) } }) eliminateRowNum += 1 } } // 列检查 for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) { if (gridList.every(rowData => rowData[columnIndex].status === GridStatus.Used)) { gridList.forEach(rowData => { const gridData = rowData[columnIndex] if (gridEliminateList.findIndex(data => data === gridData) < 0) { gridEliminateList.push(gridData) } }) eliminateColumnNum += 1 } } return { gridEliminateList, eliminateRowNum, eliminateColumnNum, } } async gameOver() { // 先禁用所有操作 this.isAutoMode = false this.editingFlag = true // 防止操作 // 执行格子变灰动画 await this.playGameOverGridAnimation() // 播放游戏结束音效 // this.audioManager.playGameOver() // 设置游戏状态 this.setGameStatus(GameStatus.Over) // 恢复操作标志 this.editingFlag = false } private async playGameOverGridAnimation(): Promise { // 按行从左到右收集所有已使用的格子 const usedGrids: GridData[] = [] for (let row = 0; row < this.max_row; row++) { for (let col = 0; col < this.max_col; col++) { const grid = this.gridList[row][col] if (grid.status === GridStatus.Used) { usedGrids.push(grid) } } } // 执行变灰动画 return new Promise((resolve) => { let completedCount = 0 usedGrids.forEach((grid, index) => { this.scheduleOnce(() => { // 获取格子的 Sprite 组件 if (grid.gridNode) { const gridSprite = grid.gridNode.children[0].getComponent(Sprite) // 创建变灰动画 if (!gridSprite) { return } tween(gridSprite) .to(0.2, { color: this.GRID_GRAY_COLOR }) .call(() => { completedCount++ if (completedCount === usedGrids.length) { resolve() } }) .start() } }, index * this.GRID_GRAY_DELAY) }) // 如果没有已使用的格子,直接完成 if (usedGrids.length === 0) { resolve() } }) } // //刷新按钮 btn_refresh() { if ( this.game_status !== GameStatus.Start ) return // 清除方块重新获取 if (this.itemNode) { this.itemNode.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() }) } this.rotateFlag = false this.rotateBrickData = null // 恢复格子颜色 this.gridColorRecovery() } //提示按钮 btn_hint() { if ( this.game_status !== GameStatus.Start ) return // 先检查是否死局 this.prompt(false).then((flag) => { if (!flag) { this.gameOver() return } this.gridColorRecovery() this.prompt() }) } //继续按钮 onContinueBtnClick() { this.setGameStatus(GameStatus.Start); } // ... 修改 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 = GridStatus.Used; gridData.gridColorKey = brickData.gridColorKey; this.generateGrid(gridData); }); } // 销毁方块 brickData.brickNode.destroy(); // 添加新方块 this.addBrick(brickData.index); } resolve(); }) .start(); } // 引导节点跟着移动 // if (this.guildNode) { // tween(this.guideNode) // .to(0.3, { // worldPosition: targetWorldPos // }) // .call(() => { // this.guideNode.active = false; // }) // .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.max_row; rowIndex++) { for (let columnIndex = 0; columnIndex < this.max_col; 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 = GridStatus.Used }) // 计算这个位置的得分 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 } //重新开始-这样正常要接广告 private btn_reopen() { console.log("重新开始"); if (this.game_status === GameStatus.None) return this.initGameData(); this.reopenGrid(); } private reopenGrid() { for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) { for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) { this.gridList[rowIndex][columnIndex].status = GridStatus.NotUse this.generateGrid(this.gridList[rowIndex][columnIndex]) } } // 初始化方块 if (this.itemNode) { if (this.itemNode) { this.itemNode.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() }) } } // ... 添加自动模式的开关方法 onAutoButtonClick() { if ( this.game_status !== GameStatus.Start || this.editingFlag ) return // 切换自动模式状态 this.isAutoMode = !this.isAutoMode if (this.isAutoMode) { this._isAutoFunc = () => { this.autoPlaceBrick() } this.schedule(this._isAutoFunc, this.autoModeInterval) console.log('开启自动模式') } else { if (this._isAutoFunc) { this.unschedule(this._isAutoFunc) this._isAutoFunc = null } console.log('关闭自动模式') } } private btn_auto() { // if ( // this.game_status !== GameStatus.Start || // this.editingFlag // ) return // 切换自动模式状态 this.isAutoMode = !this.isAutoMode if (this.isAutoMode) { this._isAutoFunc = () => { this.autoPlaceBrick() } this.schedule(this._isAutoFunc, this.autoModeInterval) console.log('开启自动模式') } else { if (this._isAutoFunc) { this.unschedule(this._isAutoFunc) this._isAutoFunc = null } console.log('关闭自动模式') } } // 修改 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); } } }