在 Java 中以我的方式解决 repaint() 问题
我打算写一个简单的太空射击游戏。我读到 repaint() 方法只是一个请求,并且不会在每次调用时执行。我相信我注意到了这一点的影响,因为当我移动我的宇宙飞船时,它往往会稍微滞后。目前,我只是在 JPanel 的 PaintComponent() 方法中绘制我的船,并定期调用 repaint() (我的面板也是 Runnable)。鉴于 repaint() 可能会搞砸我,我正在尝试找到一种方法来解决它,但是我已经没有想法了。到目前为止我的代码:
private void renderGraphics() {
if (MyImage == null) {
MyImage = new BufferedImage(getPreferredSize().width,
getPreferredSize().height, BufferedImage.TYPE_INT_RGB);
}
MyGraphics = MyImage.getGraphics();
MyGraphics.setColor(Color.BLACK);
MyGraphics.fillRect(0, 0, getPreferredSize().width, getPreferredSize().height);
MyGraphics.drawImage(ship.getImage(), ship.getCurrentX(), ship.getCurrentY(), null);
}
想法是创建我自己的图形,然后让 JPanel 绘制它,并在我的 run() 方法中继续调用它而不是 repaint() ,但是我不知道如何做到这一点。我会感谢有关此事的任何意见。
I'm planning to write a simple spaceshooter. I have read that the repaint() method is only a request, and it doesn't execute every time it's called. I believe I'm noticing the effects of this, as my spaceship tends to lag ever so slightly when I'm moving it. Currently I'm simply drawing my ship in a a JPanel's paintComponent() method, and keep calling repaint() on regular intervals (my panel's also Runnable). Seeing as repaint() may potentially screw me over, I'm trying to find a way to work arround it, however I've ran out of ideas. The code I have so far:
private void renderGraphics() {
if (MyImage == null) {
MyImage = new BufferedImage(getPreferredSize().width,
getPreferredSize().height, BufferedImage.TYPE_INT_RGB);
}
MyGraphics = MyImage.getGraphics();
MyGraphics.setColor(Color.BLACK);
MyGraphics.fillRect(0, 0, getPreferredSize().width, getPreferredSize().height);
MyGraphics.drawImage(ship.getImage(), ship.getCurrentX(), ship.getCurrentY(), null);
}
The idea was to create my own graphics, and then make the JPanel draw it, and keep calling this instead of repaint() in my run() method, however I have no idea how to do that. I'd appriciate any input on the matter.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
有多种方法可以解决这个问题。
最好的可能是使用 BufferStrategy 并借鉴它,其中我包含了一个应该适合您的代码片段。
您可以更进一步,完全放弃 Swing,只使用 Frame/BufferStrategy。我的问题中有一个完整的工作示例(从中获取并改编了代码片段):
AWT 自定义渲染 - 捕获平滑的调整大小并消除调整大小闪烁
无论如何,这里有一个您应该能够实现的
BufferStrategy
只需加入:There are multiple ways to approach this.
The best is probably to use
BufferStrategy
and draw to that, of which I have included a code snippet that should work for you.You can take this one step further and abandon Swing altogether, just using Frame/BufferStrategy. There is a fully working example (from which the code snippet was taken and adapted) in my question here:
AWT custom rendering - capture smooth resizes and eliminate resize flicker
Anyway, here is an implementation
BufferStrategy
that you should be able to just drop in:任何绘图仍将在 Swing 线程中执行,因此无论您尝试解决什么问题,都不会有所帮助。
确保您没有在摆动线程中进行任何冗长的计算,这可能会在需要执行时阻止重绘的执行
any drawing will still be performed in the Swing Thread, so no matter what you try work around, it wont help.
Make sure you are not doing any lengthy calculations in the swing thread, this may be stopping repaint from being executed as soon as it needs to be executed
将所有逻辑分为两部分。静态和动态。 (例如,海洋和移动的船。船舶在静态海洋图像上改变形状/位置)
在图像中绘制一次静态内容,然后在您的paintComponent() 中使用该图像。在静态图像之后调用动态零件绘制。
使用 setClip() 限制重画区域。
Separate all the logic into 2 parts. Static and Dynamic. (e.g. sea and moving ship. Ship changes shape/location on a static image of sea)
Draw static content in an image once and use the image in your paintComponent(). Call dynamic parts painting after the static image.
Use setClip() to restrict repainting areas.
不带任何参数调用 repaint 意味着整个面板被重新绘制。
如果您需要重新绘制屏幕的某些部分(宇宙飞船已移动到不同的位置),您应该确保只重新绘制屏幕的这些部分。保持不变的区域不应被触摸。
重绘采用应重绘的矩形的坐标。移动船舶时,您应该知道船舶的旧坐标和船舶应移动到的坐标。
这通常比调用不带参数的 repaint() 快得多。然而,您需要付出额外的努力来记住船舶的最后位置,并且必须能够计算船舶的新位置。
另请参阅:http://download.oracle.com/javase/tutorial/ uiswing/painting/index.html - 特别是步骤 3
Calling repaint without any arguments means that the whole panel is repainted.
If you need to repaint parts of the screen (the spaceship has moved to a different location) you should make shure that only those parts of the screen are repainted. The areas that stay the same should not be touched.
Repaint takes coordinates of a rectangle that should be repainted. When moving the ship you should know the old coordinates of the ship and the coordinates the ship should move to.
This is usually much faster than calling repaint() without arguments. However you have extra effort to remember the last position of the ship and must be able to calculate the new position of the ship.
See also: http://download.oracle.com/javase/tutorial/uiswing/painting/index.html - especially step 3
仅适用于您在此处发布的代码:
1/ 如果您想显示 Image/ ImageIcon,那么最好、最简单的方法就是使用标签< /a>
2/ 正如您提到的
Runnable{...}.start();
Swing 是简单的线程,所有到 GUI 的输出都必须在 EDT 上完成;你必须看看 Swing 中的并发,结果是所有BackGround Task(s)
的输出必须包装到invokeLater()
中,如果性能存在问题,则包装到invokeAndWait()
中3/如果你要切换(在
JComponents
之间)/添加/删除/更改Layout
,那么你必须调用revalidate() + repaint()
作为具体代码块编辑的最后一行:
脏黑客将是 paintImmediately()
Just for code that you post here:
1/ if you want to display Image/ImageIcon, then the best and easiest way is to Use Labels
2/ as you mentioned
Runnable{...}.start();
Swing is simple threaded and all output to GUI must be done on EDT; you have to look at Concurrency in Swing, result is that all output fromBackGround Task(s)
must be wrapped intoinvokeLater()
, and if is there problem with perfomancie then intoinvokeAndWait()
3/ if you be switch (between
JComponents
)/add/delete/changeLayout
then you have to callrevalidate() + repaint()
as last lines in concrete code blockEDIT:
dirty hack would be paintImmediately()
它会将多个 repaint() 请求合并为一个请求,以提高效率。
然后发布您的 SSCCE 来演示此问题。我怀疑问题出在你的代码上。
关于您接受的解决方案,请查看 Charles 最后发布的文章: Swing/JFrame 与 AWT/Frame 在 EDT 之外进行渲染比较 Swing 与 AWT 解决方案。
It consolidates multiple repaint() requests into one to be more efficient.
Then post your SSCCE that demonstrates this problem. I suspect the problem is your code.
Regarding the solution you accepted, take a look at Charles last posting: Swing/JFrame vs AWT/Frame for rendering outside the EDT comparing Swing vs AWT solutions.