Drawing a shape on a JPanel which already uses the paint method - java

Let's say I have two classes, the first extends JPanel and using Graphics draws a playing board on it. The second makes a JFrame and adds the panel to it.
You can imagine the frame looking something like this:
I now want to add an ellipse to a specific rectangle on click. I understand that I would be using a two-dimensional array in order to get the position I wanted, but I don't understand how the ellipse itself would be drawn on to the existing panel since I used the paint(Graphics g) to draw the playing board.
Here is the code for drawing the board itself if you need it:
class MyBoard extends JPanel {
private static int height = 6;
private static int width = 7;
private static int squareSize = 100;
private int board[][] = new int[height][width];
public void paint(Graphics g) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
g.drawRect(j * squareSize, i * squareSize, squareSize, squareSize);
}
}
}
}
Thanks!

First two things that you should remember: Never override paint but paintComponent and call super.paintComponent in there so that borders and everything works as expected. Regarding why this is the case, reference this question: Difference between paint() and paintcomponent()?
Now to answer your question. Assuming you have an existing logic to determine in which square you want to draw your Ellipse (let's assume you have two Integers elX and elY that are the column and row of your square) you can simply go and draw it after you have finished drawing the board itself.
Imagine sample code like this:
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Draw the board
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
g.drawRect(j * squareSize, i * squareSize, squareSize, squareSize);
}
}
// Draw the ellipse at the correct location using half the size of a normal square.
g.drawOval(elX * squareSize + squareSize / 4, elY * squareSize + squareSize / 4, squareSize / 2 , squareSize / 2);
}
Now the final part of how to go about actually determining where to draw your ellipse.
A simple solution would be to add a MouseListener to your panel. And then in the mouseClicked method you calculate where you actually did click.
Could look like this:
this.addMouseListener(new MouseListener()
{
#Override
public void mouseClicked(MouseEvent e)
{
int column = e.getX() / squareSize;
int row = e.getY() / squareSize;
board[column][row] = 1;
}
[...] // add the other methods to override
}
Then you slightly adapt your paintComponent method with something like this:
for (int column = 0; column < width; ++column)
{
for (int row = 0; row < height; ++row)
{
if (board[column][row] == 1)
{
g.drawOval(column * squareSize + squareSize / 4, row * squareSize + squareSize / 4, squareSize / 2, squareSize / 2);
}
}
}
and now you draw an ellipse everywhere you click. You could also check if the clicked square already has 1 set as a value and reset it to 0 to have some toggle mechanism or increment it and draw different things based on the integer value... it's all up to you :)

Related

node's are not intersection while using loops inside of an array?

My problem is wherever i click a node appears and for the second click another node appears with connected edge...so i want that When i click at any location, the node should be generated at the closest grid intersection point. I tried using loops.
and i'm trying to do that without "class"
int n_partition=10;
int length = 101;
PVector[] position = new PVector[length];
int BallNum;
void setup() {
size(600, 360);
background(255);
}
void draw() {
fill(255);
grid();
fill(0);
}
void mousePressed(){
stroke(0);
BallNum++;
position[BallNum]= new PVector(mouseX, mouseY);
circle(position[BallNum].x, position[BallNum].y, 10);
if (BallNum > 1) {
line(position[BallNum].x,position[BallNum].y,position[BallNum-
1].x,position[BallNum-1].y);
line(position[1].x,position[1].y,position[BallNum].x,position[BallNum] .y);
}
for (int i = 0; i < position[BallNum].length; ++ i) {
position[BallNum] = position[BallNum].get(i);
position[BallNum] = position[BallNum].get((i+1) % position[BallNum].length);
line(position[BallNum].x, position[BallNum].y,
position[BallNum].x, position[BallNum].y);
}
}
I EXPECT THE NODE SHOULD GO TO THE CLOSEST INTERSECTION.
You've to calculate the nearest position of the mouse to a point on the grid. For that you've to know the width (tile_width) and the height (tile_height) of cell.
The index of the cell can be calculated by the dividing the mouse position to the size of a tile and round() the result to an integral value (e.g. round(mouseX / (float)tile_width)).
Don't draw anything in int the mousePressed callback. The only thing you've to do there is to add a pint to the list:
void mousePressed(){
int tile_width = width / n_partition; // adapt this for your needs
int tile_height = height / n_partition;
int x = round(mouseX / (float)tile_width) * tile_width;
int y = round(mouseY / (float)tile_height) * tile_height;
position[BallNum]= new PVector(x, y);
BallNum++;
}
All the drawing has to be done in draw(). Draw the lines and points in separate loops:
void draw() {
background(255);
grid();
// draw the lines in a loop
strokeWeight(3);
stroke(0, 0, 255);
for (int i = 0; i < BallNum; ++ i) {
int i2 = (i+1) % BallNum;
line(position[i].x, position[i].y, position[i2].x, position[i2].y);
}
// draw balls in a loop
strokeWeight(1);
stroke(0, 0, 0);
fill (255, 0, 0);
for (int i = 0; i < BallNum; ++i) {
circle(position[i].x, position[i].y, 10);
}
}
Note, the scene is consecutively redrawn in every frame. Before the scene is drawn, the entire window has to be "cleared" by background().
See the result:

Why is my 2D Maze not being painted with JComponent?

UPDATE: I Figured out the problem and wrote it out below.If you'd like to see the full compliable code here it is on GitHub: https://github.com/scohen40/cohen-mco364-fall-2018/tree/mazeGUI/src/main/java/cohen/maze
I have a 2D Array of Cells, each with 4 walls. My generateMaze() class starts at a random point and digs out a maze. That part works correctly, and when I print out the maze in the console everything is fine.
My next goal is to have the maze painted out with a JComponent in a JPanel. The problem is that all I'm getting is one thick-lined box in the top left corner.
Here's the painting code:
public class AnimatedMaze extends JComponent {
private Maze maze;
private int componentHeight;
private int componentWidth;
private int seventhHeight;
private int seventhWidth;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
componentHeight = this.getHeight();
componentWidth = this.getWidth();
seventhHeight = componentHeight/7;
seventhWidth = componentWidth/7;
maze = new Maze(7, 7);
g.setColor(Color.black);
paintMaze(g);
}
/**
* The paintMaze() method runs through the generated maze and paints the existing walls.
* #param g
*/
void paintMaze(Graphics g) {
for (int x = 0; x < maze.getHeight(); x++) {
System.out.println("|");
for (int y = 0; y < maze.getWidth(); y++) {
Cell current = maze.getMaze()[x][y];
if(current.isWestWall()) {
g.drawLine(x, y, x, y + seventhHeight);
}
if(current.isNorthWall()){
g.drawLine(x, y,x + seventhWidth, y);
}
if(current.isEastWall()) {
g.drawLine(x + seventhWidth, y, x+ seventhWidth, y + seventhHeight);
}
if(current.isSouthWall()) {
g.drawLine(x, y + seventhHeight, x + seventhWidth, y +seventhHeight);
}
}
}
}
}
You can see in the console the generated maze but in the JPanel it'sjust a box.
In your painting code you need to multiply every x and y coordinate by 'seventhHeight', otherwise you are not painting to correct coordinates.
Building on the answer by Krzysztof Cichocki, realized that coordinates work differently from rows and tables. I switched the x's for y's and vice versa, after multiplying everything by seventhHeight. Also any additions that I did to any coordinate is always seventhHeight now, to make everything proportional.
Here's the current code for the painting method:
void paintMaze(Graphics g) {
for (int x = 0; x < maze.getHeight(); x++) {
System.out.println("|");
for (int y = 0; y < maze.getWidth(); y++) {
Cell current = maze.getMaze()[x][y];
if(current.isWestWall()) {
g.setColor(Color.black);
g.drawLine((y+1)*seventhHeight, x*seventhHeight, (y+1)*seventhHeight, x*seventhHeight + seventhHeight);
}
if(current.isNorthWall()){
g.drawLine((y+1)*seventhHeight, x*seventhHeight,(y+1)*seventhHeight + seventhHeight, x*seventhHeight);
}
if(current.isEastWall()) {
g.drawLine((y+1)*seventhHeight + seventhHeight, x*seventhHeight, (y+1)*seventhHeight + seventhHeight, x*seventhHeight + seventhHeight);
}
if(current.isSouthWall()) {
g.drawLine((y+1)*seventhHeight, x*seventhHeight + seventhHeight, (y+1)*seventhHeight + seventhHeight, x*seventhHeight +seventhHeight);
}
}
}
}
This is now what happens:

Recalculate X and Y Coordinates From New Screensize

I'm trying to display tiles from an array I have so that they always fill the size of the screen when drawn together. I am ignoring aspect ratio for now.
Here's how my code works. I have tile objects that are passed on to a tileset (class for managing an array of tile objects), and then I iterate through the tileset array, returning each tile object id and rendering a subimage of my tileset image based on said ids.
Here's my mapUpdate method, which is called on every JFrame resize event:
public synchronized void mapUpdate(Screen screen) {
factorX = (float)(screen.getWidth() / scW);
factorY = (float)(screen.getHeight() / scH);
for (int i = 0; i < tileset.getRows(); i++) {
for (int j = 0; j < tileset.getCols(); j++) {
int x = tileset.getTile(i, j).getX();
int y = tileset.getTile(i, j).getY();
tileset.getTile(i, j).setX((int)(x * factorX));
tileset.getTile(i, j).setY((int)(y * factorY));
}
}
mapTiles.clear();
for (int i = 0; i * 70 < mapImage.getWidth(); i++) {
mapTiles.add(mapImage.getSubimage(70 * i, 0, 70, 70).getScaledInstance(screen.getWidth() / 10, screen.getHeight() / 10, Image.SCALE_SMOOTH));
}
}
mapTiles is an ArrayList of Images, and on each resize event it resets the arraylist, scales my subimages to 1/10th width and height, and then re-adds the newly sized images for me to pull out for rendering (the tileset image is only 3 tiles with an original size of 70x70).
And here is the componentResized method if you were curious:
public void componentResized(ComponentEvent e) {
canvas.setSize(app.getContentPane().getWidth(), app.getContentPane().getHeight());
if (level1 != null) {
level1.mapUpdate(this);
}
}
As you can see in my mapUpdate method, I attempt to get a float to multiply each current x and y value by to receive the new correct values (it will round the integers), but this doesn't work at all.
Is there any solution to easily re-calculate my X and Y coordinates so that the tiles are drawn correctly?
Your factorX should not be a float. It should be an int. If you use a float you will get rounding, so occasionally you will have a pixel gap between tiles because of rounding. If you just use an int then you don't have to worry about this. Then the location is just the factor * the index value of the for loop.
On the other hand the easiest solution is to just use a JPanel with a GridLayout. Then you can add a JLabel with an Image icon. The GridLayout will resize each component equally.
You can then even use the Stretch Icon and the images will be dynamically resized as the frame is resized.
SOLUTION courtesy of #MadProgrammer - The solution was to simply calculate the new X and Y "origins" for each tile. I still need to implement adjusting the position for tiles that have moved based on the newly calculated origins, but this solution works for tiles that do not move (again, simply recalculate the X and Y origin positions).
public synchronized void mapUpdate(Screen screen) {
int originX = screen.getWidth() / 10;
int originY = screen.getHeight() / 10;
for (int i = 0; i < tileset.getRows(); i++) {
for (int j = 0; j < tileset.getCols(); j++) {
tileset.getTile(i, j).setX((originX * j));
tileset.getTile(i, j).setY((originY * i));
}
}
mapTiles.clear();
for (int i = 0; i * 70 < mapImage.getWidth(); i++) {
mapTiles.add(mapImage.getSubimage(70 * i, 0, 70, 70).getScaledInstance(screen.getWidth() / 10, screen.getHeight() / 10, Image.SCALE_SMOOTH));
}
}

Print a 2d array of objects

I have a class called squares and then I have a class called shapes. In this class is a 12x5 2d array of shapes. In the squares class is a method called draw that draws the square at (550, 75) . The squares have width of 25 and a height of 25. The draw method in the shapes class looks like
public void draw(Graphics g) {
for (i = 0; i < shapes.length; i++) {
for (j = 0; j < shapes[i].length; j++) {
shapes[i][j] = new Square();
shapes[i][j].draw(g);
}
}
}
This draws all the squares on top of each other. How can I draw them in a 12 by 5 format? The size of the window is 600 by 600 if that helps.
Change the draw method in the Squares class to take x and y coordinates as parameters.
You can then change your loop to:
for (i = 0; i < shapes.length; i++) {
for (j = 0; j < shapes[i].length; j++) {
shapes[i][j] = new Square();
shapes[i][j].draw(g, i * 25, j * 25);
}
}
This will mean each square is draw in to the correct position.
-- Edit --
If you can't change the draw parameters, you can add a new constructor for the Square class. This does not require its superclass to have that constructor.
int x;
int y;
public Square(int x, int y) {
this.x = x;
this.y = y;
}
Then pass in the coordinates for that Square when you create it, and modify the draw method appropriately to use these.
Alternatively, if you can't change the Squares class at all, you could translate the graphics coordinates. The square gets drawn at (550, 75), so if we translate the graphics coordinates by (-550, -75) it will appear as if we are drawing at (0,0). We can then incrementally translate for each subsequent square.
g.translate(-550, -75);
for (i = 0; i < shapes.length; i++) {
for (j = 0; j < shapes[i].length; j++) {
shapes[i][j] = new Square();
shapes[i][j].draw(g);
g.translate(0, 25); //Move down by 25
}
g.translate(25, 0); //Move across by 25
}
(Note that I haven't tested this.)

Update graphics for dots with Timer

Before I ask my question I apologize for any inconsistencies. I´m fairly new at this.
I´m making a game that for now looks like this (the picture is not important):
The red dots are supposed to move to the right and they do that with a timer. This works fine. The graphics does not update though so I have to drag the side of the window back and forth to see that my dots are moving. What can I do to fix this?
My paintcomponent method in my mainclass:
public void paintComponent(Graphics g){
super.paintComponent(g);
for (int x = 0; x < SomeInts.amount; x++){
for (int y = 0; y < SomeInts.amount; y++){
tile[x][y].colorBody(g, x, y);
Tower temp;
for (int i = 0; i < towers.size(); i++){
temp = towers.get(i);
temp.colorBody(g, tile[x][y].getSize());
temp.guard.colorBody(g, tile[x][y].getSize());
}
}
}
}
My red dot class. Also called Guard class:
public class Guard {
int x, y, size = 10, towerx, towery;
Timer timer;
public Guard(int towerx1, int towery1){
towerx = towerx1;
towery = towery1;
x = towerx + 1;
y = towery;
new Timer().schedule(new MyTask(), 1000, 1000);
}
public void colorBody(Graphics g, int tilesize){
g.setColor(new Color(255, 0, 0));
g.fillOval(x * tilesize + tilesize / 4, y * tilesize + tilesize / 4, size, size);
}
public class MyTask extends TimerTask {
public void run() {
x++;
}
}
}
Thank you for your time.
I'm guessing a bit here, but I think you need to call the repaint() method to see the changes you've made.

Categories