在android等移动平台进行游戏开发时,精确的碰撞检测是实现真实物理反馈的关键一环。对于像pong这样的经典游戏,球与挡板之间的碰撞尤其重要。传统的边界框碰撞(aabb)虽然简单,但在某些情况下可能不够精确,尤其当物体移动速度较快时,可能出现“穿透”现象。通过将球的运动轨迹和挡板的边缘视为线段,并计算它们的交点,我们可以实现更精确、更可靠的碰撞检测。
要计算两条线段的交点,我们首先需要理解直线的代数表示。一条直线在二维平面上可以表示为通用方程 Ax + By + C = 0。
1. 通过两点确定直线方程
如果一条直线经过点 P1(x1, y1) 和 P2(x2, y2),其方程可以推导为: (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0
通过这个公式,我们可以得到 A = (y1 - y2),B = (x2 - x1),C = (x1y2 - x2y1)。
2. 计算两条直线的交点
假设我们有两条直线:
要找到它们的交点 (x, y),我们可以解这个线性方程组。使用克莱默法则或代入消元法,可以得到交点的坐标:
特殊情况处理:
3. 线段交点判断
上述方法计算的是两条无限长直线的交点。但我们实际需要的是线段的交点。因此,在计算出交点 (x, y) 后,还需要进行额外的检查,以确保该交点落在两条线段的有效范围内。
对于线段 P1(x1, y1) 到 P2(x2, y2),一个交点 (x, y) 只有在满足以下条件时才位于该线段上:
这个检查必须对两条线段都进行。只有当交点同时落在两条线段的包围盒(bounding box)内时,才认为线段发生了交点碰撞。
在Pong游戏中,我们可以将:
示例:右侧挡板的碰撞边缘
根据提供的代码,右侧挡板的绘制矩形为 canvas.drawRect(7 * screenWidth / 8, rPaddle * screenHeight + halfPaddle, 7 * screenWidth / 8 + 15, rPaddle * screenHeight - halfPaddle, dark);。 这里的参数顺序是 (left, top, right, bottom)。 因此,右侧挡板的左边缘可以定义为从 (7 * screenWidth / 8, rPaddle * screenHeight - halfPaddle) 到 (7 * screenWidth / 8, rPaddle * screenHeight + halfPaddle) 的线段。
为了更好地组织代码,我们可以创建一些辅助类和方法。
import android.graphics.PointF; // PointF 适用于浮点数坐标 public class Vector2D extends PointF { public Vector2D(float x, float y) { super(x, y); } } public class LineSegment { public Vector2D p1; public Vector2D p2; public LineSegment(Vector2D p1, Vector2D p2) { this.p1 = p1; this.p2 = p2; } public LineSegment(float x1, float y1, float x2, float y2) { this.p1 = new Vector2D(x1, y1); this.p2 = new Vector2D(x2, y2); } } public class IntersectionUtils { /** * 计算两条线段的交点。 * 如果存在交点且交点在线段范围内,则返回交点坐标;否则返回null。 * * @param seg1 第一条线段 * @param seg2 第二条线段 * @return 交点坐标 (PointF) 或 null */ public static PointF getIntersectionPoint(LineSegment seg1, LineSegment seg2) { float x1 = seg1.p1.x, y1 = seg1.p1.y; float x2 = seg1.p2.x, y2 = seg1.p2.y; float x3 = seg2.p1.x, y3 = seg2.p1.y; float x4 = seg2.p2.x, y4 = seg2.p2.y; // 计算 A, B, C 参数 for seg1 float A1 = y1 - y2; float B1 = x2 - x1; float C1 = x1 * y2 - x2 * y1; // 计算 A, B, C 参数 for seg2 float A2 = y3 - y4; float B2 = x4 - x3; float C2 = x3 * y4 - x4 * y3; // 计算分母 float denominator = A1 * B2 - A2 * B1; // 如果分母接近0,则线段平行或重合 if (Math.abs(denominator) < 0.0001f) { // 使用一个小的 epsilon 值来处理浮点数精度 return null; // 平行或重合,不认为有有效交点 } // 计算交点坐标 float intersectX = (C2 * B1 - C1 * B2) / denominator; float intersectY = (C1 * A2 - C2 * A1) / denominator; // 检查交点是否落在第一条线段的范围内 if (!isPointOnSegment(intersectX, intersectY, seg1)) { return null; } // 检查交点是否落在第二条线段的范围内 if (!isPointOnSegment(intersectX, intersectY, seg2)) { return null; } return new PointF(intersectX, intersectY); } /** * 检查一个点是否在线段的包围盒内(即是否在线段上)。 * * @param x 点的X坐标 * @param y 点的Y坐标 * @param seg 线段 * @return 如果点在线段上则返回true,否则返回false */ private static boolean isPointOnSegment(float x, float y, LineSegment seg) { float minX = Math.min(seg.p1.x, seg.p2.x); float maxX = Math.max(seg.p1.x, seg.p2.x); float minY = Math.min(seg.p1.y, seg.p2.y); float maxY = Math.max(seg.p1.y, seg.p2.y); // 允许一个小范围的浮点误差 final float EPSILON = 0.0001f; return x >= minX - EPSILON && x <= maxX + EPSILON && y >= minY - EPSILON && y <= maxY + EPSILON; } }
现在,我们可以在 PongView 类的 collisionCheck() 方法中利用上述工具类进行精确的碰撞检测。
// ... PongView 类的其他成员变量和方法 ... protected void collisionCheck() { // 处理屏幕边界碰撞 (保持原有逻辑) // ... // 创建球的运动轨迹线段 LineSegment ballTrajectory = new LineSegment(oldBallX, oldBallY, ballX, ballY); // 获取右侧挡板的碰撞边缘线段 float rPaddleX = 7 * screenWidth / 8; float rPaddleTopY = rPaddle * screenHeight - halfPaddle; float rPaddleBottomY = rPaddle * screenHeight + halfPaddle; LineSegment rightPaddleCollisionEdge = new LineSegment(rPaddleX, rPaddleTopY, rPaddleX, rPaddleBottomY); // 获取左侧挡板的碰撞边缘线段 float lPaddleX = screenWidth / 8 + 15; // 左挡板的右边缘 float lPaddleTopY = lPaddle * screenHeight - halfPaddle; float lPaddleBottomY = lPaddle * screenHeight + halfPaddle; LineSegment leftPaddleCollisionEdge = new LineSegment(lPaddleX, lPaddleTopY, lPaddleX, lPaddleBottomY); // 检测与右侧挡板的碰撞 PointF intersectionWithRightPaddle = IntersectionUtils.getIntersectionPoint(ballTrajectory, rightPaddleCollisionEdge); if (intersectionWithRightPaddle != null) { // 发生碰撞,调整球的速度方向 ballSpeedX *= -1.0f; // 可选:将球的位置调整到交点处,以避免穿透 // ballX = intersectionWithRightPaddle.x; // ballY = intersectionWithRightPaddle.y; // 如果调整了位置,需要重新计算剩余时间内的运动 // 播放碰撞音效 MediaPlayer pip = MediaPlayer.create(_context.getApplicationContext(), R.raw.lil_pip); pip.start(); pip.setOnCompletionListener(MediaPlayer::release); // 释放资源 } // 检测与左侧挡板的碰撞 PointF intersectionWithLeftPaddle = IntersectionUtils.getIntersectionPoint(ballTrajectory, leftPaddleCollisionEdge); if (intersectionWithLeftPaddle != null) { // 发生碰撞,调整球的速度方向 ballSpeedX *= -1.0f; // 可选:将球的位置调整到交点处 // ballX = intersectionWithLeftPaddle.x; // ballY = intersectionWithLeftPaddle.y; // 播放碰撞音效 MediaPlayer pip = MediaPlayer.create(_context.getApplicationContext(), R.raw.lil_pip); pip.start(); pip.setOnCompletionListener(MediaPlayer::release); // 释放资源 } Log.d("TAG", "Ball is moving"); }
注意事项:
通过将球的运动轨迹和挡板边缘抽象为线段,并运用基本的几何代数原理计算它们的交点,我们
以上就是Android游戏开发:基于线段交点的精确碰撞检测的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号