How Do I Draw Two Overlapping JPanels such that the Graphics Drawn On Both Of Them Display On The Screen?

StackOverflow https://stackoverflow.com/questions/16739225

سؤال

I have two JPanels. One panel has a 100x100 rectangle drawn at 0,0. And the other has a 100x100 rectangle drawn at 100, 100. My problem is that when both JPanels are drawn on the JFrame's content pane, one JPanel (the last one drawn) covers the other, hiding its graphics. Below is oversimplified code drawing two rectangles and the things I've tried.

package playground;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Playground{

    public Playground(){
        JFrame frame = new JFrame("My Frame");
        frame.setSize(400, 400);

        JPanel backPanel = new JPanel(){;

                @Override
                public void paintComponent(Graphics g){
                    super.paintComponent(g);
                    Graphics2D g2 = (Graphics2D)g;
                    Rectangle2D rect = new Rectangle2D.Double(0, 0, 100, 100);
                    g2.draw(rect);
                }
        };
        JPanel frontPanel = new JPanel(){

            @Override
            public void paintComponent(Graphics g){
                super.paintComponent(g);
                Graphics2D g2 = (Graphics2D)g;
                Rectangle2D rect = new Rectangle2D.Double(150, 150, 100, 100);
                g2.draw(rect);
            }
        }; 
        frontPanel.setOpaque(true); //Does nothing
        frontPanel.setBackground(new Color(0, 0, 0, 0)); //Does nothing
        frontPanel.setForeground(new Color(0, 0, 0, 0)); //Erases the rectangle drawn


        frame.getContentPane().add(backPanel);
        frame.getContentPane().add(frontPanel);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args){
        System.out.println("Hello World");
        new Playground();
    }

}

If anyone cares why I want to do this,

I'm creating the game breakout. I am a novice programmer and I have no knowledge of gaming theory. So I decided the smartest way to avoid a lot of rendering and buffering is to have four JPanels. A static JPanel at the very back with an image drawn on it (fun background image). A JPanel with the paddle drawn on it. A JPanel with bricks drawn on it. And a JPanel with a ball drawn on it. My rationale is that I won't have to redraw the paddle if it is not being moved, the background, and bricks that are not being hit. If a brick lets say is hit, I will update an arrayList of bricks and call repaint on the corresponding JPanel.

هل كانت مفيدة؟

المحلول

I am a novice programmer and I have no knowledge of gaming theory.

Ok, we can work with that.

So I decided the smartest way to avoid a lot of rendering and buffering is to have four JPanels.

You've just unnecessarily complicated your program.

Think of a JPanel as a canvas. You want to draw the entire Breakout game; bricks, paddle, and ball, on one JPanel canvas. Don't worry, you'll be able to redraw the entire canvas fast enough to get 60 frames per second if you want.

The way to do this is to create a Brick class, a Paddle class, and a Ball class. You create a Game Model class that contains one instance of the Paddle class, one instance pf the Ball class, and a List of instances of the Brick class.

The Brick class would have fields to determine its position in the wall, the number of points scored when the ball collides with the brick, the color of the brick, and a draw method that knows how to draw one brick.

The ball class would have fields to determine its x, y position, its direction, its velocity, and a draw method that knows how to draw the ball.

The Paddle class would have fields to determine its x, y position, its direction, its velocity, and a draw method that knows haw to draw the paddle.

The Game Model class would have methods to determine when the ball collides with a brick, determine when the ball collides with a brick, determine when the ball collides with a wall, and a draw method that calls the other model class draw methods to draw a ball, a paddle, and a wall of bricks.

This should be enough for now to get you started going in the right direction.

Edited to answer questions:

How would I implement a draw method in all these classes?

Here's an example Ball class. I haven't tested the moveBall method, so it might need some adjustment

import java.awt.Graphics;
import java.awt.geom.Point2D;

public class Ball {

    private Point2D position;

    /** velocity in pixels per second */
    private double  velocity;

    /**
     * direction in radians
     * <ul>
     * <li>0 - Heading east (+x)</li>
     * <li>PI / 2 - Heading north (-y)</li>
     * <li>PI - Heading west (-x)</li>
     * <li>PI * 3 / 2 - Heading south (+y)</li>
     * </ul>
     * */
    private double  direction;

    public Point2D getPosition() {
        return position;
    }

    public void setPosition(Point2D position) {
        this.position = position;
    }

    public double getVelocity() {
        return velocity;
    }

    public void setVelocity(double velocity) {
        this.velocity = velocity;
    }

    public double getDirection() {
        return direction;
    }

    public void setDirection(double direction) {
        this.direction = direction;
    }

    public void moveBall(long milliseconds) {
        Point2D oldPosition = position;

        // Calculate distance of ball motion
        double distance = velocity / (1000.0D * milliseconds);

        // Calculate new position
        double newX = distance * Math.cos(direction);
        double newY = distance * Math.sin(direction);

        newX = oldPosition.getX() + newX;
        newY = oldPosition.getY() - newY;

        // Update position
        position.setLocation(newX, newY);
    }

    public void draw(Graphics g) {
        int radius = 3;
        int x = (int) Math.round(position.getX());
        int y = (int) Math.round(position.getY());

        // Draw circle of radius and center point x, y
        g.drawOval(x - radius, y - radius, radius + radius, radius + radius);

    }

}

The draw method draws the ball wherever it actually is located. That's all the draw method does.

Actually moving the ball is the responsibility of the Game Model class. The method for moving the ball is included in this class because the information necessary to move the ball is stored in the Ball class.

I gave the ball a radius of 3, or a diameter of 6 pixels. You may want to make the ball bigger, and use the fillOval method instead of drawOval.

should I just call repaint() at a 30ms interval

Basically, yes.

In psudeocode, you create a game loop

while (running) {
    update game model();
    draw game();
    wait;
}

First, you update the game model. I gave you a Ball class. You would have similar classes for the paddle and bricks. They all have draw methods.

Your Game model class calls all of these draw methods in the proper order. In Breakout, you would draw the boundaries first, then the bricks, then the paddle, and finally, the ball.

Your JPanel (canvas) calls the one draw method in the Game Model class.

I don't have an example game to show you, but if you read the article Sudoku Solver Swing GUI, you'll see how to put together a Swing GUI and you'll see how model classes implement draw methods.

I suggest that you stop working on Breakout for a while and go through the Oracle Swing Tutorial. Don't skip any sections in your haste to write a program. Go through the entire tutorial so you understand how Swing works before you try and use it.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top