项目最近在移植lua,考虑到后续会用到帧同步,就自告奋勇,尝试写一下碰撞系统,原来实现过AABB的碰撞,OBB碰撞就是在AABB的碰撞上加了个旋转,可理解为有向的AABB(AABB为轴对其,可理解为在同一坐标系中,各碰撞的正方向轴都一样),下图为AABB与OBB区别
刚开始做的时候把这个碰撞想的太简单,没有经过深思熟虑,直接就开始写,结果导致越写越乱,越写东西越多,写代码还是得先设计!
AABB碰撞检测
1.AABB构造:
矩形包围盒可采用:
①中心点加宽高
②者对角线两个点。(这是小学还是初中数学知识记不清了,确定一个矩形的方法)。
方便更新的话,我采用了中心点加宽高的构造,代码中物体变了位置,就改变一个position就行(AABB的center默认为position)
球形包围盒:圆心center,半径R
2.AABB相交;
①矩形与矩形相交:
其中min与max 都是根据AABB自身计算出的数值
②矩形与球形相交:
找到球与矩形最近的一点,最近的一点都没有相交,则不相交。这个转换需要自己想一会,刚开始我也没想明白,想明白了就好了。
if (other.Shape == ShapeType.Sphere)
{
ColliderSphere sphere = other as ColliderSphere;
FixPoint x = sphere.Position.x;
FixPoint y = sphere.Position.y;
FixPoint z = sphere.Position.z;
if (x > MaxX) { x = MaxX; }
if (x < MinX) { x = MinX; }
if (y > MaxY) { y = MaxY; }
if (y < MinY) { y = MinY; }
if (z > MaxZ) { z = MaxZ; }
if (z < MinZ) { z = MinZ; }
FixPoint distance = (x - sphere.Position.x) * (x - sphere.Position.x) +
(y - sphere.Position.y) * (y - sphere.Position.y) +
(z - sphere.Position.z) * (z - sphere.Position.z);
if (sphere.Radius * sphere.Radius >= distance)
{
return true;
}
return false;
}
③圆与圆相交:无需多言
if(other.Shape == ShapeType.Sphere)
{
Vector3FP other_pos = other.Position;
return Position.Distance(ref other_pos) <= Radius + (other as ColliderSphere).Radius;
}
AABB相交到此结束
OBB构造:
球旋转后还是自身,当做没旋转
长方体构造:
①8个顶点
②6个面
③3组平行面
④一个顶点和三个彼此正交的变相量
⑤中心点,旋转矩阵,3个1/2边长
一般都采用第五种构造方式,因为计算比较方便,但是内存也要考虑,一般都是储存欧拉角或者四元数代替旋转矩阵,在做相交测试时,又得转换回矩阵,得不偿失
一种较好的妥协方式是:只存储旋转矩阵中的两个轴,只是在测试时利用叉积计算第三个轴,相对来讲,能够降低CPU的操作开销,还能节省三个浮点数分量,降低了百分之20的内存消耗
–《实时检测碰撞算法技术》第4.4章
OBB相交:
抽象为分离轴测试,分离轴测试:对于某一轴L,如果两盒体投影半径之和小于中心点之间的投影距离,则OBB处于分离状态
下面列出c#代码(刚开始做的时候没有想太多,在网上扒了一份,自己得代码就不贡献了,我没有共享精神,写这个博客也记录一下这个工作).
public class ObbNew : MonoBehaviour
{
[HideInInspector]
public Transform m_Transform;
[HideInInspector]
public BoxCollider m_BoxCollider;
[HideInInspector]
private float m_Rotation;
[HideInInspector]
public Vector2 m_Extents;
[HideInInspector]
public Vector2[] m_Axiss;
void Start()
{
m_Transform = this.transform;
m_BoxCollider = m_Transform.GetComponent<BoxCollider>();
m_Axiss = new Vector2[2];
SetExtents();
}
// Update is called once per frame
void Update()
{
m_Rotation = m_Transform.eulerAngles.y * Mathf.PI / 180;
//两个轴
m_Axiss[0] = new Vector2((float)Mathf.Cos(m_Rotation), -(float)Mathf.Sin(m_Rotation));
m_Axiss[1] = new Vector2(Mathf.Sin(m_Rotation), Mathf.Cos(m_Rotation));
}
private void SetExtents()
{
Quaternion rotation = m_Transform.rotation;
m_Transform.rotation = new Quaternion(0, 0, 0, 1);
Vector3 center = m_BoxCollider.center;
Vector3 size = m_BoxCollider.size / 2;
Vector3 Point1 = new Vector3(center.x + size.x, center.y, center.z - size.z);
Vector3 Point2 = new Vector3(center.x - size.x, center.y, center.z + size.z);
Point1 = m_Transform.TransformPoint(Point1);
Point2 = m_Transform.TransformPoint(Point2);
m_Extents = new Vector2(Mathf.Abs(Point1.x - Point2.x) / 2, Mathf.Abs(Point2.z - Point1.z) / 2);
m_Transform.rotation = rotation;
}
public float dot(Vector2 a, Vector2 b)
{
return Mathf.Abs(a.x * b.x + a.y * b.y);
}
public float getProjectionRadius(Vector2 axis)
{
return (m_Extents.x * dot(m_Axiss[0], axis) + m_Extents.y * dot(m_Axiss[1], axis));
}
public bool intersects(ObbNew other)
{
Update();
other.Update();
Vector2 distanceVector = new Vector2(m_Transform.position.x - other.m_Transform.position.x, m_Transform.position.z - other.m_Transform.position.z);
Vector2[] checkObbVector2s =
{
m_Axiss[0],
m_Axiss[1],
other.m_Axiss[0],
other.m_Axiss[1],
};
for (int index = 0; index < checkObbVector2s.Length; index++)
{
Vector2 curVector2 = checkObbVector2s[index];
if ((getProjectionRadius(curVector2) + other.getProjectionRadius(curVector2)) <= dot(distanceVector, curVector2))
{
return false;
}
}
return true;
}
}
这是能跑的代码,直接拉过来一个cube加个BoxCollider就能跑了,不像别的博客,代码都是烂的(吐槽一下大多数博客,代码都不能运行你在那分享啥呢,误人子弟。我被坑过不少次)
球与OBB的碰撞 与上述的圆与矩形碰撞相似,不同的是将获取圆上离矩形最近一点改为球上与OBB
距离最近的一点
下面是伪代码
//给定点p,返回点q在(或在)OBB b上,最接近p
Vector2 ClosedPtPointToOBB(Point p,OBB b)
{
Vector q
Vector2 d = p -b.center
q = b.center
//2d只考虑x,y 3d则改为3个轴
//遍历 OBB的 轴
for(i = 0; i< 2;i++)
{
//把d投影到轴上,得到距离
//从盒子中心沿d轴
float dis = Vector2.Dot(d,b.u[i])
If(dis >b.e[i] )
Dis = b.e[i]
If(dis < -b.e[i])
dis = -b.e[i]
沿轴移动该距离以获得世界坐标
q +=dis * b.u[i]
}
return q
}
Test SphereOBB(Sphere s, OBB b )
{
Vector2 p = ClosedPtPointToOBB(s.center,b)
Vector2 v = p - s.center
Return Mathf.Dot(v,v) <=s.radius * s.radius
}
碰撞到此结束,主要是数学知识,从毕业一直写业务逻辑,数学知识都还给老师们了,写的我相当难受,用进废退,以后没事还是得看看数学,不能做一个沉迷于历史的程序员
好好学数学,说不定去做引擎了呢(微博狗头表情)
下面是四叉树,为何要用四叉树,上学的时候学这种枯燥的数据结构,一直提不起兴趣,当然,也没好好学,什么狗屁二叉树,叉你二大爷啊叉,有啥叉的,就分裂呗,为啥分裂,不知道,为啥要有二叉树,不知道,反正是感觉用不上,就没太在意过,然而为了应付不挂科,也稍微留了点意。
不是代码写完了能用了功能就完成了,跑起来就蒙蔽了,场景里500个墙,一发子弹打出去,每帧遍历循环这么多次,这还只是一发子弹,要是都动起来,这帧率,用不了,用不了。等于没写代码。
想了想,果然***前人总结的数据结构、算法、设计模式都不是白给的,都是趟过的坑***。
四叉树优化空间探测。不需要考虑物体的数量,只需考虑树的深度。时间复杂度这个概念也一下在我心里高大了起来
四叉树的教程有很多,我就不列出来了。
我的方案:
1.游戏初始化构造四叉树,深度定为8-10(综合项目地图和物体,这个深度需要根据不同情况变化)
2.实例化物体碰撞时,同步添加一份碰撞信息到树中。
3随着物体增多,树不断分裂
4.物体移动时,更新所在区域
5.检测碰撞时,找到这个物体可能碰撞的其余物体,四叉树递归
6.根据物体标签再进一步筛选可碰撞的list
7.碰撞检测.
总结:
1.写代码之前不设计,整个实现过程很慢,很浪费时间,如果在前***期设计规划一下,会很快的实现***。
2.数学呀!数学!
3.代码写完要优化,或者写的时候就要考虑到优化。我一直不信邪,一直被告诫c#和lua相互传递会很浪费效率,我就不以为意。直到500个碰撞体一起检测那一刹那,我的帧率快爆了
就一个get_position就这么酸爽!
Entity通过Proto数据创建碰撞体,不要访问GameObject,碰撞体有自身相对的长宽高数据,再根据LogicTransform的Position,Rotation算出对应的顶点
相机也有与GameObject位置交互的地方,后续考虑还是得挪回到c#那里写,方案待定