主要实现四个功能:
1.利用PolygonCollider2D碰撞体创建Mesh网格
2.利用Mesh网格创建单一PolygonCollider2D碰撞体
3.利用Mesh网格创建复合PolygonCollider2D碰撞体
4.根据Mesh网格大小形状计算刚体质量
通过Mesh与PolygonCollider2D的数据转化,完成创建,注释有详细说明:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class AdjustColliderOrMesh : MonoBehaviour
{
[SerializeField] bool useMass;
[SerializeField] float massCoefficient = 1;
[HideInInspector] public Vector3 centerPoint;
//按照PolygonCollider2D碰撞体的大小形状,创建MeshFilter网格
public void ApplyColliderToMesh()
{
MeshFilter meshFilter = GetComponent<MeshFilter>();
PolygonCollider2D polygon = GetComponent<PolygonCollider2D>();
if (polygon == null || meshFilter == null)
return;
ApplyColliderToMesh(polygon, meshFilter);
RecaculateMass();
SaveInEditor();
}
//按照Mesh网格的大小形状,创建PolygonCollider2D碰撞体
//只能是由一条闭合折线围成的网格,即不可以有内部孔或嵌套多个多边形
public void ApplyMeshToCollider()
{
MeshFilter meshFilter = GetComponent<MeshFilter>();
PolygonCollider2D polygon = GetComponent<PolygonCollider2D>();
if (polygon == null || meshFilter == null)
return;
ApplyMeshToCollider(meshFilter, polygon);
RecaculateMass();
SaveInEditor();
}
//按照Mesh网格的大小形状,创建PolygonCollider2D碰撞体
//可以是由多条独立的闭合折线围成的网格,即可以有内部孔或嵌套多个多边形
public void ApplyPathToCollider()
{
MeshFilter meshFilter = GetComponent<MeshFilter>();
PolygonCollider2D polygon = GetComponent<PolygonCollider2D>();
if (polygon == null || meshFilter == null)
return;
ApplyPathToCollider(meshFilter, polygon);
RecaculateMass();
SaveInEditor();
}
//根据网格形状重新计算质量
public void RecaculateMass()
{
if (useMass)
{
Rigidbody2D rigidBody = GetComponent<Rigidbody2D>();
MeshFilter meshFilter = GetComponent<MeshFilter>();
if (rigidBody == null || meshFilter == null)
return;
RecaculateMass(rigidBody, meshFilter);
}
}
// ----------
void SaveInEditor()
{
#if UNITY_EDITOR
//调用SetDirty方法保存修改部分
EditorUtility.SetDirty(gameObject);
#endif
}
//按照面积重新计算质量
void RecaculateMass(Rigidbody2D rigidBody, MeshFilter meshFilter)
{
float area = CalculateArea(meshFilter);
rigidBody.mass = area * massCoefficient;
SaveInEditor();
}
//计算多边形面积
float CalculateArea(MeshFilter meshFilter)
{
float area = 0;
//三角形顶点
Vector3[] vertices = meshFilter.mesh.vertices;
//三角形顶点索引
int[] indices = meshFilter.mesh.GetIndices(0);
//依次计算三角形面积, 累加 //每三个顶点确定一个三角形
for (int i = 0; i < indices.Length; i += 3)
{
//获取三个顶点, 计算三角形面积, 累加
area += CalculateTriangleArea(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);
}
return area;
}
//计算三角形面积
float CalculateTriangleArea(Vector3 p0, Vector3 p1, Vector3 p2)
{
float tmpArea = 0;
tmpArea += p0.x * p1.y - p1.x * p0.y;
tmpArea += p1.x * p2.y - p2.x * p1.y;
tmpArea += p2.x * p0.y - p0.x * p2.y;
tmpArea /= 2;
tmpArea = Mathf.Abs(tmpArea);
return tmpArea;
}
//计算三角形面积
float CalculateTriangleArea02(Vector3 p0, Vector3 p1, Vector3 p2)
{
return p0.x * (p1.y - p2.y) + p1.x * (p2.y - p0.y) + p2.x * (p0.y - p1.y);
}
//计算三角形面积, 该方法在部分情况会有微小误差
float CalculateTriangleArea03(Vector3 p0, Vector3 p1, Vector3 p2)
{
float a = (p1 - p0).magnitude;
float b = (p2 - p1).magnitude;
float c = (p0 - p2).magnitude;
float p = (a + b + c) * 0.5f;
return Mathf.Sqrt(p * (p - a) * (p - b) * (p - c));
}
// -----
//按照PolygonCollider2D碰撞体的大小形状修改MeshFilter的显示网格
void ApplyColliderToMesh(PolygonCollider2D polygon, MeshFilter meshFilter)
{
//坐标点转换
Vector3[] newVertices = ConvertVertex(polygon.points);
//创建网格
Mesh newMesh = new Mesh();
//重命名
newMesh.name = transform.name;
//设置顶点
newMesh.vertices = newVertices;
//获取三角形顶点索引数组
Triangulator tr = new Triangulator(newVertices);
//赋值
newMesh.triangles = tr.Triangulate();
//修改物体Mesh
//mesh是值传递, 只会修改本物体的mesh
meshFilter.mesh = newMesh;
//sharedMesh是因用户传递, 会修改所有引用该mesh的物体
//meshFilter.sharedMesh = mesh;
#if UNITY_EDITOR
EditorUtility.SetDirty(meshFilter);
EditorUtility.SetDirty(meshFilter.sharedMesh);
EditorUtility.SetDirty(gameObject);
#endif
}
//Vector2转Vector3, 生成顶点, 中心点
Vector3 tmpPoint;
Vector3[] ConvertVertex(Vector2[] vertices)
{
tmpPoint = Vector3.zero;
//Vector2转Vector3
Vector3[] newVertices = new Vector3[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
newVertices[i] = vertices[i];
centerPoint += newVertices[i];
}
//中心点, 取所有点的平均值
centerPoint = tmpPoint / vertices.Length + transform.position;
//返回
return newVertices;
}
//-----
//可以是凹多边形, 但不可以有内部孔或嵌套多个多边形, 用一段闭合折线记录
void ApplyMeshToCollider(MeshFilter meshFilter, PolygonCollider2D polygon)
{
Vector3[] vertices = meshFilter.sharedMesh.vertices;
Vector2[] points = new Vector2[vertices.Length];
//读取网格数据
for (int i = 0; i < vertices.Length; i++)
{
points[i] = vertices[i];
}
//赋值到碰撞体points
polygon.points = points;
}
//-----
//可以是凹多边形, 可以有内部孔或嵌套多个多边形, 用多段独立的闭合折线记录
void ApplyPathToCollider(MeshFilter meshFilter, PolygonCollider2D polygon)
{
//解析网格数据
List<Vector2[]> paths = MeshToColloderPaths.CreatePolygon2DColliderPoints(meshFilter.sharedMesh);
if (paths == null)
return;
//赋值到碰撞体path
polygon.pathCount = paths.Count;
for (int i = 0; i < paths.Count; i++)
{
polygon.SetPath(i, paths[i]);
}
}
// ------
public static bool IsZero(float n)
{
return Mathf.Approximately(n, 0);
}
}
解析网格数据信息,转化为多段独立的闭合折线
using UnityEngine;
using System.Collections.Generic;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(PolygonCollider2D))]
[ExecuteInEditMode]
public class MeshToColloderPaths
{
//
class Edge2D
{
public Vector2 A { get; private set; }
public Vector2 B { get; private set; }
public Edge2D (Vector2 pointA, Vector2 pointB)
{
A = pointA;
B = pointB;
}
//重写Equals
public override bool Equals(object obj)
{
if (obj is Edge2D)
{
var edge = (Edge2D)obj;
return (edge.A == A && edge.B == B) || (edge.B == A && edge.A == B);
}
return false;
}
public override int GetHashCode()
{
return A.GetHashCode() ^ B.GetHashCode();
}
}
//
public static List<Vector2[]> CreatePolygon2DColliderPoints(Mesh mesh)
{
Dictionary<Edge2D, int> edges = BuildEdgesFromMesh(mesh);
List<Vector2[]> paths = BuildColliderPaths(edges);
return paths;
}
// ------
//获取mesh的三角形边
static Dictionary<Edge2D, int> BuildEdgesFromMesh(Mesh mesh)
{
if (mesh == null)
return null;
//mesh的顶点
Vector3[] tmpVerts = mesh.vertices;
//mesh内包含的所有三角形
int[] tmpTris = mesh.triangles;
//用以保存mesh内所有的三角形边及数量
Dictionary<Edge2D, int> edgeDic = new Dictionary<Edge2D, int>();
//
Vector3 tmpPoint0;
Vector3 tmpPoint1;
Vector3 tmpPoint2;
for (int i = 0; i < tmpTris.Length - 2; i += 3)
{
//三角形顶点
tmpPoint0 = tmpVerts[tmpTris[i]];
tmpPoint1 = tmpVerts[tmpTris[i + 1]];
tmpPoint2 = tmpVerts[tmpTris[i + 2]];
//三角形边
Edge2D[] tmpEdges = new Edge2D[] {
new Edge2D(tmpPoint0, tmpPoint1),
new Edge2D(tmpPoint1, tmpPoint2),
new Edge2D(tmpPoint2, tmpPoint0)
};
//记录每条边及出现的次数
for (int j = 0; j < tmpEdges.Length; j++)
{
//判断重复, 加入字典, 需要重写Edge2D的Equals函数
if (edgeDic.ContainsKey(tmpEdges[j]))
edgeDic[tmpEdges[j]]++;
else
edgeDic.Add(tmpEdges[j], 1);
}
}
return edgeDic;
}
//计算轮廓点坐标
static List<Vector2[]> BuildColliderPaths(Dictionary<Edge2D, int> allEdges)
{
if (allEdges == null)
return null;
//获取mesh外轮廓(三角形边没有重复使用, 不是公共边, 即视为外轮廓边)
List<Edge2D> outEdges = new List<Edge2D>();
foreach (var edge in allEdges.Keys)
{
if (allEdges[edge] == 1)
outEdges.Add(edge);
}
//path列表, 对应Collider的paths, 包含多个path
List<List<Edge2D>> pathList = new List<List<Edge2D>>();
//单个path, 是一条(闭合的)折线
List<Edge2D> path = null;
//循环所有外轮廓边, 每个轮廓边都是一条线段, 若两条线段端点重合, 则可以连接这两条线段为一条折线
while (outEdges.Count > 0)
{
//新建一个path
if (path == null)
{
path = new List<Edge2D>();
//加入首个线段到path, 并从外轮廓(线段)列表中移除
path.Add(outEdges[0]);
outEdges.RemoveAt(0);
//将path添加到pathList
pathList.Add(path);
}
//至少找到一条可连接线段
bool foundAtLeastOneEdge = false;
int i = 0;
Edge2D tmeEdge;
//循环判断所有线段
while (outEdges.Count > i)
{
//依次取一条线段
tmeEdge = outEdges[i];
//如果该线段的终点与path折线的起点重合
if (tmeEdge.B == path[0].A)
{
//插入到path首位, 并从外轮廓(线段)列表中移除
path.Insert(0, tmeEdge);
outEdges.RemoveAt(i);
foundAtLeastOneEdge = true;
}
//如果该线段的起点与path折线的终点重合
else if (tmeEdge.A == path[path.Count - 1].B)
{
//添加到path的末位, 并从外轮廓(线段)列表中移除
path.Add(tmeEdge);
outEdges.RemoveAt(i);
foundAtLeastOneEdge = true;
}
//该线段不能连接到path, 跳过
else
{
i++;
}
}
//如果没有找到能与首个线段连接的其他线段, 说明该线段是孤立的(不可用的线段), 将path置空
if (!foundAtLeastOneEdge)
path = null;
}
//整理pathList
List<Vector2[]> cleanedPaths = new List<Vector2[]>();
//循环
for (int i = 0; i < pathList.Count; i++)
{
//判断空值
if (pathList[i] == null)
continue;
//获取折线的折点
List<Vector2> coords = new List<Vector2>();
for (int j = 0; j < pathList[i].Count; j++)
{
coords.Add(pathList[i][j].A);
}
//去除多余点
cleanedPaths.Add(RemoveUnusedCoords(coords));
}
//返回paths
return cleanedPaths;
}
//去除多余坐标点,如果三点共线,去除中间点
static Vector2[] RemoveUnusedCoords(List<Vector2> oldCoordsList)
{
//保存筛选之后的点
List<Vector2> newCoordsList = new List<Vector2>();
//添加第一个点
newCoordsList.Add(oldCoordsList[0]);
//标记下表
int lastAddedIndex = 0;
//循环判断
for (int i = 1; i < oldCoordsList.Count; i++)
{
//取点
Vector2 curCoords = oldCoordsList[i];
Vector2 lastAddedCoords = oldCoordsList[lastAddedIndex];
Vector2 nextCoords = (i + 1 >= oldCoordsList.Count) ? oldCoordsList[0] : oldCoordsList[i + 1];
//判断共线
if (!PointPosionOfLine(lastAddedCoords, nextCoords, curCoords))
{
newCoordsList.Add(curCoords);
lastAddedIndex = i;
}
}
return newCoordsList.ToArray();
}
//判断三点共线, 即点M在线段AB上
static bool PointPosionOfLine(Vector2 A, Vector2 B, Vector2 M)
{
//只是判断位置, 这种算法比较简单, 还可以计算三角形面积(相对繁琐一些), 面积为0, 则三点共线
return Mathf.Approximately((B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x), 0);
//公式计算过程
//直线公式:a * X + b * Y + c = 0
//将线段端点代入公式
// a * A.x + b * A.y + c = 0
// a * B.x + b * B.y + c = 0
//两式分别相加、相减
// (A.x + B.x) * a + (A.y + B.y) * b + 2c = 0
// (A.x - B.x) * a + (A.y - B.y) * b = 0
//化简得
// b = (B.x - A.x) / (A.y - B.y) * a
// c = -a * (A.y * B.x - A.x * B.y) / (A.y - B.y)
//原直线公式用a表示为
// a * X + (B.x - A.x) / (A.y - B.y) * a * Y - a * (A.y * B.x - A.x * B.y) / (A.y - B.y) = 0
//公式两边同时除a,直线公式用点A/B表示为
// X + (B.x - A.x) / (A.y - B.y) * Y - (A.y * B.x - A.x * B.y) / (A.y - B.y) = 0
//再次化简
// (A.y - B.y) * X + (B.x - A.x) * Y - (A.y * B.x - A.x * B.y) = 0
//公式左侧==0,点在直线上,公式左侧>0,点在直线右侧,工作左侧<0,点在直线左侧
//将目标点M代入公式
// (A.y - B.y) * M.x + (B.x - A.x) * M.y - (A.y * B.x - A.x * B.y)
// A.y * M.x - B.y * M.x + B.x * M.y - A.x * M.y - A.y * B.x + A.x * B.y
//整理为, 值 > 0 在右侧, = 0 在线上, < 0 在左侧
// (B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x)
}
}
可视为工具类,用于将多边形切割为最小三角形
using UnityEngine;
using System.Collections.Generic;
// http://wiki.unity3d.com/index.php?title=Triangulator
//将2D多边形Polygon切割为多个三角形
//可以是凹多边形, 但不可以有内部孔或嵌套多个多边形
public class Triangulator
{
private List<Vector2> m_points = new List<Vector2>();
public Triangulator(Vector3[] points)
{
m_points = new List<Vector2>();
for (int i = 0; i < points.Length; i++)
{
m_points.Add(points[i]);
}
}
public Triangulator(Vector2[] points)
{
m_points = new List<Vector2>(points);
}
public int[] Triangulate()
{
List<int> indices = new List<int>();
int n = m_points.Count;
if (n < 3)
return indices.ToArray();
int[] V = new int[n];
if (Area() > 0)
{
for (int v = 0; v < n; v++)
V[v] = v;
}
else
{
for (int v = 0; v < n; v++)
V[v] = (n - 1) - v;
}
int nv = n;
int count = 2 * nv;
for (int m = 0, v = nv - 1; nv > 2;)
{
if ((count--) <= 0)
return indices.ToArray();
int u = v;
if (nv <= u)
u = 0;
v = u + 1;
if (nv <= v)
v = 0;
int w = v + 1;
if (nv <= w)
w = 0;
if (Snip(u, v, w, nv, V))
{
int a, b, c, s, t;
a = V[u];
b = V[v];
c = V[w];
indices.Add(a);
indices.Add(b);
indices.Add(c);
m++;
for (s = v, t = v + 1; t < nv; s++, t++)
V[s] = V[t];
nv--;
count = 2 * nv;
}
}
indices.Reverse();
return indices.ToArray();
}
private float Area()
{
int n = m_points.Count;
float A = 0.0f;
for (int p = n - 1, q = 0; q < n; p = q++)
{
Vector2 pval = m_points[p];
Vector2 qval = m_points[q];
A += pval.x * qval.y - qval.x * pval.y;
}
return (A * 0.5f);
}
private bool Snip(int u, int v, int w, int n, int[] V)
{
int p;
Vector2 A = m_points[V[u]];
Vector2 B = m_points[V[v]];
Vector2 C = m_points[V[w]];
if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))))
return false;
for (p = 0; p < n; p++)
{
if ((p == u) || (p == v) || (p == w))
continue;
Vector2 P = m_points[V[p]];
if (InsideTriangle(A, B, C, P))
return false;
}
return true;
}
private bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
float cCROSSap, bCROSScp, aCROSSbp;
ax = C.x - B.x; ay = C.y - B.y;
bx = A.x - C.x; by = A.y - C.y;
cx = B.x - A.x; cy = B.y - A.y;
apx = P.x - A.x; apy = P.y - A.y;
bpx = P.x - B.x; bpy = P.y - B.y;
cpx = P.x - C.x; cpy = P.y - C.y;
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}
}
结合扩展Inspector面板功能,可以实现在编辑器环境下修改Mesh和多边形碰撞体~
扩展自定义类在Inspector面板的显示/在Inspector面板显示自定义类方法 CSDN
[CustomEditor(typeof(AdjustColliderOrMesh))]
public class AdjustColliderOrMeshEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
AdjustColliderOrMesh myScript = (AdjustColliderOrMesh)target;
if (GUILayout.Button("Apply Collider To Mesh"))
{
myScript.ApplyColliderToMesh();
}
else if (GUILayout.Button("Appl Mesh To Collider"))
{
myScript.ApplyMeshToCollider();
}
else if (GUILayout.Button("Apply Path To Collider"))
{
myScript.ApplyPathToCollider();
}
else if (GUILayout.Button("Recaculate Mass"))
{
myScript.RecaculateMass();
}
}
}