EliminateViewComp.ts 57 KB

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