Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Simple pong game stutters graphically (swing)

java swing pong game stutter graphic

This topic has been archived. This means that you cannot reply to this topic.
10 replies to this topic

#1 emilime93

emilime93

    CC Lurker

  • New Member
  • Pip
  • 6 posts

Posted 04 January 2014 - 08:13 AM

Hi guys!

 

So I have been trying to make a game in java for the first time and decided to go with a pong game. I have decided not to follow a tutorial or anything since I wanted to challenge myself to see if I could make a simple game with no game programming experience at all. 

 

So far i have only implemented a ball bouncing around the screen. But I got a problem. The area where the ball bounces is stuttering as if the game couldn't handle the update frequency (I doubt it).

 

But if I resize the screen as the ball bounces around it looks completely fluid. Also if i start my paintComponent() with 

 

Also as a side note, there is a offset where the ball slightly goes "out of the screen" just when bouncing against the bottom and right walls.

 

Main:

package com.bosig.pingpong;

public class Main {
	
	public static void main(String args[]) {
		
		new Controller().run();
		
	}

}
 

Controller (determines everything).

package com.bosig.pingpong;

import java.awt.BorderLayout;
import java.awt.Point;

import javax.swing.SwingUtilities;

public class Controller implements Runnable{
	
	private Frame frame;
	
	private GamePanel gamePanel;
	
	private static final int WINNING_SCORE = 5;
	
	private int leftPlayerScore;
	private int rightPlayerScore;
	
	private Shield leftPlayerShield;
	private Shield rightPlayerShield;
	
	private Ball ball;
	
	private boolean gameDone = false;
	private boolean gameRunning = true;
	
	public Controller() {
		
		frame = new Frame(this);
		gamePanel = new GamePanel(this);
		
		frame.initialize();
		frame.getContentPane().add(gamePanel, BorderLayout.CENTER);

		frame.setVisible(true);
		ball = new Ball(gamePanel.getGameCenter());
		//ball = new Ball(new Point(200,200)); //frame.getGameCenter()

		//ball.setPos(gamePanel.getGameCenter());
	}
	
	// Checks for events to be occured in the game
	public void checkGameState() {
		Point p = ball.getPos();
		//Checks to see if any player won
		if(leftPlayerScore==WINNING_SCORE) {
			leftWin();
		} else if(rightPlayerScore==WINNING_SCORE) {
			rightWin();
		}
		
		//Checks the balls position relative to the shields and field boundries.
		if(p.x>=gamePanel.getFieldWidth()) {
			ball.bounceVertical();
		} else if(p.x<=0) {
			ball.bounceVertical();
		} else if(p.y >= gamePanel.getFieldHeight()) {
			ball.bounceHorizontal();
		} else if(p.y <= 0) {
			ball.bounceHorizontal();
		}
	}
	
	public Ball getBall() {
		return this.ball;
	}
	
	private void rightWin() {
		//TODO
	}
	
	private void leftWin() {
		//TODO
	}
	
	public void resetGame() {
		ball.setPos(gamePanel.getGameCenter());
		ball.newRandDir();
	}
	
	public void pauseGame() {
		if(!gameRunning) {
			gameRunning = true;
		} else {
			gameRunning = false;
		}
	}
	
	public void setFrame(Frame f) {
		this.frame = f;
	}

	// "Runs" the game.
	@Override
	public void run() {
		while(true) {
			if(gameRunning) {
				ball.move();
				checkGameState();
				//just displays the current direction for debuging
				frame.setLabel(String.format("%4.2f",ball.getXDirection()) + ", " +  String.format("%4.2f",ball.getYDirection()));
				gamePanel.paintComponent(gamePanel.getGraphics());
			}
			try{
				Thread.sleep(1000/60);
			} catch(InterruptedException e) {
				System.out.println(e);
			}
		}
		
	}
	
	public boolean isPaused() {
		return !gameRunning;
	}

}
 

GamePanel class. A JPanel with overridden paintComponent().

package com.bosig.pingpong;

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

import javax.swing.JPanel;

public class GamePanel extends JPanel {
	
	private Controller controller;
	
	private JPanel gamePanel;
	
	private Ball ball;
	
	private Shield leftShield;
	private Shield rightShield;
	
	public GamePanel(Controller c) {
		super();
		this.controller = c;
		this.ball = controller.getBall();
	}
	
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		this.ball = controller.getBall();
		Graphics2D g2 = (Graphics2D) g;
		//g2.clearRect(0, 0, getWidth(), getHeight());
		g2.setColor(Color.RED);
		try{
			g2.fillOval(ball.getPos().x, ball.getPos().y, 10, 10);
		}catch(Exception e) {
			System.out.println("Paintfel: " + e);
		}
		g2.dispose();
	}
	
	public int getFieldWidth() {
		return getWidth();
	}

	public int getFieldHeight() {
		return getHeight();
	}
	
	public Point getGameCenter() {
		return new Point((int)getWidth()/2, (int)getHeight()/2);
	}

}
 

Ball class

package com.bosig.pingpong;

import java.awt.Point;
import java.util.Random;

public class Ball {
	
	private Point pos;
	
	private Random random;
	private boolean validDir;
	
	private double XDirection;
	private double YDirection;
	
	public Ball(Point p) {
		this.pos = p;
		
		random = new Random();
		validDir = false;
		double x=1, y=1;
                //To get a IMO "good" direction
		do{
			x=1; y=1;
			if(random.nextBoolean()) {
				x *=-1;
			}
			x *= random.nextInt(4);
			if(random.nextBoolean()) {
				y *=-1;
			}
			y *= random.nextInt(4);
			System.out.println(String.format("%f : %f",x,y));
			if((Math.abs(x)>1.3) && (Math.abs(y)>Math.abs(x))) {
				validDir = true;
			}
		}while(!validDir);
		
		XDirection = x;
		YDirection = y;
	}
	
	public void bounceVertical() {
		XDirection *= -1;
	}
	
	public void bounceHorizontal() {
		YDirection *= -1;
	}
	/**
	 * As of right now this only moves 1 unit per "tick", direction to be implemented.
	 */
	public void move() {
		pos.x += XDirection;
		pos.y += YDirection;
	}
	
	public void setPos(Point p) {
		this.pos = p;
	}
	
	public Point getPos() {
		return this.pos;
	}
	
	public void newRandDir() {
		double x=1, y=1;
                // To get a good direction (as above)
		do{
			x=1; y=1;
			if(random.nextBoolean()) {
				x *=-1;
			}
			x *= random.nextInt(4);
			if(random.nextBoolean()) {
				y *=-1;
			}
			y *= random.nextInt(4);
			System.out.println(String.format("%f : %f",x,y));
			if((Math.abs(x)>1.3) && (Math.abs(y)>Math.abs(x))) {
				validDir = true;
			}
		}while(!validDir);
		XDirection = x;
		YDirection = y;
	}
	
	public double getXDirection() {
		return XDirection;
	}
	
	public double getYDirection() {
		return YDirection;
	}

}

Shield

package com.bosig.pingpong;

import java.awt.Point;

public class Shield {
	
	//To Be Implemented
	//Length for different difficulties
	public static final int EASY = 40;
	public static final int MEDIUM = 25;
	public static final int HARD = 10;
	
	private int length;
	private Point southPoint;
	private Point northPoint;
	
	public Shield(int length) {
		this.length = 25;	//Pixels
	}
	
	public int getlenght() {
		return length;
	}
	
	public int getNorthY() {
		return northPoint.y;
	}
	
	public int getSouthY() {
		return southPoint.y;
	}
	
	public int getNorthX() {
		return northPoint.x;
	}
	
	public int getSouthX() {
		return southPoint.x;
	}

}

I have already tired different thing, such as replacing the original way of painting from overriding paint() or repaint() since people on stackoverflow said that only paintComponent() should be used for this kind of stuff. I also made the gamePanel an own class (was previously in the Frame class) to override the paintComponent().

 

I didn't find anyway of making the code inside "spoilers" to make the post shorter, also pardon my English. Not my native tongue!

 

So, can anyone tell me why this is happening, as well as tell me what to do about it?

If anyone can explain the paint offset that would be great as well!



#2 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 169 posts

Posted 04 January 2014 - 11:29 AM

You can try using a BufferStrategy fullscreen with page flipping, and that will be the fastest, The bottom line is that Swing is just not really fast and was never meant to be a game framework.   Because of this, I always just use a Swing Timer for animation so that I do not have to deal with threads and input synchronization.   You could also try enabling the Direct3D pipeline, if you use Windows.  If you are on MAC or Linux, you can enable the OpenGL pipeline.  That should speed thing up.   How does this run for you?

public class Main {
	public static void main(String[] args) {
		// enable direct3D pipeline for Windows.
		System.setProperty("sun.java2d.d3d", "True");

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				// the frame.
				JFrame frame = new JFrame("Moving Ball");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.setResizable(false);
				frame.add(new JPanel() {

					final int PWIDTH = 400;
					final int PHEIGHT = 200;
					Timer animator;
					Ball ball;

					{
						setPreferredSize(new Dimension(PWIDTH, PHEIGHT));
						setBackground(Color.black);
					}

					ActionListener animatorTask = new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							update();
							repaint();
						}
					};

					public void addNotify() {
						super.addNotify();
						animator = new Timer(15, animatorTask);
						animator.start();
						ball = new Ball();
					}

					void update() {
						ball.update();
					}

					public void paintComponent(Graphics g) {
						super.paintComponent(g);
						ball.draw(((Graphics2D) g));
					}

					class Ball {
						int x, y, dx, radius;

						Ball() {
							radius = 20;
							x = 0;
							y = (PHEIGHT / 2) - (radius / 2);
							dx = 5;
						}

						void update() {
							x += dx;
							if (x > PWIDTH - radius)
								x = 0;
						}

						void draw(Graphics2D g) {
							g.setColor(Color.WHITE);
							g.fillOval(x, y, radius, radius);
						}
					}
				});
				frame.pack();
				frame.setVisible(true);
				frame.setLocationRelativeTo(null);
			}
		});

	}
}

Edited by farrell2k, 04 January 2014 - 11:31 AM.

Averageloser.com - I used to be a programmer like you, then I took a -> in the knee. 


#3 emilime93

emilime93

    CC Lurker

  • New Member
  • Pip
  • 6 posts

Posted 05 January 2014 - 01:47 PM

Thanks for the reply!

 

Your code runs very smoothly!

 

But can you tell me why my code would lag?

I just tried it on my Windows computer as well (wrote the program on my macbook) and it doesn't stutter at all there. But, after every pause/unpause it starts to stutter, and eventually it stutters as heavily as on my mac.

 

How do i enable/disable OpenGl and Direct3D?



#4 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 169 posts

Posted 05 January 2014 - 02:24 PM

//direct3d.
System.setProperty("sun.java2d.d3d", "True");

//opengl
System.setProperty("sun.java2d.opengl", "True");

You run either one in main before you show your gui.  Do not call dispose() on a graphics object in paintComponent(), and never call paintComponent() directly and do not pass a graphics object to paintCopmponent().  This may be the cause of your issues.  In your game loop you are doing:

gamePanel.paintComponent(gamePanel.getGraphics());

replace that line with a simple call to: 

gamePanel.repaint();

Averageloser.com - I used to be a programmer like you, then I took a -> in the knee. 


#5 emilime93

emilime93

    CC Lurker

  • New Member
  • Pip
  • 6 posts

Posted 05 January 2014 - 03:50 PM

Thank you so much!  :thumbup:

 

It works flawlessly now! And it doesn't matter if i use Direct3D/OpenGL or not, but now i know how to do it at least!

Really nice of you, finally I can finnsh the game!!

 

Just a last question, there is as i mentioned in OP a offset when the ball bounces on the right and lower wall.

I know coordinates are calculated from the top left of windows in programming so that might have something to do with it?



#6 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 169 posts

Posted 05 January 2014 - 04:25 PM

You want to lock the ball in the screen?  Easy.  it's done like this:

public void lockBall() {
    if (player.x < 0) {
        player.x = 0;
    }
    if (player.x > gamePanel.width - player.width) {
        player.x = gamePanel.width - player.width;
    }
    if (player.y < 0) {
        player.y = 0;
    }
    if (player.y > gamePanel.height - player.height) {
        player.y = gamePanel.height - player.height;
    }
}//end lockBall()

Now just call that inside ball.move().   


Averageloser.com - I used to be a programmer like you, then I took a -> in the knee. 


#7 emilime93

emilime93

    CC Lurker

  • New Member
  • Pip
  • 6 posts

Posted 05 January 2014 - 04:29 PM

I already use similar check methods. 

 

If i for example call 

g2.drawRect(0, 0, getWidth(), getHeight());

inside the paintComponent you can't see the right and the bottom lines at all!



#8 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 169 posts

Posted 05 January 2014 - 04:47 PM

Oh!  I misunderstood you.  That sounds like a frame sizing issue.  First, make that Frame a JFrame.  Then in the JPanel call setPreferredSize(), passing your gamepanel with and height.  Now in your JFrame, add the game panel, then call pack() in the JFrame.  It's all in my example.


Averageloser.com - I used to be a programmer like you, then I took a -> in the knee. 


#9 emilime93

emilime93

    CC Lurker

  • New Member
  • Pip
  • 6 posts

Posted 05 January 2014 - 05:02 PM

I'm trying that!

 

Slightly unrelated; While reading through you code again I saw a thing i don't know about.

                    {
                        setPreferredSize(new Dimension(PWIDTH, PHEIGHT));
                        setBackground(Color.black);
                    }

Is that a constructor for a "variable-less" object? Since you are just creating the object with new JPane() {....};.

 

EDIT: I successfully made the perferedSize() and pack() thing work, but it didn't solve the problem!


Edited by emilime93, 05 January 2014 - 05:06 PM.


#10 farrell2k

farrell2k

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 169 posts

Posted 05 January 2014 - 08:20 PM

Sort of.  It's called an initializer field.   Anything in that field gets copied to the beginning of the class's constructor.    That being said, don't write code like I did.  I only did it because it was much quicker than creating and posting different classes in separate files.   As for your problem, I don't know.  

 

You see how I size the JPanel in my example with setPreferredSize(), then pack() the frame after I add it?   That's all you need to do, so if it is still happening, the problem is probably somewhere else.


Averageloser.com - I used to be a programmer like you, then I took a -> in the knee. 


#11 emilime93

emilime93

    CC Lurker

  • New Member
  • Pip
  • 6 posts

Posted 05 January 2014 - 08:28 PM

Ok good to know!

 

Yes I did just like you did. But in different classes, and it works (I tried setting different preferred sizes and it changed).

Yeah, ill probably just make the getFieldWidth/Height() return the panels size -5~ich until I can figure it out =)






Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download