0

I have made 2 JPanels, Panel and BackGround, in a JFrame. I am dynamically painting the Panel after 10ms(using a Timer), but the BackGround is only painted once at the beginning of the game. The Panel is responsible for the displaying of the fighters(spacecrafts), the projectiles and the aliens. The BackGround is responsible for the displaying of the background scene which is non-dynamic. The paintComponent(Graphics) method does paint the fighters and the projectiles, but flickers when they are updating. Can someone find the cause.

This is my Frame:

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Frame extends JFrame {
    private static final long serialVersionUID = 1L;

    public static final int WIDTH = 1280;
    public static final int HEIGHT = 720;

    public static final int DELAY = 10;

    private Panel panel;
    private Background bg;

    public Frame() {

        panel = new Panel();
        bg = new Background();

        initComponents();
    }

    public void initComponents() {

        this.setSize(WIDTH, HEIGHT);

        this.add(bg);
        this.add(panel);

        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        this.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_LEFT) panel.moveLeft();
                else if(e.getKeyCode() == KeyEvent.VK_RIGHT) panel.moveRight();
            }

        });

    }

    public static void main(String args[]) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Frame().setVisible(true);
            }
        });

    }

}

This is my Panel:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Panel extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;

    public static final int DELAY = Frame.DELAY;

    private Timer timer;
    private BufferedImage fighter;

    int x, y;

    public Panel() {

        timer = new Timer(DELAY, this);
        try {
            fighter = ImageIO.read(this.getClass().getResource("fighter.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        initComponents();

        timer.start();
    }

    public void initComponents() {

        this.setSize(Frame.WIDTH, Frame.HEIGHT);
        this.setDoubleBuffered(true);
        this.setBackground(new Color(0, 0, 0, 0));

        x = 150;
        y = 200;

    }


    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        doDrawing(g2d);

    }



    private void doDrawing(Graphics2D g2d) {

        g2d.drawImage(fighter, x, y, null);

    }

    @Override
    public void actionPerformed(ActionEvent arg0) {

        this.repaint();
    }

    public void moveLeft() {
        x -= 10;
    }

    public void moveRight() {
        x += 10;
    }

}

This is the BackGround:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;


public class Background extends JPanel implements ActionListener{
    private static final long serialVersionUID = 1L;

    private BufferedImage backGround;
    private Timer timer;
    public Background() {

        this.setSize(Frame.WIDTH, Frame.HEIGHT);
        this.setBackground(new Color(0, 0, 0, 0));

        timer = new Timer(Frame.DELAY, this);

        try {
            backGround = ImageIO.read(this.getClass().getResource("background.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        timer.start();

    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.drawImage(backGround, 0, 0, null);

    }

    @Override
    public void actionPerformed(ActionEvent e) {

        this.repaint();

    }

}

I expect the sprites not to flicker and not to lag(which is not happening after lots of trials).

  • 1
    Get the `repaint()` call *out* of your painting method as it *never* belongs there – Hovercraft Full Of Eels Mar 24 '19 at 12:31
  • @HovercraftFullOfEels I did not get that. – Aniket Das Mar 24 '19 at 12:37
  • 1
    https://stackoverflow.com/questions/30561690/is-calling-repaint-from-paintcomponent-a-good-practice – Hovercraft Full Of Eels Mar 24 '19 at 12:42
  • 1
    1) For better help sooner, [edit] to add a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) Use a logical and consistent form of indenting code lines and blocks. The indentation is intended to make the flow of the code easier to follow! 3) ***Every*** `JComponent` is an `ImageObserver`, so `g.drawImage(backGround, 0, 0, null);` should better be `g.drawImage(backGround, 0, 0, this);` – Andrew Thompson Mar 24 '19 at 14:06
  • @AndrewThompson I cannot put the whole code up, as it contains 8 classes in different packages. What should I do? – Aniket Das Mar 24 '19 at 14:49
  • 1
    @AndrewThompson is not asking for the whole program but rather a [MCVE](https://stackoverflow.com/help/mcve)/[SSCCE](http://www.sscce.org/). You will want to read the links that he gave you as they will explain exactly what he is (we are) requesting. – Hovercraft Full Of Eels Mar 24 '19 at 14:59
  • Edited as requested- @HovercraftFullOfEels – Aniket Das Mar 24 '19 at 15:39
  • You're still calling `repaint()` from within `paintComponent` -- why? Makes no sense and is only hurting your program. – Hovercraft Full Of Eels Mar 24 '19 at 15:40
  • If I do not use the repaint method there, then where should I use it? If I donot repaint the Background Panel, then it hurts the eyes(as it does not look good as a game) @HovercraftFullOfEels – Aniket Das Mar 24 '19 at 15:41
  • If I make Background implement ActionListener, and then add a Timer to it, and then start it, where @Override public void actionPerformed(ActionEvent e) { this.repaint(); } , then also the sprite seems to flicker. – Aniket Das Mar 24 '19 at 15:51

1 Answers1

1
  1. Do not call repaint within a painting method
  2. Get rid of the Background class and do all drawing in one JPanel. For example:
  3. See example below of both as well as of MCVE design
    • whole thing can be copy/pasted into IDE and run
    • uses images available to all online, not on disk
  4. I would also remove the Timer that simply calls repaint() and instead either
    • call repaint from within the KeyListener
    • or use the timer to do the actual sprite movement code (with repaint()). This would be useful if you want continuous movement

Example MCVE:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.*;

public class Frame1 extends JFrame {
    // Image attribution:
    // By Adam Evans - M31, the Andromeda Galaxy (now with h-alpha)
    // Uploaded by NotFromUtrecht, CC BY 2.0,
    // https://commons.wikimedia.org/w/index.php?curid=12654493
    public static final String ANDROMEDA_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/"
            + "thumb/9/98/Andromeda_Galaxy_%28with_h-alpha%29.jpg/"
            + "1280px-Andromeda_Galaxy_%28with_h-alpha%29.jpg";
    public static final String SPRITE_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/"
            + "thumb/a/a1/Glossy_3d_blue_blue2.png/" + "120px-Glossy_3d_blue_blue2.png";

    private static final long serialVersionUID = 1L;
    public static final int WIDTH = 1280;
    public static final int HEIGHT = 720;
    public static final int DELAY = 10;
    private Panel1 panel;
    // private Background bg;

    public Frame1() {
        panel = new Panel1();
        // bg = new Background();
        initComponents();
    }

    public void initComponents() {
        this.setSize(WIDTH, HEIGHT);
        // this.add(bg);
        this.add(panel);
        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT)
                    panel.moveLeft();
                else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
                    panel.moveRight();
            }
        });
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Frame1().setVisible(true);
            }
        });
    }
}

class Panel1 extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;
    public static final int DELAY = Frame1.DELAY;
    private Timer timer;
    private BufferedImage fighter;
    private BufferedImage background;
    int x, y;

    public Panel1() {
        timer = new Timer(DELAY, this);
        try {
            URL url = new URL(Frame1.SPRITE_IMAGE);
            // fighter = ImageIO.read(this.getClass().getResource("fighter.png"));
            fighter = ImageIO.read(url);

            url = new URL(Frame1.ANDROMEDA_IMAGE);
            background = ImageIO.read(url);

        } catch (IOException e) {
            e.printStackTrace();
        }
        initComponents();
        timer.start();
    }

    public void initComponents() {
        this.setSize(Frame1.WIDTH, Frame1.HEIGHT);
        this.setDoubleBuffered(true);
        this.setBackground(new Color(0, 0, 0, 0));
        x = 150;
        y = 200;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(background, 0, 0, this);
        g.drawImage(fighter, x, y, this);
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        this.repaint();
    }

    public void moveLeft() {
        x -= 10;
    }

    public void moveRight() {
        x += 10;
    }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • I know that I can do this, but the repaint method causes lag, and I don't wanted to repaint the entire screen. So I decided to make another JPanel where the background is painted permanently, so that I only had to repaint some specific areas in the upper panel. Is this even possible? – Aniket Das Mar 24 '19 at 16:31
  • @AniketDas: possible but not desirable, and most certainly will not improve your animation efficiency or quality. If you're concerned about repaints, then a better solution is to call a different repaint overload method, one that accepts 4 int parameters so you repaint a limited region. – Hovercraft Full Of Eels Mar 24 '19 at 16:35
  • "will not improve your animation efficiency or quality", okay I got you. Thank You for your suggestions. – Aniket Das Mar 24 '19 at 16:46