在 Java AWT Canvas 中绘制点矩阵太慢

Posted

技术标签:

【中文标题】在 Java AWT Canvas 中绘制点矩阵太慢【英文标题】:Drawing point matrix too slow in Java AWT Canvas 【发布时间】:2015-05-06 12:15:00 【问题描述】:

我正在尝试实现 Conway's Game of Life 来自学 Java。来自 C/C++ 也很容易理解。到目前为止,我已经相当成功并且(我认为)我几乎完成了。代码似乎可以工作,但有两件事仍然困扰着我很多。

    我将要绘制的点存储在一个矩阵中,并在双 for-loop 内的 Canvas::paint(Graphics aGraphics) 方法中使用 aGraphics.drawLine(i, j, i, j); 绘制它们。这里的问题是画布的堆积是从左到右可见的。我确定问题是重复调用drawLine() 方法,因为当我切换嵌套for-loops 的顺序时,构建是自上而下的。将点存储在图像中会更好吗?

    另一个问题是游戏本身运行速度太快。在不停止中间绘制的情况下暂停程序的计算会有什么好主意?

这是我的 Canvas 类:

class GOLCanvas extends Canvas 
private int m_iWidth;
private int m_iHeight;
private int[][] m_iPoints;
private int[][] m_iNeighbours;
private double m_dSeed;
private Random m_cRnd;

GOLCanvas(int aWidth, int aHeight, double aSeed) 
    m_iWidth = aWidth;
    m_iHeight = aHeight;
    m_dSeed = aSeed;
    m_cRnd = new Random();
    m_cRnd.setSeed(m_cRnd.nextLong());
    m_iPoints = new int[m_iHeight][m_iWidth];
    m_iNeighbours = new int[m_iHeight][m_iWidth];   


public void init() 
    // init Points randomly


private int getRandomInt(double aProbability) 
    return (m_cRnd.nextDouble() < m_dSeed) ? 1 : 0;


public void countNeighbours () 
    // ditto name   


public void calcNextStep () 
    // ditto name

@Override
public void paint(Graphics aGraphics) 
    // **ANY IDEAS TO SPEED UP THIS PART HERE?**
    for(int i = 0; i < m_iHeight; i++) 
        for(int j = 0; j < m_iWidth; j++) 
            if (m_iPoints[i][j] == 1)
                aGraphics.drawLine(i, j, i, j);
            
        
    

【问题讨论】:

你可能应该重新设计它,运行一个带有游戏循环的Thread,创建一个更新方法(包含所有逻辑)并将其同步到每秒 N 次。另外我建议使用BufferingStrategy 创建一个自定义渲染方法,它会使游戏看起来更好(消除闪烁......等......) 【参考方案1】:

查看您的代码,我建议将设计更改为动态呈现的Canvasrepaint 未实现多缓冲,可能会导致闪烁。另外,关于您的游戏运行速度很快,您需要在自己的Thread(不是主线程)中运行游戏,然后实现update 方法并使用Thread.sleep 将其同步到每秒N 次。

设计可以是这样的:

public class Game extends Canvas implements Runnable 

    // resolution
    public static final int     WIDTH         = 640;
    public static final int     HEIGHT        = 480;

    // window title
    private static final String TITLE         = "Title";

    /**
     * Number of logical/physical updates per real second
     */
    private static final int    UPDATE_RATE   = 60;

    /**
     * Number of rendering buffers
     */
    private static final int    BUFFERS_COUNT = 3;

    /**
     * Value of a second in NanoSeconds DO NOT CHANGE!
     */
    private static final long   NANOS_IN_SEC  = 1000000000L;

    /**
     * Update interval in double precision NanoSeconds DO NOT CHANGE!
     */
    private static final double UPDATE_SCALE  = (double) NANOS_IN_SEC / UPDATE_RATE;

    private JFrame              window;
    private Thread              gameThread;
    private boolean             running;

    // temp values 
    int                         x             = 0;
    int                         y             = 0;

    ////////////////

    public Game(JFrame window) 
        this.window = window;
        this.running = false;

        setPreferredSize(new Dimension(WIDTH, HEIGHT));

        this.window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        // properly ends the game by calling stop when window is closed
        this.window.addWindowListener(new WindowAdapter() 
            public void windowClosing(WindowEvent e) 
                stop();
                super.windowClosing(e);
                System.exit(0);
            
        );

        this.window.getContentPane().add(this);
        this.window.setResizable(false);
        this.window.pack();
        this.window.setLocationRelativeTo(null);
        this.window.setVisible(true);
    

    // starts the game
    public synchronized void start() 
        if (running)
            return;

        running = true;
        gameThread = new Thread(this);
        gameThread.start();
        System.out.println("Game thread started");
        System.out.println("UPDATE_RATE: " + UPDATE_RATE);
    

    // ends the game
    public synchronized void stop() 
        if (!running)
            return;

        running = false;
        boolean retry = true;
        while (retry) 
            try 
                gameThread.join();
                retry = false;
                System.out.println("Game thread stoped");
             catch (InterruptedException e) 
                System.out.println("Failed sopping game thread, retry in 1 second");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e1) 
                    e1.printStackTrace();
                
            
        
    

    private void update() 
        // this will run UPDATE_RATE times a second
        x++;
        y++;
    

    private void render() 
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) 
            createBufferStrategy(BUFFERS_COUNT);
            return;
        

        Graphics2D g2d = (Graphics2D) bs.getDrawGraphics().create();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        clear(g2d, 0);

        // render here
        g2d.setColor(Color.red);
        g2d.fillRect(x, y, 50, 50);
        //////////////

        g2d.dispose();

        bs.show();
    

    private void clear(Graphics2D g2d, int shade) 
        g2d.setColor(new Color(shade, shade, shade));
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
    

    // game loop thread
    public void run() 
        long startTime = System.currentTimeMillis();
        long tick = 1000;

        int upd = 0;
        int fps = 0;

        double updDelta = 0;

        long lastTime = System.nanoTime();

        while (running) 
            long now = System.nanoTime();
            updDelta += (now - lastTime) / UPDATE_SCALE;
            lastTime = now;

            while (updDelta > 1) 
                update();
                upd++;
                updDelta--;
            
            render();
            fps++;

            if (System.currentTimeMillis() - startTime > tick) 
                window.setTitle(TITLE + " || Upd: " + upd + " | Fps: " + fps);
                upd = 0;
                fps = 0;
                tick += 1000;
            

            try 
                Thread.sleep(5); // always a good idea to let is breath a bit
             catch (InterruptedException e) 
                e.printStackTrace();
            

        

    

用法:

public static void main(String[] args) 
    try 
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
     catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
    
    EventQueue.invokeLater(() -> 
        new Game(new JFrame()).start();
    );

显然,这只是一种方法(还有很多其他方法),请随时根据您的需要进行调整。 我花了大约 20 分钟来写,所以我希望它没有白费,这会有所帮助,如果你发现代码中有任何你无法修复的错误,请告诉我(我有点写它但没有检查它是否有效)。

【讨论】:

非常感谢您的努力。我将尝试调用您的更改。我需要稍微调整一下,因为我使用了一个继承自框架的类,该框架包含游戏画布类的对象,其对象为 ButtonTextFieldWindowAdapter 等。我仅将画布用于计算和输出。但这不应该是一个问题。这有点离题但我看到你使用摇摆类(JFrame)而不是awt,因为我对此很陌生,你介意告诉我你在摇摆类中更喜欢什么awt? @DavidWright 我将把this 放在这里。你可以自己判断。【参考方案2】:

我会查看this link 和this one

基本上归结为使用 BufferedImages。至于减慢程序的一部分,您可以使用Thread.sleep(1000),其中 1000 等于 1 秒。

【讨论】:

@DavidWright 坏主意。 NOT 让主 Thread 休眠。 Dima 是正确的,你应该只在不是主线程的线程上使用 Thread.sleep

以上是关于在 Java AWT Canvas 中绘制点矩阵太慢的主要内容,如果未能解决你的问题,请参考以下文章

Java AWT 图形界面编程Canvas 组件中使用 Graphics 绘图 ① ( AWT 绘图线程 | Component 绘图函数 )

Java AWT 图形界面编程Canvas 组件中使用 Graphics 绘图 ① ( AWT 绘图线程 | Component 绘图函数 )

Java AWT 图形界面编程Canvas 组件中使用 Graphics 绘图 ④ ( AWT 绘图窗口闪烁问题 )

Java AWT 图形界面编程Canvas 组件中使用 Graphics 绘图 ④ ( AWT 绘图窗口闪烁问题 )

确保在执行其他操作之前在屏幕上绘制 AWT Canvas

Java AWT 图形界面编程Canvas 组件中使用 Graphics 绘图 ③ ( 绘图步骤 | 绘图案例 )