2

The Situation

I am currently trying to build a 2D game with Java's Swing. For this, I have my main class Puzzle which is subclassing JFrame. To my frame I add my main JPanel which consists of several JPanels added together (each of them being a new piece).

EDIT 2: PlayingField is my model which will store the current location of each piece. One can select a piece with the mouse (the plan is to highlight it) and move it with the arrow keys as long as the next step (a full cell, so approx. 100 pixel) isn't the location of one of the other pieces. As of right now, PlayingFielddoes not store any data since the pieces are missing.

private void createAndShowGui() {
    // The playing-field with a 4x6 grid.
    PlayingField field = new PlayingField(4, 6);
    JPanel mainPanel = new ComputerView(field);
    
    setTitle("Puzzle");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(400, 600);
    add(mainPanel);
    setVisible(true);
}

The method above will create my frame and adds the main panel. The following method is my main panel which adds several JPanels to itself.

public ComputerView(PlayingField field) {
    this.field = field;
    this.setLayout(null);

    JPanel topLeft = new GamingPiece(PlayingField.TOP_LEFT);
    add(topLeft);
    
    JPanel topRight = new GamingPiece(PlayingField.TOP_RIGHT);
    add(topRight);
    
    JPanel bottomLeft = new GamingPiece(PlayingField.BOTTOM_LEFT);
    add(bottomLeft);
}

Each GamingPiece or rather my sub-JPanels are drawing a basic piece (I only drawing one and rotating the others, since all consists of the same arbitrary shape). The GamingPiece class also subclasses JPanel and invokes the JPanel#paintComponent() method to draw the piece.

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;

    g2.setColor(Color.YELLOW);
    g2.fillPolygon(pieceX, pieceY, pieceX.length);
}

The Problem And My Questions

Since I am pretty new to Java I really do not know how to do it properly. If I add my pieces by creating a new object and adding it to the main panel, it won't show all of them, only the last one added. Some don't even seem to work, even if they're the only ones added (to explain my situation: I have for pieces which are the same arbitrary shape just rotated differently but using Graphics2D#rotate() doesn't seem to work fine).

I hope I explained my situation and my problem well enough fo you guys to help me. Thanks in advance!

EDIT:

My Codes

Puzzle.java

package programming.schimmler;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import programming.schimmler.model.PlayingField;
import programming.schimmler.view.ComputerView;

public class Puzzle extends JFrame {
    
...
Invoking the createAndShowGui()
...
    
private void createAndShowGui() {
    // The playing-field with a 4x6 grid.
    PlayingField field = new PlayingField(4, 6);
    JPanel mainPanel = new ComputerView(field);
    
    setTitle("Puzzle");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(400, 600);
    add(mainPanel);
    setVisible(true);
    }
}

ComputerView.java

package programming.schimmler.view;

import java.util.HashSet;
import java.util.Set;

import javax.swing.JPanel;

import programming.schimmler.model.PlayingField;

public class ComputerView extends JPanel {

...
Instance variables
....

public ComputerView(PlayingField field) {
    this.field = field;
    this.setLayout(null);

    JPanel topLeft = new GamingPiece(PlayingField.TOP_LEFT);
    add(topLeft);
    
    JPanel topRight = new GamingPiece(PlayingField.TOP_RIGHT);
    add(topRight);
    
    JPanel bottomLeft = new GamingPiece(PlayingField.BOTTOM_LEFT);
    add(bottomLeft);
    }
}

GamingPiece.java

package programming.schimmler.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

import programming.schimmler.model.PlayingField;

/**
 * 
 */
public class GamingPiece extends JPanel {

...

public GamingPiece(int type) {
    switch (type) {
    // Need to draw each polygon from different coordinates since rotating did not work yet.
    case PlayingField.TOP_LEFT:
        pieceX = new int[] { 100, 100, 300, 300, 200, 200, 100 };
        pieceY = new int[] { 100, 100, 100, 200, 200, 300, 300 };
        break;
    case PlayingField.TOP_RIGHT:
        pieceX = new int[] { 400, 400, 300, 300, 200, 200 };
        pieceY = new int[] { 0, 200, 200, 100, 100, 0 };
        break;
    case PlayingField.BOTTOM_LEFT:
        pieceX = new int[] { 0, 200, 200, 100, 100, 0 };
        pieceY = new int[] { 400, 400, 300, 300, 200, 200 };
        break;
    case PlayingField.BOTTOM_RIGHT:
        pieceX = new int[] { 400, 400, 300, 300, 200, 200 };
        pieceY = new int[] { 400, 200, 200, 300, 300, 400 };
        break;
    case PlayingField.SQUARE:
        pieceX = new int[] { 100, 300, 300, 100 };
        pieceY = new int[] { 100, 100, 300, 300 };
        break;
    }
    
    setLayout(null);
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;

    g2.setColor(Color.YELLOW);
    g2.fillPolygon(pieceX, pieceY, pieceX.length);
    }
}

These classes above are the only classes interacting with my GUI, no other classes take part.

Community
  • 1
  • 1
Nordic88
  • 129
  • 1
  • 12
  • 1
    I see the problem.. `this.setLayout(null);` **Use. Layouts.** General tip: For better help sooner, [edit] to add a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). – Andrew Thompson Mar 05 '19 at 02:48
  • 1
    Please add a [mcve]. Without one, we can only guess at the problem(s). `setLayout(null)` comes to mind as one possibility. – AJNeufeld Mar 05 '19 at 02:49
  • @AndrewThompson I actually wanted to use Swing without a layout manager for [absolute positioning](https://docs.oracle.com/javase/tutorial/uiswing/layout/none.html). – Nordic88 Mar 05 '19 at 02:54
  • @AJNeufeld This is actually my whole code but some instance variables declaration. I could add my whole code, but this is actually the important parts. – Nordic88 Mar 05 '19 at 02:55
  • 2
    I want a flying pony that excretes ice-cream. Now we've both discussed our unattainable dreams, you should fix that code by using layouts. Or if you can't manage it, show your attempt as an MCVE / SSCCE. – Andrew Thompson Mar 05 '19 at 02:56
  • *"I could add my whole code"* Nobody.. **nobody** has suggested to post the entire code. Did you follow and read those links? What confused you? – Andrew Thompson Mar 05 '19 at 02:57
  • @AndrewThompson see my edited post for my MCVE. I tried using a layout but as of now, I wasn't able to get it to work so I went back to null layout manager. – Nordic88 Mar 05 '19 at 03:04
  • If your game area is a grid, you might be better off using a [GridLayout](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/GridLayout.html). – VGR Mar 05 '19 at 04:04
  • 1
    What is `PlayingField`? Is it an `enum` or a `class` with static members? Is it a model holding where the pieces are, or where they should go? Do you want the pieces to be dragged around with the mouse? Or do pieces move like a 4x4 “magic 16” puzzle, where clicking a piece could cause a move of a full 100 pixel grid step? Until we understand what you are trying to do, we may as well be threading a needle while wearing oven mitts; possible, but really really hard. – AJNeufeld Mar 05 '19 at 04:13
  • @AJNeufeld I'm sorry for making this so hard for you guys. I'm just pretty new to this site and programming. *"PlayingField is my model which will store the current location of each piece. One can select a piece with the mouse (the plan is to highlight it) and move it with the arrow keys as long as the next step (a full cell, so approx. 100 pixel) isn't the location of one of the other pieces. As of right now, PlayingFielddoes not store any data since the pieces are missing."*, would be the missing information. – Nordic88 Mar 05 '19 at 04:21
  • 1
    "I'm sorry for making this so hard for you guys" : I can only repeat the advice you got: mvce. Not the whole code (That is what M stands for) but code that we can copy-paste and run (E). It is clearer and shorter than the attempt to describe the code. – c0der Mar 05 '19 at 04:56

1 Answers1

2

You are overcomplicating things by trying to add JPanel puzzle pieces to your JPanel puzzle board. Problems will rapidly become apparent with your non-rectangular puzzle pieces, like the TOP_LEFT shape, which has a cut-out from the lower right side of the piece. When it comes time to overlap the puzzle pieces, to fit the cut-away portions together, the piece added later will occlude pieces drawn earlier.

For instance, if you try to nest a TOP_LEFT piece (A) and a BOTTOM_RIGHT piece (B) together.

 AAAAAA BBB   
 AAAAAA BBB
 AAA BBBBBB
 AAA BBBBBB

You will only see:

+---+------+
|AAA|   BBB|
|AAA|   BBB|
|AAA|BBBBBB|
|AAA|BBBBBB|
+---+------+

The overlapping area will be drawn by only one of the panels. The entire area of the B piece, including the blank space, will be drawn in the area of the second piece, on top of whatever the A piece hoped to display.

You might be able to get around this by setting the JPanel to be transparent, and not calling super.paintComponent() which paints the entire component the background colour. But then you still have to draw the shape on the JPanel, and position the JPanel properly on the parent JPanel.

Forget panels within panels! Just draw the pieces on the parent JPanel.

Example:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Puzzle {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(Puzzle::new);
    }

    private final static int shape_x[] = { -100, 100, 100, 0, 0, -100 };
    private final static int shape_y[] = { -100, -100, 0, 0, 100, 100 };

    public Puzzle() {
        JFrame frame = new JFrame("Puzzle");
        frame.setSize(600, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        View view = new View();
        frame.setContentPane(view);

        Shape l_shape = new Polygon(shape_x, shape_y, shape_x.length);
        view.pieces.add(new Piece(Color.YELLOW, l_shape,   0, 100, 100));
        view.pieces.add(new Piece(Color.GREEN,  l_shape, 180, 300, 300));
        view.pieces.add(new Piece(Color.RED,    l_shape,  65, 450, 145));

        frame.setVisible(true);
    }

}

@SuppressWarnings("serial")
class View extends JPanel {

    final List<Piece> pieces = new ArrayList<>();

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D gc = (Graphics2D) g;
        for (Piece piece : pieces)
            piece.draw(gc);
    }
}

class Piece {
    final Color color;
    final Shape shape;
    final int angle;

    int x, y;   // Move pieces by by changing these

    Piece(Color color, Shape shape, int angle, int x, int y) {
        this.color = color;
        this.shape = shape;
        this.angle = angle;
        this.x = x;
        this.y = y;
    }

    void draw(Graphics2D gc) {
        AffineTransform tf = gc.getTransform();
        gc.translate(x, y);
        gc.rotate(Math.toRadians(angle));
        gc.setColor(color);
        gc.fill(shape);
        gc.setTransform(tf);
    }
}

Note that this is also a Minimal, Complete and Verifiable example. Minimal: No package statement; No extra HashSet and Set imports which aren't used; doesn't try to do anything except show 3 game pieces. It is complete: a single file you can compile, and run. It is verifiable: you can run it and see it shows 3 game pieces.

As a bonus, it shows Graphics2D.rotate() working, with one piece rotated 180°, and another rotated at an angle that is not appropriate for your puzzle but useful in demonstrating the that rotate works just fine.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • Thank you so much for the code (which worked like a charm!) and the tips, highly appreciated it! :-) I'm just running into one more question: I need to select one piece by a mouse click. I figured `getComponentAt()`would do the trick, but this returns the whole view. Is it possible to only get the corresponding element (e.g. the top-left piece) and save it to an variable to e.g. change the location? – Nordic88 Mar 05 '19 at 06:22
  • 2
    " I'm just running into one more question" please post a new question. – c0der Mar 05 '19 at 06:30
  • @AJNeufeld maybe you tell me how I can move a drawn shape? E.g. when interacting with them with `KeyEvent`s so if I press the arrow-key up, it should move 100 pixel up? – Nordic88 Mar 05 '19 at 16:20
  • 2
    This posted question is "Java Swing Add JPanel to JPanel". Asked, answered & accepted. It is not about hit detection to figure out which piece a mouse is over, or about how to move/animate pieces. Posting a new question for each of those does several things. 1) Keeps the Question-Answer aspect clean; someone searching for `JPanel` in `JPanel` doesn't have to sort through unrelated answers/information. 2) Allows other people to earn reputation for on-topic answers on a new post. 3) Improve your own reputation by posting more quality questions and accepting a quality answer on each. – AJNeufeld Mar 05 '19 at 16:37