using System; using System.Linq; using UnityEngine; using Framework.Constants; using System.Collections.Generic; namespace Gameplay.Level { /// /// 用于检测整块木板和洞口位置关系 (并非木板的孔和洞的位置) /// public static class LevelUtils { /// /// 根据木板的洞决定层级顺序 /// public static List SortPlankLayer(List> holesOfPlanksIndex) { List elementCounts = holesOfPlanksIndex.Select(innerList => innerList.Count).ToList(); List sortedIndexes = elementCounts .Select((value, index) => new { Value = value, Index = index }) .OrderByDescending(item => item.Value) .Select(item => item.Index) .ToList(); return sortedIndexes; } public static bool IsCanAddThumbtack(Plank plank, Kong kong) { return IsNonOverlap(plank.Obj, kong.Obj) || IsOverlapCompletely(plank.Obj, kong.Obj); } public static bool IsOverlapCompletely(GameObject plank, GameObject hole) { return !IsIntersect(plank, hole) && JudgeContains(hole.transform.position, plank); } private static bool IsNonOverlap(GameObject plank, GameObject hole) { return !IsIntersect(plank, hole) && !JudgeContains(hole.transform.position, plank); } public static bool IsIntersect(GameObject plank, GameObject hole) { var isIntersect = false; var vertices = GetVertices(plank); var radius = GetRadius(hole); for (var i = 0; i < vertices.Length; i++) { var p1 = vertices[i]; var p2 = vertices[(i + 1) % vertices.Length]; if (IsLineCircleIntersecting(p1, p2, hole.transform.position, radius)) { isIntersect = true; break; } } return isIntersect; } public static List GetTriggerColliders(GameObject obj, string layerMask) { List hitColliders = new List(); try { Collider2D collider = obj.GetComponent(); ContactFilter2D contactFilter = new ContactFilter2D(); contactFilter.useTriggers = true; contactFilter.SetLayerMask(LayerMask.GetMask(layerMask)); Physics2D.OverlapCollider(collider, contactFilter, hitColliders); return hitColliders; } catch (Exception e) { DebugUtil.LogError("LevelUtils.GetTriggerColliders Fail. Obj: {0}, layerMask: {1}, Error:{2}", obj.name, layerMask, e); return hitColliders; } } public static Collider2D GetTriggerColliderOfPlank(GameObject obj, Plank plank) { var hits = GetTriggerColliders(obj, "HoleOfPlank"); foreach (var hit in hits) { if (plank.HolesOfPlank.TryGetValue(hit.name, out var holeOfPlank)) { return hit; } } return null; } public static float GetRadius(GameObject obj) { var bounds = obj.GetComponent().bounds; return bounds.extents.x; } private static Vector2[] GetVertices(GameObject obj) { SpriteRenderer spriteRenderer = obj.GetComponent(); Sprite sprite = spriteRenderer.sprite; Bounds spriteBounds = sprite.bounds; Vector2 bottomLeft = new Vector2(spriteBounds.min.x, spriteBounds.min.y); Vector2 bottomRight = new Vector2(spriteBounds.max.x, spriteBounds.min.y); Vector2 topLeft = new Vector2(spriteBounds.min.x, spriteBounds.max.y); Vector2 topRight = new Vector2(spriteBounds.max.x, spriteBounds.max.y); //var originalVertices = sprite.vertices; //所有顶点 var originalVertices = new[] { bottomLeft, bottomRight, topLeft, topRight }; Vector2 spriteCenter = sprite.bounds.center; // 计算每个顶点相对于Sprite中心的角度 Dictionary vertexAngles = new Dictionary(); foreach (Vector3 vertex in originalVertices) { Vector2 vertexPosition = new Vector2(vertex.x, vertex.y); Vector2 direction = vertexPosition - spriteCenter; float angle = Mathf.Atan2(direction.y, direction.x); vertexAngles.Add(vertex, angle); } Vector2[] sortedVertices = originalVertices.OrderBy(vertex => vertexAngles[vertex]).ToArray(); for (var i = 0; i < sortedVertices.Length; i++) { sortedVertices[i] = obj.transform.TransformPoint(sortedVertices[i]); } return sortedVertices; } private static bool IsLineCircleIntersecting(Vector2 lineStart, Vector2 lineEnd, Vector2 circleCenter, float circleRadius) { float distance = 0f; if (Math.Abs(lineStart.x - lineEnd.x) < float.Epsilon) { distance = Mathf.Abs(circleCenter.x - lineStart.x); } else if (Math.Abs(lineStart.y - lineEnd.y) < float.Epsilon) { distance = Mathf.Abs(circleCenter.y - lineStart.y); } else { float slope = (lineEnd.y - lineStart.y) / (lineEnd.x - lineStart.x); float intercept = lineStart.y - slope * lineStart.x; distance = Mathf.Abs(slope * circleCenter.x - circleCenter.y + intercept) / Mathf.Sqrt(Mathf.Pow(slope, 2) + 1); } return !(distance >= circleRadius); } public static bool JudgeContains(Vector3 point, GameObject Obj) { RaycastHit2D[] hits = Physics2D.RaycastAll(point, Vector2.zero); foreach (var hit in hits) { if (hit.collider != null && hit.collider.name == Obj.name) { return true; } } return false; } public static List ConvertIndexToVector2Ints(List indexList, int columns) { List coordinatesArray = new List(); foreach (int index in indexList) { int row = index / columns; int column = index % columns; Vector2Int coordinates = new Vector2Int(row, column); coordinatesArray.Add(coordinates); } return coordinatesArray; } public static List ConvertVector2IntsToIndex(List vectorList, int columns) { List indexArray = new List(); foreach (Vector2Int coordinates in vectorList) { int index = coordinates.x * columns + coordinates.y; indexArray.Add(index); } return indexArray; } public static Vector2Int ConvertIndexToVector2Int(int index, int columns) { return new Vector2Int(index / columns, index % columns); } public static int ConvertVector2IntToIndex(Vector2Int pos, int columns) { return pos.x * columns + pos.y; } public static Color GetRgbaColor(string strColor) { var color = Convert.ToUInt32(strColor, 16); return new Color((color >> 24 & 0xFF) / 255f, (color >> 16 & 0xFF) / 255f, (color >> 8 & 0xFF) / 255f, (color & 0xFF) / 255f); } /// /// 根据行数和方向创造hole的位置 /// public static List GeneratePositions(int count, int direction) { try { var positions = new List(count); var spacing = 2 * LevelConstants.Radius + LevelConstants.Spacing; var pos = 0 - (count % 2 != 0 ? count / 2 : (LevelConstants.Radius + LevelConstants.Spacing / 2) + (count / 2 - 1)) * spacing; if (direction.Equals(-1)) { pos -= LevelConstants.Offset; } pos *= direction; for (var i = 0; i < count; i++) { positions.Add(pos); pos += spacing * direction; } return positions; } catch (Exception e) { DebugUtil.LogError("LevelUtils.GeneratePositions Fail. Error: {0}", e); return new List(); } } /// ///得到可以开孔的位置 /// public static List GetAdditionalAddedHolesPos(Vector3 firstPos, int count) { var positions = new List(count); for (var i = 0; i < count; i++) { var pos = new Vector3(firstPos.x + i * (LevelConstants.Spacing + LevelConstants.Radius * 2), firstPos.y - LevelConstants.Spacing - LevelConstants.Radius * 2, firstPos.z); positions.Add(pos); } return positions; } public static string GetPlankName(LevelData.PlankType plankType, LevelData.SquareNormType squareNormType) { if (!squareNormType.Equals(LevelData.SquareNormType.None)) return plankType + squareNormType.ToString(); return plankType.ToString(); } #region 横竖方形生成 private static Vector3 GetSquarePlankPosition(List holeIndex, Dictionary holeDic, LevelData.SquareNormType squareNormType, bool isHorizontal = true) { try { var limit = JudgeSquareIndexLimit(squareNormType); if (limit == holeIndex.Count - 1) { var realIndex = holeIndex[limit / 2]; var pos = holeDic[$"Kong{realIndex}"].Obj.transform.position; float offsetValue = isHorizontal ? LevelConstants.Radius + LevelConstants.Spacing / 2 : -LevelConstants.Radius - LevelConstants.Spacing / 2; float x = limit % 2 != 0 ? pos.x + offsetValue : pos.x; float y = limit % 2 != 0 ? pos.y + offsetValue : pos.y; var center = isHorizontal ? new Vector3(x, pos.y, pos.z) : new Vector3(pos.x, y, pos.z); return center; } else { if (holeIndex.Count % 2 == 0) { var realIndex = holeIndex[(holeIndex.Count - 1) / 2]; var offset = (LevelConstants.Radius * 2 + LevelConstants.Spacing) * limit / 2; var pos = holeDic[$"Kong{realIndex}"].Obj.transform.position; var center = isHorizontal ? new Vector3(pos.x + offset, pos.y, pos.z) : new Vector3(pos.x, pos.y - offset, pos.z); return center; } else { var index = (holeIndex.Count - 1) / 2; var realIndex = holeIndex[index]; var offset = limit % 2 != 0 ? (LevelConstants.Radius + LevelConstants.Spacing) * limit / 2.0f : 0; var pos = holeDic[$"Kong{realIndex}"].Obj.transform.position; var center = isHorizontal ? new Vector3(pos.x + offset, pos.y, pos.z) : new Vector3(pos.x, pos.y - offset, pos.z); return center; } } } catch (Exception e) { DebugUtil.LogError("LevelUtils.GetSquarePlankPosition Fail. Error: {0}", e); return new Vector3(); } } #endregion #region 多排方形 private static Vector3 GetMultiSquarePlankPosition(List holeIndex, Dictionary holeDic) { var firstIndex = holeIndex[0]; var finalIndex = holeIndex[^1]; var firstPos = holeDic[$"Kong{firstIndex}"].Obj.transform.position; var finalPos = holeDic[$"Kong{finalIndex}"].Obj.transform.position; return (firstPos + finalPos) / 2; } #endregion #region 圆形 private static Vector3 GetCirclePlankPosition(List holeIndex, Dictionary holeDic) { return holeDic[$"Kong{holeIndex[0]}"].Obj.transform.position; } #endregion #region 倾斜木板 private static Vector3 GetInclinedSquarePlankPosition(List holeIndex, Dictionary holeDic) { //两孔木板 限制为1 var limit = 1; var realIndex = holeIndex[limit / 2]; var nextIndex = holeIndex[limit / 2 + 1]; var pos = holeDic[$"Kong{realIndex}"].Obj.transform.position; var nextPos = holeDic[$"Kong{nextIndex}"].Obj.transform.position; var offsetPos = new Vector3((pos.x - nextPos.x) / 2 + nextPos.x, (pos.y - nextPos.y) / 2 + nextPos.y, pos.z); return limit % 2 != 0 ? offsetPos : pos; } #endregion #region 直角三角形 private static Vector3 GetRightTrianglePlankPosition(List holeIndex, Dictionary holeDic, bool isLeft = true) { var pos = holeDic[$"Kong{holeIndex[0]}"].Obj.transform.position; var offsetX = isLeft ? pos.x + LevelConstants.Radius + LevelConstants.SpacingOfRightTriangle : pos.x - LevelConstants.Radius - LevelConstants.SpacingOfRightTriangle; return new Vector3(offsetX, pos.y - LevelConstants.Radius - LevelConstants.SpacingOfRightTriangle, pos.z); } #endregion #region 直角三角形_Down private static Vector3 GetRightTrianglePlankPositionDown(List holeIndex, Dictionary holeDic, bool isLeft = true) { var pos = holeDic[$"Kong{holeIndex[0]}"].Obj.transform.position; var offsetX = isLeft ? pos.x + LevelConstants.Radius + LevelConstants.SpacingOfRightTriangle : pos.x - LevelConstants.Radius - LevelConstants.SpacingOfRightTriangle; return new Vector3(offsetX, pos.y + LevelConstants.Radius + LevelConstants.SpacingOfRightTriangle, pos.z); } #endregion #region 倾斜三角 private static Vector3 GetInclinedTrianglePlankPositionUp(List holeIndex, Dictionary holeDic, bool isUp = true) { var pos = holeDic[$"Kong{holeIndex[0]}"].Obj.transform.position; float offsetValue = Mathf.Sqrt(2) * (LevelConstants.Radius + LevelConstants.SpacingOfTriangle); return new Vector3(pos.x, isUp ? pos.y + offsetValue : pos.y - offsetValue, pos.z); } private static Vector3 GetInclinedTrianglePlankPositionLeft(List holeIndex, Dictionary holeDic, bool isLeft = true) { var pos = holeDic[$"Kong{holeIndex[0]}"].Obj.transform.position; float offsetValue = Mathf.Sqrt(2) * (LevelConstants.Radius + LevelConstants.SpacingOfTriangle); return new Vector3(isLeft ? pos.x - offsetValue : pos.x + offsetValue, pos.y, pos.z); } #endregion public static Vector3 GetPlankPositionInfo(Dictionary holeDic, List holeIndex, LevelData.PlankType plankType, LevelData.SquareNormType squareNormType) { switch (plankType) { case LevelData.PlankType.HorizontalSquare: return GetSquarePlankPosition(holeIndex, holeDic, squareNormType); case LevelData.PlankType.VerticalSquare: return GetSquarePlankPosition(holeIndex, holeDic, squareNormType, false); case LevelData.PlankType.Circle: return GetCirclePlankPosition(holeIndex, holeDic); case LevelData.PlankType.MultiSquare: return GetMultiSquarePlankPosition(holeIndex, holeDic); case LevelData.PlankType.InclinedSquareToLeft: return GetInclinedSquarePlankPosition(holeIndex, holeDic); case LevelData.PlankType.InclinedSquareToRight: return GetInclinedSquarePlankPosition(holeIndex, holeDic); case LevelData.PlankType.RightTriangleToLeft: return GetRightTrianglePlankPosition(holeIndex, holeDic); case LevelData.PlankType.RightTriangleToRight: return GetRightTrianglePlankPosition(holeIndex, holeDic, false); case LevelData.PlankType.InclinedTriangle: return GetInclinedTrianglePlankPositionUp(holeIndex, holeDic); case LevelData.PlankType.InclinedTriangle_180: return GetInclinedTrianglePlankPositionUp(holeIndex, holeDic, false); case LevelData.PlankType.InclinedTriangle_90: return GetInclinedTrianglePlankPositionLeft(holeIndex, holeDic); case LevelData.PlankType.InclinedTriangle_270: return GetInclinedTrianglePlankPositionLeft(holeIndex, holeDic, false); case LevelData.PlankType.RightTriangleToLeft_Down: return GetRightTrianglePlankPositionDown(holeIndex, holeDic); case LevelData.PlankType.RightTriangleToRight_Down: return GetRightTrianglePlankPositionDown(holeIndex, holeDic, false); default: return Vector3.zero; } } private static int JudgeSquareIndexLimit(LevelData.SquareNormType squareNormType) { int limitIndex; switch (squareNormType) { case LevelData.SquareNormType.WithTwo: limitIndex = 1; break; case LevelData.SquareNormType.WithThree: limitIndex = 2; break; case LevelData.SquareNormType.WithFour: limitIndex = 3; break; case LevelData.SquareNormType.WithFive: limitIndex = 4; break; default: limitIndex = 0; break; } return limitIndex; } } }