如何在游戏循环中使用 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() 方法