在tilemaps中点击检测

时间:2014-05-25 21:14:10

标签: java 2d detection hit

我正在开发一款Mario游戏,并且需要有关如何为tilemap创建点击检测的帮助和建议。

目前,玩家可以走路/跳过街区。 我现在添加了一个固定的探测器到地面,我希望用常规命中探测器取代它。

我知道每个街区和玩家有四个方面。只有一些块需要命中检测,你可能需要知道的一些事情是玩家在98%的时间内保持在300px(屏幕中间)。

唯一移动的是地图

地图是从.txt文件渲染的,呈现方式如下:

for(int y=0;y<map.length;y++) {
    for(int x=0;x<map[y].length;x++) {
        int index = map[y][x];
        int yOffset = 0;

        if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
            yOffset++;
            index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
        }

        g.drawImage(tileSheet, 
            ((x * Engine.TILE_WIDTH)*scale)+position,
            ((y * Engine.TILE_HEIGHT)*scale),
            (((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
            (((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
            index * Engine.TILE_WIDTH,
            yOffset * Engine.TILE_HEIGHT,
            (index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
            (yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
           null
       );
    }
}
//This code is actually longer(included file later on)

对于多色瓷砖,颜色命中检测太慢且不一致

由于地图正在移动,我想我需要用它移动命中检测框。至于选择它应该检测的盒子可能很困难。也许最好不要使用代码检测某些瓷砖。

我的尝试以混淆代码而告终。任何人都可以建议实施命中检测的最简单方法吗? (记住我跳了)。

重要代码如下:

Board.java(绘制所有内容的面板)

package EvilMario;                                                                                          //Include this class in the EvilMario game package

import java.awt.*;                                                                                          //Imported to allow use of Image
import java.awt.event.*;                                                                                    //Imported to allow use of ActionListener

import javax.swing.*;                                                                                       //Import swing

public class Board extends JPanel implements ActionListener {                                               //Class Board
private TileLayer l;                                                                                    //Instance of TileLayer class
private Menu m;                                                                                         //Instance of menu class
private Player p;                                                                                       //Instance of player class

    Timer time;                                                                                             //A timer

    public static enum STATE {MENU,GAME};                                                                   //The game states
    public static STATE State = STATE.MENU;                                                                 //Set the first state to menu

//END
//GLOBAL
//DECLARATIONS

    public Board() {
        l = TileLayer.FromFile("D:/ICS3U1/EvilMario/map.txt");                                              //Tile map data from .txt file
        this.addMouseListener(new MouseInput());                                                            //Listen for mouse input
        this.addKeyListener(new AL());                                                                      //Listen for key input

        p = new Player();                                                                                   //Start running Player class
        m = new Menu();                                                                                     //Start running Menu class

        setFocusable(true);                                                                                 //Allows movement

        time = new Timer(20,this);                                                                          //Timer set to update "this" class every 20 milliseconds(Approximately 50fps)
        time.start();                                                                                       //Actually start the timer
    }

    public void actionPerformed(ActionEvent e) {
        p.move();                                                                                           //Call the move method from the player class
        repaint();                                                                                          //Repaint
    }

    public void paintComponent(Graphics g) {                                                                //Graphics method
        super.paintComponent(g);                                                                            //Super hero?
        Graphics2D g2d = (Graphics2D) g;                                                                    //Cast 2D graphics

        if(State==STATE.GAME) {
            if(p.distanceTraveled<300)l.DrawLayer(g,0);else l.DrawLayer(g, -(p.distanceTraveled-300));      //Draw the tile map

            g2d.drawImage(p.getImage(), p.getX(), p.getY(), 48, 48, null);                                  //Draw the player

            if(p.distanceTraveled==3488) System.out.println("You have won the game!");                      //Draw the end game screen
        } else {
            m.render(g);                                                                                    //Render the menu
        }
    }

    private class AL extends KeyAdapter {                                                                   //Action Listener extends key adapter
        public void keyPressed(KeyEvent e) {                                                                //On key press
            p.keyPressed(e);                                                                                //Send whatever key was pressed  TO the keyPressed  method in the player class
        }
        public void keyReleased(KeyEvent e) {                                                               //On key release
            p.keyReleased(e);                                                                               //Send whatever key was released TO the keyReleased method in the player class
        }
    }
}

Player.java(玩家逻辑)

package EvilMario;                                                                                          //Include this class in the EvilMario game package
import java.awt.Image;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;

public class Player {
    int x, dx, y, distanceTraveled;                                                                         //x coordinate,change in x coordinate,y coordinate,1st rep bg,2nd rep bg,dist traveled
    Image player;                                                                                           //The player variable
    ImageIcon walk_L_anim = new     ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_anim.gif");
    ImageIcon walk_L_idle = new     ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_idle.png");
    ImageIcon walk_R_anim = new    ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_anim.gif");
    ImageIcon walk_R_idle = new    ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_idle.png");

    ImageIcon jump_L_anim = new    ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_anim.gif");
    ImageIcon jump_L_idle = new    ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_idle.png");
    ImageIcon jump_R_anim = new    ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_anim.gif");
    ImageIcon jump_R_idle = new    ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_idle.png");

    boolean holdingLeft = false;
    boolean holdingRight = false;
    static boolean jumping = false;
    static boolean falling = false;
    static int jumpingTime = 350;

    public Player() {
        player = walk_R_idle.getImage();                                                                    //Give the player the image
        x = 75;                                                                                             //The original x position of the player
        y = 277;                                                                                            //The original y position of the player
        distanceTraveled = 75;                                                                              //Original distance traveled
    }

    public void move() {
        if(x>=0 && x<=300) {                                                                                //If the player is within the moving area
            x = x+dx;                                                                                       //The x position is updated to become itself+the amount you moved
        }
        if(x<0)                                                                                             //If the player has reached he very left side of the screen(0px)
            x=0;                                                                                            //Move him up a pixel so he can move again
        if(x>300)                                                                                           //If the player has reached the center of the screen(300px)
            x=300;                                                                                          //Move him down a pixel so he can move again

        distanceTraveled=distanceTraveled+dx;                                                               //Calculate distanceTraveled

        if(distanceTraveled<0)                                                                              //Make sure distanceTraveled isn't a negative
            distanceTraveled=0;                                                                             //Make sure distanceTraveled isn't a negative
        if(distanceTraveled>=300)                                                                           //Keep player at center position once past 300 mario meters
            x=300;                                                                                          //Keep player at center position once past 300 mario meters

        if(holdingLeft && !holdingRight) {
            if(distanceTraveled<300)dx=-5; else dx=-4;

            if(jumping && !falling) {
                player = jump_L_anim.getImage();
                y-=8;
            } else {
                player = walk_L_anim.getImage();
                if(y<277)
                    y+=8;
            }
        } else if(holdingRight && !holdingLeft) {
            if(distanceTraveled<300)dx=5; else dx=4;

            if(jumping && !falling) {
                player = jump_R_anim.getImage();
                y-=8;
            } else {
                player = walk_R_anim.getImage();
                if(y<277)
                    y+=8;
            }
        } else if(!holdingRight && !holdingLeft) {
            dx = 0;

            if(jumping && !falling) {
                player = jump_R_anim.getImage();
                y-=8;
            } else {
                if(y<277)
                    y+=8;
            }
        }

        if(y==277) {
            falling = false;
        }

        System.out.println("LEFT: "+holdingLeft+"        JUMP: "+jumping+"       RIGHT: "+holdingRight+"       FALLING: "+falling+"     Y: "+y);
}

    public int   getX()     { return x;      }                                                              //This method will return the x.      Is used by other classes
    public int   getY()     { return y;      }                                                              //This method will return the y.      Is used by other classes
    public Image getImage() { return player; }                                                              //This method will return the player. Is used by other classes

    public void keyPressed(KeyEvent e) {                                                                    //Called from the board class, the argument is whatever key was pressed
        int key = e.getKeyCode();                                                                           //The key originally sent from the board class

        if(key == KeyEvent.VK_LEFT && !holdingLeft)
            holdingLeft = true;
        if(key == KeyEvent.VK_RIGHT && !holdingRight)
            holdingRight = true;
        if(key == KeyEvent.VK_UP && !jumping && !falling)
            new Thread(new JumpThread(this)).start();
    }

    public void keyReleased(KeyEvent e) {                                                                   //Called from the board class, the argument is whatever key was released
        int key = e.getKeyCode();                                                                           //The key originally sent from the board class

        if(key == KeyEvent.VK_LEFT) {                                                                       //If the left or right key was released
            dx = 0;                                                                                         //Stop moving
            holdingLeft = false;
            player = walk_L_idle.getImage();
        }

        if(key == KeyEvent.VK_RIGHT) {
            dx = 0;
            holdingRight = false;
            player = walk_R_idle.getImage();
        }
    }
}

TileLayer.java(图块层的渲染)(可能是与问题相关的最重要的部分)

package EvilMario;                                                                           //Include this class in the EvilMario game package

import java.awt.Graphics;                                                                    //

public class TileLayer {

    private int[][] map;                                                                     //2D array
    private BufferedImage tileSheet;                                                         //The tile sheet

    public TileLayer(int[][] existingMap) {                                                  //
        map = new int[existingMap.length][existingMap[0].length];                            //map initialized
        for(int y=0;y<map.length;y++) {                                                      //Loop through all boxes
            for(int x=0;x<map[y].length;y++) {                                               //Loop through all boxes
                map[y][x] = existingMap[y][x];                                               //Update the map
            }
        }
        tileSheet = LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif");                 //Load the tilesheet
    }

    public TileLayer(int width, int height) {
        map = new int[height][width];
    }

    public static TileLayer FromFile(String fileName) {
        TileLayer layer = null;

        ArrayList<ArrayList<Integer>> tempLayout = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String currentLine;

            while((currentLine = br.readLine()) !=null) {
                if(currentLine.isEmpty())
                    continue;

                ArrayList<Integer> row = new ArrayList<>();
                String[] values = currentLine.trim().split(" ");

                for(String string: values) {
                    if(!string.isEmpty()) {
                        int id = Integer.parseInt(string);
                        row.add(id);
                    }
                }
                tempLayout.add(row);
            }
        } catch(IOException e) {
            System.out.println("ERROR");
        }

        int width = tempLayout.get(0).size();
        int height = tempLayout.size();

        layer = new TileLayer(width,height);
        for(int y=0;y<height;y++) {
            for(int x=0;x<width;x++) {
                layer.map[y][x] = tempLayout.get(y).get(x);
            }
        }
        layer.tileSheet = layer.LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif");

        return layer;
    }

    public BufferedImage LoadTileSheet(String fileName) {
        BufferedImage img = null;

        try {
            img = ImageIO.read(new File(fileName));
        } catch(Exception e) {
            System.out.println("Could not load image");
        }
        return img;
    }

    int scale = 2;

    public void DrawLayer(Graphics g, int position) {

        for(int y=0;y<map.length;y++) {
            for(int x=0;x<map[y].length;x++) {
                int index = map[y][x];
                int yOffset = 0;

                if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
                    yOffset++;
                    index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
                }

                g.drawImage(tileSheet, 
                        ((x * Engine.TILE_WIDTH)*scale)+position,
                        ((y * Engine.TILE_HEIGHT)*scale),

                        (((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
                        (((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),

                        index * Engine.TILE_WIDTH,
                        yOffset * Engine.TILE_HEIGHT,

                        (index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
                        (yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
                        null
                );

            }
        }
    }

}

Engine.java(不重要)(磁贴大小的简单变量)

package EvilMario;

public class Engine {
    public static final int TILE_WIDTH = 16;
    public static final int TILE_HEIGHT = 16;
}

如果您需要其他代码,请询问它们。我不是要求你给我一个问题的具体答案,而只是一个可以使用我的代码的方法。

  • 虽然具体答案很好:)

我也相信这个问题的答案对其他人有用,因为这个方法在一个流行的java 2d游戏教程视频中解释过(他们从未显示过命中检测)。

我试过的方法:

创建一个名为HitDetectionLayer的新java文件,其中包含TileLayer.java中存储数组中位置的确切代码。它失败了:(

1 个答案:

答案 0 :(得分:1)

好吧,我不完全确定你在做什么,如果你抛出一些图像就会更清楚。

无论如何,&#39;点击检测&#39; aka碰撞检测是一个非常复杂的主题,但它取决于你想做什么。如果你想要一切都是盒子或圆圈,那就很容易了。但是,如果你想要旋转东西或想要复杂形状的碰撞,那就太难了。

大多数游戏都使用圆圈或球体进行碰撞。你放置了大部分的图形(它可能不适合将图像的一部分留在圆圈中或从圆圈中移出,但它的生命)。现在让我们说你有你的马里奥雪碧和其中一只乌龟。好吧,你们周围都有圈子,一旦圈子碰到你就触发你的活动。

这方面的数学计算非常简单,因为圆圈定义为围绕恒定长度的周长。看看这个:

example1 你可能已经知道了这一点,这看起来很明显,但是如果你想一想,这就是圆圈的真正含义:在每个方向都是一致的长度。方向以度为单位进行测量,然后从那里开始进行三角测量,但您不需要这样做。你需要的是协调aka矢量。所以看看这个:

enter image description here
确定圆碰撞所需的只是圆之间的距离。无论圆圈从哪个角度碰撞都无关紧要,因为距离圆圈中心的距离始终是一致的。即使圆圈大小不同,也没关系,只考虑半径差异。

太计算所有这些,你会写一个像这样的方法:

public boolean testDistanceBetween( float radius1, float radius2, 
                                    float x1, float x2, float y1, float y2 ){

    double distanceBetween = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));

    if(distanceBetween < (radius1+radius2) ){
        return true;
    }

    return false;
}

这个故事的寓意是圈子就是那么好。如果你想进行矩形碰撞,你可以选择左下角和右上角,然后测试其他矩形是否位于这些点之间。这应该是非常简单的,每个点都是一个向量,每个矩形有4个点。如果一个矩形的4个点中的任何一个点位于另一个矩形的点之间,则存在碰撞。

您也可以使用此系统处理地面和墙壁。例如,如果地面是Y = 300,那么如果你的精灵协调是== 300,你就会暂停你的重力。

我想要解释的主要问题是,如果你打算有旋转的矩形或多边形,你想要检测它们上的碰撞...祝你好运。可以这样做,但你应该明白你正在实施复杂的物理学,特别是当你/如果你实施重力时。

所以我的回答是谨慎的:没有简单的方法来检测旋转矩形或多边形的碰撞。圆和静态矩形是限制。如果你真的想做旋转矩形/多边形得到一个物理引擎。 Box2d非常好,有一个Java版本的Jbox2d。