Scaling PolygonCollider2D

Sometimes you want to select item on screen according to its graphical shape rather than a simple rectangular collider. In that case the best solution for that is Unity’s build in PolygonCollider2D component. Unfortunatelly for some images it could calculate shape that is too thin or too thick for our needs. I’m going to present how to scale calulated path according to vertices normal points that will give such effect:

scaling_result

Let’s start with preparing the component that will work with PolygonCollider2D.

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(PolygonCollider2D))]
public class ScalePolygonCollider2D : MonoBehaviour
{
    [SerializeField][Range(0,1f)]
    private float m_scale = 0.1f; // scale setting

    private PolygonCollider2D m_polygonCollider;
    private Vector2[] m_path; // original path

}

Next we have to obtain the path generated by Unity. It’s best to do it in Awake.

private void Awake()
{
    m_polygonCollider = GetComponent();
    m_path = m_polygonCollider.GetPath(0);

    m_polygonCollider.SetPath(0, ScalePath(m_scale));
}

To scale the path you need to calculate each point normal and apply the change multiplied by desired scale. Since there can be two normals we have to find the one pointing outside. That’s very easy because the points that we get from PolygonCollider2D are ordered clockwise and the normal calulation is always the same: positive y-delta and negative x-delta.

private Vector2[] ScalePath(float scale)
{
    var scaledPath = new List();

    for (int i = 0; i < m_path.Length; i++) {

        var p1 = m_path[i]; // first point
var p2 = m_path[(i + 1) % m_path.Length]; // next

var dx = p2.x - p1.x;
        var dy = p2.y - p1.y;

var n = new Vector2(dy, -dx).normalized; // normalize for uniform effect

        scaledPath.Add(p1 + n * scale);
    }

    return scaledPath.ToArray();
}

Of course you can extend this script by adding few lines to make it work in editor without running the game. To do this just add the ExecuteInEditMode attribute before class declaration and add an editor only Update function:

#if UNITY_EDITOR
[ExecuteInEditMode]
private void Update()
{
    if(m_scale != m_currentScale) {
        m_polygonCollider.SetPath(0, ScalePath(m_scale));
        m_currentScale = m_scale;
    }
}
#endif

I’ve also used here an additional local variable to only rebuild the path when the scale changed.

One thing that you could experiment to get better results its taking different points to calulate normal: I’ve chosen the first and next point because previous and next point gave me sometimes wierd results, but there could be better solution to take the middle points between previous-to-current and current-to-next points:


var p0 = m_path[(i - 1 < 0 ? m_path.Length - 1 : i - 1)];
var p1 = m_path[i];
var p2 = m_path[(i + 1) % m_path.Length];

var point1 = (p0 + p1) / 2f;
var point2 = (p1 + p2) / 2f;

var dx = point2.x - point1.x;
var dy = point2.y - point1.y;

You can see here the new method compared to old one:

test1

What approach worked for you better? Or maybe you came up with other solution? Let me know in the comments.

Cheers!

Dodaj komentarz