EliminateViewComp.ts 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703
  1. /*
  2. * @Author: mojunshou 1637302775@qq.com
  3. * @Date: 2025-03-11 18:05:45
  4. * @LastEditors: mojunshou 1637302775@qq.com
  5. * @LastEditTime: 2025-03-17 14:54:40
  6. * @Description:
  7. */
  8. import { _decorator, Color, EventTouch, instantiate, JsonAsset, Node, Prefab, Sprite, UITransform, Vec3, Widget, tween } from "cc";
  9. import { oops } from "db://oops-framework/core/Oops";
  10. import { ecs } from "db://oops-framework/libs/ecs/ECS";
  11. import { LabelChange } from "db://oops-framework/libs/gui/label/LabelChange";
  12. import { CCComp } from "db://oops-framework/module/common/CCComp";
  13. import { GameConfig, GameStatus, GridStatus } from "../../common/config/GameConfig";
  14. import { UIID } from "../../common/config/GameUIConfig";
  15. import { randomRangeInt } from "cc";
  16. import { Vec2 } from "cc";
  17. import { log } from "console";
  18. const { ccclass, property } = _decorator;
  19. // 网格数据接口
  20. interface GridData {
  21. name: string,
  22. status: GridStatus,
  23. gridNode: Node | null,
  24. row: number,
  25. column: number,
  26. gridColorKey: string | null,
  27. }
  28. //网格接口
  29. interface Grids {
  30. row: number,
  31. column: number,
  32. }
  33. //底部方块数据定义
  34. interface BrickData {
  35. index: number,
  36. brickKey: string,
  37. rotateFlag: boolean,
  38. gridConfig: Grids[],
  39. deg: number,
  40. brickNode: Node | null,
  41. gridColorKey: string,
  42. brickInitPos: Vec3,
  43. }
  44. //可修改的数据
  45. interface EditingData {
  46. brickData: BrickData | null,
  47. gridList: GridData[],
  48. }
  49. interface GridConfigData {
  50. row: number,
  51. column: number,
  52. }
  53. /** 视图层对象 */
  54. @ccclass('EliminateViewComp')
  55. @ecs.register('EliminateView', false)
  56. export class EliminateViewComp extends CCComp {
  57. @property({ type: LabelChange, displayName: "自动提现金额" })
  58. private amountLb: LabelChange = null!;
  59. @property({ type: LabelChange, displayName: "额外奖励" })
  60. private awardLb: LabelChange = null!;
  61. @property({ type: Prefab, displayName: "金币预制体" })
  62. private coinPrefab: Prefab = null!;
  63. @property({ type: Prefab, displayName: "红包预制体" })
  64. private redPacketPrefab: Prefab = null!;
  65. //游戏状态
  66. game_status: number = GameStatus.None;
  67. max_row: number = 0;
  68. max_col: number = 0;
  69. itemSize: number = 0;
  70. itemSpacing: number = 0; //方块间距
  71. brickNum: number = 0;
  72. //没使用颜色
  73. notUseColor = new Color(255, 255, 255, 255)
  74. //可用的颜色
  75. usableColor = new Color(0, 255, 0, 100)
  76. //不可用的颜色
  77. unavailableColor = new Color(255, 0, 0, 100)
  78. //旋转容错
  79. rotateFaultTolerant = 10;
  80. //游戏变量定义
  81. _score: number = 0; //分数
  82. rotateTag: boolean = false; //旋转标记
  83. gameConfig: { GridType?: any, bricks?: any } = {} //配置数据
  84. editingFlag = false //编辑状态
  85. editingData: EditingData = {
  86. brickData: null,
  87. gridList: [],
  88. }
  89. //旋转标记
  90. rotateFlag = false
  91. rotateBrickData: BrickData | null = null;
  92. //网格列表
  93. gridList: GridData[][] = [];
  94. //砖块列表
  95. bricksList: BrickData[] = [];
  96. //网格颜色列表
  97. gridColorList: GridData[] = [];
  98. gridPrefab: Prefab | null = null;
  99. rotatePrefab: Prefab | null = null;
  100. prefabUrlMap: { [key: string]: Prefab } = {}; //预制体url映射
  101. private _isAutoFunc: (() => void) | null = null
  102. private readonly GRID_GRAY_COLOR = new Color(128, 128, 128, 255)
  103. private readonly GRID_GRAY_DELAY = 0.01
  104. isAutoMode: boolean = false;
  105. autoModeInterval: number = 1 // 自动模式的间隔时间(秒)
  106. autoModeTimer: number = 0 // 自动模式计时器
  107. //网格列表
  108. gridsNode: Node | null = null; //中间网格区域
  109. itemNode: Node | null = null; //底部三个item块区域
  110. moveNode: Node | null = null; //移动层Node
  111. rotateNode: Node | null = null; //旋转Node
  112. hammerNode: Node | null = null; //锤子节点
  113. //锤子功能
  114. hammerMode = false; // 是否在锤子模式
  115. hammerRange = 2; // 锤子影响范围(以点击位置为中心的方形区域边长)
  116. hammerPreviewNodes: Node[] = []; // 预览区域的节点列表
  117. //分数管理
  118. get score() {
  119. return this._score
  120. }
  121. set score(v: number) {
  122. this._score = v
  123. }
  124. /** 视图层逻辑代码分离演示 */
  125. async start() {
  126. // const entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
  127. this.setButton();
  128. this.initBindNode();
  129. await this.loadBrickConfig(); // 等待 loadBrickConfig 执行完毕
  130. this.loadPrefabsAsset().then(() => {
  131. this.scheduleOnce(() => {
  132. this.initGameData();
  133. this.initGrids();
  134. }, 0.1)
  135. }); // 然后再执行 loadPrefabsAsset
  136. }
  137. /** 视图对象通过 ecs.Entity.remove(GameViewComp) 删除组件是触发组件处理自定义释放逻辑 */
  138. reset() {
  139. this.node.destroy();
  140. }
  141. private initBindNode() {
  142. this.gridsNode = this.node.getChildByPath("Scene/center/GridNode");
  143. this.itemNode = this.node.getChildByPath("Scene/itemNode");
  144. this.moveNode = this.node.getChildByPath("Scene/MoveNode");
  145. this.rotateNode = this.node.getChildByPath("Scene/rotateNode");
  146. this.hammerNode = this.node.getChildByPath("Scene/hammerNode")
  147. }
  148. private btn_setting(event: EventTouch) {
  149. console.log("点击了设置按钮");
  150. oops.gui.open(UIID.Setting);
  151. }
  152. //初始化游戏数据
  153. private initGameData() {
  154. this.max_row = GameConfig.MaxRow;
  155. this.max_col = GameConfig.MaxCol;
  156. this.itemSize = GameConfig.ItemSize;
  157. this.itemSpacing = GameConfig.Spacing;
  158. this.brickNum = GameConfig.BlockNum;
  159. this.rotateFlag = false;
  160. this.rotateBrickData = null;
  161. this.setGameStatus(GameStatus.Start);
  162. }
  163. // private loadBrickConfig() {
  164. // let json_name: string = "gui/eliminate/config/GridConfig";
  165. // oops.res.load(json_name, JsonAsset, (err: Error | null, res: any) => {
  166. // if (res) {
  167. // this.gameConfig = res.json;
  168. // console.log("???????????????????", this.gameConfig);
  169. // } else {
  170. // console.log("JON数据加载失,请检查文件")
  171. // }
  172. // })
  173. // }
  174. // //加载预制体资源
  175. // private loadPrefabsAsset() {
  176. // let loadList = [];
  177. // const loadMap = {
  178. // gridPrefab: "gui/eliminate/prefabs/Grid",
  179. // rotatePrefab: "gui/eliminate/prefabs/Rotation"
  180. // }
  181. // Object.keys(loadMap).forEach(key => {
  182. // let path = loadMap[key as keyof typeof loadMap];
  183. // const keyTyped = key as 'gridPrefab' | 'rotatePrefab';
  184. // loadList.push(
  185. // new Promise((resolve, reject) => {
  186. // oops.res.load(path, Prefab, (err: Error | null, res: any) => {
  187. // if (err) {
  188. // reject(err)
  189. // return;
  190. // }
  191. // if (res) {
  192. // this[keyTyped] = res;
  193. // resolve(0)
  194. // }
  195. // })
  196. // })
  197. // )
  198. // })
  199. // //加载类型预制体
  200. // console.log("this.gameConfig", this.gameConfig)
  201. // const prefabUrls: string[] = []
  202. // const typeList = this.gameConfig['GridType'];
  203. // Object.keys(typeList).forEach(key => {
  204. // let path = typeList[key]['prefabUrl'];
  205. // prefabUrls.push(path);
  206. // })
  207. // prefabUrls.forEach(path => {
  208. // loadList.push(
  209. // new Promise((resolve, reject) => {
  210. // oops.res.load(path, Prefab, (err: Error | null, res: any) => {
  211. // if (err) {
  212. // reject(err)
  213. // return;
  214. // }
  215. // if (res) {
  216. // this.prefabUrlMap[path] = res
  217. // resolve(0)
  218. // }
  219. // })
  220. // })
  221. // )
  222. // })
  223. // console.log(">>>>>>>>>>>>>>>>loadList", loadList)
  224. // }
  225. //初始化网格
  226. private async loadBrickConfig() {
  227. let json_name: string = "gui/eliminate/config/GridConfig";
  228. return new Promise<void>((resolve, reject) => {
  229. oops.res.load(json_name, JsonAsset, (err: Error | null, res: any) => {
  230. if (res) {
  231. this.gameConfig = res.json;
  232. resolve();
  233. } else {
  234. console.log("JSON数据加载失,请检查文件");
  235. reject(err);
  236. }
  237. });
  238. });
  239. }
  240. //加载预制体资源
  241. private async loadPrefabsAsset() {
  242. const loadMap = {
  243. gridPrefab: "gui/eliminate/prefabs/Grid",
  244. rotatePrefab: "gui/eliminate/prefabs/Rotation",
  245. coinPrefab: "gui/eliminate/prefabs/Coin", // 添加金币预制体路径
  246. redPacketPrefab: "gui/eliminate/prefabs/RedPacket" // 添加红包预制体路径
  247. };
  248. Object.keys(loadMap).forEach(key => {
  249. let path = loadMap[key as keyof typeof loadMap];
  250. const keyTyped = key as 'gridPrefab' | 'rotatePrefab' | 'coinPrefab' | 'redPacketPrefab';
  251. new Promise((resolve, reject) => {
  252. oops.res.load(path, Prefab, (err: Error | null, res: any) => {
  253. if (err) {
  254. reject(err);
  255. return;
  256. }
  257. if (res) {
  258. this[keyTyped] = res;
  259. resolve(0);
  260. }
  261. });
  262. })
  263. });
  264. const prefabUrls: string[] = [];
  265. const typeList = this.gameConfig['GridType'];
  266. Object.keys(typeList).forEach(key => {
  267. let path = typeList[key]['prefabUrl'];
  268. prefabUrls.push(path);
  269. });
  270. prefabUrls.forEach(path => {
  271. new Promise((resolve, reject) => {
  272. oops.res.load(path, Prefab, (err: Error | null, res: any) => {
  273. if (err) {
  274. reject(err);
  275. return;
  276. }
  277. if (res) {
  278. this.prefabUrlMap[path] = res;
  279. resolve(0);
  280. }
  281. });
  282. })
  283. });
  284. // console.log("this.prefabUrlMap", this.prefabUrlMap)
  285. }
  286. private initGrids() {
  287. // 清理现有网格
  288. this.clearExistingGrids();
  289. // 设置网格容器大小
  290. this.setupGridsContainer();
  291. // 生成网格矩阵
  292. this.createGridMatrix();
  293. }
  294. //设置游戏状态
  295. /**
  296. * @description: 设置游戏状态
  297. * @param {GameStatus} status
  298. * @return {*}
  299. */
  300. private setGameStatus(status: GameStatus) {
  301. this.game_status = status;
  302. switch (status) {
  303. case GameStatus.None:
  304. break;
  305. case GameStatus.Start:
  306. break;
  307. case GameStatus.Over:
  308. break;
  309. default:
  310. break;
  311. }
  312. }
  313. /**
  314. * @description: 清理现有的网格数据
  315. * @return {*}
  316. */
  317. private clearExistingGrids(): void {
  318. this.gridList = [];
  319. if (this.gridsNode) {
  320. this.gridsNode.children.forEach(node => node.destroy());
  321. }
  322. }
  323. /**
  324. * @description: 设置网格大小
  325. * @return {*}
  326. */
  327. private setupGridsContainer(): void {
  328. const containerSize = {
  329. width: this.itemSize * this.max_col + this.itemSpacing * 2,
  330. height: this.itemSize * this.max_row + this.itemSpacing * 2
  331. };
  332. if (this.gridsNode) {
  333. const uiTransform = this.gridsNode.getComponent(UITransform);
  334. if (uiTransform) {
  335. uiTransform.setContentSize(
  336. containerSize.width,
  337. containerSize.height
  338. );
  339. }
  340. }
  341. }
  342. /**
  343. * @description: 设置网格容器大小
  344. * @return {*}
  345. */
  346. private createGridMatrix(): void {
  347. for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) {
  348. const currentRow: GridData[] = [];
  349. this.gridList.push(currentRow);
  350. for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) {
  351. const gridData = this.createGridData(rowIndex, columnIndex);
  352. currentRow.push(gridData);
  353. this.createGridNode(gridData);
  354. }
  355. }
  356. // 初始化格子状态
  357. for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) {
  358. for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) {
  359. this.gridList[rowIndex][columnIndex].status = GridStatus.NotUse
  360. this.generateGrid(this.gridList[rowIndex][columnIndex])
  361. }
  362. }
  363. // 初始化方块
  364. if (this.itemNode) {
  365. if (this.itemNode) {
  366. this.itemNode.children.forEach(node => { node.destroy() });
  367. }
  368. }
  369. this.bricksList.length = 0
  370. for (let i = 1; i <= this.brickNum; i++) {
  371. this.addBrick(i)
  372. }
  373. // 清除旋转数据
  374. if (this.rotateNode) {
  375. this.rotateNode.children.forEach(node => { node.destroy() })
  376. }
  377. }
  378. /**
  379. * @description: 创建格子数据
  380. * @param {number} row
  381. * @param {number} column
  382. * @return {*}
  383. */
  384. private createGridData(row: number, column: number): GridData {
  385. return {
  386. name: `Grid-${row}-${column}`,
  387. status: GridStatus.NotUse,
  388. gridNode: null,
  389. row: row,
  390. column: column,
  391. gridColorKey: null,
  392. };
  393. }
  394. /**
  395. * @description: 创建网格Node
  396. * @param {GridData} gridData
  397. * @return {*}
  398. */
  399. private createGridNode(gridData: GridData): void {
  400. const gridNode = new Node(gridData.name);
  401. if (this.gridsNode) {
  402. this.gridsNode.addChild(gridNode);
  403. }
  404. gridData.gridNode = gridNode;
  405. // 设置网格大小
  406. gridNode.addComponent(UITransform).setContentSize(this.itemSize, this.itemSize);
  407. // 设置网格位置约束
  408. this.setupGridWidget(gridNode, gridData.row, gridData.column);
  409. }
  410. /**
  411. * @description: 设置网格约束
  412. * @param {Node} gridNode
  413. * @param {number} row
  414. * @param {number} column
  415. * @return {*}
  416. */
  417. private setupGridWidget(gridNode: Node, row: number, column: number): void {
  418. const gridWidget = gridNode.addComponent(Widget);
  419. gridWidget.isAlignLeft = true;
  420. gridWidget.left = this.itemSize * column + this.itemSpacing;
  421. gridWidget.isAlignBottom = true;
  422. gridWidget.bottom = this.itemSize * row + this.itemSpacing;
  423. }
  424. /**
  425. * 生成或更新网格
  426. * @param gridData 网格数据
  427. */
  428. private generateGrid(gridData: GridData) {
  429. if (!gridData || !gridData.gridNode) {
  430. console.warn('无效的网格数据');
  431. return;
  432. }
  433. // 清理现有子节点
  434. this.clearGridChildren(gridData.gridNode);
  435. // 获取对应的预制体
  436. const prefab = this.getGridPrefab(gridData);
  437. if (!prefab) {
  438. console.warn('无法获取网格预制体');
  439. return;
  440. }
  441. // 创建并配置新节点
  442. const node = this.createNewGridNode(prefab, gridData);
  443. // 设置节点属性
  444. this.setupGridNode(node, gridData);
  445. }
  446. /**
  447. * 清理网格的子节点
  448. */
  449. private clearGridChildren(gridNode: Node): void {
  450. const children = gridNode.children.slice(); // 创建副本避免遍历时修改问题
  451. children.forEach(node => node.destroy());
  452. }
  453. /**
  454. * 获取对应状态的预制体
  455. */
  456. private getGridPrefab(gridData: GridData): Prefab {
  457. if (gridData.status === GridStatus.NotUse) {
  458. if (this.gridPrefab) {
  459. return this.gridPrefab;
  460. } else {
  461. throw new Error('Grid prefab is not loaded');
  462. }
  463. }
  464. if (gridData.status === GridStatus.Used && gridData.gridColorKey) {
  465. const prefabUrl = this.gameConfig['GridType']?.[gridData.gridColorKey]?.['prefabUrl'];
  466. return this.prefabUrlMap[prefabUrl];
  467. }
  468. throw new Error('Invalid grid status or missing gridColorKey');
  469. }
  470. /**
  471. * 创建网格节点
  472. */
  473. private createNewGridNode(prefab: Prefab, gridData: GridData): Node {
  474. const node = instantiate(prefab);
  475. if (gridData && gridData.gridNode) {
  476. gridData.gridNode.addChild(node);
  477. return node;
  478. }
  479. return node
  480. }
  481. /**
  482. * 设置网格节点的属性
  483. */
  484. private setupGridNode(node: Node, gridData: GridData): void {
  485. // 设置未使用状态的颜色
  486. if (gridData.status === GridStatus.NotUse) {
  487. const sprite = node.getComponent(Sprite);
  488. if (sprite) {
  489. sprite.color = this.notUseColor;
  490. }
  491. }
  492. // 设置节点大小
  493. const transform = node.getComponent(UITransform);
  494. if (transform) {
  495. transform.setContentSize(
  496. this.itemSize - this.itemSpacing * 2,
  497. this.itemSize - this.itemSpacing * 2
  498. );
  499. }
  500. // 设置位置
  501. node.setPosition(Vec3.ZERO);
  502. }
  503. //底部增加方块
  504. addBrick(index: number) {
  505. const brickKey = Object.keys(this.gameConfig['bricks'])[randomRangeInt(0, Object.keys(this.gameConfig['bricks']).length)]
  506. const gridColorKey = Object.keys(this.gameConfig['GridType'])[randomRangeInt(0, Object.keys(this.gameConfig['GridType']).length)]
  507. const brickConfig = this.gameConfig['bricks'][brickKey]
  508. const brickData: BrickData = {
  509. index,
  510. brickKey,
  511. rotateFlag: brickConfig['rotateFlag'],
  512. gridConfig: brickConfig['gridConfig'],
  513. deg: 0,
  514. brickNode: null,
  515. gridColorKey,
  516. brickInitPos: new Vec3(),
  517. }
  518. this.bricksList.push(brickData)
  519. // 生成方块
  520. const brickNode = this.generateBrick(brickKey, gridColorKey);
  521. if (this.itemNode) {
  522. this.itemNode.addChild(brickNode)
  523. }
  524. brickData.brickNode = brickNode
  525. // 方块间隔
  526. let offset = 220
  527. if (this.brickNum % 2 === 1) {
  528. const middleNum = Math.floor(this.brickNum / 2) + 1
  529. if (index < middleNum) {
  530. offset = - offset
  531. }
  532. else if (index === middleNum) {
  533. offset = 0
  534. }
  535. }
  536. if (brickData && brickData.brickNode) {
  537. let width = 0;
  538. if (this.itemNode) {
  539. width = this.itemNode.getComponent(UITransform)!.width;
  540. }
  541. brickData.brickNode.setPosition(offset, 0)
  542. brickData.brickNode.scale_x = 0.6;
  543. brickData.brickNode.scale_y = 0.6;
  544. brickData.brickInitPos = brickData.brickNode.getWorldPosition()
  545. this.brickAddEvent(brickData);
  546. }
  547. }
  548. touchStartLocation = new Vec2()
  549. touchStartFlag = false
  550. //方块添加监听事件
  551. brickAddEvent(brickData: BrickData) {
  552. const brickNode = brickData.brickNode
  553. if (!brickNode) {
  554. console.log("打印进这里来了")
  555. return
  556. }
  557. //这个事件不生效
  558. brickNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  559. // if (this.adShowingFlag) return
  560. // 未操作完时不能操作下一个方块
  561. if (this.editingFlag) return
  562. this.touchStartFlag = true
  563. this.editingFlag = true
  564. this.editingData.brickData = null
  565. this.editingData.gridList.length = 0
  566. // 隐藏游戏提示
  567. // this.gameTipNode.active = false
  568. // 记录开始触摸的位置,用于结束触摸时,判断位置是否是单击
  569. this.touchStartLocation.set(event.getUILocation())
  570. // 添加到移动节点里进行移动
  571. const pos = brickNode.getWorldPosition()
  572. brickNode.setParent(this.moveNode)
  573. brickNode.setWorldPosition(pos)
  574. // 添加放大动画
  575. tween(brickNode)
  576. .to(0.2, { scale: new Vec3(1, 1, 1) })
  577. .start()
  578. const index = this.bricksList.findIndex(data => data === brickData)
  579. if (index > -1) {
  580. this.editingData.brickData = this.bricksList.splice(index, 1)[0]
  581. } else {
  582. console.error("bricksList not find brickData:", brickData)
  583. }
  584. // 清除旋转数据
  585. if (this.rotateFlag && this.rotateBrickData !== this.editingData.brickData) {
  586. this.rotateFlag = false
  587. this.rotateBrickData = null
  588. if (this.rotateNode) {
  589. this.rotateNode.children.forEach(node => { node.destroy() })
  590. }
  591. }
  592. })
  593. // 触摸移动时
  594. brickNode.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  595. // 防止如放回方块回弹动画时,已经触摸在另一个方块上面,从而导致异常错误
  596. if (this.editingData.brickData !== brickData) return
  597. // 清除旋转数据
  598. if (event.getUILocation().subtract(this.touchStartLocation).length() >= this.rotateFaultTolerant) {
  599. this.rotateFlag = false
  600. this.rotateBrickData = null
  601. if (this.rotateNode) {
  602. this.rotateNode.children.forEach(node => { node.destroy() })
  603. }
  604. }
  605. // 格子颜色恢复
  606. this.gridColorRecovery()
  607. // 移动
  608. brickNode.setWorldPosition(brickNode.getWorldPosition().add(event.getUIDelta().toVec3()))
  609. // 每次移动重置数据
  610. this.editingData.gridList.length = 0
  611. // 实时获取方块位置判断在哪个格子上
  612. const tempGridList: GridData[] = []
  613. brickNode.children.forEach((brickGridNode) => {
  614. const brickGridPos = brickGridNode.getWorldPosition()
  615. let gridData: GridData | null = null
  616. for (let rowIndex = 0; rowIndex < this.max_row && gridData === null; rowIndex++) {
  617. for (let columnIndex = 0; columnIndex < this.max_col && gridData === null; columnIndex++) {
  618. const nowGridData = this.gridList[rowIndex][columnIndex];
  619. if (nowGridData && nowGridData.gridNode) {
  620. const gridPos = nowGridData.gridNode.getWorldPosition()
  621. if (Vec3.distance(gridPos, brickGridPos) <= (this.itemSize / 2 - this.itemSpacing)) {
  622. gridData = nowGridData
  623. }
  624. }
  625. }
  626. }
  627. if (gridData === null) return
  628. tempGridList.push(gridData)
  629. })
  630. // 检查整体情况
  631. let checkFlag = false
  632. if (
  633. tempGridList.length === brickData.gridConfig.length &&
  634. tempGridList.filter(d => d.status === GridStatus.NotUse).length === brickData.gridConfig.length
  635. ) {
  636. checkFlag = true
  637. tempGridList.forEach((gridData) => {
  638. this.editingData.gridList.push(gridData)
  639. })
  640. }
  641. // 格子给用户提示
  642. tempGridList.forEach((gridData) => {
  643. if (gridData.status !== GridStatus.NotUse) return
  644. if (gridData.gridNode) {
  645. const children = gridData.gridNode.children[0];
  646. if (children) {
  647. children.getComponent(Sprite)!.color = checkFlag ? this.usableColor : this.unavailableColor
  648. }
  649. }
  650. // 用于恢复格子
  651. this.gridColorList.push(gridData)
  652. })
  653. })
  654. // 触摸松开后
  655. brickNode.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
  656. // 当连击很快时,会出现1次start,2次end情况,为了避免所以通过标志位表示是一个连贯操作
  657. if (!this.touchStartFlag) return
  658. this.touchStartFlag = false
  659. // console.log("TOUCH_END", this.editingFlag, this.editingData.brickData.index)
  660. // 防止如放回方块回弹动画时,已经触摸在另一个方块上面,从而导致异常错误
  661. if (this.editingData.brickData !== brickData) return
  662. // 第二次单击,旋转
  663. if (this.rotateFlag) {
  664. const brickData = this.editingData.brickData
  665. this.brickGridRotate(brickData)
  666. // 方块放回待选区
  667. this.bricksList.push(brickData)
  668. if (this.itemNode && brickData.brickNode) {
  669. this.itemNode.addChild(brickData.brickNode)
  670. // brickData.brickNode.setWorldPosition(brickData.brickInitPos)
  671. tween(brickData.brickNode)
  672. .to(0.2, {
  673. worldPosition: brickData.brickInitPos,
  674. scale: new Vec3(0.8, 0.8, 0.8)
  675. })
  676. .start()
  677. }
  678. this.editingFlag = false
  679. }
  680. // 方块到格子
  681. else if (this.editingData.brickData && this.editingData.gridList.length > 0) {
  682. // 修改格子
  683. this.editingData.gridList.forEach((gridData) => {
  684. gridData.status = GridStatus.Used
  685. if (this.editingData.brickData) {
  686. gridData.gridColorKey = this.editingData.brickData.gridColorKey;
  687. }
  688. this.generateGrid(gridData)
  689. })
  690. // 销毁方块--这增加动画
  691. if (this.editingData.brickData && this.editingData.brickData.brickNode) {
  692. this.editingData.brickData.brickNode.destroy();
  693. }
  694. // 新增方块
  695. this.addBrick(this.editingData.brickData.index)
  696. this.scheduleOnce(() => {
  697. // 格子消除
  698. this.gridEliminate().then(() => {
  699. // 检查方块是否还能消除格子
  700. this.prompt(false).then((promptFlag) => {
  701. if (!promptFlag) {
  702. this.gameOver()
  703. }
  704. this.editingFlag = false
  705. })
  706. })
  707. })
  708. }
  709. // 方块回到待选区
  710. else {
  711. console.log("返回带待选区域")
  712. const brickData = this.editingData.brickData
  713. this.bricksList.push(brickData)
  714. // this.audioManager.playMoveFail()
  715. // 回弹动画
  716. if (brickData.brickNode) {
  717. tween(brickData.brickNode)
  718. .to(0.15, {
  719. worldPosition: this.editingData.brickData.brickInitPos,
  720. scale: new Vec3(0.8, 0.8, 0.8)
  721. })
  722. .call(() => {
  723. if (this.itemNode && brickData.brickNode) {
  724. this.itemNode.addChild(brickData.brickNode);
  725. brickData.brickNode.setWorldPosition(brickData.brickInitPos);
  726. }
  727. this.editingFlag = false;
  728. })
  729. .start();
  730. }
  731. }
  732. // 旋转标志位
  733. if (
  734. !this.rotateFlag &&
  735. this.editingData.brickData.rotateFlag &&
  736. event.getUILocation().subtract(this.touchStartLocation).length() < this.rotateFaultTolerant
  737. ) {
  738. console.log("进这里啦?????????????????????")
  739. this.rotateFlag = true
  740. this.rotateBrickData = this.editingData.brickData;
  741. if (this.rotateBrickData.brickNode) {
  742. const prosition = this.rotateBrickData.brickNode.getWorldPosition()
  743. const rotateNode = instantiate(this.rotatePrefab)
  744. if (this.rotateNode && rotateNode) {
  745. const instantiatedRotateNode = instantiate(rotateNode);
  746. this.rotateNode.addChild(instantiatedRotateNode);
  747. instantiatedRotateNode.setWorldPosition(prosition)
  748. }
  749. }
  750. }
  751. // 格子颜色恢复
  752. this.gridColorRecovery();
  753. })
  754. }
  755. // 提示
  756. prompt(tipFlag = true) {
  757. console.log("点击了提示按钮")
  758. return new Promise((resolve, reject) => {
  759. const gridPromptList: GridData[] = []
  760. let moveFlag = false
  761. // 找方块可消除位置
  762. for (let rowIndex = 0; rowIndex < this.max_row && !moveFlag; rowIndex++) {
  763. for (let columnIndex = 0; columnIndex < this.max_col && !moveFlag; columnIndex++) {
  764. const gridData = this.gridList[rowIndex][columnIndex]
  765. if (gridData.status !== GridStatus.NotUse) continue
  766. // 方块不旋转检测是否能放
  767. for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) {
  768. const brickData = this.bricksList[brickI]
  769. if (this.moveIf(rowIndex, columnIndex, brickData.gridConfig)) {
  770. // 复制整体网格,以方块设置网格状态
  771. const gridList = this.copyGridList()
  772. brickData.gridConfig.forEach((gridConfigData) => {
  773. gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex].status = GridStatus.Used
  774. })
  775. // 检查复制的整体网格是否有可消除
  776. if (this.gridEliminateCheck(gridList).gridEliminateList.length > 0) {
  777. moveFlag = true
  778. brickData.gridConfig.forEach((gridConfigData) => {
  779. gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex])
  780. })
  781. }
  782. }
  783. }
  784. // 方块旋转检测是否能放
  785. for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) {
  786. const brickData = this.bricksList[brickI]
  787. if (!brickData.rotateFlag) continue
  788. let gridConfig = brickData.gridConfig
  789. let deg = brickData.deg
  790. // 获得旋转的方块网格配置
  791. for (let count = 1; count <= 3 && !moveFlag; count++) {
  792. const next = this.nextGridRotate(gridConfig, deg)
  793. if (this.moveIf(rowIndex, columnIndex, next.gridConfig)) {
  794. // 复制整体网格,以方块设置网格状态
  795. const gridList = this.copyGridList()
  796. next.gridConfig.forEach((gridConfigData) => {
  797. gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex].status = GridStatus.Used
  798. })
  799. // 检查复制的整体网格是否有可消除
  800. if (this.gridEliminateCheck(gridList).gridEliminateList.length > 0) {
  801. moveFlag = true
  802. next.gridConfig.forEach((gridConfigData) => {
  803. gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex])
  804. })
  805. }
  806. }
  807. gridConfig = next.gridConfig
  808. deg = next.deg
  809. }
  810. }
  811. }
  812. }
  813. // 找方块可放置位置
  814. for (let rowIndex = 0; rowIndex < this.max_row && !moveFlag; rowIndex++) {
  815. for (let columnIndex = 0; columnIndex < this.max_col && !moveFlag; columnIndex++) {
  816. const gridData = this.gridList[rowIndex][columnIndex]
  817. if (gridData.status !== GridStatus.NotUse) continue
  818. // 方块不旋转检测是否能放
  819. for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) {
  820. const brickData = this.bricksList[brickI]
  821. if (this.moveIf(rowIndex, columnIndex, brickData.gridConfig)) {
  822. moveFlag = true
  823. brickData.gridConfig.forEach((gridConfigData) => {
  824. gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex])
  825. })
  826. }
  827. }
  828. // 方块旋转检测是否能放
  829. for (let brickI = 0; brickI < this.bricksList.length && !moveFlag; brickI++) {
  830. const brickData = this.bricksList[brickI]
  831. if (!brickData.rotateFlag) continue
  832. let gridConfig = brickData.gridConfig
  833. let deg = brickData.deg
  834. for (let count = 1; count <= 3 && !moveFlag; count++) {
  835. const next = this.nextGridRotate(gridConfig, deg)
  836. if (this.moveIf(rowIndex, columnIndex, next.gridConfig)) {
  837. moveFlag = true
  838. next.gridConfig.forEach((gridConfigData) => {
  839. gridPromptList.push(this.gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex])
  840. })
  841. }
  842. gridConfig = next.gridConfig
  843. deg = next.deg
  844. }
  845. }
  846. }
  847. }
  848. if (gridPromptList.length < 1) {
  849. resolve(false)
  850. return
  851. }
  852. if (!tipFlag) {
  853. resolve(true)
  854. return
  855. }
  856. // 提示用户(网格变绿)
  857. gridPromptList.forEach((gridData) => {
  858. if (gridData.gridNode && gridData.gridNode.children[0]) {
  859. const sprite = gridData.gridNode.children[0].getComponent(Sprite);
  860. if (sprite) {
  861. sprite.color = this.usableColor;
  862. }
  863. }
  864. // 用于恢复格子
  865. this.gridColorList.push(gridData)
  866. })
  867. resolve(true)
  868. })
  869. }
  870. // 复制整体网格
  871. copyGridList() {
  872. const gridList: GridData[][] = []
  873. for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) {
  874. gridList.push([])
  875. for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) {
  876. const gridData = this.gridList[rowIndex][columnIndex]
  877. gridList[rowIndex].push({
  878. name: gridData.name,
  879. status: gridData.status,
  880. gridNode: null,
  881. row: gridData.row,
  882. column: gridData.column,
  883. gridColorKey: gridData.gridColorKey,
  884. })
  885. }
  886. }
  887. return gridList
  888. }
  889. // 检查是否能放置方块
  890. moveIf(row: number, column: number, gridConfig: GridConfigData[]) {
  891. let moveFlag = true
  892. for (let i = 0; i < gridConfig.length; i++) {
  893. const gridConfigData = gridConfig[i]
  894. const gridI = row + gridConfigData.row
  895. const gridJ = column + gridConfigData.column
  896. // 边界判断
  897. if (
  898. gridI < 0 ||
  899. gridI > this.max_row - 1 ||
  900. gridJ < 0 ||
  901. gridJ > this.max_col - 1
  902. ) {
  903. moveFlag = false
  904. break
  905. }
  906. // 已用
  907. else if (this.gridList[gridI][gridJ].status === GridStatus.Used) {
  908. moveFlag = false
  909. break
  910. }
  911. }
  912. return moveFlag
  913. }
  914. brickGridRotate(brickData: BrickData) {
  915. const next = this.nextGridRotate(brickData.gridConfig, brickData.deg)
  916. brickData.deg = next.deg
  917. brickData.gridConfig = next.gridConfig;
  918. if (brickData.brickNode) {
  919. tween(brickData.brickNode).to(0.1, { angle: next.deg }).start();
  920. }
  921. // this.audioManager.playRotate()
  922. }
  923. //下一个旋转
  924. nextGridRotate(gridConfig: GridConfigData[], deg: number) {
  925. const newGridConfig: GridConfigData[] = []
  926. // 顺时针旋转
  927. let newDeg = deg - 90
  928. gridConfig.forEach((gridConfigData) => {
  929. // 例如(1,2) => (-2,1),可以画图分析
  930. newGridConfig.push({
  931. row: -gridConfigData.column,
  932. column: gridConfigData.row
  933. })
  934. })
  935. return { gridConfig: newGridConfig, deg: newDeg }
  936. }
  937. //格子颜色恢复
  938. gridColorRecovery() {
  939. while (this.gridColorList.length > 0) {
  940. const gridData = this.gridColorList.pop();
  941. if (gridData) {
  942. if (gridData.status === GridStatus.NotUse) {
  943. if (gridData.gridNode) {
  944. gridData.gridNode.children[0].getComponent(Sprite)!.color = this.notUseColor;
  945. }
  946. }
  947. }
  948. }
  949. }
  950. // 生成独立的方块节点
  951. generateBrick(brickKey: string, gridColorKey: string) {
  952. const brickConfig = this.gameConfig['bricks'][brickKey]
  953. let rowMin = 0
  954. let rowMax = 0
  955. let columnMin = 0
  956. let columnMax = 0
  957. brickConfig['gridConfig'].forEach((gridConfigData: GridConfigData) => {
  958. if (gridConfigData.row < rowMin) {
  959. rowMin = gridConfigData.row
  960. }
  961. else if (gridConfigData.row > rowMax) {
  962. rowMax = gridConfigData.row
  963. }
  964. if (gridConfigData.column < columnMin) {
  965. columnMin = gridConfigData.column
  966. }
  967. else if (gridConfigData.column > columnMax) {
  968. columnMax = gridConfigData.column
  969. }
  970. })
  971. const rowNum = (rowMax - rowMin + 1)
  972. const columnNum = (columnMax - columnMin + 1)
  973. // 生成独立的方块节点
  974. const brickNode = new Node()
  975. brickNode.name = brickKey
  976. const transformCom: UITransform = brickNode.addComponent(UITransform)
  977. transformCom.setContentSize(
  978. this.itemSize * columnNum,
  979. this.itemSize * rowNum
  980. )
  981. transformCom.setAnchorPoint(0.5, 0.5)
  982. const gridPrefab = this.prefabUrlMap[this.gameConfig['GridType'][gridColorKey]['prefabUrl']]
  983. //生成对应的配置方块
  984. brickConfig['gridConfig'].forEach((gridConfigData: GridConfigData) => {
  985. const gridNode = new Node()
  986. gridNode.name = 'grid'
  987. brickNode.addChild(gridNode)
  988. gridNode.addComponent(UITransform).setContentSize(this.itemSize, this.itemSize)
  989. const gridWidget: Widget = gridNode.addComponent(Widget)
  990. gridWidget.isAlignLeft = true
  991. gridWidget.left = this.itemSize * gridConfigData.column - this.itemSize * columnMin
  992. gridWidget.isAlignBottom = true
  993. gridWidget.bottom = this.itemSize * gridConfigData.row - this.itemSize * rowMin
  994. const node = instantiate(gridPrefab)
  995. gridNode.addChild(node)
  996. const uiTransform = node.getComponent(UITransform);
  997. if (uiTransform) {
  998. uiTransform.setContentSize(
  999. this.itemSize - this.itemSpacing * 2,
  1000. this.itemSize - this.itemSpacing * 2,
  1001. )
  1002. }
  1003. node.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  1004. event.propagationStopped = false
  1005. })
  1006. node.setPosition(Vec3.ZERO)
  1007. })
  1008. return brickNode
  1009. }
  1010. // 消除,这里增加飞的动画
  1011. gridEliminate() {
  1012. return new Promise((resolve, reject) => {
  1013. const d = this.gridEliminateCheck(this.gridList)
  1014. const gridEliminateList = d.gridEliminateList
  1015. const eliminateRowNum = d.eliminateRowNum
  1016. const eliminateColumnNum = d.eliminateColumnNum
  1017. if (gridEliminateList.length < 1) {
  1018. resolve(false)
  1019. return
  1020. }
  1021. gridEliminateList.forEach((gridData) => {
  1022. if (gridData.gridNode?.children[0]) {
  1023. const startPos = gridData.gridNode.getWorldPosition();
  1024. tween(gridData.gridNode.children[0])
  1025. .to(0.2, { scale: new Vec3(0.5, 0.5) })
  1026. .call(() => {
  1027. gridData.status = GridStatus.NotUse
  1028. this.generateGrid(gridData)
  1029. })
  1030. .start()
  1031. if (this.coinPrefab && this.amountLb?.node) {
  1032. this.createCoinFlyAnimation(
  1033. this.coinPrefab,
  1034. startPos,
  1035. this.amountLb.node.getWorldPosition(),
  1036. 5
  1037. );
  1038. }
  1039. //增加红包
  1040. this.createCoinFlyAnimation(
  1041. this.redPacketPrefab,
  1042. startPos,
  1043. this.awardLb.node.getWorldPosition(),
  1044. 5
  1045. );
  1046. }
  1047. })
  1048. this.scheduleOnce(() => {
  1049. let score = 0
  1050. for (let i = 1; i <= eliminateRowNum; i++) {
  1051. score += this.max_col * i
  1052. }
  1053. for (let i = 1; i <= eliminateColumnNum; i++) {
  1054. score += this.max_row * i
  1055. }
  1056. this.score += score
  1057. const lastGrid = gridEliminateList[gridEliminateList.length - 1];
  1058. if (lastGrid?.gridNode && this.coinPrefab && this.amountLb?.node) {
  1059. const lastPos = lastGrid.gridNode.getWorldPosition();
  1060. this.createCoinFlyAnimation(
  1061. this.coinPrefab,
  1062. lastPos,
  1063. this.amountLb.node.getWorldPosition(),
  1064. score
  1065. );
  1066. }
  1067. resolve(true)
  1068. }, 0.2)
  1069. })
  1070. }
  1071. // 检查消除
  1072. gridEliminateCheck(gridList: GridData[][]) {
  1073. const gridEliminateList: GridData[] = []
  1074. let eliminateRowNum = 0
  1075. let eliminateColumnNum = 0
  1076. // 行检查
  1077. for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) {
  1078. const rowData = gridList[rowIndex]
  1079. if (rowData.every(gridData => gridData.status === GridStatus.Used)) {
  1080. rowData.forEach(gridData => {
  1081. if (gridEliminateList.findIndex(data => data === gridData) < 0) {
  1082. gridEliminateList.push(gridData)
  1083. }
  1084. })
  1085. eliminateRowNum += 1
  1086. }
  1087. }
  1088. // 列检查
  1089. for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) {
  1090. if (gridList.every(rowData => rowData[columnIndex].status === GridStatus.Used)) {
  1091. gridList.forEach(rowData => {
  1092. const gridData = rowData[columnIndex]
  1093. if (gridEliminateList.findIndex(data => data === gridData) < 0) {
  1094. gridEliminateList.push(gridData)
  1095. }
  1096. })
  1097. eliminateColumnNum += 1
  1098. }
  1099. }
  1100. return {
  1101. gridEliminateList,
  1102. eliminateRowNum,
  1103. eliminateColumnNum,
  1104. }
  1105. }
  1106. async gameOver() {
  1107. // 先禁用所有操作
  1108. this.isAutoMode = false
  1109. this.editingFlag = true // 防止操作
  1110. // 执行格子变灰动画
  1111. await this.playGameOverGridAnimation()
  1112. // 播放游戏结束音效
  1113. // this.audioManager.playGameOver()
  1114. // 设置游戏状态
  1115. this.setGameStatus(GameStatus.Over)
  1116. // 恢复操作标志
  1117. this.editingFlag = false
  1118. }
  1119. private async playGameOverGridAnimation(): Promise<void> {
  1120. // 按行从左到右收集所有已使用的格子
  1121. const usedGrids: GridData[] = []
  1122. for (let row = 0; row < this.max_row; row++) {
  1123. for (let col = 0; col < this.max_col; col++) {
  1124. const grid = this.gridList[row][col]
  1125. if (grid.status === GridStatus.Used) {
  1126. usedGrids.push(grid)
  1127. }
  1128. }
  1129. }
  1130. // 执行变灰动画
  1131. return new Promise<void>((resolve) => {
  1132. let completedCount = 0
  1133. usedGrids.forEach((grid, index) => {
  1134. this.scheduleOnce(() => {
  1135. // 获取格子的 Sprite 组件
  1136. if (grid.gridNode) {
  1137. const gridSprite = grid.gridNode.children[0].getComponent(Sprite)
  1138. // 创建变灰动画
  1139. if (!gridSprite) {
  1140. return
  1141. }
  1142. tween(gridSprite)
  1143. .to(0.2, { color: this.GRID_GRAY_COLOR })
  1144. .call(() => {
  1145. completedCount++
  1146. if (completedCount === usedGrids.length) {
  1147. resolve()
  1148. }
  1149. })
  1150. .start()
  1151. }
  1152. }, index * this.GRID_GRAY_DELAY)
  1153. })
  1154. // 如果没有已使用的格子,直接完成
  1155. if (usedGrids.length === 0) {
  1156. resolve()
  1157. }
  1158. })
  1159. }
  1160. // //刷新按钮
  1161. btn_refresh() {
  1162. if (
  1163. this.game_status !== GameStatus.Start
  1164. ) return
  1165. // 清除方块重新获取
  1166. if (this.itemNode) {
  1167. this.itemNode.children.forEach(node => { node.destroy() });
  1168. }
  1169. this.bricksList.length = 0
  1170. for (let i = 1; i <= this.brickNum; i++) {
  1171. this.addBrick(i)
  1172. }
  1173. // 清除旋转数据
  1174. if (this.rotateNode) {
  1175. this.rotateNode.children.forEach(node => { node.destroy() })
  1176. }
  1177. this.rotateFlag = false
  1178. this.rotateBrickData = null
  1179. // 恢复格子颜色
  1180. this.gridColorRecovery()
  1181. }
  1182. //提示按钮
  1183. btn_hint() {
  1184. if (
  1185. this.game_status !== GameStatus.Start
  1186. ) return
  1187. // 先检查是否死局
  1188. this.prompt(false).then((flag) => {
  1189. if (!flag) {
  1190. this.gameOver()
  1191. return
  1192. }
  1193. this.gridColorRecovery()
  1194. this.prompt()
  1195. })
  1196. }
  1197. //继续按钮
  1198. onContinueBtnClick() {
  1199. this.setGameStatus(GameStatus.Start);
  1200. }
  1201. // ... 修改 autoPlaceBrick 方法
  1202. async autoPlaceBrick() {
  1203. if (!this.isAutoMode) return;
  1204. if (this.bricksList.length === 0) return
  1205. // 遍历所有方块,找到一个可以放置的方块
  1206. let bestMove = null;
  1207. let selectedBrickIndex = -1;
  1208. for (let i = 0; i < this.bricksList.length; i++) {
  1209. const move = this.findBestPosition(this.bricksList[i]);
  1210. if (move) {
  1211. // 优先选择可以消除的位置
  1212. if (move.score > 0) {
  1213. bestMove = move;
  1214. selectedBrickIndex = i;
  1215. break;
  1216. } else if (bestMove === null) {
  1217. // 如果还没有找到任何可放置的位置,保存这个位置
  1218. bestMove = move;
  1219. selectedBrickIndex = i;
  1220. }
  1221. }
  1222. }
  1223. // 如果所有方块都没有可放置的位置,游戏结束
  1224. if (!bestMove || selectedBrickIndex === -1) {
  1225. console.log('所有方块都没有可放置的位置,游戏结束');
  1226. this.isAutoMode = false;
  1227. if (this._isAutoFunc) {
  1228. this.unschedule(this._isAutoFunc);
  1229. this._isAutoFunc = null;
  1230. }
  1231. this.gameOver();
  1232. return;
  1233. }
  1234. // 获取选中的方块
  1235. const brickData = this.bricksList[selectedBrickIndex];
  1236. // 从列表中移除方块
  1237. this.bricksList.splice(selectedBrickIndex, 1);
  1238. // 保存方块的初始位置(从待选区开始)
  1239. const startPos = brickData.brickInitPos.clone();
  1240. // 设置方块到移动层并确保位置精确
  1241. if (brickData.brickNode) {
  1242. brickData.brickNode.setParent(this.moveNode);
  1243. brickData.brickNode.setWorldPosition(startPos);
  1244. }
  1245. // this.guideNode.active = true;
  1246. // this.guideNode.setWorldPosition(
  1247. // startPos.x,
  1248. // startPos.y + brickData.brickNode.getComponent(UITransform).height / 2,
  1249. // startPos.z
  1250. // );
  1251. // 如果需要旋转,先旋转到正确的角度
  1252. if (brickData.rotateFlag && bestMove.rotation && bestMove.rotation.deg !== brickData.deg) {
  1253. await new Promise<void>((resolve) => {
  1254. if (brickData.brickNode) {
  1255. tween(brickData.brickNode)
  1256. .to(0.2, { angle: bestMove.rotation ? bestMove.rotation.deg : 0 })
  1257. .call(() => {
  1258. if (bestMove.rotation) {
  1259. brickData.gridConfig = bestMove.rotation.gridConfig;
  1260. brickData.deg = bestMove.rotation.deg;
  1261. }
  1262. resolve();
  1263. })
  1264. .start();
  1265. }
  1266. });
  1267. }
  1268. // 获取目标位置,有些目标位置获取有问题需要修改
  1269. const targetGrid = this.gridList[bestMove.position.row][bestMove.position.column];
  1270. if (!targetGrid.gridNode) {
  1271. console.error('targetGrid.gridNode is null');
  1272. return;
  1273. }
  1274. const targetWorldPos = targetGrid.gridNode.getWorldPosition().clone();
  1275. // 创建平滑的移动动画
  1276. await new Promise<void>((resolve) => {
  1277. if (brickData.brickNode) {
  1278. tween(brickData.brickNode)
  1279. .to(0.3, {
  1280. worldPosition: targetWorldPos,
  1281. }, {
  1282. easing: 'cubicOut'
  1283. })
  1284. .call(() => {
  1285. if (brickData.brickNode) {
  1286. brickData.brickNode.setWorldPosition(targetWorldPos);
  1287. // 放置方块
  1288. if (bestMove.rotation) {
  1289. bestMove.rotation.gridConfig.forEach((gridConfigData) => {
  1290. const gridData = this.gridList[bestMove.position.row + gridConfigData.row][bestMove.position.column + gridConfigData.column];
  1291. gridData.status = GridStatus.Used;
  1292. gridData.gridColorKey = brickData.gridColorKey;
  1293. this.generateGrid(gridData);
  1294. });
  1295. }
  1296. // 销毁方块
  1297. brickData.brickNode.destroy();
  1298. // 添加新方块
  1299. this.addBrick(brickData.index);
  1300. }
  1301. resolve();
  1302. })
  1303. .start();
  1304. }
  1305. // 引导节点跟着移动
  1306. // if (this.guildNode) {
  1307. // tween(this.guideNode)
  1308. // .to(0.3, {
  1309. // worldPosition: targetWorldPos
  1310. // })
  1311. // .call(() => {
  1312. // this.guideNode.active = false;
  1313. // })
  1314. // .start();
  1315. // }
  1316. });
  1317. // 检查并执行消除
  1318. await this.gridEliminate();
  1319. // 检查是否还有任何方块可以放置
  1320. let hasValidMove = false;
  1321. for (const brick of this.bricksList) {
  1322. if (this.findBestPosition(brick)) {
  1323. hasValidMove = true;
  1324. break;
  1325. }
  1326. }
  1327. if (!hasValidMove) {
  1328. console.log('没有方块可以放置,游戏结束');
  1329. this.isAutoMode = false;
  1330. if (this._isAutoFunc) {
  1331. this.unschedule(this._isAutoFunc);
  1332. this._isAutoFunc = null;
  1333. }
  1334. this.gameOver();
  1335. }
  1336. }
  1337. // ... 修改 findBestPosition 方法,这个方法有问题,需要修改
  1338. findBestPosition(brickData: BrickData) {
  1339. let bestScore = -1
  1340. let bestPosition = null
  1341. let bestRotation = null
  1342. let fallbackPosition = null
  1343. let fallbackRotation = null
  1344. // 遍历所有可能的位置
  1345. for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) {
  1346. for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) {
  1347. // 检查当前位置的所有可能旋转
  1348. let currentGridConfig = brickData.gridConfig
  1349. let currentDeg = brickData.deg
  1350. // 最多旋转4次(0°, 90°, 180°, 270°)
  1351. for (let rotation = 0; rotation < 4; rotation++) {
  1352. if (this.moveIf(rowIndex, columnIndex, currentGridConfig)) {
  1353. // 如果还没有找到后备位置,保存第一个可放置的位置
  1354. if (fallbackPosition === null) {
  1355. fallbackPosition = { row: rowIndex, column: columnIndex }
  1356. fallbackRotation = { gridConfig: currentGridConfig, deg: currentDeg }
  1357. }
  1358. // 模拟放置并计算得分
  1359. const gridList = this.copyGridList()
  1360. currentGridConfig.forEach((gridConfigData) => {
  1361. gridList[gridConfigData.row + rowIndex][gridConfigData.column + columnIndex].status = GridStatus.Used
  1362. })
  1363. // 计算这个位置的得分
  1364. const eliminateCheck = this.gridEliminateCheck(gridList)
  1365. let positionScore = eliminateCheck.gridEliminateList.length
  1366. // 如果这个位置比之前找到的更好
  1367. if (positionScore > bestScore) {
  1368. bestScore = positionScore
  1369. bestPosition = { row: rowIndex, column: columnIndex }
  1370. bestRotation = { gridConfig: currentGridConfig, deg: currentDeg }
  1371. }
  1372. }
  1373. // 如果方块可以旋转,计算下一个旋转状态
  1374. if (brickData.rotateFlag && rotation < 3) {
  1375. const next = this.nextGridRotate(currentGridConfig, currentDeg)
  1376. currentGridConfig = next.gridConfig
  1377. currentDeg = next.deg
  1378. } else {
  1379. break
  1380. }
  1381. }
  1382. }
  1383. }
  1384. // 如果找到了最佳得分位置,返回它
  1385. if (bestPosition !== null) {
  1386. return {
  1387. position: bestPosition,
  1388. rotation: bestRotation,
  1389. score: bestScore
  1390. }
  1391. }
  1392. // 如果没有找到可消除的位置但有可放置的位置,返回第一个可放置的位置
  1393. if (fallbackPosition !== null) {
  1394. return {
  1395. position: fallbackPosition,
  1396. rotation: fallbackRotation,
  1397. score: 0
  1398. }
  1399. }
  1400. // 如果真的没有任何可放置的位置,返回null
  1401. return null
  1402. }
  1403. //重新开始-这样正常要接广告
  1404. private btn_reopen() {
  1405. console.log("重新开始");
  1406. if (this.game_status === GameStatus.None) return
  1407. this.initGameData();
  1408. this.reopenGrid();
  1409. }
  1410. private reopenGrid() {
  1411. for (let rowIndex = 0; rowIndex < this.max_row; rowIndex++) {
  1412. for (let columnIndex = 0; columnIndex < this.max_col; columnIndex++) {
  1413. this.gridList[rowIndex][columnIndex].status = GridStatus.NotUse
  1414. this.generateGrid(this.gridList[rowIndex][columnIndex])
  1415. }
  1416. }
  1417. // 初始化方块
  1418. if (this.itemNode) {
  1419. if (this.itemNode) {
  1420. this.itemNode.children.forEach(node => { node.destroy() });
  1421. }
  1422. }
  1423. this.bricksList.length = 0
  1424. for (let i = 1; i <= this.brickNum; i++) {
  1425. this.addBrick(i)
  1426. }
  1427. // 清除旋转数据
  1428. if (this.rotateNode) {
  1429. this.rotateNode.children.forEach(node => { node.destroy() })
  1430. }
  1431. }
  1432. // ... 添加自动模式的开关方法
  1433. onAutoButtonClick() {
  1434. if (
  1435. this.game_status !== GameStatus.Start ||
  1436. this.editingFlag
  1437. ) return
  1438. // 切换自动模式状态
  1439. this.isAutoMode = !this.isAutoMode
  1440. if (this.isAutoMode) {
  1441. this._isAutoFunc = () => {
  1442. this.autoPlaceBrick()
  1443. }
  1444. this.schedule(this._isAutoFunc, this.autoModeInterval)
  1445. console.log('开启自动模式')
  1446. } else {
  1447. if (this._isAutoFunc) {
  1448. this.unschedule(this._isAutoFunc)
  1449. this._isAutoFunc = null
  1450. }
  1451. console.log('关闭自动模式')
  1452. }
  1453. }
  1454. private btn_auto() {
  1455. // if (
  1456. // this.game_status !== GameStatus.Start ||
  1457. // this.editingFlag
  1458. // ) return
  1459. // 切换自动模式状态
  1460. this.isAutoMode = !this.isAutoMode
  1461. if (this.isAutoMode) {
  1462. this._isAutoFunc = () => {
  1463. this.autoPlaceBrick()
  1464. }
  1465. this.schedule(this._isAutoFunc, this.autoModeInterval)
  1466. console.log('开启自动模式')
  1467. } else {
  1468. if (this._isAutoFunc) {
  1469. this.unschedule(this._isAutoFunc)
  1470. this._isAutoFunc = null
  1471. }
  1472. console.log('关闭自动模式')
  1473. }
  1474. }
  1475. // 修改 createCoinFlyAnimation 方法为公共方法,扩展参数列表
  1476. public createCoinFlyAnimation(prefab: Prefab, startPos: Vec3, endPos: Vec3, score: number, parent: Node = this.node) {
  1477. const coinNum = Math.min(Math.max(Math.floor(score / 10), 1), 10);
  1478. for (let i = 0; i < coinNum; i++) {
  1479. if (!prefab) {
  1480. console.warn('金币预制体未加载');
  1481. return;
  1482. }
  1483. const coin = instantiate(prefab);
  1484. parent.addChild(coin);
  1485. const randomOffset = new Vec3(
  1486. (Math.random() - 0.5) * 50,
  1487. (Math.random() - 0.5) * 50,
  1488. 0
  1489. );
  1490. const startPosition = startPos.clone().add(randomOffset);
  1491. coin.setWorldPosition(startPosition);
  1492. const controlPoint = new Vec3(
  1493. (startPosition.x + endPos.x) / 2 + (Math.random() - 0.5) * 100,
  1494. (startPosition.y + endPos.y) / 2 + Math.random() * 100,
  1495. 0
  1496. );
  1497. this.scheduleOnce(() => {
  1498. tween(coin)
  1499. .parallel(
  1500. tween().to(0.2, { scale: new Vec3(1.2, 1.2, 1.2) })
  1501. .to(0.1, { scale: new Vec3(1, 1, 1) }),
  1502. tween().by(0.3, { angle: 360 })
  1503. )
  1504. .start();
  1505. const bezierCurve = (target: Node | undefined, ratio?: number) => {
  1506. if (!target || ratio === undefined) return;
  1507. const t = ratio;
  1508. const pos = new Vec3();
  1509. Vec3.multiplyScalar(pos, startPosition, (1 - t) * (1 - t));
  1510. Vec3.scaleAndAdd(pos, pos, controlPoint, 2 * (1 - t) * t);
  1511. Vec3.scaleAndAdd(pos, pos, endPos, t * t);
  1512. target.setWorldPosition(pos);
  1513. };
  1514. tween(coin)
  1515. .to(0.8, {
  1516. worldPosition: endPos
  1517. }, {
  1518. easing: 'cubicIn',
  1519. onUpdate: bezierCurve
  1520. })
  1521. .call(() => {
  1522. tween(coin)
  1523. .to(0.1, { scale: new Vec3(0.7, 0.7, 0.7) })
  1524. .call(() => {
  1525. coin.destroy();
  1526. })
  1527. .start();
  1528. })
  1529. .start();
  1530. }, i * 0.1);
  1531. }
  1532. }
  1533. }