EliminateBrickManager.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /*
  2. * @Author: mojunshou 1637302775@qq.com
  3. * @Date: 2025-04-20 10:00:00
  4. * @LastEditors: mojunshou 1637302775@qq.com
  5. * @LastEditTime: 2025-04-20 10:00:00
  6. * @Description: 消除游戏方块管理器
  7. */
  8. import { EventTouch, JsonAsset, Node, Prefab, UITransform, Vec2, Vec3, tween, instantiate } from "cc";
  9. import { oops } from "db://oops-framework/core/Oops";
  10. import { BrickData, GridConfigData, GridData } from "./EliminateTypes";
  11. export class EliminateBrickManager {
  12. private brickNode: Node | null = null; // 方块容器
  13. private rotateNode: Node | null = null; // 旋转容器
  14. private rotatePrefab: Node | null = null; // 旋转预制体
  15. private moveNode: Node | null = null; // 移动容器
  16. private itemSize: number = 76.25; // 格子大小
  17. private itemPrefabs: Prefab[] = []; // 方块预制体列表
  18. private rotateFaultTolerant = 10; // 旋转容错
  19. private yOffset = 100; // Y轴偏移
  20. private aniBrickRotate = 0; // 旋转动画时间
  21. private bricksList: BrickData[] = []; // 所有可用方块
  22. private brickConfig: { bricks?: any } = {}; // 方块配置
  23. constructor(
  24. brickNode: Node,
  25. rotateNode: Node,
  26. moveNode: Node,
  27. itemPrefabs: Prefab[],
  28. rotatePrefab: Node,
  29. itemSize: number
  30. ) {
  31. this.brickNode = brickNode;
  32. this.rotateNode = rotateNode;
  33. this.moveNode = moveNode;
  34. this.itemPrefabs = itemPrefabs;
  35. this.rotatePrefab = rotatePrefab;
  36. this.itemSize = itemSize;
  37. }
  38. /**
  39. * 加载方块配置文件
  40. */
  41. public async loadConfig(): Promise<void> {
  42. let json_name: string = "gui/eliminate/config/GridConfig";
  43. return new Promise<void>((resolve, reject) => {
  44. oops.res.load(json_name, JsonAsset, (err: Error | null, res: any) => {
  45. if (res) {
  46. this.brickConfig = res.json;
  47. resolve();
  48. } else {
  49. console.log("JSON数据加载失败,请检查文件");
  50. reject(err);
  51. }
  52. });
  53. });
  54. }
  55. /**
  56. * 初始化方块列表
  57. */
  58. public initBricks(count: number): void {
  59. if (this.brickNode) {
  60. this.brickNode.destroyAllChildren();
  61. }
  62. this.bricksList.length = 0;
  63. for (let i = 1; i <= count; i++) {
  64. this.addBrick(i);
  65. }
  66. }
  67. /**
  68. * 添加一个砖块
  69. */
  70. public addBrick(index: number, isGuideMode: boolean = false): BrickData {
  71. // 随机选择砖块类型
  72. const brickConfigs = Object.keys(this.brickConfig['bricks']);
  73. let brickKey;
  74. if (isGuideMode) {
  75. brickKey = "Brick1";
  76. } else {
  77. const randomIndex = Math.floor(Math.random() * brickConfigs.length);
  78. brickKey = brickConfigs[randomIndex];
  79. }
  80. // 随机选择颜色
  81. const randomColorIndex = Math.floor(Math.random() * 3) + 1;
  82. // 创建砖块
  83. const brickNode = this.generateBrick(brickKey, randomColorIndex);
  84. if (!brickNode || !this.brickNode) {
  85. throw new Error("无法生成砖块");
  86. }
  87. this.brickNode.addChild(brickNode);
  88. // 设置砖块位置
  89. const brickConfig = this.brickConfig['bricks'][brickKey];
  90. const rotateFlag = brickConfig.rotateFlag || false;
  91. // 创建砖块数据对象
  92. const brickData: BrickData = {
  93. index: index,
  94. brickKey: brickKey,
  95. rotateFlag: rotateFlag,
  96. gridConfig: JSON.parse(JSON.stringify(brickConfig.gridConfig)),
  97. deg: 0,
  98. brickNode: brickNode,
  99. brickInitPos: new Vec3(this.calculateInitPosition(index)),
  100. type: randomColorIndex,
  101. rotateNode: null
  102. };
  103. // 设置初始位置
  104. brickNode.setWorldPosition(brickData.brickInitPos);
  105. brickNode.scale.set(0.8, 0.8, 0.8);
  106. // 创建旋转节点
  107. if (rotateFlag) {
  108. this.createRotateNode(brickData);
  109. }
  110. // 添加到方块列表
  111. this.bricksList.push(brickData);
  112. return brickData;
  113. }
  114. /**
  115. * 生成方块节点
  116. */
  117. public generateBrick(brickKey: string, colorIndex: number): Node {
  118. const brickConfig = this.brickConfig['bricks'][brickKey];
  119. let rowMin = 0;
  120. let rowMax = 0;
  121. let columnMin = 0;
  122. let columnMax = 0;
  123. // 计算方块尺寸
  124. brickConfig['gridConfig'].forEach((gridConfigData: GridConfigData) => {
  125. if (gridConfigData.row < rowMin) {
  126. rowMin = gridConfigData.row;
  127. }
  128. else if (gridConfigData.row > rowMax) {
  129. rowMax = gridConfigData.row;
  130. }
  131. if (gridConfigData.column < columnMin) {
  132. columnMin = gridConfigData.column;
  133. }
  134. else if (gridConfigData.column > columnMax) {
  135. columnMax = gridConfigData.column;
  136. }
  137. });
  138. const rowNum = (rowMax - rowMin + 1);
  139. const columnNum = (columnMax - columnMin + 1);
  140. // 创建方块节点
  141. const brickNode = new Node();
  142. brickNode.name = brickKey;
  143. // 设置方块大小
  144. const transformCom: UITransform = brickNode.addComponent(UITransform);
  145. transformCom.setContentSize(
  146. this.itemSize * columnNum,
  147. this.itemSize * rowNum
  148. );
  149. transformCom.setAnchorPoint(0.5, 0.5);
  150. // 创建子格子
  151. const gridPrefab = this.itemPrefabs[colorIndex];
  152. brickConfig['gridConfig'].forEach((gridConfigData: GridConfigData) => {
  153. const gridNode = new Node();
  154. gridNode.name = 'grid';
  155. brickNode.addChild(gridNode);
  156. // 设置子格子大小
  157. gridNode.addComponent(UITransform).setContentSize(this.itemSize, this.itemSize);
  158. // 设置子格子位置
  159. const x = this.itemSize * gridConfigData.column - this.itemSize * columnMin;
  160. const y = this.itemSize * gridConfigData.row - this.itemSize * rowMin;
  161. gridNode.setPosition(x, y);
  162. // 创建格子显示节点
  163. const node = instantiate(gridPrefab);
  164. gridNode.addChild(node);
  165. // 设置格子显示节点大小
  166. const uiTransform = node.getComponent(UITransform);
  167. if (uiTransform) {
  168. uiTransform.setContentSize(
  169. this.itemSize,
  170. this.itemSize,
  171. );
  172. }
  173. // 设置位置
  174. node.setPosition(Vec3.ZERO);
  175. // 确保事件冒泡
  176. node.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  177. event.preventSwallow = true;
  178. }, this);
  179. node.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  180. event.preventSwallow = true;
  181. }, this);
  182. node.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
  183. event.preventSwallow = true;
  184. }, this);
  185. });
  186. return brickNode;
  187. }
  188. /**
  189. * 创建引导砖块
  190. */
  191. public createGuideBrick(
  192. gridConfig: GridConfigData[],
  193. brickKey: string,
  194. colorIndex: number,
  195. index: number,
  196. rotateFlag = false
  197. ): BrickData {
  198. const brickData = {
  199. index,
  200. brickKey: brickKey,
  201. rotateFlag,
  202. gridConfig,
  203. deg: 0,
  204. brickNode: new Node(),
  205. gridColorKey: "colorKey",
  206. brickInitPos: new Vec3(),
  207. type: colorIndex,
  208. rotateNode: new Node(),
  209. };
  210. const node = this.generateBrick(brickKey, colorIndex);
  211. if (this.brickNode) {
  212. this.brickNode.addChild(node);
  213. }
  214. const transform = this.brickNode?.getComponent(UITransform);
  215. if (transform) {
  216. const midX = transform.width / 2;
  217. node.setPosition(0, 0);
  218. brickData.brickNode = node;
  219. brickData.brickInitPos = node.getWorldPosition();
  220. this.bricksList.push(brickData);
  221. this.brickAddEvent(brickData, () => { });
  222. }
  223. if (brickData.rotateFlag && this.rotatePrefab && this.rotateNode) {
  224. brickData.rotateNode = instantiate(this.rotatePrefab);
  225. this.rotateNode.addChild(brickData.rotateNode);
  226. //先隐藏
  227. brickData.rotateNode.active = false;
  228. brickData.rotateNode.setWorldPosition(this.brickNode!.getWorldPosition());
  229. }
  230. return brickData;
  231. }
  232. /**
  233. * 给砖块添加事件
  234. */
  235. public brickAddEvent(brickData: BrickData, onDragHandler: (brickData: BrickData, position: Vec3, startPos: Vec2, delta: Vec2) => void): void {
  236. const brickNode = brickData.brickNode;
  237. if (!brickNode) {
  238. console.error("brickNode为空,无法添加事件");
  239. return;
  240. }
  241. // 记录触摸开始位置
  242. let touchStartPos = new Vec2();
  243. // 触摸开始事件
  244. brickNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  245. // 记录触摸起始位置
  246. touchStartPos.set(event.getUILocation());
  247. }, this);
  248. // 触摸移动事件
  249. brickNode.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
  250. const currentPos = event.getUILocation();
  251. const delta = new Vec2(
  252. currentPos.x - touchStartPos.x,
  253. currentPos.y - touchStartPos.y
  254. );
  255. // 如果移动距离太小,忽略
  256. if (delta.length() <= this.rotateFaultTolerant) {
  257. return;
  258. }
  259. // 调用拖动处理回调
  260. onDragHandler(brickData, event.getUILocation().toVec3().add3f(0, this.yOffset, 0), touchStartPos, delta);
  261. }, this);
  262. }
  263. /**
  264. * 旋转方块
  265. */
  266. public brickGridRotate(brickData: BrickData): Promise<boolean> {
  267. return new Promise((resolve) => {
  268. const next = this.nextGridRotate(brickData.gridConfig, brickData.deg);
  269. brickData.deg = next.deg;
  270. brickData.gridConfig = next.gridConfig;
  271. if (brickData.brickNode) {
  272. tween(brickData.brickNode)
  273. .to(this.aniBrickRotate, { angle: next.deg })
  274. .call(() => {
  275. resolve(true);
  276. })
  277. .start();
  278. } else {
  279. resolve(true);
  280. }
  281. });
  282. }
  283. /**
  284. * 计算下一个旋转状态
  285. */
  286. public nextGridRotate(gridConfig: GridConfigData[], deg: number): { gridConfig: GridConfigData[], deg: number } {
  287. const newGridConfig: GridConfigData[] = [];
  288. // 顺时针旋转
  289. let newDeg = deg - 90;
  290. gridConfig.forEach((gridConfigData) => {
  291. // 例如(1,2) => (-2,1),可以画图分析
  292. newGridConfig.push({
  293. row: -gridConfigData.column,
  294. column: gridConfigData.row
  295. });
  296. });
  297. return { gridConfig: newGridConfig, deg: newDeg };
  298. }
  299. /**
  300. * 获取所有方块
  301. */
  302. public getBricksList(): BrickData[] {
  303. return this.bricksList;
  304. }
  305. /**
  306. * 设置所有方块
  307. */
  308. public setBricksList(bricks: BrickData[]): void {
  309. this.bricksList = bricks;
  310. }
  311. /**
  312. * 移除方块
  313. */
  314. public removeBrick(brickData: BrickData): void {
  315. const index = this.bricksList.findIndex(item => item === brickData);
  316. if (index > -1) {
  317. this.bricksList.splice(index, 1);
  318. }
  319. }
  320. /**
  321. * 清理所有方块
  322. */
  323. public clearAllBricks(): void {
  324. if (this.brickNode) {
  325. this.brickNode.destroyAllChildren();
  326. }
  327. this.bricksList = [];
  328. }
  329. /**
  330. * 清理旋转节点
  331. */
  332. public clearAllRotateNodes(): void {
  333. if (this.rotateNode) {
  334. this.rotateNode.destroyAllChildren();
  335. }
  336. }
  337. /**
  338. * 给砖块添加拖动结束事件
  339. */
  340. public brickEndDrag(
  341. brickData: BrickData,
  342. onEndDragHandler: (brickData: BrickData, canPlace: boolean) => void
  343. ): void {
  344. const brickNode = brickData.brickNode;
  345. if (!brickNode) {
  346. console.error("brickNode为空,无法添加拖动结束事件");
  347. return;
  348. }
  349. // 添加触摸结束事件
  350. const touchEndHandler = (event: EventTouch) => {
  351. // 获取当前触摸位置
  352. const currentPos = event.getUILocation();
  353. // 调用处理函数
  354. onEndDragHandler(brickData, true);
  355. };
  356. brickNode.on(Node.EventType.TOUCH_END, touchEndHandler, this);
  357. brickNode.on(Node.EventType.TOUCH_CANCEL, touchEndHandler, this);
  358. }
  359. /**
  360. * 给砖块添加拖动开始事件
  361. */
  362. public brickStartDrag(
  363. brickData: BrickData,
  364. onStartDragHandler: (brickData: BrickData, startPos: Vec3) => void
  365. ): void {
  366. const brickNode = brickData.brickNode;
  367. if (!brickNode) {
  368. console.error("brickNode为空,无法添加拖动开始事件");
  369. return;
  370. }
  371. // 添加触摸开始事件
  372. brickNode.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
  373. // 记录触摸起始位置
  374. const startPos = brickNode.getWorldPosition().clone();
  375. // 调用处理函数
  376. onStartDragHandler(brickData, startPos);
  377. }, this);
  378. }
  379. /**
  380. * 计算初始位置
  381. */
  382. private calculateInitPosition(index: number): Vec3 {
  383. // 计算位置
  384. let offset = 220;
  385. const brickNum = this.bricksList.length + 1; // +1 因为当前砖块还未添加到列表
  386. if (brickNum % 2 === 1) {
  387. const middleNum = Math.floor(brickNum / 2) + 1;
  388. if (index < middleNum) {
  389. offset = -offset;
  390. }
  391. else if (index === middleNum) {
  392. offset = 0;
  393. }
  394. } else {
  395. const middleNum = brickNum / 2;
  396. if (index <= middleNum) {
  397. offset = -offset;
  398. }
  399. }
  400. return new Vec3(offset, 0, 0);
  401. }
  402. /**
  403. * 创建旋转节点
  404. */
  405. private createRotateNode(brickData: BrickData): void {
  406. if (!this.rotatePrefab || !this.rotateNode || !brickData.brickNode) {
  407. return;
  408. }
  409. brickData.rotateNode = instantiate(this.rotatePrefab);
  410. this.rotateNode.addChild(brickData.rotateNode);
  411. // 先隐藏
  412. brickData.rotateNode.active = false;
  413. brickData.rotateNode.setWorldPosition(brickData.brickNode.getWorldPosition());
  414. }
  415. }