I'm trying to make a pong game in Java but it doesn't work.
I've done some testing and it seems that the variables are updating but that when I do
repaint(); in the timers actionPerformed(ActionEvent e) doesn't call the paintComponent() method
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PongGame extends JComponent implements ActionListener, MouseMotionListener{
public int state = 1;
public int paddleX;
public String buttonColor = "blue";
public int mouseX, mouseY;
private int ballX = 400;
private int ballY = 150;
public static void main(String[] args){
JFrame window = new JFrame("Pong");
PongGame game = new PongGame();
window.add(new PongGame());
window.pack();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.setVisible(true);
Timer t = new Timer(20, game);
t.start();
}
public Dimension getPreferredSize(){
return new Dimension(800, 600);
}
public void paintComponent(Graphics g){
paddleX = mouseX;
g.setColor(Color.WHITE);
g.fillRect(0,0, 800, 600);
g.setColor(Color.BLACK);
g.fillRect(paddleX, 550, 150, 15);
g.fillOval(ballX, ballY, 30, 30);
}
#Override
public void actionPerformed(ActionEvent e) {
ballX = ballX + 10;
ballY = ballY + 10;
System.out.println(ballX + " " + ballY);
}
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
repaint();
}
}
you haven't registered the implemented MouseMotionListener to any component:
game.addMouseMotionListener(game);
you are not adding your first created instance of PongGame to the frame rather added a new one producing bug:
PongGame game = new PongGame();
window.add(new PongGame()); // <<--- why creating the new instance ?
// it should be window.add(game);
As a good programming practice: try putting the add listener code in the component's own creating context i.e., in their constructor to make your code better readable.
The problem is:
PongGame game = new PongGame();
window.add(new PongGame());
You have two instances of PongGame. One added to the frame (new PongGame()) and the other (game) that actually reacts to the timer. Change this row to:
window.add(game);
To correct the actual problem. Add a constructor (tested locally):
PongGame() {
addMouseMotionListener(this);
}
repaint() does not invoke paint() directly. It schedules a call to an intermediate method, update(). Finally, update() calls paint() (unless you override update).
The reason for this complexity is Java's support for concurrent programming. It does this using threads.
Using repaint() can be tricky for at least three reasons.
the interaction between repaint() and the spontaneous painting done by the GUI thread
the fact that repaint() just asks the thread system to schedule a call to update()/paint() and then exits. The repaint() method is asynchronous.
the problem of preventing your drawing from being erased when being updated.
I suggest you to try the same with update().
Useful link : http://www.scs.ryerson.ca/~mes/courses/cps530/programs/threads/Repaint/index.html
Related
I'm having trouble getting smooth and fluid motion with java Swing. Using this code, which involves using a BufferedImage to draw graphics to before applying it to the panel, gives me results with shaky motion and stuttering. How can I fix this?
public class SmoothMotion extends JPanel {
public static final int size = 800;
public static int pos = 0;
public static void main(String[] args) {
JPanel panel = new SmoothMotion();
JFrame frame = new JFrame("Smooth As");
frame.setSize(size, size);
frame.add(panel);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(17, new ActionListener() {
public void actionPerformed(ActionEvent e) {
SmoothMotion.updateItems();
panel.repaint();
}
});
timer.start();
}
public static void updateItems() {
pos += 4;
pos = pos > 750 ? 0 : pos;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, size, size);
g.setColor(Color.RED);
g.fillRect(pos > 375 ? 750 - pos : pos, 100, 50, 50);
}
}
don't override paint(). Custom painting is done by overriding paintComponent(...)
there is no need for the BufferedImage. Swing is double buffered by default. Just invoke super.paintComponent(graphics) first to clear the background and then do your custom painting.
the painting method should NOT change properties of the class. You should have a method like updatePos(…) to update the position.
The ActionLIstener would invoke the updatePos() which would then invoke repaint().
See: get width and height of JPanel outside of the class for an example that demonstrates many of the above suggestions.
I'm following along in a (dated) Java book, and this project is supposed to "animate" a circle moving across the screen. However, when the program is run, the circle remains in one spot. My code looks identical to the book's. Am I forgetting something? Am I calling repaint() at the wrong time?
public class Animation
{
JFrame f;
int x, y;
public static void main(String [] args)
{
Animation a = new Animation();
a.go();
}
public void go()
{
f=new JFrame();
myPanel p=new myPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
for(int i=0; i<=50; i++)
{
p.repaint();
x++;
y++;
}
}
class myPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 40, 40);
}
}
}
So, immediately, two things jump out at me.
It's possible that the loop which is updating the values is NOT running on the Event Dispatching Thread, which could be leading to dirty read/writes
repaint can consolidate requests to reduce the amount of work placed on the Event Queue/Event Dispatching Thread. Since there is not "artificial" delay in the loop, it's possible that all your requests are been reduced to a single update pass by the RepaintManager
The first thing I would do is isolate the responsibility for the management of the position of the oval, because in your current code, it could be updated from anywhere, which is just a mess
class MyPanel extends JPanel {
private Point posy = new Point(0, 0);
public Point getPosy() {
return posy;
}
public void move() {
Point posy = getPosy();
posy.x++;
posy.y++;
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(posy.x, posy.y, 40, 40);
}
}
Next, I'd ensure that the context of the UI is been modified from within the EDT...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Animation a = new Animation();
a.go();
}
});
}
and finally, I'd make use of Swing Timer to act as a pseudo loop for the animation...
public void go() {
f = new JFrame();
MyPanel p = new MyPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.move();
}
});
timer.start();
}
There are a number of reasons for using a Swing Timer in this context. First, the "ticks" are executed ON the EDT, making it safe to update the UI from within and it won't block the UI while it's "waiting" between ticks
I would recommend having a look at:
Painting in AWT and Swing which will give you a better understanding into how painting actually works
Concurrency in Swing as it will give you a heads up into some of the pitfalls in trying to do more then one thing within the GUI
How to Use Swing Timers because they are really useful for this kind of thing
After finishing and running a simple program that lets you change the color of a shape when it's clicked on:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class MyPanel extends JPanel{
int x = 200,y = 200,r = 50;
static Color co = Color.BLUE;
static final JFrame frame = new JFrame();
public static void main(String[] args){
frame.setTitle("Color Change with Mouse Click");
frame.setSize(500,500);
MyPanel pane = new MyPanel();
frame.add(pane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pane.setVisible(true);
}
public void Panel(){
addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) & (co == Color.GREEN)){
co = Color.BLUE;
repaint();
};
if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) & (co == Color.BLUE)){
co = Color.GREEN;
repaint();
};
}
});
}
Timer timer = new Timer(1, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
Panel();
}
});
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
timer.start();
Panel();
g.setColor(co);
g.fillOval(x-r, y-r, 2*r, 2*r);
repaint();
}
}
I encountered a problem that I simply don't know how to fix. The JPanel never updates on the second mouse click, only on the first. I thought that adding the timer would take care of this, but apparently it didn't. Help is much appreciated.
edit:
I changed my code per Aqua's suggestions:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class MyPanel extends JPanel{
int x = 200,y = 200,r = 50;
static Color co = Color.BLUE;
public static void main(String[] args){
final JFrame frame = new JFrame();
frame.setTitle("Color Change with Mouse Click");
MyPanel pane = new MyPanel();
frame.add(pane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public void panel(){
addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) && (co == Color.GREEN)){
co = Color.BLUE;
repaint();
}else if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) && (co == Color.BLUE)){
co = Color.GREEN;
repaint();
}else{
repaint();
};
}
});
}
public MyPanel(){
Timer timer = new Timer(20, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
panel();
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(co);
g.fillOval(x-r, y-r, 2*r, 2*r);
repaint();
}
}
And the color change works now, however the color change is a bit unpredictable. sometimes it has a delay, sometimes it doesn't, and sometimes it just outright won't change back. Is this a problem with my timer?
What is the point of the Timer? A Timer is used to schedule an event. You are not scheduling any events, you are waiting for the user to click the mouse. There is no need for the Timer. If you ever do need a Timer then:
you would never schedule the Timer to fire every millisecond, that is too fast for the machine to respond to. Use a more realistic value depending on the requirement. Maybe 50 or 100ms.
you would never start the Timer in a painting method. You would probably start the Timer in the constructor of your class.
Your MouseListener code is wrong. You start the Timer and invoke the Panel() method which invokes the addMouseListener() method. So after 1 second you will have added 1000 MouseListeners added to your panel. This is obviously wrong. The MouseListener should be added in the constructor of your class.
There is no need for the "co" variable (if it was needed it would NOT be static). All Swing components support a setForeground() method to set the color of the painted text. In your case you are painting a shape, but you can still use the setForeground() method when you want to change the color. Then in your painting code you would just use:
//g.setColor(co);
g.setColor(getForeground());
Since you added the MouseListener to the panel there is no need to check the point of the mouse click. The event will only be generated when you click on the panel. So all you need to do is use setForeground(...) to the color you want. You don't even need to invoke repaint since Swing is smart enough to repaint itself when you change one of its properties.
Calling Panel() from paintComponent() triggers an endless loop that results in StackOverflowError exception. As repaint() eventually results in a call to paintComponent().
You should attach a mouse listener once in the initialization of a panel, not on every repaint. Same goes for the timer. But, it is not clear what is the intention of timer in this sample.
Some other problems with the posted code:
Call frame.setVisible(true); instead of pane.setVisible(true); As posted the code does not work. There is also no need to keep a static member JFrame frame in the panel. You can simply declare and use JFrame it in main().
paintComponent() is for painting only. Avoid complex logic in that method. Do not setup timers or call other initialization methods. You have little control when paintComponent is executed and it has to be fast for optimal drawing results. Go through Performing Custom Painting tutorials. Move out initialization of timer and mouse listener to panel's constructor.
Do not use frame.setSize(500,500);, instead override JPanel.getPreferredSize(). See Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? for details.
Stick to Java naming conventions. Method named Panel() is confusing. See Naming Conventions.
There is a missing else statement in Panel() method. On the second click, you first get into the first if statement, set the color BLUE, then, enter the second if statement and reset the color to GREEN.
As mentioned above in comments, & and && are different operators. See Operators.
My question is about how to get repaint() to work properly when executed from a method, or more specifically from an actionlistener. To illustrate my point, the moveIt() method when executed from the initial go(), invokes repaint() as expected and you see the circle slide. When moveIt() is called from the button ActionListener, the circle jumps from start position to end position. I have included a println statement before the call to repaint() and inside the repaint() and you can see that repaint() is called 10 times at startup and only once when the button is pressed. - Thanks in advance for your assistance.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SimpleAnimation {
int x=70;
int y=70;
MyDrawPanel drawPanel;
public static void main(String[] args) {
SimpleAnimation gui = new SimpleAnimation();
gui.go();
}
public void go(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawPanel = new MyDrawPanel();
JButton button = new JButton("Move It");
frame.getContentPane().add(BorderLayout.NORTH, button);
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
button.addActionListener(new ButtonListener());
//frame.getContentPane().add(drawPanel);
frame.setSize(300,300);
frame.setVisible(true);
// frame.pack(); Tried it with frame.pack with same result.
moveIt();
}//close go()
public void moveIt(){
for (int i=0; i<10; i++){
x++;
y++;
drawPanel.revalidate();
System.out.println("before repaint"); //for debugging
drawPanel.repaint();
try{
Thread.sleep(50);
}catch (Exception e){}
}
}//close moveIt()
class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
moveIt();
}
}//close inner class
class MyDrawPanel extends JPanel{
public void paintComponent(Graphics g){
System.out.println("in repaint"); //for debugging
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.blue);
g.fillOval(x, y, 100, 100);
}
}//close inner class
}
Calling Thread.sleep in the ActionListener blocks the EDT and causes the GUI to "freeze". You could use a Swing timer here instead.
You are performing a long running task (sleeping) on the EDT(Event Dispatch Thread). As a result when the EDT is sleeping it cannot update the UI and repaints do not work as expected.
To correct the situation always sleep on a seperate thread.
Use SwingWorker or Timer for such situations
Look at this post for more info about how to access swing components in a thread-safe way.
UPDATE: This page explains it better.
I'm learning some swing and awt programming in Java so I decided to make Pong. The Main class is the parent JFrame. It instantiates a Ball and a Paddle and adds them (they are JPanels). However, only the last one added is displayed. How do I fix this?
Code:
public class Main extends JFrame {
public Main() {
super("Pong");
add(new Ball());
add(new Paddle());
setSize(500, 500);
setBackground(Color.orange);
setLocationRelativeTo(null);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new Main().setVisible(true);
}
}
The Ball class:
public class Ball extends JPanel implements ActionListener {
Timer timer;
private Vec2d position;
private Vec2d velocity;
private Dimension ballSize;
public Ball() {
super();
position = new Vec2d(50, 50);
velocity = new Vec2d(2, 3);
timer = new Timer(25, this);
ballSize = new Dimension(40, 40);
timer.start();
}
#Override
public void actionPerformed(ActionEvent ae) {
//Perform game frame logic
bounceBall();
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillArc((int)position.x, (int)position.y, ballSize.width,
ballSize.height, 0, 360);
position.add(velocity);
}
private void bounceBall() {
if(position.x < 0 || position.x > getWidth() - ballSize.width) {
velocity.x *= -1;
}
if (position.y < 0|| position.y > getHeight() - ballSize.height) {
velocity.y *= -1;
}
}
}
And finally, the Paddle class:
public class Paddle extends JPanel implements ActionListener {
private Vec2d position;
private double yVelocity;
private Rectangle rect;
private Timer timer;
public Paddle() {
super();
position = new Vec2d(30, 250);
yVelocity = 0;
timer = new Timer(25, this);
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect((int) position.x, (int) position.y, 20, 40);
}
#Override
public void actionPerformed(ActionEvent ae) {
repaint();
}
}
Note that Vec2d is simply a small two dimensional Vector class I threw together. Also, the Pong logic (collision between Paddle and ball, scoring, etc) is not implemented. I just want to get it to draw correctly
Thanks in advance for the help!
The first thing to do is to add the JPanels to the content pane of your window, not to the window itself. I'm surprised you didn't get a runtime warning about that.
Also, it looks like you are planning to have each of your panels fill the screen, but paint only a small section of it. If that is really the way you want to do it, then you need to do setOpaque(false) on them so the panels under them can show through. But probably a better solution would be to have a single JPanel that is the drawing surface, and have its paintComponent() pass off the Graphics to each of the game objects to let them draw themselves.
add(new Ball());
add(new Paddle());
By default, the layout manager of a JFrame is a BorderLayout. And if you don't specify where you want to add a component (BorderLayout.WEST, or EAST, etc.), the it's added at the center. So you add two components at the same place: in the center. So, only one of them is displayed.
You are adding the Ball and Paddle to the same BorderLayout.CENTER position so only the last one added, (i.e. the Paddle) is displayed. You could use a GridLayout here to display:
setLayout(new GridLayout(1, 2));
add(new Paddle());
add(new Ball());
in the Paddle class, you never add the velocity to position with position.add(velocity) like you do in your ball class..