Can't get paint function to update in simple pong game - java

I'm a relatively inexperienced Java programmer when it comes to graphics. I'm trying to make a simple pong game to learn more about how graphics are done in Java.
In the program, the left paddle (the only paddle thus far implemented) should move up 5 pixels when 'W' is pressed on the keyboard. According to the console, it is recognizing that the key is being pressed, and it is updating a variable that represents the y-coordinate for the left paddle accordingly. However, the paddle's location isn't actually being updated on the screen.
Help?
Game class:
import javax.swing.JFrame;
public class Game {
static int WIDTH = 500;
static int HEIGHT = 500;
public static void main(String[] args) {
window();
}
public static void window() {
JFrame frame = new JFrame();
frame.setSize(WIDTH, HEIGHT);
frame.setLocationRelativeTo(null);
frame.setAlwaysOnTop(true);
frame.setVisible(true);
frame.setResizable(false);
frame.add(new Panel());
frame.addKeyListener(new Panel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Panel class:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class Panel extends JPanel implements KeyListener {
int leftPaddlePos = 100;
public void paint(Graphics g) {
g.clearRect(0, 0, Game.WIDTH, Game.HEIGHT);
g.setColor(Color.black);
g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
g.setColor(Color.white);
g.fillRect(75, leftPaddlePos, 15, 100);
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W) {
System.out.println("W pressed");
leftPaddlePos = leftPaddlePos + 5;
System.out.println(leftPaddlePos);
}
}
#Override
public void keyReleased(KeyEvent e) {
}
}

public class Panel extends JPanel implements KeyListener {
Don't call your class Panel. There is an AWT class with that name so it is confusing. Your class name should be more descriptive. Maybe something like PongPanel.
public void paint(Graphics g) {
Don't override paint(...);
public void paintComponent(Graphics g) {
Custom painting in Swing is done by overriding paintComponent(...).
g.clearRect(0, 0, Game.WIDTH, Game.HEIGHT);
g.setColor(Color.black);
g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
Don't use above code to paint the background of the panel. Instead, you just use:
super.paintComponent(g);
So in the constructor of your class you can invoke setBackground(...) to set the desired background color.
However, the paddle's location isn't actually being updated on the screen.
leftPaddlePos = leftPaddlePos + 5;
You update the position, but you haven't told the component that a property of the class has changed, so it doesn't know it needs to paint itself again.
You should not change the property of the class in the listener. Instead you should have a method like adjustLeftPaddlePosition(int value)
The method would be:
public void adjustLeftPaddlePostion(int value)
{
leftPaddlePos = leftPaddlePos + value;
repaint();
}
Then in the KeyListener you would use:
adjustLeftPaddlePosition(5);
Also, you should not be using a KeyListener. Instead you should be using "Key Bindings" Check out Motion Using the Keyboard for more information and working examples.

Related

Java: Using graphics component within an ActionListener

I have two separate class and driver files, and in the class file I create the paint method:
public void paint(Graphics g){
g.drawLine(......
....
//along with all of my other draw commands
}
Further down in the code, I create a JButton and within this button's action listener I don't know how to use a Graphics object to create more graphics in the JFrame. Should I be adding something to my driver to make this happen, or is there a way to use these graphics within my action listener? Thank you, and any help is appreciated.
You need to draw everything within the paint method. The actionPerformed should only change the state of something already in the paint method, and then call repaint. For example
boolean drawHello = true;
boolean drawWorld = false;
protected void paintComponent(Graphics g) {
super.paintCompoent(g);
if (drawHello)
g.drawString("Hello", 50, 50);
if (drawWorld)
g.drawString("World", 10, 10);
}
Then in your actionPerformed, you can change the state of drawWorld to true and call repaint().
public void actionPerformed(ActionEvent e) {
drawWorld = true;
repaint();
}
So as you can see, everything should be drawn in the paintComponent method. You can just hide and paint renderings, and make them "visible" from a action command. You should already have predefined what could posibly be drawn. Then just change the state of it rendering
And as #MadPrgrammer pointed out, you should not be painting on top-level containers like JFrame. Instead paint on a custom JPanel or JComponent and override the paintComponent method, instead of JFrame and paint
Here's an example where I draw a new square every time the button is pressed. If look at the code, you will see that in the paintComponent method, I loop through a list of Squares and draw them, and in the actionPerformed all I do is add a new Square to the List and call repaint()
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class AddSquares {
private int R = 0;
private int G = 0;
private int B = 0;
private int xLoc = 0;
private int yLoc = 0;
List<Square> squares = new ArrayList<>();
private JButton addSquare = new JButton("Add Square");
private RectsPanel panel = new RectsPanel();
public AddSquares() {
addSquare.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
Color color = new Color(R, G, B);
squares.add(new Square(xLoc, yLoc, color));
panel.repaint();
R += 10;
G += 20;
B += 30;
xLoc += 20;
yLoc += 20;
}
});
JFrame frame = new JFrame("Draw Squares");
frame.add(panel, BorderLayout.CENTER);
frame.add(addSquare, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private class RectsPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Square square : squares) {
square.drawSquare(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
}
private class Square {
int x = 0;
int y = 0;
Color color;
public Square(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void drawSquare(Graphics g) {
g.setColor(color);
g.fillRect(x, y, 75 ,75);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AddSquares addSquares = new AddSquares();
}
});
}
}
It's difficult to be 100%, but it would seem as you don't understand how custom painting is performed in Swing.
Start by taking a look at Performing Custom Painting and Painting in AWT and Swing.
Essentially, painting is arranged by the Repaint Manager, which decides what and when something should be painted. It then calls (through a chain of methods) the paint method of the components it thinks need to be updated, passing it a reference to a Graphics context that should be used to actually paint on.
Basically, when ever your paint method is called, you should create paint the current state of your painting.
You should avoid overriding paint and instead use paintComponent from classes the extend JComponent
Your question is a little on the vague side as to what you are actually wondering about but generally speaking:
We don't override paint in Swing, we override paintComponent.
If you are already aware of this, you may be overriding paint because you are doing it on a JFrame and you found that JFrame does not have a paintComponent method. You shouldn't override paint on a JFrame. Instead, create a JPanel or something to put inside the frame and override paintComponent on the panel.
Question about the ActionListener.
It sounds like you are wanting to do painting outside of paintComponent in which case probably the best way is to do painting to a separate Image. Then you paint the Image on to the panel in paintComponent. You can also put an Image in a JLabel as an ImageIcon. Here is a very simple drawing program using MouseListener that demonstrates this (taken from here):
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
class PaintAnyTime {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
final BufferedImage image = (
new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB)
);
final JFrame frame = new JFrame();
final JLabel label = new JLabel(new ImageIcon(image));
final MouseAdapter drawer = new MouseAdapter() {
Graphics2D g2D;
#Override
public void mousePressed(MouseEvent me) {
g2D = image.createGraphics();
g2D.setColor(Color.BLACK);
}
#Override
public void mouseDragged(MouseEvent me) {
g2D.fillRect(me.getX(), me.getY(), 3, 3);
label.repaint();
}
#Override
public void mouseReleased(MouseEvent me) {
g2D.dispose();
g2D = null;
}
};
PaintAnyTime() {
label.setPreferredSize(
new Dimension(image.getWidth(), image.getHeight())
);
label.addMouseListener(drawer);
label.addMouseMotionListener(drawer);
frame.add(label);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
#MadProgrammer has already linked to the articles that I was going to link to.

Make mouse events on JComponent after transform work?

Hello and sorry for my imperfect English.
I have a class MyNode (draws a rectangle) that extends JComponent. I have mouse listener for mouseEntered and mouseExited to change its color.
The big problem is that i need to work with transforms (the main app, not this SSCE, it is a graph (nodes&edges) drawer.)
I read about this problem already but I want to ask you how can i make it work. If I apply a scale(2,2) on my panel i have two main problems:
The mouse position doesn't correspond anymore to the drew MyNode. I know i can apply inverseTransform and this works just well, but I must search in all node collection of the panel and check the contains method with the inversed point (inefficient imo). (hope you get what i am saying here). How i can make the mouseEntered, mouseExited etc. work by itself in the new scaled system.
If I scale the panel, the MyNode paintComponent() doesn't apply its owner (Panel) transforms. The node is drew with its initial size.
I am very curious if this can be resolved, and I wait any other question to help you understand what I am looking for. It seems even hard to explain the problem, can't imagine the solution :)
This is a demo of my problem (you can run it):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SSCE extends JPanel {
public SSCE() {
setLayout(null);
setBackground(Color.yellow);
setSize(new Dimension(500, 300));
setPreferredSize(new Dimension(500, 200));
add(new MyNode(0, 0));
setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.scale(2, 2);
super.paintComponent(g2d);
paintComponents(g2d);
}
}
class MyNode extends JComponent {
private Color activeColor = Color.red;
public MyNode(int x, int y) {
this.setLocation(x, y);
this.setSize(50, 50);
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
colorOnMouseHover();
}
#Override
public void mouseExited(MouseEvent e) {
colorOnMouseLeave();
}
});
}
private void colorOnMouseHover() {
this.activeColor = Color.blue;
repaint();
}
private void colorOnMouseLeave() {
this.activeColor = Color.red;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(activeColor);
g.fillRect(0, 0, 50, 50);
}
}
class Run {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 200);
frame.add(new SSCE());
frame.setVisible(true);
}
}

JButton subclass with custom shape shifts during repainting

I have just started working with Swing and am trying to draw a button with a custom shape, a triangle in this example. I called the JButton subclass 'ShiftingButton' in the following code because of its unusual behavior. When the mouse enters its region, it is repainted with an offset from its original position. Furthermore the shifted, offset version is drawn in addition to the original position so that both the original and shifted versions appear together. That is, when I run this code, the button is shown as a triangle along the left edge of the window. Then when I run the mouse over the button, a new triangle is drawn (in addition to the old one), shifted down and to the right by about 10 pixels. Resizing the window changes the offset of the phantom button from the original.
Experimenting with mouse clicks shows that only the original, correctly-positioned button is active. The region of the offset phantom button is not active.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Polygon;
public class ShiftingButton extends JButton implements ActionListener {
private Polygon shape;
public ShiftingButton () {
initialize();
addActionListener(this);
}
protected void initialize() {
shape = new Polygon();
setSize(120, 120);
shape.addPoint(0, 0);
shape.addPoint(0, 60);
shape.addPoint(90, 0);
setMinimumSize(getSize());
setMaximumSize(getSize());
setPreferredSize(getSize());
}
// Hit detection
public boolean contains(int x, int y) {
return shape.contains(x, y);
}
#Override
public void paintComponent (Graphics g) {
System.err.println("paintComponent()");
g.fillPolygon(shape);
}
protected void paintBorder(Graphics g) {
}
#Override
public void actionPerformed (ActionEvent ev) {
System.out.println("ShiftingButton ActionEvent!");
}
public static void main (String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
ShiftingButton button = new ShiftingButton();
panel.add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
You failed to call super.paintComponent(g) inside the overridden paintComponent(...) method. Moreover, while overriding a method of the Base class, always try to keep the access specifier of the methods, the same, as much as possible. In this case it's protected and not public :-) Now function should be like this :
#Override
protected void paintComponent (Graphics g) {
System.err.println("paintComponent()");
super.paintComponent(g);
g.fillPolygon(shape);
}
EDIT 1 :
Moreover, since you are using a custom shape to be drawn, hence you again failed to specify the ContentAreaFilled property for this JButton in question, hence inside your constructor, you should write setContentAreaFilled(false), for it to work nicely. Though if this doesn't works (for reasons specified in the Docs), then you have to use the plain old Opaque property and set it to false for this JButton using setOpaque(false) :-)
Here is your code with modified changes :
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Polygon;
public class ShiftingButton extends JButton implements ActionListener {
private Polygon shape;
public ShiftingButton () {
setContentAreaFilled(false);
initialize();
addActionListener(this);
}
protected void initialize() {
shape = new Polygon();
setSize(120, 120);
shape.addPoint(0, 0);
shape.addPoint(0, 60);
shape.addPoint(90, 0);
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(120, 120));
}
// Hit detection
public boolean contains(int x, int y) {
return shape.contains(x, y);
}
#Override
protected void paintComponent(Graphics g) {
System.err.println("paintComponent()");
super.paintComponent(g);
g.fillPolygon(shape);
}
protected void paintBorder(Graphics g) {
}
#Override
public void actionPerformed (ActionEvent ev) {
System.out.println("ShiftingButton ActionEvent!");
}
public static void main (String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
ShiftingButton button = new ShiftingButton();
panel.add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}

Transitioning to an abstract class

I am desperately trying to implement some things which I don't think I fully understand. I am attempting to set it up so the commented out actions can be taken (I will need to change syntax but I want to make sure I am on the right track first).
Am I going about this the right way? Where will my drawing actions go if not in the draw method? I get lots of errors when I move it there. Thanks
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Graphics2D;
import java.awt.Graphics;
public class Test extends JPanel{
abstract class graphic {
public Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private int[] location = new int[] {screenSize.width/2,screenSize.height/2};
}
public class gladiator extends graphic {
void draw() {
//g2d.setColor(Color.green);
//g2d.fillArc(location[0], location[1], 100, 100, 45, 90);
//g2d.setColor(Color.black);
//g2d.fillArc((location[0]+50-10),(location[1]+50-10), 20, 20, 0, 360);
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
new Timer(200, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// setLocation((location[0]+1),location[1]);
repaint();
System.out.println("repainting");
}
}).start();
}
public void setLocation(int x, int y){
//this.location[0] = x;
//this.location[1] = y;
}
public static void main(String[] args){
JFrame jf=new JFrame();
jf.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
jf.setPreferredSize(Toolkit.getDefaultToolkit().getScreenSize());
jf.add(new Test());
jf.pack();
jf.setVisible(true);
}
}
My original code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Test extends JPanel{
private int[] location = new int[2];
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillArc(location[0], location[1], 100, 100, 45, 90);
g.setColor(Color.black);
g.fillArc((location[0]+50-10),(location[1]+50-10), 20, 20, 0, 360);
new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setLocation((location[0]+50),50);
repaint();
System.out.println("repainting");
}
}).start();
}
public void setLocation(int x, int y){
this.location[0] = x;
this.location[1] = y;
}
public static void main(String[] args){
JFrame jf=new JFrame();
jf.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
jf.setPreferredSize(new Dimension(300,500));
jf.setLocation(100,100);
jf.add(new Test());
jf.pack();
jf.setVisible(true);
}
}
Edit: Should have included this bit, first commenter was right.
The error is cannot find symbol, referring to g2d or g, whichever. I take it to mean that drawing can only happen inside of paint components and that I will have to find a way to include all the instructions for drawing there. I want to make sure I'm just doing something fundamentally wrong, though as this is my first brush with both abstract classes and 2d drawing in java. Also, I know the location[0] etc will not work as is. Lets ignore that.
The bottom code is what I am trying to accomplish (at least at first), but I am trying to use something similar to the top code to create multiple instances of it which can operate independently.
Move the timer out of paintcomponent.
You have declared an abstract class that has a draw method that needs a Graphics2D object to be able to draw, it has no access to it. It also makes little sense to declare a class just to hold two values (screensize and location) if that class also does stuff like drawing.
To fix the issues with 2, you can either let your gladiator extend JComponent, override its paintcomponent and place your draw() code in there and add gladiator to the panel as a component.
You can alternatively do active rendering, which means that you get the Graphic2D object of your canvas (the panel in this case) and take control of the rendering yourself instead of relying on swing.
Since you are working with swing I will give you a working example of what you probably intend to do. If you have more specific questions please do leave a comment.
public class Test extends JPanel {
public static abstract class graphic extends JComponent {
public Dimension dim = new Dimension(500, 500);
private int[] loc = new int[] { 250, 250 };
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public int[] getLoc() {
return loc;
}
public Dimension getDim() {
return dim;
}
}
public static class gladiator extends graphic {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.green);
g2d.fillArc(getLoc()[0], getLoc()[1], 100, 100, 45, 90);
g2d.setColor(Color.black);
g2d.fillArc((getLoc()[0] + 50 - 10), (getLoc()[1] + 50 - 10), 20,
20, 0, 360);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
Test canvas = new Test();
gladiator gladiator = new gladiator();
canvas.add(gladiator);
frame.getContentPane().add(canvas);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
This renders

repaint() method in java not working properly

am doing that program to paint the mouse location in a panel , the program works fine but after like 10 seconds it stops painting the points... any help?
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
import javax.swing.JFrame;
public class Draw extends JPanel {
public static int newx;
public static int newy;
public void paint(Graphics g) {
Mouse mouse = new Mouse();
mouse.start();
int newx = mouse.x;
int newy = mouse.y;
g.setColor(Color.blue);
g.drawLine(newx, newy, newx, newy);
repaint();
}
public static void main(String[] args) {
JFrame frame = new JFrame("");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.white);
frame.setSize(2000,2000 );
frame.setVisible(true);
frame.getContentPane().add(new Draw());
frame.revalidate();
frame.getContentPane().repaint();
}
}
public void paint(Graphics g) should be public void paintComponent(Graphics g).
And you isn't supposed to call repaint() inside this method.
You should add an mouse listener outside this method, too.
An adapted example from Java Tutorials
public class MouseMotionEventDemo extends JPanel
implements MouseMotionListener {
//...in initialization code:
//Register for mouse events on blankArea and panel.
blankArea.addMouseMotionListener(this);
addMouseMotionListener(this);
...
}
public void mouseMoved(MouseEvent e) {
Point point = e.getPoint();
updatePanel(point); //create this method to call repaint() on JPanel.
}
public void mouseDragged(MouseEvent e) {
}
}
}
You call repaint within the paint method, causing an infinite loop. Swing Timers are preferred for running periodic updates on components.
For custom painting in Swing the method paintComponent rather than paint should be overridden not forgetting to call super.paintComponent.

Categories