25

I am wanting to make a game that has each level loaded from an image. I want to draw up the whole level in Photoshop, and then set it as the background and allow the player to walk over it. I want another invisible image to go over top which will be black in all places that I want to collide with.

The reason I don't want to use tiles, which are much easier with rectangle collision and such, is because there will be complex corners and not everything will be rectangle.

Is this a good idea, and is it possible to do easily? Would this be a big CPU hog or is there a better way to do this?

Level image

Level image

Obstacles shown in red

Obstacle in Red

Community
  • 1
  • 1
calebmanley
  • 421
  • 1
  • 5
  • 16
  • 2
    *"there will be complex corners and not everything will be rectangle."* I think this could be achieved by drawing and dealing with `Shape` and `Area` instances. – Andrew Thompson Jan 29 '13 at 01:10
  • 6
    You can actually do both: An approximate (cheap) initial rectangle collision check to quickly rule out most of the candidates and a more precise subsequent one if the previous succeeds. – Theodoros Chatzigiannakis Jan 29 '13 at 02:29
  • Look into [HTML5 canvas](https://developer.mozilla.org/en-US/docs/HTML/Canvas/Tutorial) – Kyle Weller Jan 29 '13 at 02:31
  • This all seems a little to complex for me, thanks for the help though! – calebmanley Jan 29 '13 at 02:34
  • 1
    Don't forget to add @TheodorosChatzigiannakis (or whoever) to notify them of a new comment. As an aside, who were you replying to? 3 different people suggested 3 different approaches. To 'back up' my approach, I added an SSCCE. – Andrew Thompson Jan 29 '13 at 03:12
  • @AndrewThompson The comment was for the OP, actually. I made it a comment because it wasn't really a direct answer to his question, felt more like a suggestion that occurred to me due to OP's worry about per-pixel collision being slower than rectangular. But anyway, I wasn't aware of the fact that mentioning someone also notifies them (and that *not* doing so doesn't notify them), so thanks for the tip! – Theodoros Chatzigiannakis Jan 29 '13 at 05:22
  • @TheodorosChatzigiannakis Part of my aim in mentioning it is to educate not only the OP, but also (occasionally) the commenter. :) – Andrew Thompson Jan 29 '13 at 06:37
  • 1
    @TheodorosChatzigiannakis Forgot to mention that approach you outlined is an excellent optimization. Perhaps it could have been/should be an answer. Or at least, part of one. I've never really loaded the approach in the accepted answer down with 100s of shapes, or many large shapes scaled and transformed.. – Andrew Thompson Oct 25 '13 at 23:44

1 Answers1

26

..there will be complex corners and not everything will be rectangle.

This could be achieved by drawing and dealing with Shape and Area instances. E.G.

  • Yellow is a little animated 'player'.
  • The bounds of the image represent walls that contain the path of the player (it bounces off them).
  • Obstacles are painted green when not in collision, red otherwise.

ShapeCollision

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

class ShapeCollision {

    private BufferedImage img;
    private Area[] obstacles = new Area[4];
    private Area walls;

    int x; 
    int y;
    int xDelta = 3;
    int yDelta = 2;

    /** A method to determine if two instances of Area intersect */
    public boolean doAreasCollide(Area area1, Area area2) {
        boolean collide = false;

        Area collide1 = new Area(area1);
        collide1.subtract(area2);
        if (!collide1.equals(area1)) {
            collide = true;
        }

        Area collide2 = new Area(area2);
        collide2.subtract(area1);
        if (!collide2.equals(area2)) {
            collide = true;
        }

        return collide;
    }

    ShapeCollision() {
        int w = 400;
        int h = 200;
        img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        final JLabel imageLabel = new JLabel(new ImageIcon(img));
        x = w/2;
        y = h/2;

        //circle 
        obstacles[0] = new Area(new Ellipse2D.Double(40, 40, 30, 30));

        int[] xTriangle = {330,360,345};
        int[] yTriangle = {60,60,40};
        //triangle 
        obstacles[1] = new Area(new Polygon(xTriangle, yTriangle, 3));

        int[] xDiamond = {60,80,60,40};
        int[] yDiamond = {120,140,160,140};
        //diamond 
        obstacles[2] = new Area(new Polygon(xDiamond, yDiamond, 4));

        int[] xOther = {360,340,360,340};
        int[] yOther = {130,110,170,150};
        // other 
        obstacles[3] = new Area(new Polygon(xOther, yOther, 4));

        walls = new Area(new Rectangle(0,0,w,h));

        ActionListener animate = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                animate();
                imageLabel.repaint();
            }
        };
        Timer timer = new Timer(50, animate);

        timer.start();
        JOptionPane.showMessageDialog(null, imageLabel);
        timer.stop();
    }

    public void animate() {
        Graphics2D g = img.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.BLUE);
        g.fillRect(0, 0, img.getWidth(), img.getHeight());
        x+=xDelta;
        y+=yDelta;
        int s = 15;
        Area player = new Area(new Ellipse2D.Double(x, y, s, s));

        // Acid test of edge collision;
        if (doAreasCollide(player,walls)) {
            if ( x+s>img.getWidth() || x<0 ) {
                xDelta *= -1;
            } 
            if(y+s>img.getHeight() || y<0 ) {
                yDelta *= -1;
            }
        }
        g.setColor(Color.ORANGE);
        for (Area obstacle : obstacles) {
            if (doAreasCollide(obstacle, player)) {
                g.setColor(Color.RED);
            } else {
                g.setColor(Color.GREEN);
            }
            g.fill(obstacle);
        }

        g.setColor(Color.YELLOW);
        g.fill(player);


        g.dispose();
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                new ShapeCollision();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }
}

Edit

make it detect all the red color and set that as the collision bounds

At start-up, use the source seen in the Smoothing a jagged path question to get an outline of the red pixels (see the getOutline(Color target, BufferedImage bi) method). Store that Area as the single obstacle on start-up.

Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433