游戏中的2D OBB碰撞模型的碰撞算法介绍和实践
前言
上一篇博文说道,射线与场景中模型上的所有三角形求交时,会大幅度影响效率且花费比较多的时间,因此会采取使用包围盒的形式,进行一个加速求交。在此文中介绍OBB碰撞模型的碰撞算法
OBB的碰撞模型
有没有想过为什么需要用到OBB模型呢,假设一个场景内两个人物相撞了,你怎么判断它们是否相撞呢,大概就是它们的碰撞体接触在了一起就相撞了。那怎么算碰撞在一起呢(此处只讨论2D规则的包围盒模型)?
方向包围盒OBB"("Oriented Bounding Box)是目前比较流行的一种包围盒,OBB最大的特点是其方向的任意性,这使得可以根据被包围的对象的形状特点尽可能紧密地包围对象,Unity中的BoxCollider的其实就是OBB模型,它不是轴对称模型,而是有方向的

OBB模型
与之相反的是AABB模型,是轴对称模型,即它的边一定与坐标轴平行,算法简单,但使用的局限性比较大,更多还是使用OBB模型

碰撞算法分析
想要判断两个OBB模型碰撞,也就是两个矩形相交,我们分为几个步骤,首先先转换问题,什么时候两个矩形不相交,两个矩形相离可看成有多个直线可将它们之间分开。
当逐渐移动某个矩形,使得某个时刻,两个矩形只有一个交点,交点属于矩形的某条边上,此时为临界状态,当且仅当只有一条直线将他们两个分开,此时这条直线必定与某条边平行。


所以我们只需找两个矩形的四条边分别作为轴,两个矩形的xVt、yVt分别进行投影,看投影后的两个线段是否相离,如果相离则在这个轴上可以将这两个进行分开,故此时两个矩形不相交,反之若相交,则继续接着其他轴进行判断,若所有轴都不能分开,则这两个矩形相交
我们观察 AB proj 与 boxA、boxB 的 xVt proj 、yVt proj 之间的关系,可以得出结论:
AB proj > sum(Vt proj) ,则矩形相离
AB proj = sum(Vt proj) ,则矩形相切
AB proj < sum(Vt proj) ,则矩形相交

参考网上某大佬的代码
参考博文:https://www.cnblogs.com/hont/p/9501169.html
分别取两个矩形的两个边,总共进行四次投影对称,作为对称轴
axis1 = (P1 - P0).normalized;
axis2 = (P3 - P0).normalized;
axis3 = (other.P1 - other.P0).normalized;
axis4 = (other.P3 - other.P0).normalized;
mDebugInternalAxisIndex = 0;
bool isNotIntersect = false;
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis1);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis2);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis3);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis4);
这里是取带符号的长度,用来比较投影后的线段是否相交
float x_p0 = xProject_P0.magnitude * Mathf.Sign(Vector3.Dot(xProject_P0, axis));
float x_p1 = xProject_P1.magnitude * Mathf.Sign(Vector3.Dot(xProject_P1, axis));
float x_p2 = xProject_P2.magnitude * Mathf.Sign(Vector3.Dot(xProject_P2, axis));
float x_p3 = xProject_P3.magnitude * Mathf.Sign(Vector3.Dot(xProject_P3, axis));
相交判断:
if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;

简约的示例图
using UnityEngine;
// OBB.cs
public class OBB : MonoBehaviour
{
public bool enableDebug;
public int debug_axisIndex;
int mDebugInternalAxisIndex;
public Vector2 size;
public Color gizmosColor = Color.white;
Vector2 P0 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(-size * 0.5f); } }
Vector2 P1 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(size.x * 0.5f, -size.y * 0.5f, 0)); } }
Vector2 P2 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(size * 0.5f); } }
Vector2 P3 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(-size.x * 0.5f, size.y * 0.5f, 0)); } }
Vector2 axis1, axis2, axis3, axis4;
// 较参考博文添加以下变量,用来缓存向量减少gc
Vector3 xProject_P0;
Vector3 xProject_P1;
Vector3 xProject_P2;
Vector3 xProject_P3;
Vector3 yProject_P0;
Vector3 yProject_P1;
Vector3 yProject_P2;
Vector3 yProject_P3;
public bool Intersects(OBB other)
{
axis1 = (P1 - P0).normalized;
axis2 = (P3 - P0).normalized;
axis3 = (other.P1 - other.P0).normalized;
axis4 = (other.P3 - other.P0).normalized;
mDebugInternalAxisIndex = 0;
bool isNotIntersect = false;
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis1);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis2);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis3);
isNotIntersect |= ProjectionIsNotIntersect(this, other, axis4);
return isNotIntersect ? false : true;
}
bool ProjectionIsNotIntersect(OBB x, OBB y, Vector2 axis)
{
xProject_P0 = Vector3.Project(x.P0, axis);
xProject_P1 = Vector3.Project(x.P1, axis);
xProject_P2 = Vector3.Project(x.P2, axis);
xProject_P3 = Vector3.Project(x.P3, axis);
float x_p0 = xProject_P0.magnitude * Mathf.Sign(Vector3.Dot(xProject_P0, axis));
float x_p1 = xProject_P1.magnitude * Mathf.Sign(Vector3.Dot(xProject_P1, axis));
float x_p2 = xProject_P2.magnitude * Mathf.Sign(Vector3.Dot(xProject_P2, axis));
float x_p3 = xProject_P3.magnitude * Mathf.Sign(Vector3.Dot(xProject_P3, axis));
yProject_P0 = Vector3.Project(y.P0, axis);
yProject_P1 = Vector3.Project(y.P1, axis);
yProject_P2 = Vector3.Project(y.P2, axis);
yProject_P3 = Vector3.Project(y.P3, axis);
float y_p0 = yProject_P0.magnitude * Mathf.Sign(Vector3.Dot(yProject_P0, axis));
float y_p1 = yProject_P1.magnitude * Mathf.Sign(Vector3.Dot(yProject_P1, axis));
float y_p2 = yProject_P2.magnitude * Mathf.Sign(Vector3.Dot(yProject_P2, axis));
float y_p3 = yProject_P3.magnitude * Mathf.Sign(Vector3.Dot(yProject_P3, axis));
float xMin = Mathf.Min(x_p0, x_p1, x_p2, x_p3);
float xMax = Mathf.Max(x_p0, x_p1, x_p2, x_p3);
float yMin = Mathf.Min(y_p0, y_p1, y_p2, y_p3);
float yMax = Mathf.Max(y_p0, y_p1, y_p2, y_p3);
if (enableDebug)
{
if (debug_axisIndex == mDebugInternalAxisIndex)
{
Debug.DrawRay(Vector3.Project(x.P0, axis), Vector3.one * 0.1f);
Debug.DrawRay(Vector3.Project(x.P2, axis), Vector3.one * 0.1f);
Debug.DrawRay(Vector3.Project(y.P0, axis), Vector3.one * 0.1f, Color.white * 0.9f);
Debug.DrawRay(Vector3.Project(y.P2, axis), Vector3.one * 0.1f, Color.white * 0.9f);
Debug.DrawRay(Vector3.zero, Vector3.one * 0.1f, Color.black);
Debug.DrawRay(Vector3.zero, axis, Color.yellow);
Debug.DrawRay(xMin * Vector3.right, Vector3.one * 0.1f, Color.blue);
Debug.DrawRay(xMax * Vector3.right, Vector3.one * 0.1f, Color.cyan);
Debug.DrawRay(yMin * Vector3.right, Vector3.one * 0.1f, Color.red * 0.5f);
Debug.DrawRay(yMax * Vector3.right, Vector3.one * 0.1f, Color.red * 0.5f);
Debug.Log("(yMin >= xMin && yMin <= xMax): " + (yMin >= xMin && yMin <= xMax) + " frame count: " + Time.frameCount);
Debug.Log("(yMax >= xMin && yMax <= xMax): " + (yMax >= xMin && yMax <= xMax) + " frame count: " + Time.frameCount);
Debug.Log("(xMin >= yMin && xMin <= yMax): " + (xMin >= yMin && xMin <= yMax) + " frame count: " + Time.frameCount);
Debug.Log("(xMax >= yMin && xMax <= yMax): " + (xMax >= yMin && xMax <= yMax) + " frame count: " + Time.frameCount);
}
mDebugInternalAxisIndex++;
}
if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;
// 此处只需做两次判断即可,参考博文做了四次判断
// if (xMin >= yMin && xMin <= yMax) return false;
// if (xMax >= yMin && xMax <= yMax) return false;
return true;
}
void OnDrawGizmos()
{
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.color = gizmosColor;
Gizmos.DrawWireCube(Vector3.zero, new Vector3(size.x, size.y, 1f));
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// OBBTest.cs
public class OBBTest : MonoBehaviour
{
public OBB a;
public OBB b;
void Update()
{
var isIntersects = a.Intersects(b);
if (isIntersects)
{
a.gizmosColor = Color.red;
b.gizmosColor = Color.red;
}
else
{
a.gizmosColor = Color.white;
b.gizmosColor = Color.white;
}
}
}
效果


游戏中的2D OBB碰撞模型的碰撞算法介绍和实践的更多相关文章
- 游戏中的AOI(Area of Interest)算法
游戏中的AOI(Area of Interest)算法 游戏的AOI算法应该算作游戏的基础核心了,许多逻辑都是因为AOI进出事件驱动的,许多网络同步数据也是因为AOI进出事件产生的.因此,良好的AOI ...
- 游戏中的 2D 可见性
转自:http://www.gameres.com/469173.html 拖动圆点转一圈,看看玩家都能看到些什么: 这个算法也能计算出给定光源所照亮的区域.对每条光线,我们可以构建出被照亮区域的光线 ...
- 如何在Cocos2D游戏中实现A*寻路算法(六)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- Unity 2D游戏开发教程之游戏中精灵的跳跃状态
Unity 2D游戏开发教程之游戏中精灵的跳跃状态 精灵的跳跃状态 为了让游戏中的精灵有更大的活动范围,上一节为游戏场景添加了多个地面,于是精灵可以从高的地面移动到低的地面处,如图2-14所示.但是却 ...
- 地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了
地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了 四叉树对于区域查询,效率比较高. 原理图
- Unity3D 2D游戏中寻径算法的一些解决思路
需求 unity3d的3d开发环境中,原生自带了Navigation的组件,可以很便捷快速的实现寻路功能.但是在原生的2d中并没有相同的功能. 现在国内很多手机游戏都有自动寻路的功能,或者游戏中存在一 ...
- 《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9374935 作者:七十一雾央 新浪微博:http:// ...
- Unity3D系列教程--使用免费工具在Unity3D中开发2D游戏 第一节
声明: 本博客文章翻译类别的均为个人翻译,版权全部.出处: http://blog.csdn.net/ml3947,个人博客:http://www.wjfxgame.com. 译者说明:这是一个系 ...
- 《MFC游戏开发》笔记十 游戏中的碰撞检测进阶:地图类型&障碍物判定
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9394465 作者:七十一雾央 新浪微博:http:// ...
随机推荐
- IDEA的注册方式
http://idea.lanyus.com/ 使用前请将"0.0.0.0 account.jetbrains.com"添加到hosts文件中 hosts文件在C:\Windows ...
- 关于Java中Collections.sort和Arrays.sort的稳定性问题
一 问题的提出 关于Java中Collections.sort和Arrays.sort的使用,需要注意的是,在本文中,比较的只有Collections.sort(List<T> ele ...
- Java例题_17 猴子偷桃问题
1 /*17 [程序 17 猴子吃桃问题] 2 题目:猴子吃桃问题 3 猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 4 第二天早上又将剩下的桃子吃掉一半,又多吃了一个. 5 以后每天 ...
- 移动端调试vConsole
当我们在进行移动端开发的时候,经常会出现在pc显示正常,在移动端却各种异常的情况.这时候我们在手机上又看不到error log. 所以我们就需要vConsole这样一个移动端开发神器. 那具体要怎么使 ...
- jQuery核心函数和静态方法
jQuery核心函数 从jQuery文档中可以看出,jQuery核心函数一共3大类4小类 jQuery(callback) 当DOM加载完成后执行传入的回调函数 <script> $(fu ...
- 小心,别被eureka坑了
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringCloud将它集成在其子项 ...
- oo第二单元博客总结
P1 设计结构 三次作业的架构都没有较大的改动,基本上都是靠调度器接受输入的请求并放入队列,然后调度器根据不同的电梯的当前状态来把请求分配至不同电梯的请求队列中,最后电梯再根据自己的请求队列去运行.因 ...
- 如何自己设计一个类似dubbo的rpc框架?
(1)上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用zookeeper来做,对吧 (2)然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存 ...
- 《疯狂Kotlin讲义》读书笔记4——流程控制
流程控制 与Java类似,Kotlin同样提供了两种基本的流程控制结构:分支结构和循环结构. Kotlin提供了 if 和 when 两种分支语句,其中 when 语句可以代替Java的switch语 ...
- shell 使用 cat 配合 EOF 创建文件并写入多行内容
之前折腾 GtiHub Actions 想实现提交 issue 后将 issue 的内容生成一个 Markdown 文件提交到仓库,从而实现自动发布到 GitHub Pages 的目的.倒是有一些现成 ...