1

I have a problem with collision detection in a 2D Java game.

Normally, what I would do is create a getBounds() method for an object that can collide with other objects. This method would return a new Rectangle(x,y,width,height), where x and y are the coordinates for the top-left corner of the sprite, and width and height are the width and height of the sprite.

But in the game I'm currently working on, there is a "tank" controlled by the user. The sprite of this tank rotates as long as the player holds one of the left or right arrow buttons. In other words, it can rotate to any angle. The tank's sprite is a rectangle.

So I can't simply do what I always do in this case.

How can I detect collision with this kind of sprite? Thanks

user3150201
  • 1,901
  • 5
  • 26
  • 29

3 Answers3

6

A lot will depend on how you are managing your objects, but...

Rotate....

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class RotateTest {

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

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

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

    public class TestPane extends JPanel {

        private Rectangle rect01;
        private Rectangle rect02;

        private int angle = 0;

        public TestPane() {

            rect01 = new Rectangle(0, 0, 100, 100);
            rect02 = new Rectangle(0, 0, 100, 100);

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    angle++;
                    repaint();
                }
            });
            timer.start();

        }

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

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

            int width = getWidth();
            int height = getHeight();

            AffineTransform at = new AffineTransform();
        
            int center = width / 2;
        
            int x = center + (center - rect01.width) / 2;
            int y = (height - rect01.height) / 2;
            at.translate(x, y);
            at.rotate(Math.toRadians(angle), rect01.width / 2, rect01.height / 2);
            GeneralPath path1 = new GeneralPath();
            path1.append(rect01.getPathIterator(at), true);
            g2d.fill(path1);

            g2d.setColor(Color.BLUE);
            g2d.draw(path1.getBounds());

            at = new AffineTransform();
            x = (center - rect02.width) / 2;
            y = (height - rect02.height) / 2;
            at.translate(x, y);
            at.rotate(Math.toRadians(-angle), rect02.width / 2, rect02.height / 2);
            GeneralPath path2 = new GeneralPath();
            path2.append(rect02.getPathIterator(at), true);
            g2d.fill(path2);

            g2d.setColor(Color.BLUE);
            g2d.draw(path2.getBounds());

            Area a1 = new Area(path1);
            Area a2 = new Area(path2);
            a2.intersect(a1);
            if (!a2.isEmpty()) {
                g2d.setColor(Color.RED);
                g2d.fill(a2);
            }
        
            g2d.dispose();
        }

    }

}

Basically, what this does, is it creates a PathIterator of the Rectangle, which allows me to apply a AffineTransformation to the shape without effecting the original shape...don't know if this is important or not, but this is how I did it...

Then I created a GeneralPath which allows me to paint the PathIterator.

Now, the funky bit...

I create two Areas, one for each GeneralPath representing each object I want to check. I then use the Area's intersect method, which generates a Area that represents the intersection points of the two objects and then check to see if this result is empty or not.

If it's empty, it's does not intersect, if it's not (empty), they touch.

Have fun playing with that ;)

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • 1
    Forget _playing_ with it. I'm having fun just _looking_ at it! :) – Paul Samsotha Jan 05 '14 at 05:37
  • @peeskillet Given some of the other collision detection stuff I've done (not a great deal) this definatly the simplest ;) – MadProgrammer Jan 05 '14 at 05:38
  • I haven't really gotten into collision detection, so I'm still wowed! I didn't realize there was so little code involved. – Paul Samsotha Jan 05 '14 at 05:40
  • @peeskillet It depends on what you have available. The Graphics2D API is quite powerful. I also did [this](http://stackoverflow.com/questions/13261767/java-ball-object-doesnt-bounce-off-of-drawn-rectangles-like-its-supposed-to/13263022#13263022) and [this](http://stackoverflow.com/questions/15594424/line-crosses-rectangle-how-to-find-the-cross-points/15594751#15594751) and [this](http://stackoverflow.com/questions/15514906/how-to-check-intersection-between-a-line-and-a-rectangle/15515114#15515114) examples which have varying levels of complexities - this has being the easiest ;) – MadProgrammer Jan 05 '14 at 05:44
0

What if you keep all the objects the tank can run into in an ArrayList, and simply use a for statement in a loop in a separate thread to determine if the tank has hit something. For example, create a class for each object, say class Wall(), and keep the x, y variables available. Place the Wall() into the ArrayList, in this case tanks. Then in a for loop, within a while loop: EC

while(true) {
    for(int i = 0; i < tanks.size(); i++) {
         //Do the code to determine if the tank has hit something.
         if(tanks.get(i).x => tank.x) //Do whatever
    }
}

Of course adapt this to what you need, but the idea is the same. If the object intersects the tank, or vice versa, do whatever you want to happen. To sum it up, just check the area the tank takes up, and see if that area intersects another object.

Actually, even better:

I assume youre using the paint() method? Whenever you update the Component, simply add some code that does basically what I just said to, checks to see if the area is intersecting with an if() statement. EC:

 if(tanks.get(i).x => tank.x || tanks.get(i).y => tank.y) DO SOMETHING

of course, again adapt this if() to suit your needs

  • That's my problem. I don't know what's the area that the tank is filling, since it can rotate to any angle, and thus the area that it fills changes. – user3150201 Jan 04 '14 at 21:59
  • Ah, I see.. All right, so if your tanks x is 0, and your tanks width is 5, than your tank is 5 long. If your height is 10, than the area of your tank is 50. This will always be true. To find the line itself, on the actual painted surface, use slope intercept form. y=mx+b. Or if you have two points, (y2-y1)/(x2-x1). It doesnt matter if x1 or x2 is the tank, same with y. This will find the slope of your line, on the plane (playing surface) a slope 1 means on a graph, go up one unit, and right one unit from the origin – Einstein12345 Jan 04 '14 at 22:07
  • Basically a good knowledge of linear algebra will help you detect rotated collisions – BWG Jan 04 '14 at 22:11
  • Yeah, thanks I guess thats what I was trying to say in a nutshell lol. – Einstein12345 Jan 04 '14 at 22:13
0

You could use a Polygon rather than a Rectangle.

http://docs.oracle.com/javase/7/docs/api/java/awt/Polygon.html

This would manual calculation of the (x,y) corners however. It's possible that there is some way to construct a shape by rotating a Rectangle, but this would work at least. It has a getBounds() method.

Joshua Nelson
  • 581
  • 2
  • 10