【Unity】根据LineRenderer形状给物体添加PolygonCollider2D碰撞体

951 阅读3分钟

我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛

物理小游戏的功能需求: image.png

在实时的绘制完成LineRenderer之后,会动态的添加PolygonCollider2D多边形碰撞体。读取LineRenderer的点位(一条折线),左右各偏移一定距离,形成一个闭合的线框,利用线框的点位修改PolygonCollider2D形状。

image.png

工作流程:

  • 通过Input监听鼠标事件
  • 按下鼠标,开始绘制LineRenderer图形
  • 鼠标移动过程中,实时刷新图形
  • 抬起鼠标,结束图形绘制,添加碰撞盒

说明:touchPosList是一条手指移动轨迹点位的连线,只是一条折线,可以用于绘制LineRenderer,并给可以用于绘制LineRenderer设置显示宽度;但不能直接用于生成PolygonCollider,需要将点位连线转换成一条有宽度的面。这里将一个点位沿法线方向与法线反方向各偏移一定距离,生成2个新的点位,按顺序连接新点位,形成一个闭合的轮廓。

绿色是捕捉到的点位,紫色是偏移后的点位: image.png

LineRenderer.SetPositions(Vector3[] positions):

  • 设置LineRenderer的图形绘制路径。 LineRenderer.SetWidth(float start,float end):
  • 设置LineRenderer的起始、结束宽度。

PolygonCollider.SetPath(int index, Vector2[] points):

  • index:多边形可能有孔和不连续部分,因此其形状不一定由单个路径定义。例如,多边形可能实际上有3个单独的路径。这种情况下将调用SetPath(0)、SetPath(1)、SetPath(2)。
  • points:定义多边形轮廓的各点之间的线段的循环序列,points中的点位可以围成一个闭合且不交叉的折线。

完整测试代码,关键代码的作用都写在注释里:

using System.Collections.Generic;
using UnityEngine;
 
public class LineCollider : MonoBehaviour
{
    //绘制过程中显示用LineRenderer
    [SerializeField] LineRenderer drawingLine;
    //带有碰撞体的LineRenderer, 要有PolygonCollider2D、LineRenderer组件, 
    [SerializeField] GameObject colliderLinePrefab;
    //灵敏度 
    [SerializeField] float minTouchDistance = 0.1f;
 
    //相机设置为正交模式,才能转换为世界坐标tmpTouchPos = mainCamera.ScreenToWorldPoint(Input.mousePosition)
    Camera mainCamera;
 
    void Start()
    {
        mainCamera = Camera.main;
        //
        drawingLine.enabled = false;
        drawingLine.positionCount = 0;
    }
 
    Vector3 tmpTouchPos;
    bool tmpIsMouseDown;
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            tmpIsMouseDown = true;
            tmpTouchPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
            tmpTouchPos.z = 0;
            OnPressScreen(tmpTouchPos);
        }
        else if (Input.GetMouseButton(0) && tmpIsMouseDown)
        {
            tmpTouchPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
            tmpTouchPos.z = 0;
            OnTouchScreen(tmpTouchPos);
        }
        else if (Input.GetMouseButtonUp(0) && tmpIsMouseDown)
        {
            tmpTouchPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
            tmpTouchPos.z = 0;
            OnReleaseScreen(tmpTouchPos);
            tmpIsMouseDown = false;
        }
    }
 
    //鼠标点击
    Vector3 lastPos;
    List<Vector3> touchPosList = new List<Vector3>();
    public void OnPressScreen(Vector3 pressPos)
    {
        touchPosList.Clear();
        lastPos = pressPos;
 
        drawingLine.enabled = true;
        drawingLine.positionCount = 0;
    }
 
    //鼠标拖拽
    Vector3 tmpStartPoint;
    Vector3 tmpEndPoint;
    public void OnTouchScreen(Vector3 touchPos)
    {
        tmpStartPoint = lastPos;
        tmpEndPoint = touchPos;
 
        //显示绘制line
        if (Vector3.Distance(tmpStartPoint, tmpEndPoint) > minTouchDistance)
        {
            touchPosList.Add(lastPos);
 
            drawingLine.positionCount = touchPosList.Count;
            drawingLine.SetPosition(touchPosList.Count - 1, touchPosList[touchPosList.Count - 1]);
 
            lastPos = touchPos;
        }
    }
 
    //鼠标抬起
    public void OnReleaseScreen(Vector3 releasePos)
    {
        drawingLine.positionCount = 0;
        drawingLine.enabled = false;
 
        //绘制结束,生成碰撞line
        touchPosList.Add(releasePos);
        if (touchPosList.Count > 3)
            CreateColliderLine(touchPosList);
    }
 
    //生成碰撞line
    void CreateColliderLine(List<Vector3> pointList)
    {
        GameObject prefab = Instantiate(colliderLinePrefab, transform);
        LineRenderer lineRenderer = prefab.GetComponent<LineRenderer>();
        PolygonCollider2D polygonCollider = prefab.GetComponent<PolygonCollider2D>();
 
        lineRenderer.positionCount = pointList.Count;
        lineRenderer.SetPositions(pointList.ToArray());
 
        List<Vector2> colliderPath = GetColliderPath(pointList);
        polygonCollider.SetPath(0, colliderPath.ToArray());
    }
 
    //计算碰撞体轮廓
    float colliderWidth;
    List<Vector2> pointList2 = new List<Vector2>();
    List<Vector2> GetColliderPath(List<Vector3> pointList3)
    {
        //碰撞体宽度
        colliderWidth = drawingLine.startWidth;
        //Vector3转Vector2
        pointList2.Clear();
        for (int i = 0; i < pointList3.Count; i++)
        {
            pointList2.Add(pointList3[i]);
        }
        //碰撞体轮廓点位
        List<Vector2> edgePointList = new List<Vector2>();
        //以LineRenderer的点位为中心, 沿法线方向与法线反方向各偏移一定距离, 形成一个闭合且不交叉的折线
        for (int j = 1; j < pointList2.Count; j++)
        {
            //当前点指向前一点的向量
            Vector2 distanceVector = pointList2[j - 1] - pointList2[j];
            //法线向量
            Vector3 crossVector = Vector3.Cross(distanceVector, Vector3.forward);
            //标准化, 单位向量
            Vector2 offectVector = crossVector.normalized;
            //沿法线方向与法线反方向各偏移一定距离
            Vector2 up = pointList2[j - 1] + 0.5f * colliderWidth * offectVector;
            Vector2 down = pointList2[j - 1] - 0.5f * colliderWidth * offectVector;
            //分别加到List的首位和末尾, 保证List中的点位可以围成一个闭合且不交叉的折线
            edgePointList.Insert(0, down);
            edgePointList.Add(up);
            //加入最后一点
            if (j == pointList2.Count - 1)
            {
                up = pointList2[j] + 0.5f * colliderWidth * offectVector;
                down = pointList2[j] - 0.5f * colliderWidth * offectVector;
                edgePointList.Insert(0, down);
                edgePointList.Add(up);
            }
        }
        //返回点位
        return edgePointList;
    }
}