Automatically Create Polygon Collider 2D From 2D Mesh in Unity
Sep 6, 2016ProgrammingComments (14)
I was working on a 2D game in Unity and needed some way to automatically create a collider for a complex 2D level mesh. Editing a Polygon Collider 2D in the editor was out of the question, since there were thousands of vertices. I ended up writing a script that analyzes the MeshFilter of the mesh, and programmatically creates a Polygon Collider 2D for it. It also supports meshes that have mutliple non-connected components.

How to Use It
To use it, create a ColliderCreator C# script, copy the code in, and add the Script component to a GameObject that has a 2D mesh. When you hit Play, the script will generate a Polygon Collider 2D for that GameObject based on the mesh. While the game is running, it's best to save the generated PolygonCollider2D using Copy Component, and paste it into the prefab. Then the script won't have to run every time.

ColliderCreator C# Code
using System.Collections.Generic;
using UnityEngine;

public class ColliderCreator : MonoBehaviour
{
void Start()
{
// Stop if no mesh filter exists or there's already a collider
if (GetComponent<PolygonCollider2D>() || GetComponent<MeshFilter>() == null) {
return;
}

// Get triangles and vertices from mesh
int[] triangles = GetComponent<MeshFilter>().mesh.triangles;
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;

// Get just the outer edges from the mesh's triangles (ignore or remove any shared edges)
Dictionary<string, KeyValuePair<int, int>> edges = new Dictionary<string, KeyValuePair<int, int>>();
for (int i = 0; i < triangles.Length; i += 3) {
for (int e = 0; e < 3; e++) {
int vert1 = triangles[i + e];
int vert2 = triangles[i + e + 1 > i + 2 ? i : i + e + 1];
string edge = Mathf.Min(vert1, vert2) + ":" + Mathf.Max(vert1, vert2);
if (edges.ContainsKey(edge)) {
edges.Remove(edge);
} else {
edges.Add(edge, new KeyValuePair<int, int>(vert1, vert2));
}
}
}

// Create edge lookup (Key is first vertex, Value is second vertex, of each edge)
Dictionary<int, int> lookup = new Dictionary<int, int>();
foreach (KeyValuePair<int, int> edge in edges.Values) {
if (lookup.ContainsKey(edge.Key) == false) {
lookup.Add(edge.Key, edge.Value);
}
}

// Create empty polygon collider
PolygonCollider2D polygonCollider = gameObject.AddComponent<PolygonCollider2D>();
polygonCollider.pathCount = 0;

// Loop through edge vertices in order
int startVert = 0;
int nextVert = startVert;
int highestVert = startVert;
List<Vector2> colliderPath = new List<Vector2>();
while (true) {

// Add vertex to collider path
colliderPath.Add(vertices[nextVert]);

// Get next vertex
nextVert = lookup[nextVert];

// Store highest vertex (to know what shape to move to next)
if (nextVert > highestVert) {
highestVert = nextVert;
}

// Shape complete
if (nextVert == startVert) {

// Add path to polygon collider
polygonCollider.pathCount++;
polygonCollider.SetPath(polygonCollider.pathCount - 1, colliderPath.ToArray());
colliderPath.Clear();

// Go to next shape if one exists
if (lookup.ContainsKey(highestVert + 1)) {

// Set starting and next vertices
startVert = highestVert + 1;
nextVert = startVert;

// Continue to next loop
continue;
}

// No more verts
break;
}
}
}
}


Old Version


The old version of the code was much slower. You can still see it below, but I highly recommend using the above one.

using UnityEngine;
using System.Collections.Generic;

public class ColliderCreator : MonoBehaviour
{
private int currentPathIndex = 0;
private PolygonCollider2D polygonCollider;
private List<Edge> edges = new List<Edge>();
private List<Vector2> points = new List<Vector2>();
private Vector3[] vertices;

void Start()
{
// Get the polygon collider (create one if necessary)
polygonCollider = GetComponent<PolygonCollider2D>();
if (polygonCollider == null) {
polygonCollider = gameObject.AddComponent<PolygonCollider2D>();
}

// Get the mesh's vertices for use later
vertices = GetComponent<MeshFilter>().mesh.vertices;

// Get all edges from triangles
int[] triangles = GetComponent<MeshFilter>().mesh.triangles;
for (int i = 0; i < triangles.Length; i += 3) {
edges.Add(new Edge(triangles[i], triangles[i + 1]));
edges.Add(new Edge(triangles[i + 1], triangles[i + 2]));
edges.Add(new Edge(triangles[i + 2], triangles[i]));
}

// Find duplicate edges
List<Edge> edgesToRemove = new List<Edge>();
foreach (Edge edge1 in edges) {
foreach (Edge edge2 in edges) {
if (edge1 != edge2) {
if (edge1.vert1 == edge2.vert1 && edge1.vert2 == edge2.vert2 || edge1.vert1 == edge2.vert2 && edge1.vert2 == edge2.vert1) {
edgesToRemove.Add(edge1);
}
}
}
}

// Remove duplicate edges (leaving only perimeter edges)
foreach (Edge edge in edgesToRemove) {
edges.Remove(edge);
}

// Start edge trace
edgeTrace(edges[0]);
}

void edgeTrace(Edge edge)
{
// Add this edge's vert1 coords to the point list
points.Add(vertices[edge.vert1]);

// Store this edge's vert2
int vert2 = edge.vert2;

// Remove this edge
edges.Remove(edge);

// Find next edge that contains vert2
foreach (Edge nextEdge in edges) {
if (nextEdge.vert1 == vert2) {
edgeTrace(nextEdge);
return;
}
}

// No next edge found, create a path based on these points
polygonCollider.pathCount = currentPathIndex + 1;
polygonCollider.SetPath(currentPathIndex, points.ToArray());

// Empty path
points.Clear();

// Increment path index
currentPathIndex ++;

// Start next edge trace if there are edges left
if (edges.Count > 0) {
edgeTrace(edges[0]);
}
}
}

class Edge
{
public int vert1;
public int vert2;

public Edge(int Vert1, int Vert2)
{
vert1 = Vert1;
vert2 = Vert2;
}
}
Comments (14)
Add a Comment
SticKai   Feb 02, 2024
Hey bro um I was just wondering why the script doesn't get rid of the initial polygon, otherwise it works great!
SteveB   Sep 14, 2022
Man, this is what I was searching for. The mesh colliders from imported geometry simply didn't work so i needed to somehow create my own after import. Absolutely brilliant! Thanks!
YoYo   Aug 02, 2021
Thankyou!! Exactly what I was looking for!
Vov   Aug 31, 2020
Ignore my last comment, not sure what causes it but I'm getting an out of memory error on some quite complex shapes
Vov   Aug 31, 2020
Absolutely great, except... Where one mesh is interpreted as 2 shapes despite sharing 1 vertex, the routine goes into an infinite loop. You can reproduce this by creating 2 squares diagonally next to each other sharing 1 corner vertex. For now I've just commented out the part where it cycles around to address additional shapes because I don't need it anyway :)
Ä°brahim Çimentepe   Jun 15, 2020
This is absolutely perfect mate. Thank you very much!
Abood   Oct 25, 2018
This is perfect! I was looking for something like this but I only have one problem which is when I have a mesh that has a hole in it, for example, the collider covers the inner gap too how can I fix that?
Jônatas Q. Lima   Apr 22, 2018
I'm so grateful. I researched for a long time how to let the shape of the collider equal the sprite. I failed a lot, when I found an answer, it did not work, or sometimes I did not find anything. Until that came this wonderful site, even though a mesh collider was generated from the 2d mesh I modified that part: ushort [] triangles = GetComponent <SpriteRenderer> (). sprite.triangles; Vector3 [] vertices = GetComponent <SpriteRenderer> (). Sprite.vertices; Now the collider is generated based on the shape of the Sprite. I solved my problem. Many thanks H3X3D!
Ghislain Nadeau   Mar 19, 2018
Great work! I wonder why this isn't even part of Unity by default. The 3D version does...
TeDj   Dec 02, 2017
Very helpful script! Thank you very much!
Pawel   Nov 20, 2017
Very useful !
Agoston David   Dec 05, 2016
thanks!
Nick Sephton   Nov 09, 2016
This script crashes on my system, as my gameobjects don't have a MeshFilter component. If I try to add one, then it says it conflicts with my Sprite Renderer that I'm using to render the image. :/
Silvan Herrema   Sep 13, 2016
This is (almost) exactly what I was looking for! Thanks!