Java Swing 中球穿过对象导致的多次碰撞

问题描述 投票:0回答:1

我正在编写一个基本的乒乓球游戏,球从由玩家控制的两个飞行垫反弹回来。我的问题开始于球与“右垫”碰撞的地方。我有两个垫,与左垫的碰撞效果很好,但球与右垫的碰撞没有按我想要的方式工作。

球穿过右侧的垫子(我不知道为什么,它不应该这样做)并与其碰撞多次,这会导致游戏中令人不愉快的视觉和声音。左垫不会发生这种情况。我添加了一个视频以便更好地理解:

输出(游戏)和错误的视频

我的代码有点长,所以为了让你更容易,我会提到我怀疑的内容。

  • checkCollision():在这个方法中我只计算了球与垫的碰撞。该方法适用于左板,但由于某种原因不适用于右板。
public void checkCollision() {

        for (ColoredShape tile : tiles) {

            if (ball.getShape().getBounds().intersects(tile.getShape().getBounds())) {

                playSound("sounds\\collisionsound1.wav");

                // Calculating the angle of incidence
                double tileCenterX = tile.getShape().getBounds2D().getCenterX();
                double tileCenterY = tile.getShape().getBounds2D().getCenterY();
                double ballCenterX = ball.getShape().getCenterX();
                double ballCenterY = ball.getShape().getCenterY();

                double angle = Math.atan2(ballCenterY - tileCenterY, ballCenterX - tileCenterX);

                if (Math.abs(angle) < Math.PI / 2) {
                    ball.setVx(-ball.getVx());
                } else {
                    ball.setVy(-ball.getVy());
                }

            }

        }
    }
  • moveBall()方法:
public void moveBalls() {
            
        if (!gameOver && ballShouldMove) {    
            ball.setX(ball.getX() + ball.getVx());
            ball.setY(ball.getY() + ball.getVy());
        }
    
        if (ball.getX() > getWidth() - ball.getShape().width || ball.getX() < 0) {
            
            playSound("sounds\\collisionsound3.wav");
            
            if (ball.getX() > getWidth() - ball.getShape().width) {
                
                ball.setVx(-Math.abs(ball.getVx()));
                leftScore += 1;

            } else if (ball.getX() < 0) {
                
                ball.setVx(Math.abs(ball.getVx()));
                rightScore += 1;

            }

            resetBallLocation();
            ballShouldMove = false;

        }

        if (ball.getY() > getHeight() - ball.getShape().height || ball.getY() < 0) {
            
            playSound("sounds\\collisionsound2.wav");
            ball.setVy(-ball.getVy());

        }

    }
  • moveTiles() 方法,当玩家按下右键时移动桨:
public void moveTiles() {

        if (moveTile1Up) {
            Rectangle bounds = (Rectangle) tiles.get(0).getShape().getBounds2D();
            int newY = (int) (bounds.getY() - 5);
            
            if (newY >= 0) { 
                tiles.get(0).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        } else if (moveTile1Down) {
            
            Rectangle bounds = (Rectangle) tiles.get(0).getShape().getBounds2D();
            int newY = (int) (bounds.getY() + 5);
            
            if (newY + tileHeight <= getHeight()) { // Check if the new position is within the frame
                tiles.get(0).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        }
    
        if (moveTile2Up) {

            Rectangle bounds = (Rectangle) tiles.get(1).getShape().getBounds2D();
            int newY = (int) (bounds.getY() - 5);
            
            if (newY >= 0) { // Check if the new position is within the frame
                tiles.get(1).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        } else if (moveTile2Down) {

            Rectangle bounds = (Rectangle) tiles.get(1).getShape().getBounds2D();
            int newY = (int) (bounds.getY() + 5);
            
            if (newY + tileHeight <= getHeight()) { // Check if the new position is within the frame
                tiles.get(1).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        }
    }
  • 我在这里声明了 pad 对象,我认为这里没有问题,但以防万一。
Shape tile1 = new Rectangle(tileWidth / 2, Interface.frameHeight / 3, tileWidth, tileHeight);
Shape tile2 = new Rectangle(Interface.frameWidth - tileWidth*2, Interface.frameHeight / 3, tileWidth, tileHeight);
ColoredShape tileLeft = new ColoredShape(tile1, Color.white);
ColoredShape tileRight = new ColoredShape(tile2, Color.white);
tiles.add(0, tileLeft);
tiles.add(1, tileRight);

--源代码--

接口.java

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;

public class Interface extends JFrame {

    protected static int frameHeight = 637;
    protected static int frameWidth = 1214;
    protected int tileWidth = 20;
    protected int tileHeight = 200;

    public Interface() {

        setSize(frameWidth, frameHeight);
        getContentPane().setBackground(Color.BLACK);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        setTitle("Ping Pong");
        setResizable(false);
        add(new Core(tileWidth, tileHeight));
        setVisible(true);

    }

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

}

class ColoredShape {
    
    private Shape shape;
    private Color color;

    public ColoredShape(Shape shape, Color color) {
        
        this.shape = shape;
        this.color = color;

    }

    public Shape getShape() {
        return shape;
    }

    public void setShape(Shape shape) {
        this.shape = shape;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

}

class Ball {
    
    private Ellipse2D.Double shape;
    private Color color;
    private int vx;
    private int vy;

    public Ball(int x, int y, int diameter, Color color, int vx, int vy) {
        
        this.shape = new Ellipse2D.Double(x, y, diameter, diameter);
        this.color = color;
        this.vx = vx;
        this.vy = vy;

    }

    public Ellipse2D.Double getShape() {
        return shape;
    }

    public Color getColor() {
        return color;
    }

    public int getVx() {
        return vx;
    }

    public void setVx(int vx) {
        this.vx = vx;
    }

    public int getVy() {
        return vy;
    }

    public void setVy(int vy) {
        this.vy = vy;
    }

    public int getX() {
        return (int) shape.x;
    }

    public void setX(int x) {
        shape.x = x;
    }

    public int getY() {
        return (int) shape.y;
    }

    public void setY(int y) {
        shape.y = y;
    }

}

核心.java

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.lang.Math;
import javax.sound.sampled.*;
import java.io.*;

class Core extends JPanel {

    private String winner = "";
    protected int x;
    protected int y;
    protected int diameter = 10;
    private int vx = 10, vy = 3;
    private int rightScore = 0, leftScore = 0;
    private int tileWidth, tileHeight;
    private boolean moveTile1Up, moveTile1Down, moveTile2Up, moveTile2Down;
    private boolean ballShouldMove = false;
    private boolean gameOver = false;
    private Timer speedIncreaseTimer;
    private ArrayList<ColoredShape> tiles;
    protected Ball ball;


    public Core(int tileWidth, int tileHeight) {
        
        setOpaque(false);
        
        this.tileWidth = tileWidth;
        this.tileHeight = tileHeight;
        this.tiles = new ArrayList<>();

        x = (int) Interface.frameWidth / 2;
        y = (int) Interface.frameHeight / 2;
        vx = (Math.random() < 0.5) ? vx : -1*vx;
        vy = (Math.random() < 0.5) ? vy : -1*vy;

        Shape tile1 = new Rectangle(tileWidth / 2, Interface.frameHeight / 3, tileWidth, tileHeight);
        Shape tile2 = new Rectangle(Interface.frameWidth - tileWidth*2, Interface.frameHeight / 3, tileWidth, tileHeight);
        ColoredShape tileLeft = new ColoredShape(tile1, Color.white);
        ColoredShape tileRight = new ColoredShape(tile2, Color.white);
        tiles.add(0, tileLeft);
        tiles.add(1, tileRight);

        ball = new Ball(x, y, diameter, Color.white, vx, vy);

        // Add KeyListener to detect key presses
        setFocusable(true);
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                if (keyCode == KeyEvent.VK_W) {
                    moveTile1Up = true;
                } else if (keyCode == KeyEvent.VK_S) {
                    moveTile1Down = true;
                } else if (keyCode == KeyEvent.VK_UP) {
                    moveTile2Up = true;
                } else if (keyCode == KeyEvent.VK_DOWN) {
                    moveTile2Down = true;
                }

                if (keyCode == KeyEvent.VK_SPACE) {
                    
                    if (gameOver) {
                        restartGame();
                    } else {
                        ballShouldMove = true;
                    }

                }

                if (keyCode == KeyEvent.VK_ESCAPE) {

                    restartGame();

                }
            }

            @Override
            public void keyReleased(KeyEvent e) {
                int keyCode = e.getKeyCode();
                if (keyCode == KeyEvent.VK_W) {
                    moveTile1Up = false;
                } else if (keyCode == KeyEvent.VK_S) {
                    moveTile1Down = false;
                } else if (keyCode == KeyEvent.VK_UP) {
                    moveTile2Up = false;
                } else if (keyCode == KeyEvent.VK_DOWN) {
                    moveTile2Down = false;
                }
            }
        });

        ImageIcon originalIcon = new ImageIcon("images\\restartButton.jpg");

        int scaledWidth = 50;
        int scaledHeight = -1; // Automatically calculate the height to preserve aspect ratio
        Image scaledImage = originalIcon.getImage().getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);

        ImageIcon scaledIcon = new ImageIcon(scaledImage);

        JLabel restartLabel = new JLabel(scaledIcon);

        add(restartLabel);

        restartLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                restartGame();
            }
        });
        
        Timer timer = new Timer(10, new ActionListener() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                
                moveBalls();
                checkCollision();
                moveTiles();
                gameOver();
                repaint();
                
            }

        });

        timer.start();

        speedIncreaseTimer = new Timer(30000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                
                if (ballShouldMove) {

                    ball.setVx((int) (ball.getVx() * 1.25));
                    ball.setVy((int) (ball.getVy() * 1.25));

                }

            }
        });
        
        speedIncreaseTimer.start();

    }

    public void moveBalls() {
            
        if (!gameOver && ballShouldMove) {    
            ball.setX(ball.getX() + ball.getVx());
            ball.setY(ball.getY() + ball.getVy());
        }
    
        if (ball.getX() > getWidth() - ball.getShape().width || ball.getX() < 0) {
            
            playSound("sounds\\collisionsound3.wav");
            
            if (ball.getX() > getWidth() - ball.getShape().width) {
                
                ball.setVx(-Math.abs(ball.getVx()));
                leftScore += 1;

            } else if (ball.getX() < 0) {
                
                ball.setVx(Math.abs(ball.getVx()));
                rightScore += 1;

            }

            resetBallLocation();
            ballShouldMove = false;

        }

        if (ball.getY() > getHeight() - ball.getShape().height || ball.getY() < 0) {
            
            playSound("sounds\\collisionsound2.wav");
            ball.setVy(-ball.getVy());

        }

    }

    public void gameOver() {

        if (rightScore >= 10 || leftScore >= 10) {
            
            playSound("sounds\\gameoversound1.wav");
            gameOver = true;
            if (rightScore >= 10) {
                winner = "Right Player";
            } else {
                winner = "Left Player";
            }

            rightScore = 0;
            leftScore = 0;

        }

    }

    private void restartGame() {

        gameOver = false;
        winner = "";
        resetBallLocation();
        ballShouldMove = false;
        rightScore = 0;
        leftScore = 0;

    }
    
    public void resetBallLocation() {
        
        ball.setX((int) (Interface.frameWidth / 2));
        ball.setY((int) (Interface.frameHeight / 2));

        ball.setVx(10);
        ball.setVy(3);

        vy = (Math.random() < 0.5) ? vy : -1*vy;
    }
    

    public void checkCollision() {
            
        for (ColoredShape tile : tiles) {
                
            if (ball.getShape().getBounds().intersects(tile.getShape().getBounds())) {

                playSound("sounds\\collisionsound1.wav");
                
                // Calculating the angle of incidence
                double tileCenterX = tile.getShape().getBounds2D().getCenterX();
                double tileCenterY = tile.getShape().getBounds2D().getCenterY();
                double ballCenterX = ball.getShape().getCenterX();
                double ballCenterY = ball.getShape().getCenterY();

                double angle = Math.atan2(ballCenterY - tileCenterY, ballCenterX - tileCenterX);

                if (Math.abs(angle) < Math.PI / 2) {
                    ball.setVx(-ball.getVx());
                } else {
                    ball.setVy(-ball.getVy());
                }
                    
            }

        }
    }

    public void moveTiles() {

        if (moveTile1Up) {
            Rectangle bounds = (Rectangle) tiles.get(0).getShape().getBounds2D();
            int newY = (int) (bounds.getY() - 5);
            
            if (newY >= 0) { 
                tiles.get(0).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        } else if (moveTile1Down) {
            
            Rectangle bounds = (Rectangle) tiles.get(0).getShape().getBounds2D();
            int newY = (int) (bounds.getY() + 5);
            
            if (newY + tileHeight <= getHeight()) { // Check if the new position is within the frame
                tiles.get(0).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        }
    
        if (moveTile2Up) {

            Rectangle bounds = (Rectangle) tiles.get(1).getShape().getBounds2D();
            int newY = (int) (bounds.getY() - 5);
            
            if (newY >= 0) { // Check if the new position is within the frame
                tiles.get(1).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        } else if (moveTile2Down) {

            Rectangle bounds = (Rectangle) tiles.get(1).getShape().getBounds2D();
            int newY = (int) (bounds.getY() + 5);
            
            if (newY + tileHeight <= getHeight()) { // Check if the new position is within the frame
                tiles.get(1).setShape(new Rectangle((int) bounds.getX(), newY, tileWidth, tileHeight));
            }

        }
    }
    

    public void playSound(String filePath) {

        try {

            File sound = new File(filePath);
            AudioInputStream audioInput = AudioSystem.getAudioInputStream(sound);
            Clip clip = AudioSystem.getClip();
            clip.open(audioInput);
            clip.start();

        } catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {

            e.printStackTrace();

        }

    }

    public void paintComponent(Graphics g) {
        
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;

        for (ColoredShape tile : tiles) {
            g2d.setColor(tile.getColor());
            g2d.fill(tile.getShape());
        }

        g2d.setColor(ball.getColor());
        g2d.fill(ball.getShape());

        String strLeftScore = String.valueOf(leftScore);
        String strRightScore = String.valueOf(rightScore);

        Font scoreFont = new Font("Arial", Font.BOLD, 36);
        g2d.setFont(scoreFont);

        FontMetrics fmLeft = g.getFontMetrics(scoreFont);
        FontMetrics fmRight = g.getFontMetrics(scoreFont);
        int xLeft = (getWidth() - fmLeft.stringWidth(strLeftScore)) / 2 - 80;
        int xRight = (getWidth() - fmRight.stringWidth(strRightScore)) / 2 + 80;
        int yLeft = 40;
        int yRight = 40;
            
        g2d.drawString(strLeftScore, xLeft, yLeft);
        g2d.drawString(strRightScore, xRight, yRight);

        if (gameOver) {
            
            String gameOverMessage = "Game Over, " + winner + " won.";
            
            Font font = new Font("Arial", Font.BOLD, 36);
            g2d.setFont(font);
            
            FontMetrics fm = g.getFontMetrics(font);
            int x = (getWidth() - fm.stringWidth(gameOverMessage)) / 2;
            int y = getHeight() / 2;
            
            g2d.drawString(gameOverMessage, x, y);
            
        }
        
    }

    public int getRightScore() {
        return rightScore;
    }

    public int getLeftScore() {
        return leftScore;
    }

}
java swing awt
1个回答
0
投票

您的碰撞检测算法不足。如果球的速度增加到在帧之间移动显着距离的程度,或者如果垫对象的方向垂直于运动方向并且球以较小的角度接近,则球可能会穿过对象而不被检测到(至少在正确的时间)通过碰撞检测机制。这就是所谓的

tunneling

  • 我根据最大速度分量将球的运动分为 numSteps 个子步。这可确保对球的运动进行充分采样以正确检测碰撞。
  • 我在 setVy 和 setVx 中使用了 (int) 转换,因为您已将 vx 和 vy 定义为整数。

新的 checkCollision() 方法:

public void checkCollision() {
            
        for (ColoredShape tile : tiles) {
                
            double ballX = ball.getX();
            double ballY = ball.getY();
            double ballRadius = ball.getRadius();
            double ballVx = ball.getVx();
            double ballVy = ball.getVy();

            Shape tileShape = tile.getShape();
            Rectangle2D tileBounds = tileShape.getBounds2D();
            double tileLeft = tileBounds.getMinX();
            double tileRight = tileBounds.getMaxX();
            double tileTop = tileBounds.getMinY();
            double tileBottom = tileBounds.getMaxY();

            // Calculate the number of steps based on the maximum velocity component
            double maxSpeed = Math.max(Math.abs(ballVx), Math.abs(ballVy));
            int numSteps = (int) Math.ceil(maxSpeed / ballRadius);

            // Perform collision detection for each step
            for (int i = 0; i < numSteps; i++) {
                double stepX = ballX + ballVx * (i + 1) / numSteps;
                double stepY = ballY + ballVy * (i + 1) / numSteps;

                if (stepX + ballRadius >= tileLeft && stepX - ballRadius <= tileRight &&
                stepY + ballRadius >= tileTop && stepY - ballRadius <= tileBottom) {

                    playSound("sounds\\collisionsound1.wav");

                    // Determine the side of the collision
                    double overlapX = Math.max(0, Math.min(stepX + ballRadius, tileRight) - Math.max(stepX - ballRadius, tileLeft));
                    double overlapY = Math.max(0, Math.min(stepY + ballRadius, tileBottom) - Math.max(stepY - ballRadius, tileTop));

                    boolean collisionFromLeftOrRight = overlapX < overlapY;

                    if (collisionFromLeftOrRight) {
                        // Adjust x velocity
                        ball.setVx((int) -ballVx);
                    } else {
                        // Adjust y velocity
                        ball.setVy((int) -ballVy);
                    }
                    break; // Exit the loop after handling the collision
                }
            }

        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.