Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if two images collide in Java?

Tags:

java

I am creating a game in Java, and I am using BufferedImages to show items on the screen. These images have transparent pixels on their borders, so the objects are not squares but unique shapes. Additionally, the objects may rotate on screen. How can I detect collisions between two images?

My first thought was to generate a list of all of the locations of the pixels in each image, and then detect if any of the locations of the pixels are shared by the two images. However, this method seems inefficient, and I am not sure how to generate these lists. Do you have any ideas?

like image 911
Jack Humphries Avatar asked Feb 14 '23 02:02

Jack Humphries


1 Answers

The basic approach would be to first determine if there is collision of the image boundaries.

If the two images overlap, you will need to determine if any of non-transparent pixels in the image overlap/collide.

This can be achieved through the use of BufferedImage#getRGB

As an example, check out the second example in the answer

Updated with example

Collision

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TransparentImageCollision {

    public static void main(String[] args) {
        new TransparentImageCollision();
    }

    public TransparentImageCollision() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage fly;
        private BufferedImage spider;

        private Rectangle spiderBounds;
        private Rectangle flyBounds;

        private Point spiderDelta;
        private Point flyDelta;

        private Rectangle collision;

        public TestPane() {
            try {
                fly = ImageIO.read(getClass().getResource("/fly.png"));
                spider = ImageIO.read(getClass().getResource("/spider.png"));

                Dimension size = getPreferredSize();
                int width = size.width;
                int height = size.height;

                spiderBounds = new Rectangle();
                spiderBounds.setSize(spider.getWidth(), spider.getHeight());
                spiderBounds.setLocation(0, (height - spider.getHeight()) / 2);

                flyBounds = new Rectangle();
                flyBounds.setSize(fly.getWidth(), fly.getHeight());
                flyBounds.setLocation(width - fly.getWidth(), (height - fly.getHeight()) / 2);

                spiderDelta = new Point(1, 0);
                flyDelta = new Point(-1, 0);

                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        update(spiderBounds, spiderDelta);
                        update(flyBounds, flyDelta);
                        detectCollision();
                        repaint();
                    }
                });
                timer.start();
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }

        protected void update(Rectangle bounds, Point delta) {
            bounds.x += delta.x;
            bounds.y += delta.y;
            if (bounds.x < 0) {
                bounds.x = 0;
                delta.x *= -1;
            }
            if (bounds.x + bounds.width > getWidth()) {
                bounds.x = getWidth() - bounds.width;
                delta.x *= -1;
            }
            if (bounds.y < 0) {
                bounds.y = 0;
                delta.y *= -1;
            }
            if (bounds.y + bounds.height > getHeight()) {
                bounds.y = getHeight() - bounds.height;
                delta.y *= -1;
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.drawImage(spider, spiderBounds.x, spiderBounds.y, this);
            g2d.drawImage(fly, flyBounds.x, flyBounds.y, this);

            if (collision != null) {
                g2d.setColor(new Color(255, 0, 0, 128));
                g2d.fill(collision);
            }

            g2d.dispose();
        }

        /**
         * Used to detect the collision between non-alpha portions of the two
         * images
         */
        protected void detectCollision() {
            collision = null;
            // Check if the boundires intersect
            if (spiderBounds.intersects(flyBounds)) {
                // Calculate the collision overlay
                Rectangle bounds = getCollision(spiderBounds, flyBounds);
                if (!bounds.isEmpty()) {
                    // Check all the pixels in the collision overlay to determine
                    // if there are any non-alpha pixel collisions...
                    for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
                        for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
                            if (collision(x, y)) {
                                collision = bounds;
                                break;
                            }
                        }
                    }
                }
            }
        }

        protected Rectangle getCollision(Rectangle rect1, Rectangle rect2) {
            Area a1 = new Area(rect1);
            Area a2 = new Area(rect2);
            a1.intersect(a2);
            return a1.getBounds();
        }

        /**
         * Test if a given x/y position of the images contains transparent
         * pixels or not...
         * @param x
         * @param y
         * @return 
         */
        protected boolean collision(int x, int y) {
            boolean collision = false;

            int spiderPixel = spider.getRGB(x - spiderBounds.x, y - spiderBounds.y);
            int flyPixel = fly.getRGB(x - flyBounds.x, y - flyBounds.y);
            // 255 is completely transparent, you might consider using something
            // a little less absolute, like 225, to give you a sligtly
            // higher hit right, for example...
            if (((spiderPixel >> 24) & 0xFF) < 255 && ((flyPixel >> 24) & 0xFF) < 255) {
                collision = true;
            }
            return collision;
        }
    }
}
like image 83
MadProgrammer Avatar answered Feb 28 '23 04:02

MadProgrammer