Как создать игровой цикл Java без мерцания?

Я пытался создать игровой цикл Java, который отображает что-то простое для начала, но, что бы я ни пытался, я не могу заставить его перестать мерцать. Я пробовал решения для поиска в Google, но либо мой google-fu не совсем в порядке, либо я делаю что-то серьезно не так.

Я размещаю этот код здесь в надежде, что я просто делаю что-то не так, что можно исправить. На этом этапе у меня возникает соблазн начать все сначала на другом языке, в прошлый раз, когда я использовал его, SFML был хорош. Прошу прощения за ужасный код.

Main.java:

public class Main {
    public static void main(String[] args) {
        new GameFrame("Game");
    }
}

GameFrame.java:

import java.awt.Color;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Toolkit;

import javax.swing.JFrame;

public class GameFrame extends JFrame {
    protected GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

    public GameFrame(String title) {
        super(title);
        init();
    }

    public void init() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // Calculate the size of the window, taking into account the size of toolbars etc.
        Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(device.getDefaultConfiguration());
        setSize(
            (int) ((device.getDefaultConfiguration().getBounds().width - insets.left - insets.right) * 0.8),
            (int) ((device.getDefaultConfiguration().getBounds().height - insets.top - insets.bottom) * 0.8)
        );

        // Centre and start maximised.
        setLocationRelativeTo(null);
        setExtendedState(MAXIMIZED_BOTH);

        if(false) {
            // Important otherwise you get an ugly border.
            super.setVisible(false);
            setUndecorated(true);
            device.setFullScreenWindow(this);
        } else {
            setUndecorated(false);
            device.setFullScreenWindow(null);
            super.setVisible(true);
        }

        GamePanel panel = new GamePanel();

        panel.setBackground(Color.BLACK);
        new Thread(panel).start();

        add(panel);
    }
}

GamePanel.java:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;

import javax.swing.JPanel;

public class GamePanel extends JPanel implements Runnable {
    protected boolean running = false;

    protected long lastTime;

    protected int width  = 0;
    protected int height = 0;

    protected Image image;
    protected Graphics2D graphics;

    protected double x = 0;
    protected double y = 0;

    protected void updateState(double delta) {
        x = x + 0.00001 * delta;
        y = y + 0.00001 * delta;
    }

    public void run() {
        running  = true;
        lastTime = System.nanoTime();

        while(width == 0 || height == 0) {
            width  = getBounds().width;
            height = getBounds().height;
        }

        setDoubleBuffered(true);

        while(running) {
            long   now          = System.nanoTime();
            long   updateLength = now - lastTime;
            double delta        = updateLength / (10 ^ 9 / 60);

            updateState(delta);

            paintComponent(getGraphics());

            lastTime = System.nanoTime();

            try {Thread.sleep((now - lastTime + (10 ^ 9 / 60)) / (10 ^ 6));} catch (Exception e) {}
        }
    }

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

        if(isVisible() && width > 0 && height > 0) {

            setDoubleBuffered(true);

            if(image == null) {
                image    = createImage(width, height);
                graphics = (Graphics2D) image.getGraphics();
                graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            }

            graphics.setColor(Color.black);
            graphics.fillRect(0, 0, width, height);

            graphics.setColor(Color.WHITE);
            graphics.fillRect(100, 20, 100, 100);

            graphics.fillOval((int) x, (int) y, 30, 30);

            g.drawImage(image, 0, 0, this);
        }
    }
}

person Matthew    schedule 17.01.2015    source источник


Ответы (1)


Фрагменты вашей программы некорректны по нескольким причинам:

  • Объекты графического интерфейса Swing должны создаваться и управляться только на поток отправки событий.

  • Не используйте getGraphics(); графический контекст только действителен в paintComponent().

  • JPanel по умолчанию имеет двойную буферизацию.

  • Используйте экземпляр javax.swing.Timer для ускорения анимации; полный пример представлен в этом AnimationTest.

person trashgod    schedule 18.01.2015
comment
Если я не могу использовать getGraphics (), как бы вы предложили принудительное графическое обновление? Насколько мне известно, repaint () просто ставит в очередь обновление. - person Matthew; 18.01.2015
comment
@Matt: Вы можете взглянуть на paintImmediately(), но я обычно полагаюсь на подходы, показанные здесь и здесь. - person trashgod; 18.01.2015