您的位置:首页 > 移动开发 > Unity3D

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

2017-06-04 17:25 531 查看
一、       前言

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



看到标题其实有人就要问了,自制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、好好学习,天天向上!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: