1

I'm trying to understand how BufferStrategy is working. I've made a simple app that is drawing some Sprite objects over and over again every 60 frames per second. I can see the images on the canvas but they are flickering for some reason. Can you tell me why? If you don't want to read all the code, just focus on the Paint method and the main game loop.

public abstract class Frame extends JFrame implements KeyListener {

    private static final long serialVersionUID = 1L;

    //------------------------Variables------------------------//
    private boolean initialized = false;
    private boolean fullScreen = false;

    public boolean running = true;
    private GraphicsDevice vc;
    private BufferStrategy strategy;
    private Graphics2D g2d;
    private int timer = 0;
    //------------------------Variables------------------------//

    public final void __init__() {
        this.addKeyListener(this); //Adding key listener.
        this.setVisible(true);
        this.setIgnoreRepaint(true);
        this.createBufferStrategy(2);
        this.strategy = this.getBufferStrategy();
        this.setResizable(false);
        this.initialized = true;   //Initialized.
    }

    //Create a window.
    public final void set_mode(int width, int height, boolean fullScreen) throws NotInitializedException {
        //Frame not initialized.
        if (!this.initialized) {
            throw new NotInitializedException("Frame not initialized!");
        } else {
            //--------------------Variables--------------------//
            GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
                        //--------------------Variables--------------------//

            //Setting vc equal to the default graphics device of the system.
            this.vc = env.getDefaultScreenDevice();

            //Full Screen.
            if (fullScreen) {
                this.fullScreen = fullScreen;

                //Creating the display mode.
                DisplayMode mode = new DisplayMode(width, height, 32, DisplayMode.REFRESH_RATE_UNKNOWN);

                //If display settings are allow to change display mode.
                if (this.vc.isDisplayChangeSupported()) {
                    this.vc.setDisplayMode(mode); //Change to the new mode.
                }

                //Set the screen to full screen.
                this.vc.setFullScreenWindow(this);
            } //Not full screen.
            else {
                this.setSize(width, height);
                this.addWindowListener(new WindowHandler(this));
            }
        }

    }

    //This mehod is been called from Sprite.draw() method.
    public void paint(Sprite sprite) {

        do {

            do {
                this.g2d = (Graphics2D) this.strategy.getDrawGraphics();
                g2d.drawImage(sprite.getImage(), sprite.getX(), sprite.getY(), sprite.getWidth(), sprite.getHeight(), null);
                this.g2d.dispose();

            } while (this.strategy.contentsRestored());

            this.strategy.show();

        } while (this.strategy.contentsLost());
    }

    public final int tick(int fps) {
        int ms = 1000 / fps;
        timer += 1;

        //Try to sleep.
        try {
            Thread.sleep(ms);
        } //Thread interrupted.
        catch (Exception e) {
            System.out.println(e.getMessage());
            System.exit(0);
        }

        return timer;
    }

    public class MyApp extends Frame {

        public static String BG_PATH = "C:/Users/admin/Desktop/game/bg.jpg";
        public static String PL_PATH = "C:/Users/admin/Desktop/game/player.png";
        public static String EN_PATH = "C:/Users/admin/Desktop/game/enemy.png";

        private int speed = 20;

        private boolean left = false;
        private boolean right = false;

        @Override
        public void keyPressed(KeyEvent arg0) {
            // TODO Auto-generated method stub

            if (arg0.getKeyCode() == KeyEvent.VK_LEFT) {
                this.left = true;
            } else if (arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
                this.right = true;
            } else if (arg0.getKeyCode() == KeyEvent.VK_ESCAPE) {
                this.close();
            }

        }

        @Override
        public void keyReleased(KeyEvent arg0) {
            // TODO Auto-generated method stub

            if (arg0.getKeyCode() == KeyEvent.VK_LEFT) {
                this.left = false;
            } else if (arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
                this.right = false;
            }

        }

        @Override
        public void keyTyped(KeyEvent arg0) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onWindowClose() {
            // TODO Auto-generated method stub

        }

        //This method starts the game.
        public void startApp() {
            this.__init__(); //initialize the frame.

            Sprite bg = new Sprite(this, Picture.load(BG_PATH), "bg"); //Create a new sprite obj

            this.set_mode(bg.getWidth() - 500, bg.getHeight() - 100, false); //Create the window.

            Sprite player = new Sprite(this, Picture.load(PL_PATH), "player");

            player.setX(bg.getWidth() / 3);
            player.setY(bg.getHeight() / 2);

            //Game Main Loop
            while (this.running) {

                bg.draw();
                player.draw();

                player.moveHorizontal(left, right, speed); //Dont worry about this line.
                this.tick(50);
            }
        }
    }
}
Blackwood
  • 4,504
  • 16
  • 32
  • 41
babaliaris
  • 663
  • 8
  • 16
  • My opinion? Invest into OpenGL or Direct3D. You can offload the drawing onto your gpu, instead of letting your cpu handle it. – Krythic Dec 18 '15 at 14:52
  • I always thought BufferedImage from Java is already adopting double buffering, why implement it once more? I never had flickering problems with BufferedImages... – user3437460 Dec 18 '15 at 19:57
  • 1
    He could just render straight to a JPanel by getting it's graphics, too. This would completely circumvent the need for a BufferStrategy. – Krythic Dec 18 '15 at 19:59
  • @babaliaris The nested loop in your paint method doesn't look good at all. Your paint method (general) should be simple with minimal codes. The double loop is highly likely the cause for flickering. Did you try simply painting the components without the loops first? Actually I don't see a need to adopt bufferStrategy at all. Try the simple things first, if it work, then leave it. Don't go for premature optimizations. – user3437460 Dec 18 '15 at 20:03
  • @user3437460 The flickering is due to the fact that he is calling strategy.show(); more than once per frame. – Krythic Dec 18 '15 at 20:05

1 Answers1

1

You have a few issues that I can clearly spot.

First off, you must understand that drawing in Swing/Awt is not known for it's speed, it's actually known for the exact opposite. The fact is, even though you're telling your game to run at 60fps, it probably can't do it. Thus the flickering. Essentially, your application is caught within a "drawing-data race", and it's always slightly behind. Try something real quick; set Thead.Sleep() to 10 or 30. I feel as though that might solve your problem entirely.

If not, consider the second problem. You're calling this.strategy.show(); inside the player.Draw(); function, when it needs to be the last thing that you do in your draw call. In other words:

                //Game Main Loop
                while (this.running) {

                    bg.draw(); // DON'T SWAP BUFFERS!
                    player.draw(); // DON'T SWAP BUFFERS!
                    // draw other entities
                    player.moveHorizontal(left, right, speed); //Dont worry about this line.
                    this.tick(50);
                    this.strategy.show(); // This needs to be the very last thing you do. You're swapping buffers here, which only needs to be done once per frame.
                }

My guess is you're also swapping your buffer during the bg.Draw(); function as well, and that is actually why your screen is flickering. So those are two things right there. Try lowering the frames per second down to something that Java can actually handle, and don't swap your buffer until the VERY end of your drawing routine.

Some other recommendations:

Use direct variable access instead of getters and setters. There is overhead when calling "player.getX()". When you could just call "player.x".

There is no future in Java 2D game development. Swing/AWT(which you're using) is entirely dead. If you want to make a game, and be serious about it, take the time to learn OpenGL(In your case it would be Lwjgl as a wrapper).

Krythic
  • 4,184
  • 5
  • 26
  • 67
  • 1
    Thank you very much! The strategy.show() was the problem. I made a new methon update(){ this.strategy.show() } and i call it in the last line of main loop. I'm not trying to make a serious game, this is just for excersise before getting to the real thing. – babaliaris Dec 19 '15 at 16:02
  • 1
    @babaliaris Don't forget to give an upvote and accept me then! Good luck on your game! – Krythic Dec 19 '15 at 18:26