Unity实用小工具或脚本—自制2D碰撞体

一、       前言

又是一个好久没更新文章了,最近实在是太忙了。前段时间跳槽了,离开了自己曾经熟悉的一个环境,进入了一个全新的环境,初来乍到需要更对的精力去应对。还是废话不多说,一贯的惯例直接上图。


看到标题其实有人就要问了,自制2D的碰撞体。楼主你这是在炫技吗,Unity不是有自带的碰撞体和刚体可以用吗,为什么要吃饱了自己写一个?无奈!职场生活,装逼必死无疑。Unity的碰撞体和刚体只能在TmeScale!=0的时候使用。但是,那么在TimeScale=0的时候我们还需要这个功能怎么办呢?有人要说了,这不是要搞事情自己作死吗?没办法,需求摆在那,明知是坑也要去填啊于是乎,我就自己写了一个可以在TimeScale=0的情况下执行的碰撞。

一、       说明

1、尽管TimeScale=0了,但是在Update()函数里的脚本还是可以执行的,不能执行的是FixedUpdate()函数的脚本。因此,我的逻辑脚本都是在Update里写的。

2、这个碰撞体只实现了带三个点以上的多边形,比如长方形、棱形、不规则多边形等。对于没有点和边的形状,如圆形、椭圆等还不能实现碰撞。

3、仅仅是实现了2D的碰撞体。

二、       实现

1、判断点是否在线段上:

假设点的坐标为P(x1,y1),线段上的直线方程为Ax+By+C=0;将P点带入方程中得到的值如果等于0表示P在线段上。

代码如下:

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

public class UI2DPointToLine : MonoBehaviour {

    public Image imagePoint;

    public Image imageStartPoint;

    public Image imageEndPoint;

    public float value; 
	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
        value = Intersect();
        Debug.Log(value);
	}
    private float Intersect()
    {
        Vector3 startScreenPos = Camera.main.WorldToScreenPoint(imageStartPoint.transform.position);
        Vector3 endScreenPos = Camera.main.WorldToScreenPoint(imageEndPoint.transform.position);
        Vector3 pointSceenPos = Camera.main.WorldToScreenPoint(imagePoint.transform.position);

        //线段1的直线方程
        float A1 = endScreenPos.y - startScreenPos.y;
        float B1 = startScreenPos.x - endScreenPos.x;
        float C1 = endScreenPos.x * startScreenPos.y - startScreenPos.x * endScreenPos.y;

        float v11 = A1 * pointSceenPos.x + B1 * pointSceenPos.y + C1;

        return v11;
    }
}

1、判断两个线段是否相交:

假设线段P1O1的直线方程为A1x+B1y+C1;P2O2的直线方程为A2x+B2y+C2=0;

1>将P1O1的两个点带入到P2O2的直线方程中,得到的值相反或者有一个至少为零则P1O1线段和P2O2所在的直线方程相交;

2>同样,将P2O2带入P1O1的直线方程,判断的结果和1>一样;

3>如果1>和2>都满足则表示两个线段是相交的

如图所示:

Edge边的定义代码如下:

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

public class UI2DLineIntersect : MonoBehaviour {

    public Text resultText;

    public UI2DImageLine line1;
    public UI2DImageLine line2;

    public Vector3 line1StartInScreenPos;
    public Vector3 line1EndInScreenPos;

    public Vector3 line2StartInScreenPos;
    public Vector3 line2EndInScreenPos;

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        bool isCollision = Intersect();
        resultText.text = isCollision.ToString();
        Debug.Log(isCollision);
    }
    private bool Intersect()
    {
        bool isIntersect = false;

        bool yes1 = false;
        bool yes2 = false;

        line1StartInScreenPos = Camera.main.WorldToScreenPoint(line1.imageStartPoint.transform.position);
        line1EndInScreenPos = Camera.main.WorldToScreenPoint(line1.imageEndPoint.transform.position);

        line2StartInScreenPos = Camera.main.WorldToScreenPoint(line2.imageStartPoint.transform.position);
        line2EndInScreenPos = Camera.main.WorldToScreenPoint(line2.imageEndPoint.transform.position);

        //线段1的直线方程
        float A1 = line1EndInScreenPos.y - line1StartInScreenPos.y;
        float B1 = line1StartInScreenPos.x - line1EndInScreenPos.x;
        float C1 = line1EndInScreenPos.x * line1StartInScreenPos.y - line1StartInScreenPos.x * line1EndInScreenPos.y;

        //线段2的开始点在线段1直线方程的值
        float v11 = A1 * line2StartInScreenPos.x + B1 * line2StartInScreenPos.y + C1;
        float v12 = A1 * line2EndInScreenPos.x + B1 * line2EndInScreenPos.y + C1;

        //线段2的直线方程
        float A2 = line2EndInScreenPos.y - line2StartInScreenPos.y;
        float B2 = line2StartInScreenPos.x - line2EndInScreenPos.x;
        float C2 = line2EndInScreenPos.x * line2StartInScreenPos.y - line2StartInScreenPos.x * line2EndInScreenPos.y;

        //线段1的开始点在线段2直线方程的值
        float v21 = A2 * line1StartInScreenPos.x + B2 * line1StartInScreenPos.y + C2;
        float v22 = A2 * line1EndInScreenPos.x + B2 * line1EndInScreenPos.y + C2;

        Debug.Log("v11:" + v11 + "v12:" + v12 + "v21:" + v21 + "v22:" + v22+"...." +Mathf.Round(v11));

        if(Mathf.Round(v11)==0|| Mathf.Round(v12) == 0 || Mathf.Round(v21) == 0 || Mathf.Round(v22) == 0)
        {
            return true;
        }

        if((v11>0&&v12<0)||(v11<0&&v12>0))
        {
            yes1 = true;
        }

        if((v21>0&&v22<0)||(v21<0&&v22>0))
        {
            yes2 = true;
        }
        isIntersect = yes1 && yes2;

        return isIntersect;
    }
}

3、判断两个Box是否碰撞:有了2的判断剩下的就是将两个Box的每一个边进行相交判断;遍历比较就可以了。碰撞发生之后,两个物体要分别向位置向量的相反方向运动。代码如下:

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

public class UI2DEdge : MonoBehaviour {

    public EDGE_TYPE edgeType;

    public Vector3 StartPointInScreenPos;
    public Vector3 endPointInScreenPos;

    [SerializeField]
    private Image imageStartPoint;
    [SerializeField]
    private Image imageEndPoint;
//    [SerializeField]
    private Image imageParent;
    [SerializeField]
    private Image imageCurLine;
    private Vector3 startPointPos;
    private Vector3 endPointPos;
	// Use this for initialization
	void Start () {

        imageParent = transform.parent.GetComponent<Image>();
	}
	
	// Update is called once per frame
	void Update () {

        float halfWidth = imageParent.rectTransform.sizeDelta.x / 2.0f;
        float halfHeight = imageParent.rectTransform.sizeDelta.y / 2.0f;

        Canvas canvas = GameObject.Find("Canvas").GetComponent<Canvas>();

        switch (edgeType)
        {
            case EDGE_TYPE.TOP:
                {
                    startPointPos = new Vector3(-halfWidth, halfHeight, 0);
                    endPointPos = new Vector3(halfWidth, halfHeight, 0);
                    imageCurLine.rectTransform.sizeDelta = new Vector2(halfWidth * 2.0f, 1.0f);
                }
                break;
            case EDGE_TYPE.RIGHT:
                {
                    startPointPos = new Vector3(halfWidth, halfHeight, 0);
                    endPointPos = new Vector3(halfWidth, -halfHeight, 0);
                    imageCurLine.rectTransform.sizeDelta = new Vector2(1.0f, halfHeight * 2.0f);
                }
                break;
            case EDGE_TYPE.DOWN:
                {
                    startPointPos = new Vector3(halfWidth, -halfHeight, 0);
                    endPointPos = new Vector3(-halfWidth, -halfHeight, 0);
                    imageCurLine.rectTransform.sizeDelta = new Vector2(halfWidth * 2.0f, 1.0f);
                }
                break;
            case EDGE_TYPE.LEFT:
                {
                    startPointPos = new Vector3(-halfWidth, -halfHeight, 0);
                    endPointPos = new Vector3(-halfWidth, halfHeight, 0);
                    imageCurLine.rectTransform.sizeDelta = new Vector2(1.0f, halfHeight * 2.0f);
                }
                break;
        }

        imageStartPoint.transform.localPosition = startPointPos;
        imageEndPoint.transform.localPosition = endPointPos;

        imageCurLine.transform.localPosition = (endPointPos + startPointPos) / 2.0f;

        StartPointInScreenPos = Camera.main.WorldToScreenPoint(imageStartPoint.transform.position);
        endPointInScreenPos = Camera.main.WorldToScreenPoint(imageEndPoint.transform.position);
    } 

    public bool Is_Intersect(UI2DEdge otherEdge)
    {
        bool isIntersect = false;

        bool yes1 = false;
        bool yes2 = false;

       
        //线段1的直线方程
        float A1 = endPointInScreenPos.y - StartPointInScreenPos.y;
        float B1 = StartPointInScreenPos.x - endPointInScreenPos.x;
        float C1 = endPointInScreenPos.x * StartPointInScreenPos.y - StartPointInScreenPos.x * endPointInScreenPos.y;

        //线段2的开始点在线段1直线方程的值
        float v11 = A1 * otherEdge.StartPointInScreenPos.x + B1 * otherEdge.StartPointInScreenPos.y + C1;
        float v12 = A1 * otherEdge.endPointInScreenPos.x + B1 * otherEdge.endPointInScreenPos.y + C1;

        //线段2的直线方程
        float A2 = otherEdge.endPointInScreenPos.y - otherEdge.StartPointInScreenPos.y;
        float B2 = otherEdge.StartPointInScreenPos.x - otherEdge.endPointInScreenPos.x;
        float C2 = otherEdge.endPointInScreenPos.x * otherEdge.StartPointInScreenPos.y - otherEdge.StartPointInScreenPos.x * otherEdge.endPointInScreenPos.y;

        //线段1的开始点在线段2直线方程的值
        float v21 = A2 * StartPointInScreenPos.x + B2 * StartPointInScreenPos.y + C2;
        float v22 = A2 * endPointInScreenPos.x + B2 * endPointInScreenPos.y + C2;

      //     Debug.Log("v11:" + v11 + "v12:" + v12 + "v21:" + v21 + "v22:" + v22);

        if (Mathf.Round(v11) == 0 || Mathf.Round(v12) == 0 || Mathf.Round(v21) == 0 || Mathf.Round(v22) == 0)
        {
            return true;
        }

        if ((v11 > 0 && v12 < 0) || (v11 < 0 && v12 > 0))
        {
            yes1 = true;
        }

        if ((v21 > 0 && v22 < 0) || (v21 < 0 && v22 > 0))
        {
            yes2 = true;
        }
        isIntersect = yes1 && yes2;

        return isIntersect;
    }
}
public enum EDGE_TYPE
{
    TOP,RIGHT,DOWN,LEFT
}
碰撞体逻辑代码:

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

public class UI2DCollisionBox : MonoBehaviour {

    public const float MoveSpeed = 1.1f;

    public int ID;

    private UI2DEdge[] edges;
    private bool isInit = false;

    private List<UI2DCollisionBox> listOtherCollisionBox;
    // Use this for initialization
    void Start () {
        edges = GetComponentsInChildren<UI2DEdge>();
	}
	
	// Update is called once per frame
	void Update () {

        if (!isInit) return;
        Collision_Move();

	}

    private void Collision_Move()
    {
        if (null == listOtherCollisionBox) return;
        for (int i = 0; i < listOtherCollisionBox.Count; i++)
        {
            bool isCollision = Collision(listOtherCollisionBox[i]);
            if (isCollision)
            {
                float dis = Vector3.Distance(transform.position, listOtherCollisionBox[i].transform.position);
                Vector3 dir;
                if (Mathf.Round(dis) == 0)
                {
                    dir = Vector2.one;
                }
                else
                {
                    dir = listOtherCollisionBox[i].transform.position - transform.position;
                }
                transform.position -= dir.normalized * MoveSpeed;
            }
        }
    }

    private bool Collision(UI2DCollisionBox other)
    {
        bool isCollision = false;

        for (int i = 0; i <edges.Length; i++)
        {
            for (int j = 0; j < other.edges.Length; j++)
            {
                if(edges[i].Is_Intersect(other.edges[j]))
                {
                    return true;
                }
            }
        }

        return isCollision;
    }

    internal void Init(List<UI2DCollisionBox> listAllCollsionBox)
    {

        listOtherCollisionBox = listAllCollsionBox.FindAll(p => p.ID != ID);

        isInit = true;
    }
}

四、       总结

1、其实都是数学问题,而且用到的都是非常简单的数学原理。而用代码实现的过程才是我们程序员能力价值所在。

2、好好学习,天天向上!




猜你喜欢

转载自blog.csdn.net/zhangxiao13627093203/article/details/72860120
今日推荐