521 lines
19 KiB
C#
521 lines
19 KiB
C#
|
using System;
|
||
|
using System.Linq;
|
||
|
using UnityEngine;
|
||
|
using Framework.Constants;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
namespace Gameplay.Level
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 用于检测整块木板和洞口位置关系 (并非木板的孔和洞的位置)
|
||
|
/// </summary>
|
||
|
public static class LevelUtils
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 根据木板的洞决定层级顺序
|
||
|
/// </summary>
|
||
|
public static List<int> SortPlankLayer(List<List<int>> holesOfPlanksIndex)
|
||
|
{
|
||
|
List<int> elementCounts = holesOfPlanksIndex.Select(innerList => innerList.Count).ToList();
|
||
|
List<int> 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<Collider2D> GetTriggerColliders(GameObject obj, string layerMask)
|
||
|
{
|
||
|
List<Collider2D> hitColliders = new List<Collider2D>();
|
||
|
try
|
||
|
{
|
||
|
Collider2D collider = obj.GetComponent<Collider2D>();
|
||
|
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<SpriteRenderer>().bounds;
|
||
|
return bounds.extents.x;
|
||
|
}
|
||
|
|
||
|
private static Vector2[] GetVertices(GameObject obj)
|
||
|
{
|
||
|
SpriteRenderer spriteRenderer = obj.GetComponent<SpriteRenderer>();
|
||
|
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<Vector3, float> vertexAngles = new Dictionary<Vector3, float>();
|
||
|
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<Vector2Int> ConvertIndexToVector2Ints(List<int> indexList, int columns)
|
||
|
{
|
||
|
List<Vector2Int> coordinatesArray = new List<Vector2Int>();
|
||
|
|
||
|
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<int> ConvertVector2IntsToIndex(List<Vector2Int> vectorList, int columns)
|
||
|
{
|
||
|
List<int> indexArray = new List<int>();
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// 根据行数和方向创造hole的位置
|
||
|
/// </summary>
|
||
|
public static List<float> GeneratePositions(int count, int direction)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var positions = new List<float>(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<float>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
///得到可以开孔的位置
|
||
|
/// </summary>
|
||
|
public static List<Vector3> GetAdditionalAddedHolesPos(Vector3 firstPos, int count)
|
||
|
{
|
||
|
var positions = new List<Vector3>(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<int> holeIndex,
|
||
|
Dictionary<string, Kong> 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<int> holeIndex,
|
||
|
Dictionary<string, Kong> 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<int> holeIndex,
|
||
|
Dictionary<string, Kong> holeDic)
|
||
|
{
|
||
|
return holeDic[$"Kong{holeIndex[0]}"].Obj.transform.position;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region 倾斜木板
|
||
|
|
||
|
private static Vector3 GetInclinedSquarePlankPosition(List<int> holeIndex, Dictionary<string, Kong> 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<int> holeIndex, Dictionary<string, Kong> 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<int> holeIndex, Dictionary<string, Kong> 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<int> holeIndex, Dictionary<string, Kong> 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<int> holeIndex,
|
||
|
Dictionary<string, Kong> 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<string, Kong> holeDic, List<int> 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;
|
||
|
}
|
||
|
}
|
||
|
}
|