I'm learning Java these days, my first project is to create a "Go board", 9 * 9 rows and columns, and place black and white stones on the intersections.
I created a board with 9 * 9 lines and columns, now I have to create black and white stones using the JButton component.
Other than the color, size, and position of the button on the first row (setLayout), I was unable to turn the button into a circle and place the stone on the intersection points.
From multiple searches for related guides, I have noticed that there is some unique structure that I am not familiar with for creating and designing buttons.
And now my question comes in - what is the code structure I need to create in order to produce a button in the shape of a circle, size 65 * 65, in black or white? Do I need to create a new class for this? How and where should I integrate JPanel?
public class Main {
public static void main(String[] args) {
Board board = new Board(900, 900, "Go board");
}
}
import java.awt.*;
import javax.swing.*;
public class Board extends JPanel {
private int width;
private int height;
private String title;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Board(int width, int height, String title) {
super();
this.width = width;
this.height = height;
this.title = title;
this.initBoard();
}
public Board() {
super();
}
public void initBoard() {
JFrame f = new JFrame(this.getTitle());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// f.getContentPane().setBackground(Color.getHSBColor(25, 75, 47));
f.setSize(this.getWidth(), this.getHeight());
// f.setLocation(550, 25);
f.add(this, BorderLayout.CENTER);
f.setVisible(true);
JButton stone = new JButton(" ");
f.add(stone);
f.setLayout(new FlowLayout());
stone.setBackground(Color.BLACK.darker());
stone.setBorder(BorderFactory.createDashedBorder(getForeground()));
stone.setPreferredSize(new Dimension(65, 65));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < 10; i++) {
g.drawLine(0, 10 + (i * ((this.getWidth() - 20) / 9)), this.getWidth(),
10 + (i * ((this.getWidth() - 20) / 9)));
g.drawLine(10 + (i * ((this.getHeight() - 20) / 9)), 0, 10 + (i * ((this.getHeight() - 20) / 9)),
this.getHeight());
}
}
}
Before uploading the post, I read the following posts:
Design Button in Java (like in CSS)
How can I set size of a button?
Java: JButton with custom Shape: Fill with Metal Look and Feel Gradient
How to Use Borders
Java JButton
How to use setUI method in javax.swing.JButton
Note: I do not want to access posts that explain how to produce a "Go board", the learning process in this context is my goal.
Use a JPanel with a 9x9 GridLayout and ad to it JButtons configured to your need as demonstrated in the following very basic mre:
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
public class GridOfButtons extends JPanel {
private static final int ROWS = 9, COLS = 9, SIZE = 65, BORDER = 2;
private static final Color BOARD_COLOR = Color.BLACK;
public GridOfButtons() {
setLayout(new GridLayout(ROWS, COLS, BORDER, BORDER));
setBackground(BOARD_COLOR);
StonesFactory factory = new StonesFactory(SIZE);
boolean isBlack = false;
for (int col = 0; col < COLS; col++) {
for (int row = 0; row < ROWS; row++) {
add(factory.makeButton(isBlack));
isBlack = !isBlack;
}
}
this.initBoard();
}
public void initBoard() {
JFrame f = new JFrame("Board Of Buttons");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridBagLayout());
f.add(this);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new GridOfButtons());
}
}
class StonesFactory{
private static final Color STONE = Color.YELLOW, WHITE_STONE = Color.WHITE, BLACK_STONE = Color.BLACK;
private final int size;
private final ImageIcon whiteIcon, blackIcon;
public StonesFactory(int size) {
this.size = size;
whiteIcon = new ImageIcon(createImage(false));
blackIcon = new ImageIcon(createImage(true));
}
JButton makeButton(boolean isBlack){
JButton stone = new JButton();
stone.setPreferredSize(new Dimension(size, size));
stone.setBackground(STONE);
stone.setIcon(isBlack ? blackIcon : whiteIcon);
return stone;
}
//construct image for button's icon
private BufferedImage createImage(boolean isBlack) {
BufferedImage img = new BufferedImage(size , size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setColor(isBlack ? BLACK_STONE : WHITE_STONE);
g2.fillOval(0,0,size,size);
g2.dispose();
return img;
}
}
(Run it online)
Alternatively you can produce the board by custom painting of a JPanel. This will make the individual "stones" not clickable and more difficult to modify:
import java.awt.*;
import javax.swing.*;
public class GridByPainting extends JPanel {
private static final int ROWS = 9, COLS = 9, SIZE = 65, BORDER = 2;
private static final Color BOARD_COLOR = Color.BLACK, STONE = Color.YELLOW,
WHITE_STONE = Color.WHITE, BLACK_STONE = Color.BLACK;
private final Dimension size;
public GridByPainting() {
int x = BORDER + COLS*(SIZE + BORDER);
int y = BORDER + ROWS*(SIZE + BORDER);
size = new Dimension(x,y);
this.initBoard();
}
#Override
public Dimension getPreferredSize() {
return size;
}
public void initBoard() {
JFrame f = new JFrame("Grid By Painting");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridBagLayout());
f.add(this);
f.pack();
f.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth(); int height = getHeight();
int stoneWidth = (width - BORDER) / COLS - BORDER;
int stoneHeight = (height -BORDER)/ ROWS - BORDER ;
//draw board
g.setColor(BOARD_COLOR);
g.fillRect(0, 0, width, height);
boolean isBlack = true;
//draw square stones
for (int col = 0; col < COLS; col++) {
for (int row = 0; row < ROWS; row++) {
int x = BORDER + col*(stoneWidth + BORDER);
int y = BORDER + row*(stoneHeight + BORDER);
g.setColor(STONE);
g.fillRect(x, y, stoneWidth, stoneHeight);
//draw circle
g.setColor(isBlack ? BLACK_STONE : WHITE_STONE);
isBlack = !isBlack;
g.fillOval(x, y, stoneWidth, stoneHeight);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new GridByPainting());
}
}
(Run it online)
It seems like you skipped over some of the important parts of the Oracle tutorial, Creating a GUI With Swing.
Here's my comment from August 23rd, just 10 days ago.
Generally, you create a logical model of a Go board using a plain Java
getter / setter class. You use a drawing JPanel to create the Go board
in the GUI and draw circles to represent the stones. The Oracle
tutorial, Creating a GUI With Swing, will show you the steps to
creating a Swing GUI. Skip the Netbeans section.
So, where's your logical model? Where's your drawing JPanel?
Here's a quick GUI I created.
My code has a logical model. My code has a drawing JPanel.
The first thing I did was create a plain Java getter / setter class to hold a logical representation of a Go Board. I named this the Board class.
The next thing I did was start my Swing GUI with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
I used the run method of my Runnable class to create the JFrame. The JFrame methods must be called in a specific order. This is the order I use for all my Swing applications.
I separate the creation of the JFrame from the creation of any subsequent JPanels. I do this to keep my code organized, easy to read, and easy to understand.
I extend a JPanel to create the drawing JPanel. I do this so I can override the paintComponent method of the JPanel class. The drawing JPanel draws (paints) the board state. That's all. Nothing else. Another class will take care of adding pieces to the logical Go board and repainting the drawing JPanel.
The MoveListener class implements a MouseListener (extends a MouseAdapter) to respond to mouse clicks on the Go board. The MoveListener class keeps track of whose turn it is. In a more elaborate version of a Go board, you would have another plain Java getter / setter class to keep track of the game state.
Here's the complete runnable code. I made all the classes inner classes so I could post this code as one block.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class GoBoard implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new GoBoard());
}
private Board board;
private DrawingPanel drawingPanel;
public GoBoard() {
this.board = new Board();
}
#Override
public void run() {
JFrame frame = new JFrame("Go Board");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.drawingPanel = new DrawingPanel(board);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final int margin, pieceRadius, lineSpacing;
private Board board;
public DrawingPanel(Board board) {
this.board = board;
this.margin = 60;
this.pieceRadius = 40;
this.lineSpacing = 100;
this.setBackground(new Color(0x993300));
int width = 8 * lineSpacing + margin + margin;
this.setPreferredSize(new Dimension(width, width));
this.addMouseListener(new MoveListener(board));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
paintHorizontalLines(g2d);
paintVerticalLines(g2d);
paintPieces(g2d);
}
private void paintHorizontalLines(Graphics2D g2d) {
int x = margin;
int y1 = margin;
int y2 = getHeight() - margin;
g2d.setColor(Color.YELLOW);
g2d.setStroke(new BasicStroke(3f));
for (int index = 0; index < 9; index++) {
g2d.drawLine(x, y1, x, y2);
x += lineSpacing;
}
}
private void paintVerticalLines(Graphics2D g2d) {
int x1 = margin;
int x2 = getWidth() - margin;
int y = margin;
g2d.setColor(Color.YELLOW);
g2d.setStroke(new BasicStroke(3f));
for (int index = 0; index < 9; index++) {
g2d.drawLine(x1, y, x2, y);
y += lineSpacing;
}
}
private void paintPieces(Graphics2D g2d) {
int[][] b = board.getBoard();
for (int row = 0; row < b.length; row++) {
for (int column = 0; column < b[row].length; column++) {
int x = column * lineSpacing + margin;
int y = row * lineSpacing + margin;
if (b[row][column] == 1) {
g2d.setColor(Color.BLACK);
g2d.fillOval(x - pieceRadius, y - pieceRadius,
pieceRadius + pieceRadius, pieceRadius + pieceRadius);
} else if (b[row][column] == 2) {
g2d.setColor(Color.WHITE);
g2d.fillOval(x - pieceRadius, y - pieceRadius,
pieceRadius + pieceRadius, pieceRadius + pieceRadius);
}
}
}
}
}
public class MoveListener extends MouseAdapter {
private boolean isBlackTurn = true;
private Board board;
public MoveListener(Board board) {
this.board = board;
}
#Override
public void mouseReleased(MouseEvent event) {
Point point = event.getPoint();
int margin = 60;
int pieceRadius = 40;
int lineSpacing = 100;
int column = (point.x - margin + pieceRadius) / lineSpacing;
int row = (point.y - margin + pieceRadius) / lineSpacing;
int piece = (isBlackTurn) ? 1 : 2;
board.setPiece(piece, row, column);
drawingPanel.repaint();
isBlackTurn = !isBlackTurn;
}
}
public class Board {
private int[][] board;
public Board() {
this.board = new int[9][9];
}
/**
* <p>
* This method inserts a piece on the board.
* </p>
*
* #param piece - 1 for black, 2 for white
* #param row - row of piece
* #param column - column of piece
*/
public void setPiece(int piece, int row, int column) {
this.board[row][column] = piece;
}
public int[][] getBoard() {
return board;
}
}
}
Related
I'm currently working on a 2D game in Java for school. We have to use an Abstract Factory design pattern. For the 2D implementation I use a factory as follows:
public class Java2DFact extends AbstractFactory {
public Display display;
private Graphics g;
public Java2DFact() {
display = new Display(2000, 1200);
}
#Override
public PlayerShip getPlayership()
{
return new Java2DPlayership(display.panel);
}
In my display class I create a JFrame and Jpanel
public class Display {
public JFrame frame;
public JPanel panel;
public int width, height;
public Display(int width, int height) {
this.width = width;
this.height = height;
frame = new JFrame();
frame.setTitle("SpaceInvaders");
frame.setSize(1200,800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
}
};
panel.setFocusable(true);
frame.add(panel);
}
}
Now from my main gameloop I call the visualize method inside the Java2DPLayership class to visualize my Playership
public class Java2DPlayership extends PlayerShip {
private JPanel panel;
private Graphics2D g2d;
private Image image;
private BufferStrategy bs;
public Java2DPlayership(JPanel panel) {
super();
this.panel = panel;
}
public void visualize() {
try {
image = ImageIO.read(new File("src/Bee.gif"));
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
//g.setColor(new Color(0, 0, 0));
//g.fillRect(10, 10, 12, 8);
g.drawImage(image, (int) super.getMovementComponent().x, (int) super.getMovementComponent().y, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
panel.repaint();
} catch(Exception e){
System.out.println(e.toString());
}
}
}
My goal is to pass around the JPanel to every entity and let it draw its contents onto the panel before showing it. However I can't seem to figure out how to do this. When using this approach by changing the Graphics of the panel I get a lot of flickering.
Here is a fully functional, albeit simple example, I wrote some time ago. It just has a bunch of balls bouncing off the sides of the panel. Notice that the render method of the Ball class accepts the graphics context from paintComponent. If I had more classes that needed to be rendered, I could have created a Renderable interface and have each class implement it. Then I could have a list of Renderable objects and just go thru them and call the method. But as I also said, that would need to happen quickly to avoid tying up the EDT.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Bounce extends JPanel {
private static final int COLOR_BOUND = 256;
private final static double INC = 1;
private final static int DIAMETER = 40;
private final static int NBALLS = 20;
private final static int DELAY = 5;
private final static int PANEL_WIDTH = 800;
private final static int PANEL_HEIGHT = 600;
private final static int LEFT_EDGE = 0;
private final static int TOP_EDGE = 0;
private JFrame frame;
private double rightEdge;
private double bottomEdge;
private List<Ball> balls = new ArrayList<>();
private Random rand = new Random();
private List<Long> times = new ArrayList<>();
private int width;
private int height;
public Bounce(int width, int height) {
this.width = width;
this.height = height;
frame = new JFrame("Bounce");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
addComponentListener(new MyComponentListener());
frame.pack();
frame.setLocationRelativeTo(null);
rightEdge = width - DIAMETER;
bottomEdge = height - DIAMETER;
for (int j = 0; j < NBALLS; j++) {
int r = rand.nextInt(COLOR_BOUND);
int g = rand.nextInt(COLOR_BOUND);
int b = rand.nextInt(COLOR_BOUND);
Ball bb = new Ball(new Color(r, g, b), DIAMETER);
balls.add(bb);
}
frame.setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
public static void main(String[] args) {
new Bounce(PANEL_WIDTH, PANEL_HEIGHT).start();
}
public void start() {
/**
* Note: Using sleep gives a better response time than
* either the Swing timer or the utility timer. For a DELAY
* of 5 msecs between updates, the sleep "wakes up" every 5
* to 6 msecs while the other two options are about every
* 15 to 16 msecs. Not certain why this is happening though
* since the other timers are run on threads.
*
*/
Timer timer = new Timer(0,(ae)-> {repaint();
for (Ball b : balls) {
b.updateDirection();
}} );
timer.setDelay(5); // 5 ms.
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball ball : balls) {
ball.render(g2d);
}
}
class MyComponentListener extends ComponentAdapter {
public void componentResized(ComponentEvent ce) {
Component comp = ce.getComponent();
rightEdge = comp.getWidth() - DIAMETER;
bottomEdge = comp.getHeight() - DIAMETER;
for (Ball b : balls) {
b.init();
}
}
}
class Ball {
private Color color;
public double x;
private double y;
private double yy;
private int ydir = 1;
private int xdir = 1;
private double slope;
private int diameter;
public Ball(Color color, int diameter) {
this.color = color;
this.diameter = diameter;
init();
}
public void init() {
// Local constants not uses outside of method
// Provides default slope and direction for ball
slope = Math.random() * .25 + .50;
x = (int) (rightEdge * Math.random());
yy = (int) (bottomEdge * Math.random()) + diameter;
xdir = Math.random() > .5 ? -1
: 1;
ydir = Math.random() > .5 ? -1
: 1;
y = yy;
}
public void render(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillOval((int) x, (int) y, diameter, diameter);
}
public void updateDirection() {
x += (xdir * INC);
yy += (ydir * INC);
y = yy * slope;
if (x < LEFT_EDGE || x > rightEdge) {
xdir = -xdir;
}
if (y < TOP_EDGE || y > bottomEdge) {
ydir = -ydir;
}
}
}
}
I am making an asteroid game. Every so often an asteroid needs to be generated and fly across the screen. For some reason when more then 1 asteroid is created, the screen glitches out. If you maximize the screen you will be able to see the glitching. I have tried using paint instead of paintComponent. I have also tried extending JFrame instead of JPanel but that just makes it worse. The class below sets up the screen and handles the game loop
public class Game extends JPanel {
static ArrayList<Asteroids> rocks = new ArrayList<Asteroids>();
//This variable determines whether the game should keep running
static boolean running = true;
//Counter to access arraylist
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
//Creating the window
JFrame frame = new JFrame("Asteroid Game");
frame.getContentPane().setBackground(Color.BLACK);
frame.setSize(1100, 1000);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Asteroids a = new Asteroids();
frame.add(a);
//Game loop
while(running) {
if(counter % 4 == 0) {
rocks.add(new Asteroids());
frame.add(rocks.get(rocks.size() - 1));
}
for(int i = 0; i < rocks.size(); i++) {
rocks.get(i).repaint();
rocks.get(i).move();
if(!rocks.get(i).isPosFine()) {
rocks.remove(i);
i--;
}
}
Thread.sleep(17);
counter++;
}
}
}
The class below sets up the asteroids
public class Asteroids extends JPanel {
//These arrays store the coordinates of the asteroid
private int[] xPos = new int[8];
private int[] yPos = new int[8];
//Determines whether asteroid should be generated from top or bottom
private int[] yGen = {-100, 1100};
//Determines the direction the asteroid shold go
int genLevel;
/**
* #param g Graphics
* This method paints the asteroid
*/
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D r = (Graphics2D)g;
r.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
r.setColor(Color.decode("#52575D"));
r.fillPolygon(xPos, yPos, 8);
}
/**
* This constructor sets up the asteroid location points
*/
public Asteroids() {
int x = (int)(Math.random() * (700 - 1) + 100);
int y = yGen[(int)(Math.random() * (1 + 1 - 0))];
updateAsteroids(x, y);
genLevel = y;
System.out.println("Created!");
}
/**
* #param x int
* #param y int
* This method generates the asteroid based on the points passed in
*/
public void updateAsteroids(int x, int y) {
xPos[0] = x;
xPos[1] = x + 20;
xPos[2] = x + 40;
xPos[3] = x + 35;
xPos[4] = x + 40;
xPos[5] = x + 4;
xPos[6] = x - 16;
xPos[7] = x - 20;
yPos[0] = y;
yPos[1] = y + 7;
yPos[2] = y + 20;
yPos[3] = y + 40;
yPos[4] = y + 80;
yPos[5] = y + 70;
yPos[6] = y + 40;
yPos[7] = y;
}
/**
* This moves the asteroid
*/
public void move() {
int moveSpeedx = (int)(Math.random() * (10 - 1) + 1);
int moveSpeedy = (int)(Math.random() * (10 - 1) + 1);
for(int i = 0; i < 8; i++) {
if(genLevel > 0) {
xPos[i] -= moveSpeedx;
yPos[i] -= moveSpeedy;
}
else {
xPos[i] += moveSpeedx;
yPos[i] += moveSpeedy;
}
}
}
/**
* #return if the asteroid should be kept on the screen or not
*/
public boolean isPosFine() {
for(int i = 0; i < 8; i++) {
if(xPos[i] > 1250 || xPos[i] < -150)
return false;
if(yPos[i] > 1250 || yPos[i] < - 150)
return false;
}
return true;
}
}```
Your biggest problem that I can see is that you made your Asteroids class extend JPanel, making it much heavier weight than it should be, and making it difficult for more than one to show and for them to interact well and easily.
I recommend that you:
Make Asteroid a non-component logical class,
one that knows how to draw itself by giving it a public void draw(Graphics2D g2) method
one that knows how to move itself in response to your game loop's tick
Create one JPanel just for drawing the entire animation
Give this JPanel a collection of Asteroid objects, say in an ArrayList
In this JPanel's paintComponent, loop through all Asteroids in the collection, calling each one's draw(...) method
Drive the whole animation in a Swing thread-safe and controllable way using a Swing Timer. In this timer's actionPerformed, tell each asteroid to move, and then call repaint() on the drawing JPanel
Don't call .repaint() within the loop, but rather after the loop is finished
Create a small BufferedImage sprite from your Shapes, and draw those as the asteroid
A simple example illustrating what I mean:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Game2 extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 800;
private static final int TIMER_DELAY = 20;
private List<Asteroid2> asteroids = new ArrayList<>();
public Game2() {
setBackground(Color.BLACK);
int rows = 5;
int cols = 5;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
Asteroid2 asteroid = new Asteroid2();
asteroid.setX(j * (PREF_W / cols));
asteroid.setY(i * (PREF_H / rows));
asteroids.add(asteroid);
}
}
new Timer(TIMER_DELAY, e -> {
for (Asteroid2 asteroid2 : asteroids) {
asteroid2.move();
}
repaint();
}).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Asteroid2 asteroid : asteroids) {
asteroid.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
Game2 mainPanel = new Game2();
JFrame frame = new JFrame("Game2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class Asteroid2 {
private static final int[] POLY_X = { 20, 40, 60, 55, 60, 24, 4, 0 };
private static final int[] POLY_Y = { 0, 7, 20, 40, 80, 70, 40, 0 };
private static final Color ASTEROID_COLOR = Color.decode("#52575D");
private Image image;
private int x;
private int y;
public Asteroid2() {
Polygon poly = new Polygon(POLY_X, POLY_Y, POLY_X.length);
image = new BufferedImage(60, 80, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(ASTEROID_COLOR);
g2.fill(poly);
g2.dispose();
}
public void move() {
x++;
y++;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void draw(Graphics g) {
if (image != null) {
g.drawImage(image, x - 20, y, null);
}
}
}
I have written a short game. In the existing implementation I have a GridBagLayout with buttons located as chess board. Each button occupies the whole grid. Game works fine. My next task is to change the board to be consist of hexagonal buttons, not rectangles like currently. I completely don't know how to do this. Buttons should look like these on the picture:
This isn't the prettiest way, but It will at least give you an Idea:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class HexagonPattern extends JPanel {
private static final long serialVersionUID = 1L;
private static final int ROWS = 7;
private static final int COLUMNS = 7;
private HexagonButton[][] hexButton = new HexagonButton[ROWS][COLUMNS];
public HexagonPattern() {
setLayout(null);
initGUI();
}
public void initGUI() {
int offsetX = -10;
int offsetY = 0;
for(int row = 0; row < ROWS; row++) {
for(int col = 0; col < COLUMNS; col++){
hexButton[row][col] = new HexagonButton(row, col);
hexButton[row][col].addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
HexagonButton clickedButton = (HexagonButton) e.getSource();
System.out.println("Button clicked: [" + clickedButton.getRow() + "][" + clickedButton.getCol() + "]");
}
});
add(hexButton[row][col]);
hexButton[row][col].setBounds(offsetY, offsetX, 105, 95);
offsetX += 87;
}
if(row%2 == 0) {
offsetX = -52;
} else {
offsetX = -10;
}
offsetY += 76;
}
}
public static void main(String[] args) {
HexagonPattern hexPattern = new HexagonPattern();
JFrame frame = new JFrame();
frame.setTitle("Hexagon Pattern");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(new Point(700, 300));
frame.add(hexPattern);
frame.setSize(550, 525);
frame.setResizable(false);
frame.setVisible(true);
}
//Following class draws the Buttons
class HexagonButton extends JButton {
private static final long serialVersionUID = 1L;
private static final int SIDES = 6;
private static final int SIDE_LENGTH = 50;
public static final int LENGTH = 95;
public static final int WIDTH = 105;
private int row = 0;
private int col = 0;
public HexagonButton(int row, int col) {
setContentAreaFilled(false);
setFocusPainted(true);
setBorderPainted(false);
setPreferredSize(new Dimension(WIDTH, LENGTH));
this.row = row;
this.col = col;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Polygon hex = new Polygon();
for (int i = 0; i < SIDES; i++) {
hex.addPoint((int) (50 + SIDE_LENGTH * Math.cos(i * 2 * Math.PI / SIDES)), //calculation for side
(int) (50 + SIDE_LENGTH * Math.sin(i * 2 * Math.PI / SIDES))); //calculation for side
}
g.drawPolygon(hex);
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
}
}
Test it out!
This program consists of 2 classes:
HexagonButton, which uses Graphics to draw a hexagon into a JButton. It also returns the row and column values when getRow or getCol are called.
HexagonPattern, which is the main class. It makes the pattern by laying them out with setBounds(x, y, width, height). It uses an ActionListener to print the coordinates of the Hexagon clicked, by calling getRow and getCol.
Like I said, this isn't the greatest program. If you want to make the hexagons smaller, then you'll have to change many variables.
I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}
I want to create multiple squares that move independently and at the same time,and I think the most efficient way is through the transform method in Graphics2D,but I'm not sure how to make it work for each square.I want the square object to be self contained and create its own transforms(instance transforms). Here's what I have so far.
TransformPanel
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class TranformPanel extends JPanel {
private int[] xcoords = {250,248,253,255,249};
private int[] ycoords = {250,253,249,245,250};
private double randomx = 0;
private double randomy = 0;
public void paintComponent(Graphics g)
{
super.paintComponent(g);
drawTransform(g,randomx,randomy);
}
private void drawTransform(Graphics g,double randomx,double randomy)
{
Random rn = new Random();
int xnum = rn.nextInt(10)-5;
randomx = xnum;
int ynum = rn.nextInt(10)-5;
randomy = ynum;
Rectangle rect = new Rectangle(250,250,10,10);
AffineTransform transform = new AffineTransform();
Graphics2D g2d = (Graphics2D)g;
transform.translate(randomx,randomy);
g2d.draw(transform.createTransformedShape(rect));
}
}
TransformDraw
import java.awt.*;
import javax.swing.*;
import java.awt.geom.AffineTransform;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class TransformDraw{
private static TranformPanel panel = new TranformPanel();
public static void main(String[] args) {
// Setup our JFrame details
JFrame frame = new JFrame();
frame.setTitle("Transform Polygon Example");
frame.setSize(500,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.setVisible(true);
frame.add(panel);
Scanner input = new Scanner(System.in);
for (int i=0;i<10;i++)
{
try {
TimeUnit.SECONDS.sleep(1);
frame.repaint();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thanks is Advance!
Start by creating something that can manage it's location (and other properties) and which can be "painted"
public interface Box {
public void update(Dimension size);
public void paint(Graphics2D g2d);
}
So, this is pretty basic, all it can do is be updated (within a given area) and be painted. You could expose other properties (like it's bounding box for example) based on your particular needs
Next, we need a simple implementation, something like...
public class DefaultBox implements Box {
private Color color;
private Rectangle bounds;
private int xDelta;
private int yDelta;
public DefaultBox(Color color, Dimension size) {
this.color = color;
bounds = new Rectangle(new Point(0, 0), size);
xDelta = 1 + (int) (Math.random() * 10);
yDelta = 1 + (int) (Math.random() * 10);
}
#Override
public void update(Dimension size) {
bounds.x += xDelta;
bounds.y += yDelta;
if (bounds.x < 0) {
bounds.x = 0;
xDelta *= -1;
} else if (bounds.x + bounds.width > size.width) {
bounds.x = size.width - bounds.width;
xDelta *= -1;
}
if (bounds.y < 0) {
bounds.y = 0;
yDelta *= -1;
} else if (bounds.y + bounds.height > size.height) {
bounds.y = size.height - bounds.height;
yDelta *= -1;
}
}
#Override
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fill(bounds);
}
}
Now, this maintains a simple Rectangle instance, which describes the location and size of the object, it also maintains properties about the color and it's speed.
When update is called, it updates it's location and does some simple bounds checking to make sure that the box remains within the specified area.
When paint is called, it simply paints itself.
Finally, we need some way to update and paint these boxes....
public class TestPane extends JPanel {
private List<Box> boxes;
private Color[] colors = {Color.RED, Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.WHITE, Color.YELLOW};
public TestPane() {
boxes = new ArrayList<>(25);
for (int index = 0; index < 100; index++) {
Color color = colors[(int) (Math.random() * colors.length)];
int width = 10 + (int) (Math.random() * 9);
int height = 10 + (int) (Math.random() * 9);
boxes.add(new DefaultBox(color, new Dimension(width, height)));
}
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Box box : boxes) {
box.update(getSize());
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Box box : boxes) {
Graphics2D g2d = (Graphics2D) g.create();
box.paint(g2d);
g2d.dispose();
}
}
}
Okay, so again, this is pretty simple. It maintains a List of Box's, a Swing Timer to periodically update the List of Box's, calling their update method. The Timer the simply calls repaint, which (in a round about way) ends up calling paintComponent, which then just calls paint on each instance of Box.
100 boxes...