检测复杂形状的碰撞

25

我想制作一个游戏,每个关卡都从一张图片中加载。 我想在Photoshop里绘制整个关卡,然后将其设置为背景,并允许玩家在上面行走。 我想要另一个不可见的图像放在顶部,在我想要碰撞的所有地方都是黑色的。

我不想使用瓷砖,因为那样很容易实现矩形碰撞等,但是会有复杂的拐角并且不是所有东西都是矩形的。

这是一个好主意吗?是否可以轻松地实现? 这会增加CPU的负担吗?还是有更好的方法来实现这个目标?

关卡图片

关卡图片

红色表示障碍物

红色表示障碍物


2
"会有复杂的角落,并不是所有的东西都是矩形。" 我认为这可以通过绘制和处理 ShapeArea 实例来实现。 - Andrew Thompson
6
你实际上可以同时进行两步操作:首先进行一个近似(廉价)的矩形碰撞检查,以快速排除大部分可能性,然后进行更精确的检查,如果前一步成功的话。 - Theodoros Chatzigiannakis
请了解HTML5画布 - Kyle Weller
1
不要忘记添加@TheodorosChatzigiannakis(或其他人)以通知他们有新评论。另外,你是在回复谁?三个不同的人提出了三种不同的方法。为了“支持”我的方法,我添加了一个SSCCE。 - Andrew Thompson
1
@TheodorosChatzigiannakis 忘了提到你所列出的方法是一个非常好的优化。也许它可以成为答案,或者至少是其中的一部分。我从来没有真正用数百个形状或者很多大的、经过缩放和转换的形状来加载已接受的答案中的方法。 - Andrew Thompson
显示剩余3条评论
1个回答

26

会有复杂的角落,不是所有的东西都会是矩形。

通过绘制和处理Shape(图形)Area(区域)实例可以实现这一点。例如:

  • 黄色是一个小动画“玩家”。
  • 图片的边界代表包含玩家路径的墙壁(它会从墙壁上弹开)。
  • 当障碍物没有碰撞时,它们被涂成绿色;否则为红色。

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);
    }
}

编辑

使其检测所有的红色区域并将其设置为碰撞边界

在启动时,使用Smoothing a jagged path问题中提到的源代码获取红色像素的轮廓(请参见getOutline(Color target, BufferedImage bi)方法)。将该Area作为启动时的单个障碍物存储。


+1 这是我迄今为止最喜欢的碰撞检测答案。很棒的例子,谢谢 Andrew。 - leigero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接