1. 概述

碰撞检测是游戏开发、计算机图形学和仿真软件中的核心组件。在Java中,开发者可以根据应用复杂度和性能需求,采用多种方法检测图像(或精灵)之间的碰撞。

本教程将深入探讨两个图像之间的碰撞检测原理,涵盖基础边界形状检测到高级像素级检测的完整方案。

2. Java的Swing和AWT库

我们将使用Swing构建GUI框架(如JFrame和JPanel),并借助AWT进行渲染和几何计算。但需注意:这套方案并非为高性能渲染优化,存在以下明显缺陷:

  • 缺乏内置循环系统
  • 复杂场景下图像渲染性能较差
  • 需要手动实现所有物理和碰撞逻辑

有几种方式可实现循环系统(如Timer或Thread),本文选择Thread实现。创建一个继承JPanel并实现Runnable的类:

public class Game extends JPanel implements Runnable

这样我们就能:

  1. 通过paintComponent()绘制组件
  2. run()方法中实现游戏循环
gameThread = new Thread(this);
gameThread.start();
public void run() {
    while (!collided) {
        repaint();

        try {
            Thread.sleep(16); // 约60FPS
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

我们还需要一个GameObject类封装图像数据:

public class GameObject

添加核心方法:

public GameObject(int x, int y, BufferedImage image) {
    this.x = x;
    this.y = y;
    this.image = image;
    this.width = image.getWidth();
    this.height = image.getHeight();
}
public void move(int dx, int dy) {
    x += dx;
    y += dy;
}
public void draw(Graphics g) {
    g.drawImage(image, x, y, null);
}

加载达斯·维达和卢克·天行者的对决素材:

BufferedImage vaderImage = ImageIO.read(new File("src/main/resources/images/vader.png"));
BufferedImage lukeImage = ImageIO.read(new File("src/main/resources/images/luke.png"));
vader = new GameObject(170, 370, vaderImage);
luke = new GameObject(1600, 370, lukeImage);

最后实现绘制逻辑:

protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    vader.draw(g);
    luke.draw(g);

    if (collided) {
        g.setColor(Color.RED);
        g.setFont(new Font("SansSerif", Font.BOLD, 50));
        g.drawString("COLLISION!", getWidth() / 2 - 100, getHeight() / 2);
    }
}

3. 边界形状碰撞检测

现在我们探索基于几何形状的碰撞检测方法,从最简单的边界框开始。

3.1 边界框检测

最简单快速但精度最低的方法
通过检测图像矩形区域是否重叠实现碰撞。AWT的Rectangle类让实现变得简单:

public Rectangle getRectangleBounds() {
    return new Rectangle(x, y, width, height);
}

在游戏循环中添加移动和碰撞检测逻辑:

vader.move(2, 0);
luke.move(-2, 0);
if (vader.getRectangleBounds().intersects(luke.getRectangleBounds())) {
    collided = true;
}

效果演示: 矩形碰撞效果

3.2 椭圆形区域检测

⚠️ 精度提升但性能下降
当图像接近圆形时,使用Ellipse2D更合理。但注意:**intersects()方法只支持矩形**,需通过Area类转换:

public Area getEllipseAreaBounds() {
    Ellipse2D.Double coll = new Ellipse2D.Double(x, y, width, height);
    return new Area(coll);
}

碰撞检测逻辑:

Area areaVader = vader.getEllipseAreaBounds();
Area areaLuke = luke.getEllipseAreaBounds();
areaVader.intersect(areaLuke);

if (!areaVader.isEmpty()) { // 区域不为空表示有重叠
    collided = true;
}

效果演示: 椭圆碰撞效果

3.3 圆形距离检测

高性能替代方案
当对象超过1000个时,Area运算会变得沉重。此时可改用纯数学计算(仅适用于正圆):

double dx = circleVader.getCenterX() - circleLuke.getCenterX();
double dy = circleVader.getCenterY() - circleLuke.getCenterY();

double distance = Math.sqrt(dx * dx + dy * dy);
double radiusVader = circleVader.getWidth() / 2.0;
double radiusLuke = circleLuke.getWidth() / 2.0;

boolean collided = distance < radiusVader + radiusLuke; // 圆心距 < 半径和

效果演示: 圆形碰撞效果

3.4 多边形碰撞检测

🔷 自定义形状检测
通过多边形实现精确边界控制:

int[] xPoints = new int[4];
int[] yPoints = new int[4];

// 菱形偏移量
int[] xOffsets = {100, 200, 100, 0};
int[] yOffsets = {0, 170, 340, 170};
public Polygon getPolygonBounds(int imgX, int imgY) {
    for (int i = 0; i < xOffsets.length; i++) {
        xPoints[i] = imgX + xOffsets[i];
        yPoints[i] = imgY + yOffsets[i];
    }
    return new Polygon(xPoints, yPoints, xOffsets.length);
}

碰撞检测(同样借助Area类):

Polygon polyVader = getPolygonBounds(vader.x, vader.y);
Polygon polyLuke = getPolygonBounds(luke.x, luke.y);

Area areaVader = new Area(polyVader);
Area areaLuke = new Area(polyLuke);

areaVader.intersect(areaLuke);

if (!areaVader.isEmpty()) {
    collided = true;
}

效果演示: 多边形碰撞效果

关键优势:通过Area类还能检测不同形状间的碰撞(如椭圆vs多边形)。

4. 像素级完美碰撞

🎯 最高精度方案
当视觉精确度至关重要时(如光剑击中头部),需要检测非透明像素的实际重叠:

public boolean isPixelCollision(BufferedImage img1, int x1, int y1, 
                                BufferedImage img2, int x2, int y2) {
    // 1. 计算重叠区域
    int top = Math.max(y1, y2);
    int bottom = Math.min(y1 + img1.getHeight(), y2 + img2.getHeight());
    int left = Math.max(x1, x2);
    int right = Math.min(x1 + img1.getWidth(), x2 + img2.getWidth());

    // 2. 提前终止无重叠情况
    if (right <= left || bottom <= top) return false;

    // 3. 逐像素检查重叠区域
    for (int y = top; y < bottom; y++) {
        for (int x = left; x < right; x++) {
            int img1Pixel = img1.getRGB(x - x1, y - y1);
            int img2Pixel = img2.getRGB(x - x2, y - y2);

            // 检查alpha通道(透明度)
            if (((img1Pixel >> 24) & 0xff) != 0 && ((img2Pixel >> 24) & 0xff) != 0) {
                return true; // 两个像素均不透明
            }
        }
    }
    return false;
}

效果演示: 像素级碰撞效果

5. 结论

在Java中实现图像碰撞检测时,需要根据具体需求权衡方案:

方法 精度 性能 适用场景
边界框 ⭐⭐⭐⭐⭐ 快速原型/简单游戏
椭圆/圆形 ⭐⭐⭐ 规则形状物体
多边形 ⭐⭐ 复杂形状物体
像素级 完美 需要精确视觉反馈

核心原则:在精度和性能间找到平衡点。对于大型游戏,通常采用分层检测(先用边界框粗筛,再用像素级精判)。

完整代码实现可在GitHub获取。