├── 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 |
11 | 12 | 13 | 14 |
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