6

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.

sanchixx
  • 245
  • 2
  • 5
  • 12

3 Answers3

7

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

Community
  • 1
  • 1
MadProgrammer
  • 323,026
  • 21
  • 204
  • 329
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).

Qwerky
  • 17,564
  • 6
  • 43
  • 76
-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];
    }
}
Loki
  • 3,747
  • 4
  • 24
  • 47