You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PNG图片干扰碰撞检测逻辑,未接触即触发游戏结束求助

小行星游戏PNG图片导致碰撞检测误触发的修复方案

问题背景

开发的小行星游戏中,使用PNG格式的小行星图片时,飞船与小行星仍有一段距离就触发游戏结束。排查后确定问题与PNG图片相关,当前碰撞检测代码及小行星类实现如下:

碰撞检测代码

for (int i = 0; i < asteroidCount; i++) {
    asteroids[i].move();
    
    if (asteroids[i].getY() <= 0 || asteroids[i].getY() + asteroids[i].getAsteroidHeight() >= getHeight()) {
        asteroids[i].setYDir(-asteroids[i].getYDir());
    }
    if (asteroids[i].getX() <= 0 || asteroids[i].getX() + asteroids[i].getAsteroidWidth() >= getWidth()) {
        asteroids[i].setXDir(-asteroids[i].getXDir());
    }
    
    //Collision code for when the asteroid hits the ship
    if (ship.getX() < asteroids[i].getX() + asteroids[i].getAsteroidWidth() && ship.getX() + ship.getshipWidth() > asteroids[i].getX() && ship.getY() < asteroids[i].getY() + asteroids[i].getAsteroidHeight() &&ship.getY() + ship.getshipHeight() > asteroids[i].getY()) {
        
        //Game over, stop the timer
        t.stop();
        JOptionPane.showMessageDialog(this, "Game Over! Asteroid hit the ship.");
        System.exit(0);
    }
}        
repaint();

Asteroids类实现

import javax.swing.*;
import java.awt.*;

public class Asteroids {
    
    //Declare fields
    private int xPos, yPos, asteroidHeight, asteroidWidth;
    private ImageIcon imgAsteroid;
    private int xDir, yDir;
    private Color c;
    private boolean useImage;

    //Create a 3rd constructor that creates a ball using an image
    public Asteroids(int x, int y, ImageIcon img) {
        xPos = x;
        yPos = y;
        imgAsteroid = img;
        asteroidWidth = img.getIconWidth();
        asteroidHeight = img.getIconHeight();
        useImage = true;
        xDir = 2; 
        yDir = 2; 
    }
    
    //Accessor methods to return the values of the fields
    public int getX() {
        return xPos;
    }
    
    public int getY() {
        return yPos;
    }
    
    public int getAsteroidWidth() {
        return asteroidWidth;
    }
    
    public int getAsteroidHeight() {
        return asteroidHeight;
    }
    
    public Image getImage() {
        return imgAsteroid.getImage();
    }
    
    //Mutator methods to change the values of fields
    public void setAsteroidWidth(int width) {
        asteroidWidth = width;
    }
    
    public void setAsteroidHeight(int height) {
        asteroidHeight = height;
    }
    
    public void setColor(Color col) { 
        c = col;
    }
    
    public void setImageIcon(ImageIcon imgAsteroids) {
        imgAsteroid = imgAsteroids;
    }
    
    public void setX(int x) {
        xPos = x;
    }
    
    public void setY(int y) {
        yPos = y;
    }
    
    public void draw(Graphics2D g2) {
        if (useImage == false) {
            g2.setColor(c);
            g2.fillOval(xPos, yPos, asteroidWidth, asteroidHeight);
        } else {
            g2.drawImage(getImage(), xPos, yPos, null);
        }
    }
    
    //Overload the move method so the parameters determine how many pixels to move
    public void move() {
        xPos += xDir;
        yPos += yDir;
    }
   
    public int getXDir() {
        return xDir;
    }

    public void setXDir(int xDir) {
        this.xDir = xDir;
    }

    public int getYDir() {
        return yDir;
    }

    public void setYDir(int yDir) {
        this.yDir = yDir;
    }
    
}

问题根源

当前碰撞检测使用的是图片的完整矩形边界,但PNG图片通常包含大量透明区域(比如小行星是不规则形状,图片画布为矩形,周围全是透明像素)。碰撞检测会把这些透明区域也判定为小行星的碰撞范围,导致飞船还未接触到实际的小行星图形就触发碰撞。

修复方案

方案1:手动调整碰撞边界(快速简单)

不需要修改碰撞检测逻辑,而是根据小行星的实际可视区域,手动设置更小的碰撞宽高,并调整位置偏移让碰撞中心与图片中心对齐:

public Asteroids(int x, int y, ImageIcon img) {
    xPos = x;
    yPos = y;
    imgAsteroid = img;
    // 保留图片原始宽高用于绘制
    int originalWidth = img.getIconWidth();
    int originalHeight = img.getIconHeight();
    // 设置实际碰撞的宽高(比如取图片宽高的70%,根据实际图形调整)
    asteroidWidth = (int)(originalWidth * 0.7);
    asteroidHeight = (int)(originalHeight * 0.7);
    // 调整坐标,让碰撞矩形与图片中心对齐
    xPos += (originalWidth - asteroidWidth) / 2;
    yPos += (originalHeight - asteroidHeight) / 2;
    useImage = true;
    xDir = 2; 
    yDir = 2; 
}

如果不想修改绘制用的坐标,可以给Asteroids类单独添加collisionX、collisionY、collisionWidth、collisionHeight属性,碰撞检测时使用这些属性,绘制时仍用原始的xPos、yPos和图片宽高。

方案2:像素级碰撞检测(精确)

通过检测两个物体的非透明像素是否重叠来触发碰撞,完全忽略透明区域:

  1. 给Asteroids类添加获取碰撞掩码的方法:
private boolean[][] collisionMask;

// 在构造函数中初始化掩码
public Asteroids(int x, int y, ImageIcon img) {
    // ... 原有代码 ...
    collisionMask = generateCollisionMask(img.getImage());
}

private boolean[][] generateCollisionMask(Image img) {
    int width = img.getWidth(null);
    int height = img.getHeight(null);
    boolean[][] mask = new boolean[width][height];
    PixelGrabber pg = new PixelGrabber(img, 0, 0, width, height, true);
    try {
        pg.grabPixels();
        int[] pixels = (int[]) pg.getPixels();
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                // 判断像素是否非透明(alpha通道不为0)
                mask[x][y] = ((pixels[y * width + x] >> 24) & 0xFF) != 0;
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return mask;
}

// 提供获取掩码的方法
public boolean[][] getCollisionMask() {
    return collisionMask;
}
  1. 修改碰撞检测逻辑:
for (int i = 0; i < asteroidCount; i++) {
    asteroids[i].move();
    
    // ... 原有边界反弹逻辑 ...
    
    // 计算两个物体的重叠区域
    int overlapXStart = Math.max(ship.getX(), asteroids[i].getX());
    int overlapXEnd = Math.min(ship.getX() + ship.getshipWidth(), asteroids[i].getX() + asteroids[i].getAsteroidWidth());
    int overlapYStart = Math.max(ship.getY(), asteroids[i].getY());
    int overlapYEnd = Math.min(ship.getY() + ship.getshipHeight(), asteroids[i].getY() + asteroids[i].getAsteroidHeight());
    
    // 无重叠区域则跳过
    if (overlapXStart >= overlapXEnd || overlapYStart >= overlapYEnd) {
        continue;
    }
    
    boolean[][] asteroidMask = asteroids[i].getCollisionMask();
    boolean[][] shipMask = ship.getCollisionMask(); // 假设Ship类也实现了相同的掩码逻辑
    
    boolean isCollided = false;
    // 遍历重叠区域的每个像素
    for (int x = overlapXStart; x < overlapXEnd; x++) {
        for (int y = overlapYStart; y < overlapYEnd; y++) {
            // 转换为物体自身的局部坐标
            int asteroidLocalX = x - asteroids[i].getX();
            int asteroidLocalY = y - asteroids[i].getY();
            int shipLocalX = x - ship.getX();
            int shipLocalY = y - ship.getY();
            
            // 检查坐标是否在掩码范围内,且两个像素都非透明
            if (asteroidLocalX >= 0 && asteroidLocalX < asteroidMask.length
                && asteroidLocalY >= 0 && asteroidLocalY < asteroidMask[0].length
                && shipLocalX >= 0 && shipLocalX < shipMask.length
                && shipLocalY >= 0 && shipLocalY < shipMask[0].length
                && asteroidMask[asteroidLocalX][asteroidLocalY]
                && shipMask[shipLocalX][shipLocalY]) {
                isCollided = true;
                break;
            }
        }
        if (isCollided) break;
    }
    
    if (isCollided) {
        t.stop();
        JOptionPane.showMessageDialog(this, "Game Over! Asteroid hit the ship.");
        System.exit(0);
    }
}        
repaint();

注意:像素级检测性能略低于矩形检测,若游戏中有大量小行星,建议提前缓存碰撞掩码,避免每次检测都重新生成。


内容的提问来源于stack exchange,提问作者user22660358

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 05:35:17