├── Screenshots
├── Fence1.PNG
├── FenceFinal.PNG
├── FenceVerts.PNG
├── FenceErosion.PNG
├── FenceDilation.PNG
├── FenceBinaryimage.PNG
├── FenceSubtraction.PNG
└── FenceVertReduced.PNG
├── README.md
└── destructibleSprite.cs
/Screenshots/Fence1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/Fence1.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceFinal.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceFinal.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceVerts.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceVerts.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceErosion.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceErosion.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceDilation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceDilation.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceBinaryimage.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceBinaryimage.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceSubtraction.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceSubtraction.PNG
--------------------------------------------------------------------------------
/Screenshots/FenceVertReduced.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Avatarchik/unity-sprite-runtime-collider-generator/HEAD/Screenshots/FenceVertReduced.PNG
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ##Generating a polygon collider for a sprite at runtime
2 |
3 |
4 |
5 | Starting with the image we convert the texture to a binary image.
6 |
7 |
8 |
9 | We perform some image processing techniques to get the outline of the image.
10 |
15 |
16 | From this we follow the outline assigning each pixel as a vertex. We can allow islands( shown in a different colour) and concave shapes.
17 |
18 |
19 |
20 | Removing the unnecessary vertices we can simplify the collider greatly.
21 |
22 |
23 |
24 | This lets us generate a collider for complex sprites.
25 |
26 |
27 |
--------------------------------------------------------------------------------
/destructibleSprite.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 |
5 | public class destructionPolygon : MonoBehaviour {
6 |
7 | public Texture2D tex;
8 |
9 | public bool doBI;
10 | public bool doErosion;
11 | public bool doDilation;
12 | public bool doSub;
13 | public bool doVert;
14 |
15 | public float pixelsToUnits = 100f; // Pixels to (unity)Units 100 to 1
16 | public float pixelOffset = 0.5f;
17 |
18 | public bool[,] binaryImage;
19 |
20 | PolygonCollider2D poly;
21 |
22 | public float xBounds;
23 | public float yBounds;
24 |
25 | public int islandCount=0;
26 |
27 | // Use this for initialization
28 | void Start () {
29 |
30 | poly = gameObject.GetComponent();
31 |
32 | if(poly == null)
33 | poly = gameObject.AddComponent();
34 |
35 | if(tex == null)
36 | tex = gameObject.GetComponent().sprite.texture;
37 |
38 | xBounds = gameObject.GetComponent().sprite.bounds.extents.x;
39 | yBounds = gameObject.GetComponent().sprite.bounds.extents.y;
40 |
41 | binaryImageFromTex(out binaryImage, ref tex);
42 |
43 | gameObject.GetComponent().sprite.texture.wrapMode = TextureWrapMode.Clamp;
44 |
45 | // texFromBinaryImage(out tex, binaryImage);
46 | // gameObject.GetComponent().sprite = Sprite.Create(tex, gameObject.GetComponent().sprite.rect, new Vector2(0.5f, 0.5f));
47 | }
48 |
49 | void Update() {
50 | if(doBI) {
51 | doBI=false;
52 | texFromBinaryImage(out tex, binaryImage);
53 | gameObject.GetComponent().sprite = Sprite.Create(tex, gameObject.GetComponent().sprite.rect, new Vector2(0.5f, 0.5f));
54 | }
55 |
56 | if(doErosion) {
57 | doErosion=false;
58 |
59 | texFromBinaryImage(out tex, erosion(ref binaryImage));
60 | gameObject.GetComponent().sprite = Sprite.Create(tex, gameObject.GetComponent().sprite.rect, new Vector2(0.5f, 0.5f));
61 | }
62 |
63 | if(doDilation) {
64 | doDilation=false;
65 |
66 | texFromBinaryImage(out tex, dilation(ref binaryImage));
67 | gameObject.GetComponent().sprite = Sprite.Create(tex, gameObject.GetComponent().sprite.rect, new Vector2(0.5f, 0.5f));
68 | }
69 |
70 | if(doSub) {
71 | doSub=false;
72 |
73 | texFromBinaryImage(out tex, subtraction(binaryImage, erosion(ref binaryImage)));
74 | gameObject.GetComponent().sprite = Sprite.Create(tex, gameObject.GetComponent().sprite.rect, new Vector2(0.5f, 0.5f));
75 | }
76 |
77 | if(doVert) {
78 | doVert=false;
79 | bool[,] binaryImageOutline = subtraction(binaryImage, erosion(ref binaryImage));
80 | List > paths = getPaths(ref binaryImageOutline);
81 |
82 | texFromBinaryImageUsingPaths(out tex, binaryImage, paths);
83 | gameObject.GetComponent().sprite = Sprite.Create(tex, gameObject.GetComponent().sprite.rect, new Vector2(0.5f, 0.5f));
84 |
85 | poly.pathCount = paths.Count;
86 | islandCount = paths.Count;
87 |
88 | for(int i=0; i().sprite.texture;
103 | tex.wrapMode = TextureWrapMode.Clamp;
104 |
105 | binaryImageFromTex(out binaryImage, ref tex);
106 |
107 | bool[,] binaryImageOutline = subtraction(binaryImage, erosion(ref binaryImage));
108 | List > paths = getPaths(ref binaryImageOutline);
109 | // simplify paths
110 | // convert paths to world space paths
111 |
112 | poly.pathCount = paths.Count;
113 | islandCount = paths.Count;
114 |
115 | for(int i=0; i > paths = getPaths(ref binaryImageOutline);
131 | // simplify paths
132 | // convert paths to world space paths
133 |
134 | poly.pathCount = paths.Count;
135 | islandCount = paths.Count;
136 |
137 | for(int i=0; i simplifyPath(ref List path) {
147 |
148 | List shortPath = new List();
149 |
150 | Vector2 prevPoint = path[0];
151 | int x=(int)path[0].x, y=(int)path[0].y;
152 |
153 | shortPath.Add(prevPoint);
154 |
155 | for(int i=1; i3) { // if we have more than 3 points we can start checking if we can remove triangle points
164 | Vector2 first = shortPath[shortPath.Count-1];
165 | Vector2 last = shortPath[shortPath.Count-3];
166 | if(first.x == last.x-1 && first.y == last.y-1 ||
167 | first.x == last.x+1 && first.y == last.y+1 ||
168 | first.x == last.x-1 && first.y == last.y+1 ||
169 | first.x == last.x+1 && first.y == last.y-1) {
170 | shortPath.RemoveAt(shortPath.Count-2);
171 | }
172 | }
173 | if(shortPath.Count>3) {
174 | Vector2 first = shortPath[shortPath.Count-1];
175 | Vector2 middle = shortPath[shortPath.Count-2];
176 | Vector2 last = shortPath[shortPath.Count-3];
177 |
178 | if((first.x==middle.x+1&&middle.x+1==last.x+2 && first.y==middle.y+1&&middle.y+1==last.y+2) ||
179 | (first.x==middle.x+1&&middle.x+1==last.x+2 && first.y==middle.y-1&&middle.y-1==last.y-2) ||
180 | (first.x==middle.x-1&&middle.x-1==last.x-2 && first.y==middle.y+1&&middle.y+1==last.y+2) ||
181 | (first.x==middle.x-1&&middle.x-1==last.x-2 && first.y==middle.y-1&&middle.y-1==last.y-2)) {
182 | shortPath.RemoveAt(shortPath.Count-2);
183 | }
184 | }
185 | }
186 | prevPoint = path[i];
187 | }
188 |
189 | // for(int i=1; i > getPaths(ref bool[,] b) {
204 | int w = b.GetLength(0); // width
205 | int h = b.GetLength(1); // height
206 |
207 | Vector2 startPoint = Vector2.zero;
208 | List > paths = new List >();
209 |
210 | bool[,] temp = b;//(bool[,]) b.Clone();
211 |
212 | while( findStartPoint(ref temp, ref startPoint) ) {
213 | List points = new List();
214 |
215 | // Get vertices from outline
216 | List path = getPath2(ref temp, ref points, startPoint);
217 |
218 | // remove points from temp
219 | foreach(Vector2 point in path) {
220 | temp[(int)point.x,(int)point.y] = false;
221 | }
222 | paths.Add ( simplifyPath( ref path ) );
223 | // paths.Add ( path ); //REMOVE
224 |
225 | }
226 |
227 | return paths;
228 | }
229 |
230 | // returns true if found a start point
231 | bool findStartPoint(ref bool[,] b, ref Vector2 startPoint) {
232 | int w = b.GetLength(0); // width
233 | int h = b.GetLength(1); // height
234 |
235 | for(int x= 0; x getPath2(ref bool[,] b, ref List prevPoints, Vector2 startPoint) {
248 |
249 | int[,] dirs = {{0,1},{1,0},{0,-1},{-1,0}};
250 |
251 | int w = b.GetLength(0); // width
252 | int h = b.GetLength(1); // height
253 |
254 | Vector2 currPoint= Vector2.zero, newPoint = Vector2.zero;
255 | bool isOpen = true; // Is the path closed?
256 |
257 | for(int z=0; z=0 && j=0) {
261 | if(b[i,j]) {
262 | currPoint = new Vector2(i,j);
263 | }
264 | }
265 | }
266 |
267 | prevPoints.Add(startPoint);
268 |
269 | int count = 0;
270 |
271 | while(isOpen && count<500) {
272 | count++;
273 |
274 | Debug.Log(currPoint);
275 |
276 | prevPoints.Add(currPoint);
277 |
278 | // Check each direction around the start point and repeat for each new point
279 | for(int z=0; z=0 && j=0) {
283 | if(b[i,j]) {
284 | if(!prevPoints.Contains(new Vector2(i,j))) {
285 | newPoint = new Vector2(i,j);
286 | break;
287 | } else {
288 | if(new Vector2(i,j)==startPoint) {
289 | isOpen = false;
290 | break;
291 | }
292 | }
293 | }
294 | }
295 | }
296 |
297 | if(!isOpen) continue;
298 |
299 | // Deadend
300 | if(newPoint==currPoint) {
301 | for(int p=prevPoints.Count-1; p>=0; p--) {
302 | for(int z=0; z=0 && j=0) {
306 | if(b[i,j]) {
307 | if(!prevPoints.Contains(new Vector2(i,j))) {
308 | newPoint = new Vector2(i,j);
309 | break;
310 | }
311 | }
312 | }
313 | }
314 | if(newPoint!=currPoint) break;
315 | }
316 | Debug.Log("NEVER GETS PRINTED");
317 | }
318 | currPoint = newPoint;
319 | }
320 | Debug.Log("count<500?: "+count);
321 | return prevPoints;
322 | }
323 |
324 | // recursive function - its gonna explode
325 | // Single island vert mapping
326 | List getPath(ref bool[,] b, ref List prevPoints, Vector2 currPoint) {
327 | int[,] dirs = {{0,1},{1,0},{0,-1},{-1,0}};
328 |
329 | int w = b.GetLength(0); // width
330 | int h = b.GetLength(1); // height
331 |
332 | // get direction
333 | if(prevPoints.Count == 0) {
334 | prevPoints.Add(currPoint); // startpoint
335 |
336 | for(int z=0; z=0 && j=0) {
340 | if(b[i,j]) {
341 | return getPath (ref b, ref prevPoints, new Vector2(i,j));
342 | }
343 | }
344 | }
345 | return prevPoints;
346 | }
347 |
348 | for(int z=0; z=0 && j=0) {
352 | if(b[i,j]) { // if there is a point
353 | Vector2 point = new Vector2(i,j);
354 | if( prevPoints.Contains(point) ) {
355 | if(prevPoints[0] == point && prevPoints.Count>2) {
356 | prevPoints.Add (currPoint);
357 | return prevPoints;
358 | }
359 | } else {
360 | prevPoints.Add (currPoint);
361 | return getPath (ref b, ref prevPoints, point);
362 | }
363 |
364 | // if(!(i== prevPoints[prevPoints.Count-1].x && j== prevPoints[prevPoints.Count-1].y)) { // check its not the point we just added
365 | // if(i==prevPoints[0].x && j==prevPoints[0].y && prevPoints[0]!=prevPoints[prevPoints.Count-1]) { // Is it the start point?
366 | // prevPoints.Add (currPoint);
367 | // return prevPoints;
368 | // } else { // Add it and start looking for the next point
369 | // prevPoints.Add (currPoint);
370 | // return getPath (ref b, ref prevPoints, new Vector2(i,j));
371 | // }
372 | // }
373 | }
374 | }
375 | }
376 |
377 | // Deadend? backtrack to find another path to take
378 | for(int p=prevPoints.Count-1; p>=0; p--) {
379 | for(int z=0; z=0 && j=0) {
383 | if(b[i,j]) {
384 | if(!prevPoints.Contains(new Vector2(i,j))) {
385 | return getPath (ref b, ref prevPoints, new Vector2(i,j));
386 | }
387 | }
388 | }
389 | }
390 | }
391 |
392 | foreach(Vector2 point in prevPoints) {
393 | for(int z=0; z=0 && j=0) {
397 | if(b[i,j]) {
398 | if(!prevPoints.Contains(new Vector2(i,j))) {
399 | return getPath (ref b, ref prevPoints, new Vector2(i,j));
400 | }
401 | }
402 | }
403 | }
404 | }
405 |
406 | return prevPoints; // stupid c# all paths must return crap
407 | }
408 |
409 | bool[,] subtraction(bool[,] b1, bool[,] b2) {
410 |
411 | int w = b1.GetLength(0); // width
412 | int h = b1.GetLength(1); // height
413 |
414 | bool[,] temp = new bool[w,h];
415 |
416 | for(int x=0; x=0 && j=0) {
441 | if(!b[i,j]) temp[x,y] = false;
442 | }
443 | else temp[x,y] = false;
444 | }
445 | }
446 | }
447 |
448 | return temp;
449 | }
450 |
451 | bool[,] dilation( ref bool[,] b) {
452 |
453 | bool[,] temp = b; //(bool[,]) b.Clone();
454 | int[,] dirs = {{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}};
455 |
456 | int w = b.GetLength(0); // width
457 | int h = b.GetLength(1); // height
458 |
459 | for(int x=0; x=0 && j=0)
466 | temp[i,j] = true;
467 | // else temp[i,j] = false; // Should already be false when initialsslslslsed
468 | }
469 | }
470 | }
471 | }
472 | return temp;
473 | }
474 |
475 | void binaryImageFromTex(out bool[,] b, ref Texture2D t) {
476 |
477 | b = new bool[t.width,t.height];
478 |
479 | for(int x=0; x 0); // If alpha >0 true then 1 else 0
482 | }
483 | }
484 | }
485 |
486 | // test functions below
487 | public void texFromBinaryImage(out Texture2D t, bool[,] b) {
488 |
489 | t = new Texture2D(b.GetLength(0),b.GetLength(1));
490 | t.wrapMode = TextureWrapMode.Clamp;
491 |
492 | for(int x=0; x > paths) {
501 |
502 | List colorList = new List() {
503 | Color.red,
504 | Color.green,
505 | Color.blue,
506 | Color.magenta,
507 | Color.yellow
508 | };
509 |
510 | t = new Texture2D(b.GetLength(0),b.GetLength(1));
511 | t.wrapMode = TextureWrapMode.Clamp;
512 |
513 | for(int i=0; i