3
\$\begingroup\$

I'm doing a tiled based platformer in unity, and I was having some issues with collisions and I figured the problem was the way my colliders were arranged.

So right now, my collision look like this:

example 1

You see each tile has its own collider, outlined in black. Polygon colliders in unity are formed by lists of points in space, and I can manipulate them freely. What I would ultimately want, is to have all this colliders in the example image to merge into just two colliders ( or maybe one, depending on how the edges of those squares touching would be handled ):

example 2

How could I achieve that? Is there any known algorithm?

\$\endgroup\$
7
  • \$\begingroup\$ I've done this in the past using the Clipper libs: angusj.com/delphi/clipper.php \$\endgroup\$ – jgallant Jul 22 '16 at 10:28
  • \$\begingroup\$ Are you asking how to take two adjacent colliders A and B and combine them and their collision polygons? Or are you asking how to, given an arbitrary collection of colliders, determine which ones form a set of adjacent, connected colliders so you can merge them? \$\endgroup\$ – user1430 Jul 22 '16 at 15:44
  • \$\begingroup\$ @JoshPetrie I think checking witch colliders need to be merged, and executing the merge, would be two parts of the whole process, unless the algorithm somehow makes this checking be unnecessary. \$\endgroup\$ – Felipe Müller Jul 22 '16 at 16:04
  • \$\begingroup\$ You're right they're both part of the process, but if you already know how to do one or the other, that clarification can impact potential answerers. \$\endgroup\$ – user1430 Jul 22 '16 at 16:10
  • \$\begingroup\$ @Jon thanks! I managed to apply this library into my project and its working. The only thing is that some of my collisions areas were still being divided where they shouldn't be, I don't know if this is a problem with my position precision, witch I checked and it doesn't seem to be, or if it's and issue with the library itself. Fortunately the library has an offset feature witch I used to expand my polygons a tiny bit outside, just to make them touch, and this got rid of all the separations. \$\endgroup\$ – Felipe Müller Jul 23 '16 at 0:14
4
\$\begingroup\$

Ok, so I solved this problem using the Clipper library: http://www.angusj.com/delphi/clipper.php

The library has an all in one .cs file, so the easiest way to use it in unity, is to just paste this file in your Assets folder.

Here is what the collision looks like in my scene view, before and after:

enter image description here enter image description here

It's not 100% perfect, but I think I'll do in most cases.

Here is the commented code:

using UnityEngine;
using System.Collections.Generic;
using System;

using ClipperLib;
using Path = System.Collections.Generic.List<ClipperLib.IntPoint>;
using Paths = System.Collections.Generic.List<System.Collections.Generic.List<ClipperLib.IntPoint>>;

//this function takes a list of polygons as a parameter, this list of polygons represent all the polygons that constitute collision in your level.
public List<List<Vector2>> UniteCollisionPolygons(List<List<Vector2>> polygons)
{
    //this is going to be the result of the method
    List<List<Vector2>> unitedPolygons = new List<List<Vector2>>();
    Clipper clipper = new Clipper();

    //clipper only works with ints, so if we're working with floats, we need to multiply all our floats by
    //a scaling factor, and when we're done, divide by the same scaling factor again
    int scalingFactor = 10000;

    //this loop will convert our List<List<Vector2>> to what Clipper works with, which is "Path" and "IntPoint"
    //and then add all the Paths to the clipper object so we can process them
    for (int i = 0; i < polygons.Count; i++)
    {
        Path allPolygonsPath = new Path(polygons[i].Count);

        for (int j = 0; j < polygons[i].Count; j++)
        {
            allPolygonsPath.Add(new IntPoint(Mathf.Floor(polygons[i][j].x * scalingFactor), Mathf.Floor(polygons[i][j].y * scalingFactor)));
        }
        clipper.AddPath(allPolygonsPath, PolyType.ptSubject, true);

    }

    //this will be the result
    Paths solution = new Paths();

    //having added all the Paths added to the clipper object, we tell clipper to execute an union
    clipper.Execute(ClipType.ctUnion, solution);

    //the union may not end perfectly, so we're gonna do an offset in our polygons, that is, expand them outside a little bit
    ClipperOffset offset = new ClipperOffset();
    offset.AddPaths(solution, JoinType.jtMiter, EndType.etClosedPolygon);
    //5 is the ammount of offset
    offset.Execute(ref solution, 5f);

    //now we just need to conver it into a List<List<Vector2>> while removing the scaling
    foreach (Path path in solution)
    {
        List<Vector2> unitedPolygon = new List<Vector2>();
        foreach (IntPoint point in path)
        {
            unitedPolygon.Add(new Vector2(point.X / (float)scalingFactor, point.Y / (float)scalingFactor));
        }
        unitedPolygons.Add(unitedPolygon);
    }

    //this removes some redundant vertices in the polygons when they are too close from each other
    //may be useful to clean things up a little if your initial collisions don't match perfectly from tile to tile
    unitedPolygons = RemoveClosePointsInPolygons(unitedPolygons);

    //everything done
    return unitedPolygons;
}

//create the collider in unity from the list of polygons
public void CreateLevelCollider(List<List<Vector2>> polygons)
{
    GameObject colliderObj = new GameObject("LevelCollision");
    colliderObj.layer = GR.inst.GetLayerID(Layer.PLATFORM);
    colliderObj.transform.SetParent(level.levelObj.transform);

    PolygonCollider2D collider = colliderObj.AddComponent<PolygonCollider2D>();

    collider.pathCount = polygons.Count;

    for (int i = 0; i < polygons.Count; i++)
    {
        Vector2[] points = polygons[i].ToArray();

        collider.SetPath(i, points);
    }
}

public List<List<Vector2>> RemoveClosePointsInPolygons(List<List<Vector2>> polygons)
{
    float proximityLimit = 0.1f;

    List<List<Vector2>> resultPolygons = new List<List<Vector2>>();

    foreach(List<Vector2> polygon in polygons)
    {
        List<Vector2> pointsToTest = polygon;
        List<Vector2> pointsToRemove = new List<Vector2>();

        foreach (Vector2 pointToTest in pointsToTest)
        {
            foreach (Vector2 point in polygon)
            {
                if (point == pointToTest || pointsToRemove.Contains(point)) continue;

                bool closeInX = Math.Abs(point.x - pointToTest.x) < proximityLimit;
                bool closeInY = Math.Abs(point.y - pointToTest.y) < proximityLimit;

                if (closeInX && closeInY)
                {
                    pointsToRemove.Add(pointToTest);
                    break;
                }
            }
        }
        polygon.RemoveAll(x => pointsToRemove.Contains(x));

        if(polygon.Count > 0)
        {
            resultPolygons.Add(polygon);
        }
    }

    return resultPolygons;
}
\$\endgroup\$
2
  • \$\begingroup\$ Can this be adapted for BoxColliders and 3D? \$\endgroup\$ – Aaron Franke Aug 25 '17 at 20:49
  • \$\begingroup\$ Not very easily I don't think, because the library works with 2d points only. \$\endgroup\$ – Felipe Müller Aug 25 '17 at 22:36
-4
\$\begingroup\$

use Composite Collider component

\$\endgroup\$
1
  • 3
    \$\begingroup\$ How would they do that? Could you detail your answer? \$\endgroup\$ – Vaillancourt Nov 25 '18 at 19:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.