Java Swing Animation Stutters on 60hz Monitor - java

I've been working on a 2D game in Swing and accidently came across an issue. Normally, when I run my code and test the game, the game window is on my 240hz monitor and everything is nice and smooth, however, when I move the window to my 60hz monitor I notice that the animation for the "side-scrolling" obstacles aren't smooth at all. They're moving across the screen, but they're micro-stuttering the whole way across, and at regular intervals I've noticed.
I've tried using a thread instead of a timer and although the micro-stutters are reduced at lot, specifically when I set the delay to 16, they do still occur about every 1 second.
I'm not really sure what to do at this point, so any advice would be much appreciated!
Here are the relevant classes:
package dinoRun;
public class Main {
public static void main(String[] args) {
new Window();
}
}
package dinoRun;
import javax.swing.*;
public class Window extends JFrame {
static GamePanel panel;
Window() {
panel = new GamePanel();
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(panel);
this.pack();
this.setLocationRelativeTo(null);
this.setTitle("Dino Run");
this.setVisible(true);
}
}
package dinoRun;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Random;
public class GamePanel extends Renderer implements ActionListener {
final int WIDTH = 1280, HEIGHT = 720;
Rectangle dino;
ArrayList<Rectangle> cactii;
GamePanel() {
this.setPreferredSize(new Dimension(WIDTH, HEIGHT));
dino = new Rectangle(WIDTH / 20, HEIGHT / 2, 100, 100);
cactii = new ArrayList<>();
addCactus();
Timer timer = new Timer(1, this);
timer.start();
}
void paintDino(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(dino.x, dino.y, dino.width, dino.height);
}
void addCactus() {
Random rand = new Random();
int space = rand.nextInt(64) * 10;
cactii.add(new Rectangle(space + WIDTH * (cactii.size() + 1), HEIGHT / 2, 30, 100));
}
void paintCactus(Graphics g, Rectangle cactus) {
g.setColor(Color.BLACK);
g.fillRect(cactus.x, cactus.y, cactus.width, cactus.height);
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
void repaint(Graphics g) {
g.setColor(Color.BLACK);
paintDino(g);
g.drawLine(0, HEIGHT / 2 + 100, WIDTH, HEIGHT / 2 + 100);
for (int i = 0; i < cactii.size(); i++){
if (cactii.get(i).getX() + cactii.get(i).getWidth() != 0) {
paintCactus(g, cactii.get(i));
cactii.get(i).translate(-10, 0);
} else {
cactii.remove(cactii.get(i));
i--;
}
}
addCactus();
}
}
package dinoRun;
import javax.swing.*;
import java.awt.*;
public class Renderer extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Window.panel.repaint(g);
}
}

Related

Flickering when drawing onto bufferedImage

I have a 3d-object composed of multiple polygons that I draw using graphics2D. When I rotate it it, it seems as if it has not enough time to draw the entire object at every frame since at some frames, some of the polygons are simply missing(not drawn). I don't understand how that can be since I in paintComponent first draw all the polygons onto the bufferedImage myImg, and then draw the finished image onto the screen. When I remove clearRect, this issue is resolved but then of course it doesn't remove the last frame's drawing before drawing the next.
Note: I'm an amateur but I've tried really hard understanding and so this is my last resort and would be really glad to get some help. The code (with unnecessary code removed is as follows) :
public class Main {
long temp = System.currentTimeMillis() + frameRate;
public static void main(String[] args) {
myGUI = new GUI(width, height);
while(true) {
if (System.currentTimeMillis() >= temp) {
temp += frameRate;
rotateObject();
myGUI.myCanvas.myLabel.repaint();
}
}
}
}
public class GUI extends JFrame {
public Canvas myCanvas;
public GUI(int w, int h) {
this.setSize(w, h);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myCanvas = new Canvas(w, h);
this.getContentPane().add(myCanvas);
this.setVisible(true);
this.pack();
}
}
public class Canvas extends JPanel {
public BufferedImage myImg;
public Graphics2D g2d;
public JLabel myLabel;
public Canvas(int w, int h) {
myImg = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
myLabel = new JLabel(new ImageIcon(myImg));
this.add(myLabel);
g2d = myImg.createGraphics();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g2d.clearRect(0, 0, myImg.getWidth(), myImg.getHeight());
g2d.setColor(Color.RED));
g2d.fillPolygon(pointsX, pointsY, 3);
g.drawImage(myImg, 0, 0, null);
}
}
This is how my object is flickering
You really need to take the time to read through:
Performing Custom Painting
Painting in AWT and Swing
2D Graphics
Concurrency in Swing
How to Use Swing Timers
These aren't "beginner" topics and a reasonable understanding of Swing in general and the language in particular would be very advantageous.
Don't, ever, use getGraphics on a component. This is simply a bad idea (and I have no idea why this method is public).
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage myImg;
private double rotation;
public TestPane() throws IOException {
myImg = ImageIO.read(getClass().getResource("/images/happy.png"));
Timer timer = new Timer(33, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
rotation += Math.toRadians(5);
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = AffineTransform.getTranslateInstance((getWidth() - myImg.getWidth()) / 2, (getHeight() - myImg.getHeight()) / 2);
at.rotate(rotation, myImg.getWidth() / 2, myImg.getHeight() / 2);
g2d.transform(at);
g2d.drawImage(myImg, 0, 0, this);
g2d.dispose();
}
}
}

Calling repaint() draws the new shape, but leaves the old shape there

I'm sure this is a simple answer, but I can't figure it out.
I am trying to make a basic shape that I can control in a window. Obviously it will be more involved when the whole project is complete, but I am working on the early steps still. I am using WindowBuilder to make the layout, and have a JPanel and a JButton. The JPanel draws a rectangle, and has a method to move it. The JButton calles that moving command. And that's it. The problem is in the repaint. The shape keeps all old versions of itself, and the button makes weird copies of itself. All these go away when I resize the window, which I thought was the same as calling repaint. Again, I'm sure is something simple I am missing. Below are my 2 classes.
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.BorderLayout;
import javax.swing.JPanel;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Drawing {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Drawing window = new Drawing();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public Drawing() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
drawpanel panel = new drawpanel();
panel.setBounds(58, 68, 318, 182);
frame.getContentPane().add(panel);
JButton btnMove = new JButton("move");
btnMove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.moves();
}
});
btnMove.setBounds(169, 34, 89, 23);
frame.getContentPane().add(btnMove);
}
}
^ This one, besides the buttonListener, was autocreated by WindowBuilder.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class drawpanel extends JPanel {
int x = 50, y = 50;
int sizeX = 50, sizeY = 50;
public void paintComponent( Graphics g) {
super.paintComponents(g);
g.setColor(Color.BLACK);
g.drawRect(x, y, sizeX, sizeY);
}
public void moves() {
x +=5;
repaint();
}
}
^ This one has my drawing of the shape and the moving/repainting method. It was written mostly from other examples I found on this site.
Thanks to any help in advanced.
public void paintComponent(Graphics g) {
super.paintComponents(g); // wrong method! (Should not be PLURAL)
Should be:
public void paintComponent(Graphics g) {
super.paintComponent(g); // correct method!

Java Isn't Displaying gui right

I have a simple gui and when I run is it is perfect, but occasionally it doesn't display right. I have a circle in a JPanel. When it runs right it displays in the middle for the JFrame like I intended it to, but when it does not work right the circle appears closer to the bottom of the screen. How can I fix this so it displays right each time? Is my code incorrect (I hope not! :) ), or is it some bug in java. So here is my code:
Update: It seems that the window height is changing.
Run.java--------------------------------------------------------------------------------
import javax.swing.JFrame;
public class Run {
public static void main(String args[]) {
Window w = new Window();
w.setSize(800, 500);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
w.setVisible(true);
}
}
Window.java----------------------------------------------------------------------------
import javax.swing.JFrame;
public class Window extends JFrame {
public Window() {
super("Wheel");
Gui g = new Gui();
add(g);
}
}
Gui.java--------------------------------------------------------------------------------
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Gui extends JPanel {
private Color wheelColor = new Color(0, 0, 255);
public Gui() {
setOpaque(true);
setBackground(new Color(255, 0, 0));
}
public void paintComponent(Graphics g) {
g.setColor(wheelColor);
g.fillOval(40, 40, 420, 420);
}
}
Also I'm using Ubuntu, I don't know if that would affect it at all. Thanks in advance.
Make sure you create the UI within the context of the Event Dispatching Thread, see Initial Threads for more details.
Make sure you are calling super.paintComponent within the paintComponent method before you do any custom painting, see Painting in AWT and Swing for more details
Don't rely on magic numbers, the size of the window is the size of it's content + its frame decorations. You should get using getWidth and getHeight to determine the size of the Gui panel and overriding getPreferredSize to allow the window to determine how much room it needs to (optimally) display it
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Run {
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Window w = new Window();
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
w.pack();
w.setLocationRelativeTo(null);
w.setVisible(true);
}
});
}
public static class Window extends JFrame {
public Window() {
super("Wheel");
Gui g = new Gui();
add(g);
}
}
public static class Gui extends JPanel {
private Color wheelColor = new Color(0, 0, 255);
public Gui() {
setOpaque(true);
setBackground(new Color(255, 0, 0));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(440, 440);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(wheelColor);
int width = getWidth();
int height = getHeight();
int x = (width - 420) / 2;
int y = (height - 420) / 2;
g.fillOval(x, y, 420, 420);
}
}
}
Also beware of naming your classes after classes that already exist within the default API, java.awt.Window already exists and can cause confusion not only for yourself, but for other developers ;)

Using canvas in Netbeans GUI builder [duplicate]

This question already has an answer here:
netBeans gui problem
(1 answer)
Closed 8 years ago.
I'm trying to use canvas in java. I'm using netbeans GUI builder to add several canvases to the window but I'm not sure how to for example draw the line or rectangle on them. I read several manuals how to do that but I'm still beginner in Java and I didn't quite understand what am I supposed to do. The constructor of the class looks like this:
public Classname() {
initComponents();
canvas1.setBackground(Color.red); // That works.
// Now I want to (for example) draw a line on the canvas1 (or some other canvas)
}
Could somebody please explain me what code should I write and where to put it? Thanks in advance. (Sorry for my english.)
Assuming your mean java.awt.Canvas, I'd recommend that you shouldn't be using it. Two main reasons, one, it's a heavy weight component, which introduces a list of issues when mixed with Swing/lightweight components and two, it's not double buffered, which just adds additional overheads you're going to have to deal with.
The preferred means by which to perform custom painting is to generally create a new class that extends from JPanel and the override it's paintComponent method for example
public class PaintPane extends JPanel {
public PaintPane () {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
g2d.dispose();
}
}
This will draw a simple line across the middle of the panel.
You can then drag the class from the "Projects" tab into the form editor (and onto an existing form container), like you would with components from the palette
Take a look at Performing Custom Painting and 2D Graphics for more details
Updated with example based on comments
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawExample {
public static void main(String[] args) {
new DrawExample();
}
private DrawPane drawPane;
public DrawExample() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
drawPane = new DrawPane();
JButton addRect = new JButton("Add Rectangle");
addRect.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int width = drawPane.getWidth() - 1;
int height = drawPane.getHeight() - 1;
int x = (int)(Math.random() * (width - 5));
int y = (int)(Math.random() * (height - 5));
width -= x;
height -= y;
int rectWidth = (int)(Math.random() * width);
int rectHeight = (int)(Math.random() * height / 2);
drawPane.addRect(x, y, rectWidth, rectHeight);
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(drawPane);
frame.add(addRect, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DrawPane extends JPanel {
private List<Shape> shapes;
public DrawPane() {
shapes = new ArrayList<Shape>(25);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Shape shape : shapes) {
g2d.draw(shape);
}
g2d.dispose();
}
public void addRect(int x, int y, int width, int height) {
shapes.add(new Rectangle(x, y, width, height));
repaint();
}
}
}

Java JLayeredPane doesn't show it's elements

I am trying to use JLayeredPane to overlay one JPanel on top of another. For some reason the added panels don't show up.
This is the code that creates the JLayeredPane and adds elements to it:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.SwingUtilities;
public class CarAnimator extends JFrame {
public CarAnimator()
{
JLayeredPane racingOverlay = new JLayeredPane();
CarAnimatorJPanel animation = new CarAnimatorJPanel();
Racetrack racetrack = new Racetrack();
racingOverlay.add(racetrack,JLayeredPane.DEFAULT_LAYER);
racingOverlay.add(animation,new Integer(2));
racingOverlay.setBorder(BorderFactory.createTitledBorder("Can't see a thing"));
this.getContentPane().add(racingOverlay,BorderLayout.CENTER);
this.setLocationByPlatform(true);
this.setPreferredSize(new Dimension(850,650));
this.setMaximumSize(new Dimension(850,650));
this.setMinimumSize(new Dimension(850,650));
this.setResizable(false);//prevents user from resizing the window
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> {
(new CarAnimator()).setVisible(true);
});
}
}
This is the code for the racing track JPanel:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Racetrack extends JPanel
{
Graphics g = this.getGraphics();
#Override
public void paintComponent(Graphics g)
{
Color c1 = Color.green;
g.setColor( c1 );
g.fillRect( 150, 200, 550, 300 ); //grass
Color c2 = Color.black;
g.setColor( c2 );
g.drawRect(50, 100, 750, 500); // outer edge
g.drawRect(150, 200, 550, 300); // inner edge
Color c3 = Color.yellow;
g.setColor( c3 );
g.drawRect( 100, 150, 650, 400 ); // mid-lane marker
Color c4 = Color.white;
g.setColor( c4 );
g.drawLine( 425, 500, 425, 600 ); // start line
}
}
And this is the JPanel animation example, taken from Java: How to Program book (http://java.uom.gr/~chaikalis/javaLab/Java_HowTo_9th_Edition.pdf), adapted to work with my code:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CarAnimatorJPanel extends JPanel
{
protected ImageIcon images[];
private int currentImage=0;
private final int ANIMATION_DELAY=50;
private int width;
private int height;
private Timer animationTimer;
public CarAnimatorJPanel()
{
try
{
File directory = new File("C://Users/eltaro/Desktop/Car images");
File[] files = directory.listFiles();
images = new ImageIcon[files.length];
for (int i=0;i<files.length;i++)
{
if (files[i].isFile())
{
images[i]=new ImageIcon(ImageIO.read(files[i]));
}
}
width = images[0].getIconWidth();
height = images[0].getIconHeight();
}
catch(java.io.IOException e)
{
e.printStackTrace();
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
images[currentImage].paintIcon(this,g,0,0);
if(animationTimer.isRunning())
{
currentImage=(currentImage+1)%images.length;
}
}
public void startAnimation()
{
if(animationTimer==null)
{
currentImage=0;
animationTimer=new Timer(ANIMATION_DELAY, new TimerHandler());
animationTimer.start();
}
else
{
if(!animationTimer.isRunning())
{
animationTimer.restart();
}
}
}
public void stopAnimation()
{
animationTimer.stop();
}
#Override
public Dimension getMinimumSize()
{
return getPreferredSize();
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(width,height);
}
private class TimerHandler implements ActionListener
{
#Override
public void actionPerformed(ActionEvent actionEvent)
{
repaint();
}
}
}
When I add the two JPanels to the JLayeredPane inside CarAnimator constructor, they fail to show:
Some recommendations:
Since I'm guessing that your CarAnimatorJPanel sits on top of a background JPanel, you'd better set CarAnimatorJPanel to be non-opaque via setOpaque(false) so that the JPanel behind it can be seen.
When adding components to a JLayeredPane, you're essentially adding the components to a null layout. This means that you absolutely need to set the sizes (or override getSize()) of those components, not the preferred sizes.
As always, when dealing with image files, debug that you're able to adequately read in the image in a separate small test program. As usual, consider getting the image in as a resource and not as a File.
You have some program logic code in your paintComponent(Graphics g) method -- you change the state of the currentImage field. Never do this since you never have full control over when or even if paintComponent is called. This bit of code belongs in your animation loop, your Swing Timer's ActionListener.

Categories

Resources