GameUtil.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import { Vec2, Node, v2, PolygonCollider2D, Rect, CircleCollider2D } from "cc";
  2. /** 游戏工具类 */
  3. export class GameUtil {
  4. /** 转换成hh:mm格式*/
  5. // static formatToTimeString(totalSeconds: number): string {
  6. // const minutes = Math.floor(totalSeconds / 60);
  7. // const seconds = totalSeconds % 60;
  8. // const formattedMinutes = String(minutes).padStart(2, '0');
  9. // const formattedSeconds = String(seconds).padStart(2, '0');
  10. // return `${formattedMinutes}:${formattedSeconds}`;
  11. // }
  12. /** 重量单位转换*/
  13. static formatWeight(weight: number): string {
  14. if (weight < 1000) {
  15. return `${weight}KG`;
  16. }
  17. // 等于或超过1000时,转换为吨(T),保留两位小数并向下取整
  18. const inTons = Math.floor((weight / 1000) * 100) / 100;
  19. return `${inTons}T`;
  20. }
  21. /**
  22. * 将 16 进制颜色转换为 RGBA 格式
  23. * @param hex - 16 进制颜色字符串 (#FFE73A 或 FFE73A)
  24. * @param alpha - 可选的透明度值(范围 0~255,默认 255)
  25. * @returns Color - 包含 r, g, b, a 的对象
  26. */
  27. static hexToRGBA(hex: string, alpha: number = 255): { r: number; g: number; b: number; a: number } {
  28. // 去掉可能存在的 '#' 前缀
  29. hex = hex.replace(/^#/, '');
  30. // 如果是简写格式 (如 #F7A),转换为完整格式 (FF77AA)
  31. if (hex.length === 3) {
  32. hex = hex.split('').map(char => char + char).join('');
  33. }
  34. // 转换为 r, g, b
  35. const r = parseInt(hex.substring(0, 2), 16);
  36. const g = parseInt(hex.substring(2, 4), 16);
  37. const b = parseInt(hex.substring(4, 6), 16);
  38. // 返回 RGBA 颜色对象
  39. return { r, g, b, a: alpha };
  40. }
  41. /** 获取节点的世界坐标并转换为 Vec2*/
  42. static getWorldPositionAsVec2(node: Node): Vec2 {
  43. const worldPosition = node.getWorldPosition().clone(); // 获取世界坐标
  44. return v2(worldPosition.x, worldPosition.y); // 转换为 Vec2
  45. }
  46. /** 射线检测
  47. * @param fromNode 起始节点
  48. * @param rayLength 射线长度
  49. * @return 射线结束点的世界坐标 (作为 Vec2)
  50. */
  51. static calculateRayEnd(fromNode: Node, rayLength: number): Vec2 {
  52. const rotation = fromNode.angle;
  53. // 根据角度计算方向向量
  54. let direction = v2(0, 0);
  55. if (rotation === -90) {
  56. direction = v2(1, 0); // 朝右
  57. } else if (rotation === 0) {
  58. direction = v2(0, 1); // 朝上
  59. } else if (rotation === 90) {
  60. direction = v2(-1, 0); // 朝左
  61. } else if (rotation === 180) {
  62. direction = v2(0, -1); // 朝下
  63. } else {
  64. const adjustedAngle = rotation - 90;
  65. direction = v2(-Math.cos(adjustedAngle * (Math.PI / 180)), Math.sin(-adjustedAngle * (Math.PI / 180)));
  66. }
  67. // 计算射线起点坐标
  68. const objs = this.getWorldPositionAsVec2(fromNode);
  69. const obje = objs.add(direction.multiplyScalar(rayLength));
  70. return obje;
  71. }
  72. /**
  73. * 判断两个 AABB 是否相交
  74. */
  75. static isAABBIntersecting(rect1: Rect, rect2: Rect): boolean {
  76. return !(
  77. rect1.xMax < rect2.xMin ||
  78. rect1.xMin > rect2.xMax ||
  79. rect1.yMax < rect2.yMin ||
  80. rect1.yMin > rect2.yMax
  81. );
  82. }
  83. /**
  84. * 检查点是否在多边形内
  85. * @param point 点的坐标
  86. * @param polygonPoints 多边形的顶点数组
  87. * @returns 是否在多边形内
  88. */
  89. static isPointInPolygon(point: Vec2, polygonPoints: Readonly<Vec2[]>): boolean {
  90. let isInside = false;
  91. for (let i = 0, j = polygonPoints.length - 1; i < polygonPoints.length; j = i++) {
  92. const xi = polygonPoints[i].x, yi = polygonPoints[i].y;
  93. const xj = polygonPoints[j].x, yj = polygonPoints[j].y;
  94. const intersect = (yi > point.y) !== (yj > point.y) &&
  95. point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi;
  96. if (intersect) isInside = !isInside;
  97. }
  98. return isInside;
  99. }
  100. /**
  101. * 检查圆是否与线段相交
  102. * @param circleCenter 圆心坐标
  103. * @param radius 圆的半径
  104. * @param lineStart 线段起点
  105. * @param lineEnd 线段终点
  106. * @returns 是否相交
  107. */
  108. static isCircleIntersectingLine(circleCenter: Vec2, radius: number, lineStart: Vec2, lineEnd: Vec2): boolean {
  109. const lineDir = lineEnd.subtract(lineStart);
  110. const toCircle = circleCenter.subtract(lineStart);
  111. const projection = toCircle.dot(lineDir.normalize());
  112. const closestPoint = lineStart.add(lineDir.normalize().multiplyScalar(projection));
  113. const distance = circleCenter.subtract(closestPoint).length();
  114. return distance <= radius;
  115. }
  116. /**
  117. * 获取圆形碰撞器的 AABB(轴对齐边界框)
  118. */
  119. static getCircleAABB(circleCollider: CircleCollider2D): Rect {
  120. const radius = circleCollider.radius;
  121. const center = circleCollider.worldPosition;
  122. const minX = center.x - radius;
  123. const minY = center.y - radius;
  124. const size = radius * 2;
  125. return new Rect(minX, minY, size, size);
  126. }
  127. /**
  128. * 获取多边形碰撞器的 AABB(轴对齐边界框)
  129. */
  130. static getPolygonAABB(polygonCollider: PolygonCollider2D): Rect {
  131. const points = polygonCollider.worldPoints;
  132. let minX = points[0].x;
  133. let maxX = points[0].x;
  134. let minY = points[0].y;
  135. let maxY = points[0].y;
  136. for (let i = 1; i < points.length; i++) {
  137. const p = points[i];
  138. if (p.x < minX) minX = p.x;
  139. if (p.x > maxX) maxX = p.x;
  140. if (p.y < minY) minY = p.y;
  141. if (p.y > maxY) maxY = p.y;
  142. }
  143. return new Rect(minX, minY, maxX - minX, maxY - minY);
  144. }
  145. /**
  146. * 判断多边形与圆是否相交
  147. * @param polygonCollider 多边形碰撞器
  148. * @param circleCollider 圆形碰撞器
  149. * @returns 是否相交
  150. */
  151. static isPolygonAndCircleIntersecting(
  152. polygonCollider: PolygonCollider2D,
  153. circleCollider: CircleCollider2D
  154. ): boolean {
  155. // 1. AABB 检测
  156. // const polygonAABB = this.getPolygonAABB(polygonCollider);
  157. // const circleAABB = this.getCircleAABB(circleCollider);
  158. // if (!this.isAABBIntersecting(polygonAABB, circleAABB)) {
  159. // return false;
  160. // }
  161. // 2. 精确检测
  162. const circleCenter = new Vec2(circleCollider.node.worldPosition.x, circleCollider.node.worldPosition.y); // 圆心
  163. const radius = circleCollider.radius;
  164. // 检查圆心是否在多边形内部
  165. if (this.isPointInPolygon(circleCenter, polygonCollider.worldPoints)) {
  166. return true;
  167. }
  168. // 检查圆是否与多边形的边相交
  169. const points = polygonCollider.worldPoints;
  170. for (let i = 0; i < points.length; i++) {
  171. const start = points[i];
  172. const end = points[(i + 1) % points.length];
  173. if (this.isCircleIntersectingLine(circleCenter, radius, start, end)) {
  174. return true;
  175. }
  176. }
  177. return false;
  178. }
  179. static delay(seconds) {
  180. return new Promise(resolve => setTimeout(resolve, seconds * 1000));
  181. }
  182. }