拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C++碰撞检测算法精解:从球体、AABB到GJK与SAT实战

1. 项目概述为什么碰撞检测是C工程师的硬核技能如果你正在用C开发游戏、物理仿真、机器人导航或者任何需要处理物体交互的应用程序那么“碰撞检测”绝对是你绕不开的核心课题。这不仅仅是判断两个物体是否碰到一起那么简单它直接关系到程序的物理真实性、运行效率和用户体验。一个粗糙的碰撞检测会让你的角色穿墙而过让物理模拟变得滑稽可笑而一个高效精准的检测算法则是构建沉浸式虚拟世界的基石。我见过太多项目初期为了快速验证想法用一个简单的包围盒检测就糊弄过去了。结果到了后期角色在复杂地形上抖动、子弹从敌人身体缝隙穿过、物理堆叠时莫名爆炸……这些问题排查起来极其痛苦往往需要伤筋动骨地重构整个碰撞系统。所以掌握几种关键的碰撞检测算法并深刻理解其适用场景和性能代价是每一位追求卓越的C工程师的必备技能。今天我就结合自己踩过的坑和实战经验为你拆解五种在C中实现精准碰撞检测的关键算法从最基础的到相对复杂的让你知其然更知其所以然。2. 核心需求解析从“是否碰到”到“如何优雅地碰到”在深入算法之前我们必须先厘清碰撞检测要解决的核心问题。它通常分为两个阶段广义阶段和狭义阶段。广义阶段负责快速剔除那些明显不可能发生碰撞的物体对比如一个在屏幕左边的角色和一个在屏幕右边的子弹这阶段追求速度。狭义阶段则对经过筛选的、可能发生碰撞的物体对进行精确的几何相交测试这阶段追求精度。我们的需求不仅仅是返回一个“true”或“false”。在一个成熟的系统中我们通常还需要知道碰撞点具体在哪一个或哪些点发生了接触碰撞法线碰撞表面的方向这对于计算反弹角度至关重要。穿透深度一个物体嵌入另一个物体的深度用于在物理响应中将其“推离”。性能必须在每帧通常是16.6毫秒或更短内处理成百上千个物体算法必须高效。不同的几何形状球体、AABB、OBB、凸多边形、凹多边形和不同的应用场景实时游戏、离线仿真、VR/AR对上述需求的侧重点完全不同。下面我们就从最常见的形状开始逐一剖析五种关键算法。3. 算法一球体与球体碰撞检测——最基础的起点球体碰撞检测是所有碰撞检测中最简单、最快速的一种因其数学上的简洁性常被用作碰撞检测的第一道关卡或粗略检测。3.1 算法原理与数学基础球体的碰撞检测原理基于三维空间中的距离公式。一个球体可以由其球心坐标(cx, cy, cz)和半径r唯一定义。判断两个球体是否相交本质上就是判断两个球心之间的距离是否小于两球半径之和。设球体A球心C1 (x1, y1, z1)半径r1。 设球体B球心C2 (x2, y2, z2)半径r2。 两球心距离的平方为distanceSq (x2-x1)^2 (y2-y1)^2 (z2-z1)^2。 如果distanceSq (r1 r2)^2则两球体相交或相切。注意在计算机中我们通常先比较距离的平方而不是先开方得到距离。因为开方运算sqrt()相对昂贵。直接比较平方值可以避免不必要的开方计算这是性能优化中的一个经典技巧。3.2 C实现与优化技巧一个基础的C实现如下所示#include cmath struct Sphere { float center[3]; float radius; }; bool spheresIntersect(const Sphere a, const Sphere b) { float dx b.center[0] - a.center[0]; float dy b.center[1] - a.center[1]; float dz b.center[2] - a.center[2]; float distanceSq dx*dx dy*dy dz*dz; float radiusSum a.radius b.radius; return distanceSq radiusSum * radiusSum; }实操心得与优化内存布局对于需要处理大量球体的场景比如粒子系统可以考虑使用结构数组而不是对象数组即Sphere spheres[N]这有利于CPU缓存预取提升访问速度。SIMD优化在现代CPU支持SSE/AVX指令集上我们可以使用SIMD指令一次性计算多个维度上的差值。例如使用__m128类型来加载和存储中心坐标用一条指令完成三个维度的减法、乘法和加法大幅提升批量检测的速度。提前剔除在广义阶段可以将空间划分为网格或使用空间索引结构如四叉树、八叉树只对同一单元格或相邻单元格内的球体进行精确检测。4. 算法二轴对齐包围盒检测——应用最广泛的快速方案轴对齐包围盒简称AABB是指其边与坐标轴平行的长方体。它是游戏和图形学中使用最广泛的包围体因为它的相交测试极其高效。4.1 AABB的定义与优势一个AABB可以用两个点来定义最小点min (x_min, y_min, z_min)和最大点max (x_max, y_max, z_max)。这个盒子包含了物体在所有轴向上的最大和最小边界。它的核心优势在于相交测试的简单性。判断两个AABB是否相交只需要检查它们在每一个坐标轴上的投影区间是否重叠。如果所有轴上的投影都重叠则两个AABB相交只要有一个轴不重叠它们就不相交。这被称为分离轴定理在AABB上的一个特例。4.2 分离轴定理的简化应用对于AABB分离轴就是三个坐标轴X, Y, Z。测试如下 两个AABB A和B相交当且仅当A.max.x B.min.x且A.min.x B.max.xX轴重叠并且A.max.y B.min.y且A.min.y B.max.yY轴重叠并且A.max.z B.min.z且A.min.z B.max.zZ轴重叠这个测试只需要最多6次比较操作速度极快。4.3 C实现与动态更新策略struct AABB { float min[3]; float max[3]; }; bool aabbsIntersect(const AABB a, const AABB b) { // 检查是否在任一轴上分离 if (a.max[0] b.min[0] || a.min[0] b.max[0]) return false; if (a.max[1] b.min[1] || a.min[1] b.max[1]) return false; if (a.max[2] b.min[2] || a.min[2] b.max[2]) return false; // 所有轴都重叠则相交 return true; }动态物体的AABB更新 对于会移动、旋转或变形的物体其AABB需要每帧更新。有几种策略从顶点重新计算遍历物体的所有顶点找出每个维度上的最小值和最大值。最准确但最耗时适用于顶点数不多的物体或变形剧烈的帧。变换原始AABB如果物体只是进行了刚体变换平移、旋转、缩放可以变换其原始局部坐标系下的AABB的8个角点然后从这8个变换后的点中计算新的世界空间AABB。但注意对于旋转变换后的包围盒可能不再是轴对齐的我们需要计算其新的AABB这通常会比原始AABB大。固定膨胀AABB对于旋转物体一个常见的折衷方案是使用一个固定的、足够大的AABB来包围物体在所有可能旋转角度下的形状。这牺牲了一些精度会产生更多“假阳性”碰撞即检测到碰撞但实际未发生但换来了恒定的更新开销和检测速度。踩坑记录我曾在一个项目中对所有物体使用策略3结果在密集场景中由于假阳性太多导致狭义阶段更精确的检测压力巨大帧率骤降。后来改为对静态物体使用精确AABB对动态但旋转有限的小物体使用策略2对复杂旋转物体才使用策略3性能立刻得到改善。关键是要根据物体的运动特性选择合适的更新策略。5. 算法三方向包围盒检测——更紧密的包围与更复杂的计算当物体旋转时AABB会变得非常“臃肿”包含大量空白空间导致碰撞检测不精确产生过多不必要的后续检测。方向包围盒就是为了解决这个问题而生的。5.1 OBB的概念与表示方向包围盒简称OBB是一个可以任意旋转的长方体。它比AABB更紧密地包裹物体但相交测试也更复杂。一个OBB通常由以下参数定义中心点C。三个互相垂直的单位向量u,v,w分别代表OBB的三个本地坐标轴方向。三个半长e_u,e_v,e_w分别表示从中心点到各面的距离。5.2 分离轴定理的完全体OBB的碰撞检测是分离轴定理的经典应用场景。定理指出两个凸多边形多面体不相交当且仅当存在一条直线轴使得两个多边形在该直线上的投影不重叠。对于两个OBB我们需要测试的潜在分离轴包括每个OBB的3个本地轴共6个。每个轴与其他每个轴的叉积共9个。但由于叉积可能得到零向量或平行向量实际需要测试的是两个方向向量的两两叉积即A.u x B.u,A.u x B.v,A.u x B.w,A.v x B.u, ...共9个轴。因此总共需要测试15条轴。在每一条轴上我们需要将两个OBB投影到该轴上得到两个投影区间。计算这两个区间的重叠情况。如果存在一条轴上区间不重叠则两个OBB分离不相交如果所有15条轴上的投影都重叠则两个OBB相交。5.3 C实现详解与性能考量实现OBB检测涉及大量的向量点积和矩阵运算。以下是核心步骤的简化伪代码struct OBB { glm::vec3 center; // 中心点 glm::vec3 axes[3]; // 三个本地轴 (单位向量) glm::vec3 halfExtents; // 三个方向的半长 }; bool testAxis(const OBB a, const OBB b, const glm::vec3 axis) { // 计算两个OBB在该轴上的投影“半径” float ra a.halfExtents.x * std::abs(glm::dot(axis, a.axes[0])) a.halfExtents.y * std::abs(glm::dot(axis, a.axes[1])) a.halfExtents.z * std::abs(glm::dot(axis, a.axes[2])); float rb b.halfExtents.x * std::abs(glm::dot(axis, b.axes[0])) b.halfExtents.y * std::abs(glm::dot(axis, b.axes[1])) b.halfExtents.z * std::abs(glm::dot(axis, b.axes[2])); // 计算两个中心点在该轴上的投影距离 glm::vec3 d b.center - a.center; float distance std::abs(glm::dot(d, axis)); // 如果距离大于半径之和则在此轴上分离 return distance (ra rb); } bool obbsIntersect(const OBB a, const OBB b) { // 测试15条轴... // 1. 测试A的本地轴 if (!testAxis(a, b, a.axes[0])) return false; if (!testAxis(a, b, a.axes[1])) return false; if (!testAxis(a, b, a.axes[2])) return false; // 2. 测试B的本地轴 if (!testAxis(a, b, b.axes[0])) return false; if (!testAxis(a, b, b.axes[1])) return false; if (!testAxis(a, b, b.axes[2])) return false; // 3. 测试两两轴的叉积 (共9个) for (int i 0; i 3; i) { for (int j 0; j 3; j) { glm::vec3 axis glm::cross(a.axes[i], b.axes[j]); // 如果叉积结果是零向量轴平行跳过 if (glm::length2(axis) 1e-6f) continue; axis glm::normalize(axis); if (!testAxis(a, b, axis)) return false; } } return true; // 所有轴都重叠相交 }性能陷阱与优化早期跳出一旦在任何一条轴上检测到分离就可以立即返回false这是算法的主要优化点。零向量检查计算叉积轴时必须检查结果是否接近零向量并跳过否则归一化会出错。计算开销OBB检测比AABB慢一个数量级以上。因此绝对不要直接用OBB进行广义阶段的粗检测。标准的做法是先用快速的AABB或球体进行粗筛只有AABB相交的物体对才进行昂贵的OBB精确检测。缓存友好性频繁计算点积和叉积对CPU缓存不友好。如果一帧内需要对同一对OBB进行多次检测比如在迭代求解器中可以考虑预计算一些中间结果。6. 算法四凸多边形/多面体GJK算法——获取碰撞信息的利器前面的算法主要回答“是否碰撞”。但在物理引擎中我们还需要知道“如何碰撞”即碰撞点、法线和穿透深度以便计算碰撞响应反作用力。吉尔伯特-约翰逊-克里尔算法简称GJK算法是解决凸体碰撞检测和计算最近点/穿透向量的经典算法。6.1 GJK算法的核心思想闵可夫斯基差与单纯形GJK算法的精妙之处在于它将两个凸体A和B是否相交的问题转化为判断原点是否在另一个凸体闵可夫斯基差内部的问题。闵可夫斯基差A - B定义为从B中任意一点到A中任意一点的向量的集合。数学上S {a - b | a ∈ A, b ∈ B}。这个性质非常强大如果A和B相交则原点在S内部。如果A和B不相交则原点不在S内部并且从原点到S的最近点就是A和B的最近点向量。GJK算法通过迭代构建一个位于S内部的单纯形点、线段、三角形或四面体来逼近原点。它使用一个称为支撑函数的工具。支撑函数support(shape, d)返回在形状shape上沿着方向d投影最远的点。对于闵可夫斯基差S其支撑点可以通过分别计算A和B在方向d和-d上的支撑点来得到support_S(d) support_A(d) - support_B(-d)。6.2 GJK算法步骤拆解初始化选择一个初始搜索方向d通常可以是A的中心到B的中心的向量。构建初始单纯形一个点即support_S(d)。迭代 a. 计算新的支撑点p support_S(d)。 b. 如果p在方向d上的投影小于0即点乘p·d 0说明沿着d找不到跨越原点的点了原点不在S中不相交。 c. 将p加入单纯形。 d. 判断原点是否在当前单纯形内部。如果是则相交。 e. 如果不是则更新单纯形保留最靠近原点的部分可能是点、线段或三角形的一个子集并计算一个新的搜索方向d指向原点相对于当前单纯形的位置。收敛重复步骤2直到满足终止条件如单纯形包含原点或迭代次数达到上限。6.3 EPA算法补充获取穿透深度GJK只能判断是否相交。如果相交要获取穿透深度和方向需要用到扩展多边形算法。EPA以GJK终止时的单纯形此时单纯形包含原点为起点不断扩展这个多边形直到它成为S的边界的一个近似然后找出离原点最近的边该边的法线方向就是分离或穿透方向原点到该边的距离就是穿透深度。6.4 C实现关键点与调试技巧实现GJK需要扎实的几何知识。以下是支撑函数和一次GJK迭代的核心概念代码// 假设我们有一个凸体类能实现支撑函数 struct ConvexHull { std::vectorglm::vec3 vertices; glm::vec3 support(const glm::vec3 direction) const { float maxDot -FLT_MAX; glm::vec3 result; for (const auto v : vertices) { float dot glm::dot(v, direction); if (dot maxDot) { maxDot dot; result v; } } return result; } }; // 闵可夫斯基差的支撑函数 glm::vec3 support(const ConvexHull a, const ConvexHull b, const glm::vec3 dir) { return a.support(dir) - b.support(-dir); } // GJK算法主体简化版仅示意循环结构 bool gjkIntersect(const ConvexHull a, const ConvexHull b) { glm::vec3 d b.center - a.center; // 初始方向 Simplex simplex; simplex.add(support(a, b, d)); d -simplex.getLastPoint(); // 指向原点 for (int i 0; i MAX_ITERATIONS; i) { glm::vec3 p support(a, b, d); if (glm::dot(p, d) 0) { return false; // 不相交 } simplex.add(p); if (simplex.containsOrigin(d)) { // 这个函数更新单纯形和方向d return true; // 相交 } } return false; // 未收敛通常视为不相交 }调试与心得可视化调试GJK算法迭代过程抽象最好的调试方法是可视化。在每一帧绘制当前的搜索方向d、支撑点p和单纯形。观察单纯形如何逼近或远离原点。退化情况当单纯形退化比如三个点共线时需要小心处理。在更新单纯形的函数中必须能正确处理点、线段、三角形各种情况。容差浮点数精度问题会导致误判。比较点积时应使用一个小的容差值例如if (glm::dot(p, d) epsilon)。性能支撑函数通常是性能瓶颈尤其是对于顶点很多的凸体。可以考虑使用凸体的特征如顶点、边、面来加速支撑点的查找或者使用缓存上一次支撑点结果的方法。7. 算法五分离轴定理与多边形碰撞——2D游戏的基石在2D游戏如很多独立游戏、手机游戏中我们处理的是多边形。分离轴定理在这里有更直观和高效的应用。7.1 2D SAT算法原理对于两个凸多边形SAT算法需要测试的分离轴是每个多边形的每条边的法线方向。为什么是边的法线因为两个凸多边形在某个方向上投影不重叠这个分离方向一定平行于其中一个多边形的某条边。步骤对于多边形A的每条边计算其法线向量垂直于边。将多边形A和多边形B的所有顶点投影到这条法线上得到两个投影区间。检查这两个区间是否重叠。如果不重叠则找到了分离轴两个多边形不相交算法结束。对多边形B的每条边重复步骤1-3。如果所有测试的轴上投影区间都重叠则两个多边形相交。7.2 C实现与碰撞信息计算struct Polygon2D { std::vectorglm::vec2 vertices; // 顶点按顺时针或逆时针顺序排列 }; // 计算多边形在某个轴上的投影区间 void projectPolygon(const glm::vec2 axis, const Polygon2D poly, float min, float max) { min max glm::dot(axis, poly.vertices[0]); for (const auto v : poly.vertices) { float proj glm::dot(axis, v); if (proj min) min proj; if (proj max) max proj; } } // 检查两个区间是否重叠并返回重叠量用于计算穿透深度 bool intervalsOverlap(float minA, float maxA, float minB, float maxB, float overlap) { if (maxA minB || maxB minA) return false; // 分离 // 计算重叠量 overlap std::min(maxA, maxB) - std::max(minA, minB); return true; } bool satIntersect(const Polygon2D a, const Polygon2D b, glm::vec2 mtvAxis, float mtvOverlap) { mtvOverlap FLT_MAX; mtvAxis glm::vec2(0.0f); // 测试A的边 for (size_t i 0; i a.vertices.size(); i) { glm::vec2 v1 a.vertices[i]; glm::vec2 v2 a.vertices[(i 1) % a.vertices.size()]; glm::vec2 edge v2 - v1; glm::vec2 axis glm::vec2(-edge.y, edge.x); // 法线 axis glm::normalize(axis); float minA, maxA, minB, maxB; projectPolygon(axis, a, minA, maxA); projectPolygon(axis, b, minB, maxB); float overlap; if (!intervalsOverlap(minA, maxA, minB, maxB, overlap)) { return false; // 找到分离轴 } // 记录最小穿透深度和对应的轴最小平移向量 if (overlap mtvOverlap) { mtvOverlap overlap; mtvAxis axis; } } // 测试B的边 for (size_t i 0; i b.vertices.size(); i) { glm::vec2 v1 b.vertices[i]; glm::vec2 v2 b.vertices[(i 1) % b.vertices.size()]; glm::vec2 edge v2 - v1; glm::vec2 axis glm::vec2(-edge.y, edge.x); axis glm::normalize(axis); float minA, maxA, minB, maxB; projectPolygon(axis, a, minA, maxA); projectPolygon(axis, b, minB, maxB); float overlap; if (!intervalsOverlap(minA, maxA, minB, maxB, overlap)) { return false; } if (overlap mtvOverlap) { mtvOverlap overlap; mtvAxis axis; } } // 确保MTV方向是从A指向B或反之取决于你的约定 glm::vec2 centerA computeCentroid(a); glm::vec2 centerB computeCentroid(b); glm::vec2 direction centerB - centerA; if (glm::dot(direction, mtvAxis) 0) { mtvAxis -mtvAxis; } return true; // 所有轴都重叠相交 }SAT算法的优势与局限优势原理直观易于实现和调试。不仅能检测碰撞还能直接计算出最小平移向量MTV即解决碰撞所需的最小移动方向和距离这对于物理响应非常方便。局限只适用于凸多边形。对于凹多边形需要先将其分解为多个凸多边形凸分解。算法复杂度为O(n*m)其中n和m是多边形的边数。对于复杂多边形性能可能成为问题。8. 算法选型与性能优化实战指南了解了五种算法后如何在项目中选用和组合它们呢这没有银弹完全取决于你的具体需求。8.1 根据场景选择算法组合场景推荐算法组合理由大量简单物体粒子、子弹球体检测计算量极小适合数量庞大的物体进行两两检测。2D游戏平台跳跃、RPGAABB广义 2D SAT狭义AABB快速剔除SAT处理精确的凸多边形碰撞并生成MTV。对于非凸角色可分解为多个凸部分。3D游戏物体有旋转AABB/球体广义 OBB/GJK狭义先用粗糙但快速的包围体筛选再用精确算法处理可能碰撞的对。OBB适合规则刚体GJK适合任意凸体且能提供碰撞信息。物理引擎核心AABB树/球体树广义 GJK/EPA狭义使用空间划分结构如动态AABB树高效管理动态物体对。GJK判断是否碰撞EPA计算穿透深度和法线为求解器提供数据。刚体且形状固定OBB如果物体是长方体且旋转是主要运动OBB是最佳平衡点比GJK简单比AABB精确。8.2 性能优化核心策略空间划分这是应对大量物体的不二法门。将空间划分为网格、四叉树2D、八叉树3D或使用更高级的动态AABB树。每帧只检查同一区域或相邻区域内的物体对。两阶段检测永远遵循“广义Broad Phase- 狭义Narrow Phase”的流程。广义阶段用廉价算法如AABB快速产生潜在碰撞对列表。狭义阶段才对列表中的每一对进行精确但昂贵的检测。时间一致性利用帧与帧之间的连贯性。如果物体移动不快上一帧的碰撞对列表和空间划分结构可以在下一帧复用并做局部更新而不是完全重建。简化碰撞体不要用高精度的渲染网格作为碰撞体。为每个物体创建简化的碰撞几何体如用球体、胶囊体、AABB、OBB或凸包的组合来近似。这能极大减少检测的复杂度。并行化碰撞检测是“令人尴尬的并行”问题。潜在碰撞对列表中的检测相互独立可以很容易地使用多线程如C11/14/17的thread或任务系统或GPU如CUDA、OpenCL进行加速。8.3 常见问题排查与调试技巧物体“抖动”或“穿透”原因通常是物理积分步长delta time太大或者碰撞响应如冲量求解迭代次数不足。排查减小时间步长增加物理迭代次数。检查碰撞检测返回的穿透深度和法线是否准确可通过绘制法线向量来可视化。高速物体穿透子弹打薄墙原因在单帧内物体从墙的一侧运动到了另一侧AABB检测可能通过但连续的两帧之间没有与墙的AABB相交。解决使用连续碰撞检测。不是检测两个静态形状是否相交而是检测从上一帧位置到当前帧位置的运动线段或扫掠体是否与目标物体相交。这计算量更大通常只对子弹等高速物体启用。GJK/EPA算法陷入无限循环或给出错误结果原因浮点数精度问题或支撑函数返回了非凸体上的点如果模型不是严格凸的。调试设置最大迭代次数如50。可视化每一轮的单纯形和搜索方向。确保输入的几何体是严格凸的可以使用凸包生成算法预处理。性能突然下降排查使用性能分析工具如Visual Studio Profiler, Very Sleepy, Tracy找到热点。通常是广义阶段的空间划分结构失效如所有物体都在一个节点或狭义阶段检测的物体对数量激增。检查是否漏掉了粗筛步骤。9. 从理论到实践一个简单的2D碰撞系统搭建示例让我们用一个简单的2D示例来串联部分知识。假设我们有一个2D游戏里面有矩形AABB和凸多边形两种物体。系统设计基类Collider包含位置、类型AABB, Polygon以及一个世界空间的AABB用于广义阶段。广义阶段所有Collider每帧更新其世界AABB。使用一个简单的基于网格的空间划分。将世界划分为固定大小的网格每个Collider根据其AABB注册到覆盖的网格单元格中。碰撞检测时只检查同一单元格或相邻单元格内的Collider对。狭义阶段对于广义阶段产生的每一对(ColliderA, ColliderB)如果两者都是AABB直接调用快速的aabbsIntersect。如果至少有一个是Polygon先用它们的AABB做快速拒绝因为AABB检测比SAT快得多。如果AABB相交再调用satIntersect进行精确检测并获取MTV。碰撞响应如果satIntersect返回true并提供了MTV我们就可以将其中一个物体沿mtvAxis方向移动mtvOverlap的距离从而解决穿透。关键代码片段广义阶段网格管理class CollisionGrid { int cellSize; std::unordered_mapGridCoord, std::vectorCollider* gridMap; public: void clear() { gridMap.clear(); } void addCollider(Collider* col) { AABB bounds col-getWorldAABB(); GridCoord minCell worldToCell(bounds.min); GridCoord maxCell worldToCell(bounds.max); for (int x minCell.x; x maxCell.x; x) { for (int y minCell.y; y maxCell.y; y) { gridMap[{x, y}].push_back(col); } } } void getPotentialPairs(std::vectorColliderPair pairs) { std::unordered_setuint64_t addedPairs; // 用于去重 for (const auto [coord, colliders] : gridMap) { // 检查当前单元格内所有物体两两之间 for (size_t i 0; i colliders.size(); i) { for (size_t j i 1; j colliders.size(); j) { uint64_t id getPairID(colliders[i], colliders[j]); if (addedPairs.insert(id).second) { pairs.push_back({colliders[i], colliders[j]}); } } } // 可选检查相邻单元格的物体对避免物体在边界处漏检 // 这需要遍历当前单元格的8个邻居... } } };这个简单的系统已经包含了碰撞检测的核心思想空间划分、两阶段检测、分情况处理。你可以在此基础上根据需要引入更复杂的形状如圆形、更高效的空间结构如动态AABB树以及完整的物理响应。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门