1

我正在开发一款马里奥游戏,需要有关如何为瓷砖地图创建命中检测的帮助和建议。

目前,玩家可以步行/跳过方块。我现在在地面上添加了一个固定的检测,我希望用常规的命中检测来代替它。

我知道每个街区和球员都有四个方面。只有一些方块需要命中检测,而您可能需要知道的一些事情是玩家有 98% 的时间停留在 300 像素(屏幕中间)。

唯一移动的是地图

地图是从 .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 中存储在数组中的位置的确切代码。它失败了:(

4

1 回答 1

1

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

无论如何,“命中检测”又名碰撞检测是一个非常复杂的主题,但这取决于您想要做什么。如果你希望一切都是盒子或圆圈,那么这很容易。但是,如果您想要旋转物体或想要复杂形状的碰撞,那将变得非常困难。

大多数游戏使用圆圈或球体进行碰撞。您放置了大部分图形(它可能不完全适合将部分图像留在圈内或圈外,但这就是生活)。现在假设你有你的马里奥精灵和其中一只海龟。好吧,你在他们周围都有圈子,一旦圈子接触你就会触发你的事件。

数学很简单,因为根据定义,圆圈是围绕恒定长度的周长。看这个:

示例1 你可能已经知道这一点,而且看起来很明显,但如果你仔细想想,这就是一个圆的真正含义:在每个可测方向上的长度都是一致的。方向以度为单位测量,然后从那里继续三角学,但您不需要它。你需要的是协调又名向量。所以看看这个:

在此处输入图像描述
确定圆碰撞所需的只是圆之间的距离。无论圆圈从哪个角度碰撞都无关紧要,因为到圆圈中心的距离一直是一致的。即使圆的大小不同,也没关系,只要考虑半径差异即可。

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

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,那么如果您的精灵的 y 坐标 == 300,则暂停重力。

我想解释的主要内容是,如果您打算旋转矩形或多边形并且想要检测它们的碰撞......祝您好运。可以,但是你应该明白你正在实现复杂的物理,尤其是当你实现重力时。

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

于 2014-09-24T15:56:11.260 回答