Stop flickering in swing when i repaint too much
Asked Answered
C

3

7

I am making an RPG with a tilemap. To generate the tilemap i loop through a 2 dimensional array but that means that when I repaint I have to do that each time. If I repaint too much the screen flickers how could I stop this.

package sexyCyborgFromAnOtherDimension;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel
{
    KeyLis listener;
    int mapX = 20;
    int mapY = 20;
    boolean up = false;
    boolean down = false;
    boolean right = false;
    boolean left = false;
    String[][] map;

    public Game()
    {
        super();
        try 
        {
            map = load("/maps/map1.txt");
        } 

        catch (IOException e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        listener = new KeyLis();
        this.setFocusable(true);
        this.requestFocus();
        this.addKeyListener(listener);

        Timer timer = new Timer();
        TimerTask task = new TimerTask() 
        {
            @Override
            public void run() 
            {
                if(up)
                {
                    mapY++;
                    repaint();
                }

                if(down)
                {
                    mapY--;
                    repaint();
                }

                if(right)
                {
                    mapX--;
                    repaint();
                }

                if(left)
                {
                    mapX++;
                    repaint();
                }
            }
        };
        timer.scheduleAtFixedRate(task, 0, 10);
    }

    public void paint(Graphics g) 
    {
        super.paintComponent(g);
        setDoubleBuffered(true);
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        for (int x = 0; x < map.length; x++) 
        {
            for (int y = 0; y < map[x].length; y++) 
            {
                switch(map[x][y])
                {
                case "0":
                    g.setColor(Color.GREEN);
                    break;
                case "1":
                    g.setColor(Color.GRAY);
                    break;
                }

                g.fillRect(y*20+mapX, x*20+mapY, 20, 20);
            }
        }
        g.setColor(Color.BLACK);
        g.fillRect(400, 400, 20, 20);
    }

    String[][] load(String file) throws IOException
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
        int lines = 1;
        int length = br.readLine().split(" ").length;
        while (br.readLine() != null) lines++;
        br.close();
        BufferedReader br1 = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
        String[][] map = new String[lines][length];
        for (int i = 0; i < lines; i++)
        {
            String line = br1.readLine();
            String[] parts = line.split(" ");
            for (int y = 0; y < length; y++)
            {
                map[i][y] = parts[y];
            }
        }
        br1.close();
        return map;
    }

    private class KeyLis extends KeyAdapter 
    {   
        @Override
        public void keyPressed(KeyEvent e) 
        {
            switch (e.getKeyCode())
            {
            case KeyEvent.VK_UP:
                up = true;
                break;
            case KeyEvent.VK_DOWN:
                down = true;
                break;
            case KeyEvent.VK_LEFT:
                left = true;
                break;
            case KeyEvent.VK_RIGHT:
                right = true;
                break;
            }
        }

        @Override
        public void keyReleased(KeyEvent e) 
        {
            switch (e.getKeyCode())
            {
            case KeyEvent.VK_UP:
                up = false;
                break;
            case KeyEvent.VK_DOWN:
                down = false;
                break;
            case KeyEvent.VK_LEFT:
                left = false;
                break;
            case KeyEvent.VK_RIGHT:
                right = false;
                break;
            }
        }
    }
}

Thank you for your help

Edit

Using javax.swing.Timer removes all flickering even with a 10 ms delay.

Chino answered 31/7, 2013 at 8:55 Comment(6)
+1 for your package name.Checkerboard
@Checkerboard indeed nice - but not complying to java naming conventions <g>Incidental
@Incidental Ok i will change it to com.sexyCyborgFromAnOtherDimension.Chino
the domain is not the point: package names shouldn't contain uppercase letters :-)Incidental
several thingies wrong with your code (may be unrelated to the problem) a) override paintComponent for custom painting (not paint) b) don't change the component's state in a paint method c) favour KeyBindings over KeyListeners Other than that: 10ms looks like a very tight loop...Incidental
good point because 10ms looks like to overload latency for standard Native OSAliciaalick
S
10

A number of small things jump out at me.

Firstly. You might be better using javax.swing.Timer instead of java.util.Timer, this will at least allow the events to flow a little better.

Secondly, 10 milliseconds is to short a time period, seriously, you don't need 100fps, 60fps is about 17 milliseconds, I normally use 40 milliseconds for 25fps. This might give the EDT some breathing room to actually respond to the repaint requests.

Thirdly, you should be using paintComponent instead of paint. It's low enough in the call chain to guaranteed to be double buffered

Fourthly, you should avoid calling any method that might reschedule a repaint (like setDoubleBuffered for example, to this in the constructor if you must, but, Swing components are double buffered by default)

Fifthly, where possible, paint all "static" or slow changing content to a backing buffer and paint that instead. This will increase the speed at which paint can work as it doesn't get stuck in a lot of small loops.

You may want to take a look at Painting in AWT and Swing for more details about the paint process

Some additional examples...

And because kleo scares me (and it's also a good idea), you should also take a look at How to use Key Bindings, which will solve your focus issues with the KeyListener

Scree answered 31/7, 2013 at 9:15 Comment(3)
Ok I have very little flickering now :)Chino
@Chino Then you're still doing something wrong, there should no flickering. If you take time to look over some of the examples I linked, you should find that none of them suffer from any flickering at allScree
I have no more flickeringChino
C
0

You have set double buffering on the panel, but the things you are drawing on it yourself are not being double buffered. Calling setDoubleBuffered(true) only applies buffering for child components of the container.

You need manually buffer stuff you draw yourself.

I would suggest moving away from a JPanel and use a Canvas instead. Have a look at how a Canvas is used with a buffer strategy in this class, where it can handle hundreds of things being drawn on screen with perfect smoothness and no flickering (the game runs on a 10ms sleep loop too).

Checkerboard answered 31/7, 2013 at 9:7 Comment(5)
Or use paintComponent instead of paint where it is double buffered...and using javax.swing.Timer wouldn't hurt eitherScree
not really true: the RepaintManager bubbles up repaint request to the nearest parent that is doubleBuffered. JPanel is so by default, even if it set to not doubleBuffered, the rootPane is :-)Incidental
@Scree - just noticed he's calling super.paintComponent() from inside paint() :SCheckerboard
@Checkerboard Well, that's going to screw things up :PScree
@Incidental Thanks for the additional info, nice to knowScree
M
-1

Not really sure if it solves your problem, but you can consider to use a 1dimensional array.

when int width; is the width of your screen/tile/whatever you loop and int height is the height you coud do the following:

instead of, what you do:

for(int x = 0; x < width; x++)
{
    for(int y = 0; y < height; y++)
    {
        // dosomething with your array
        myarray[x][y];
    }
}

you could go through this way

for(int y = 0; y < height; y++)
{
    for(int x = 0; x < width; x++)
    {
        // dosomething with your array
        myarray[x + y * width];
    }
}
Mun answered 31/7, 2013 at 9:8 Comment(1)
That would not solve my problem and I like using a 2 dimensional array.Chino

© 2022 - 2024 — McMap. All rights reserved.