如何在游戏循环中使用 repaint() 方法

Posted

技术标签:

【中文标题】如何在游戏循环中使用 repaint() 方法【英文标题】:How to use the repaint() method with a game loop 【发布时间】:2015-06-07 23:53:31 【问题描述】:

我正在尝试学习如何使用 java 创建游戏循环并每秒绘制一定次数的新屏幕。游戏循环运行良好,但是当我尝试使用 repaint() 调用paint 方法时,不会调用paint 方法。 这是我的代码:

import javax.swing.JButton;
import javax.swing.JComponent;
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.image.*;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;

public class mainFrame extends Thread
static boolean gameIsRunning = false;
MyCanvas2 myCanvas2 = new MyCanvas2();
static final int TARGET_FPS = 1;
static int x = 10;
static int y = 10;
static long startTime = 0;
static long elapsedTime = 0;
static long waitTime = 0;
public void createFrame()
  JFrame window = new JFrame("Out from Eden");
  window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  window.setBounds(30, 30, 700, 500);
  window.setVisible(true);
  gameIsRunning = true;
  gameStart();



public void setGame(boolean game)
    gameIsRunning = game;


public void gameStart()
    (new Thread(new mainFrame())).start();



public void run()
        while(gameIsRunning == true)
           startTime = System.nanoTime();
           myCanvas2.updateGame();
           myCanvas2.renderGame();
           elapsedTime = System.nanoTime() - startTime;
           waitTime = (TARGET_FPS*10) - (elapsedTime / 1000000);

           if (waitTime < 0) 
               waitTime = 5;
            


           try 
                    Thread.sleep(waitTime);
                catch (Exception e) 
                    e.printStackTrace();
               

        
    







class MyCanvas2 extends JPanel
private static int x = 100;
private static int y = 100;
static int i =0;
public void updateGame()



public void renderGame()
    repaint();
    System.out.println("Hello");



public void paint(Graphics g)

    Graphics2D g2 = (Graphics2D) g;
    g.setColor(Color.blue);
    g2.drawString("Test", x,y);
    System.out.println("Goodbye");



Hello 被打印了很多次,但 Goodbye 从未被调用,这让我相信 paint 方法从未被调用过。为什么repaint方法不会更新我的paint方法?

【问题讨论】:

【参考方案1】:

你永远不会将你的 MyCanvas2 对象添加到任何东西上,所以看起来没有任何东西被渲染,因此没有被绘制。因此,我的主要建议是,如果您希望渲染 MyCanvas2 JPanel,请将其添加到您的 GUI 中。另外:

您永远不会调用 createFrame(),因此永远不会创建 GUI (JFrame) -- 所以请确保您调用它。 您的gameStart() 方法,MainFrame 的一个实例方法会创建一个 MainFrame 实例,这是一个危险且不必要的递归——不要这样做。

其他方面的建议和说明:

您需要覆盖paintComponent绘制,因为这会自动为您提供双缓冲,从而获得更流畅的动画/图形。 不要忘记在你的覆盖中调用 super 的绘画方法,以免破坏绘画链。所以如果你重写了paintComponent方法,那么就调用superpaintComponent方法。 致电repaint() 并不能保证绘画一定会完成,因为这只是一个建议。如果重绘请求堆积如山,例如由于处理繁重或等待时间太短,则某些重绘请求将被忽略。 一般情况下,您应该避免扩展 Thread,而是实现 Runnable,或者在此处使用 Swing Timer。 您需要学习并遵循 Java 命名规则,以免混淆其他试图阅读和理解您的程序并为您提供帮助的人。

例如这里使用 Swing Timer 并做一个简单的动画:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class MainGui 

   private static final int TIMER_DELAY = 100;

   private static void createAndShowGui() 
      // create our JPanel
      MainGuiPanel mainPanel = new MainGuiPanel();
      // and create our ActionListener for the Swing Timer
      MyTimerListener myTimerListener = new MyTimerListener(mainPanel);

      // create a JFrame
      JFrame frame = new JFrame("Main Gui");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

      // add our JPanel to it
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);  // display the GUI

      // create and start the Swing Timer
      new Timer(TIMER_DELAY, myTimerListener).start();
   

   public static void main(String[] args) 
      SwingUtilities.invokeLater(new Runnable() 
         public void run() 
            createAndShowGui();
         
      );
   


@SuppressWarnings("serial")
class MainGuiPanel extends JPanel 
   private static final int PREF_W = 700;
   private static final int PREF_H = 500;
   private int textX = 0;
   private int textY = 10;

   @Override
   protected void paintComponent(Graphics g) 
      super.paintComponent(g);
      g.setColor(Color.blue);
      g.drawString("Test", textX, textY);
      System.out.println("Goodbye");
   

   @Override
   // make our GUI larger
   public Dimension getPreferredSize() 
      Dimension superSz = super.getPreferredSize();
      if (isPreferredSizeSet()) 
         return superSz;
      
      int prefW = Math.max(superSz.width, PREF_W);
      int prefH = Math.max(superSz.height, PREF_H);
      return new Dimension(prefW, prefH);
   

   public void moveText() 
      textX++;
      textY++;
      repaint();
   


// ActionListener for Swing Timer that drives the game loop
class MyTimerListener implements ActionListener 
   private MainGuiPanel mainGuiPanel;

   public MyTimerListener(pkg.MainGuiPanel mainGuiPanel) 
      this.mainGuiPanel = mainGuiPanel;
   

   @Override
   // this method gets called each time the timer "ticks"
   public void actionPerformed(ActionEvent e) 
      // make sure our GUI is not null and is displayed
      if (mainGuiPanel != null && mainGuiPanel.isDisplayable()) 
         // call method to animate. 
         mainGuiPanel.moveText(); // this method calls repaint
       else 
         ((Timer) e.getSource()).stop();
            
   

【讨论】:

我会将 MyCanvas2 对象添加到窗口中,但由于没有输出,甚至没有调用该方法,我想知道为什么不调用 paint 方法。 啊,是不是因为我有太多要重绘的调用而没有调用paint方法? 对不起,我不明白,错误在哪里,是在paint方法中吗?我应该打电话给超级吗?我只是想立即显示“再见”。 另外,createFrame() 是从不同的类调用的。【参考方案2】:

我有另一个问题闪烁组件。

我找到的解决方案是停止在事件调度线程之外请求重绘。 (来源:http://www.java-gaming.org/topics/double-buffering-with-a-jpanel/33983/view.html

这是我使用的代码:

SwingUtilities.invokeLater(() ->   // Runs update on the EDT (JDK8 isn't necessary)
    vue.refreshViewPane(); //All my components&redrawing soup is here
);

我在我的“刷新”方法中注释掉了对 repaint() 和 revalidate 的调用,它们甚至没有必要。 我只添加了一个布尔值,以确保如果“刷新”已经挂起,我会跳过整个汤,但无论如何,在我目前使用的 30 FPS 下,性能似乎很顺利。 “汤”内容基本上是在 JLayeredPane 中清空 3 个重叠的 JPanel(地形、单位、选择),并用代表我的游戏元素的其他图像填充它们。

我没有将 Canvas 用于我的图形,只使用 ImageIcon (这可能是糟糕的表现......我不知道。)无论如何,我的游戏是 回合制的,不是实时的,所以只要光标和单位移动流畅,我就不用太担心了。

我在这里回答主要是因为当我问到如何在 Java 游戏循环中有效地重新绘制时,Google 把我带到了这里,所以我认为我的两分钱可能会对某人有所帮助。


【讨论】:

以上是关于如何在游戏循环中使用 repaint() 方法的主要内容,如果未能解决你的问题,请参考以下文章

使用带有 actionPerformed 的 repaint() 方法

Java 中的 repaint() 方法在翻译后不会重新绘制我的 Rectangle

我如何将游戏循环放入我的代码中?

将逻辑线程与事件调度线程分离

如何在opengl游戏循环中使用双核?

如何在游戏循环中使用 addEventListener