游戏中的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:// ...
随机推荐
- 如何强制删除一个apk
有些apk安装完后无法卸载,现在收集了一些方法,以及个人的一些手段. 1. 假设该app名为ketech,安装包名为ketech.apk. 2. 查看/data/app里面是否有名称包含ketech的 ...
- HDU_3071 Gcd & Lcm game 【素数分解 + 线段树 + 状压】
一.题目 Gcd & Lcm game 二.分析 非常好的一题. 首先考虑比较暴力的做法,肯定要按区间进行处理,对于$lcm$和$gcd$可以用标准的公式进行求,但是求$lcm$的时候是肯定 ...
- python plt画图横纵坐标0点重合
# -*- coding: utf-8 -*- import numpy as np import matplotlib.mlab as mlab import matplotlib.pyplot a ...
- 【Linux学习笔记0】-虚拟机运行CentOS(VMware12+CentOS)
目录 一,资源 二,VMware12安装 记录自己学习linux的过程.这将会是一个系列,本文是该系列的第一部分,主要记录虚拟机(VMware12)及对应操作系统(CentOS)的安装过程. 虚拟机( ...
- 别再面向 for 循环编程了,JDK 自带的观察者模式就很香!
大家好,你还在面向 for 循环编程吗? 还有谁不会用观察者模式吗? 本篇栈长带来<观察者模式>理论及实战- 什么是观察者模式? 观察者模式(Observer Pattern)定义了对象间 ...
- Vue组件(35)动态组件 component 的 is 到底可以是啥?
component 动态组件 Vue官网上提供了一个动态组件 <component :is="currentTabComponent"> ,那么这里的 is 到底是什么 ...
- CMS前世今生
CMS一直是面试中的常考点,今天我们用通俗易懂的语言简单介绍下. 垃圾回收器为什么要分区分代? 如上图:JVM虚拟机将堆内存区域分代了,先生代是朝生夕死的区域,老年代是老不死的区域,不同的年代对象有不 ...
- Database | 浅谈Query Optimization (2)
为什么选择左深连接树 对于n个表的连接,数量为卡特兰数,近似\(4^n\),因此为了减少枚举空间,早期的优化器仅考虑左深连接树,将数量减少为\(n!\) 但为什么是左深连接树,而不是其他样式呢? 如果 ...
- [Fundamental of Power Electronics]-PART II-8. 变换器传递函数-8.1 Bode图回顾
8.0 序 工程设计过程主要包括以下几个过程: 1.定义规格与其他设计目标 2.提出一个电路.这是一个创造性的过程,需要利用工程师的实际见识和经验. 3.对电路进行建模.变换器的功率级建模方法已经在第 ...
- 软工案例分析作业-CSDN
项目 内容 这个作业属于哪个课程 2021春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 案例分析作业要求 我在这个课程的目标是 提升软件开发能力与团队意识 这个作业在哪个具体方面帮助我实 ...