JPanel闪烁问题
JPanel flickering issue
对于我的游戏,我绘制到 java.awt.Image
然后将图像绘制到 JPanel 上。我这样做有几个原因,主要是因为我不希望游戏渲染在 EDT 上占用 cpu 个周期,并且为了便携性。
使用
时出现问题导致 java.awt.JPanel
闪烁
graphics#drawImage(Image img, int x, int y, int width, int height,
ImageObserver observer)
.
然而,
graphics#drawImage(Image img, int x, int y, ImageObserver observer)
没有引起这个问题。
这是我的代码:
Sandbox.java
public class Sandbox implements Paintable {
public static void main(String[] args) throws Exception {
GameWindow window = new GameWindow("Test", 800, 600);
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.setVisible(true);
boolean running = true;
while(running) {
sandbox.update();
screen.getPaintBuffer().clear();
screen.getPaintBuffer().paint(sandbox);
screen.repaint();
Thread.sleep(1000 / 60);
}
}
private int x = 0, y = 0;
public void update() {
x++;
y++;
}
public void paint(Graphics g) {
g.drawRect(x, y, 50, 50);
}
}
GameWindow.java
public class GameWindow extends JFrame {
public GameWindow(String title, int width, int height) {
setTitle(title);
setSize(width, height);
setResizable(false);
setLocationByPlatform(true);
setDefaultCloseOperation(3);
}
}
GameScreen.java
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
public void paint(Graphics g) {
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), null);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
Paintable.java
public interface Paintable {
public void paint(Graphics g);
}
ImageBuffer.java
public class ImageBuffer {
private final Image buffer;
private int width, height;
public ImageBuffer(int width, int height) {
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.width = width;
this.height = height;
}
public void paint(Paintable paintable) {
paintable.paint(buffer.getGraphics());
}
public void clear() {
buffer.getGraphics().clearRect(0, 0, width, height);
}
public Image getBuffer() {
return buffer;
}
}
将 GameScreen
的 paint
方法更改为...
- 替代
paintComponent
- 调用
super.paintComponent
以维护油漆链的合约
- 将
this
作为 ImageObsever
参数传递给 drawImage
例如...
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
查看 Painting in AWT and Swing and Performing Custom Painting 了解有关绘画工作原理的更多详细信息
已更新
基本问题是缩放问题...
您用来绘制的图像是 800x600
,但 GameScreen
实际上是 794x572
(在我的电脑上),这会导致图像被缩放。现在,Swing 的默认缩放比例并不漂亮,并且通常基于速度而不是质量。
现在,您可以通过多种方法来改进这一点,但一种快速的方法是应用一些更高的渲染提示,例如
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
g2d.dispose();
}
现在,我已经过火了,所以你可能想删除一些并看看有什么变化
已更新
呈现提示可能会减慢呈现过程,因此更好的解决方案可能是覆盖 GameScreen
的 getPreferredSize
方法和 return 预期大小
public static class GameScreen extends JPanel {
private ImageBuffer buffer;
private Dimension size;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
this.size = new Dimension(width, height);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
然后,而不是将大小传递给 GameWindow
,只需调用 pack
GameWindow window = new GameWindow("Test");
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.pack();
window.setVisible(true);
这样大家的尺码都一样
对于我的游戏,我绘制到 java.awt.Image
然后将图像绘制到 JPanel 上。我这样做有几个原因,主要是因为我不希望游戏渲染在 EDT 上占用 cpu 个周期,并且为了便携性。
使用
时出现问题导致java.awt.JPanel
闪烁
graphics#drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
.
然而,
graphics#drawImage(Image img, int x, int y, ImageObserver observer)
没有引起这个问题。
这是我的代码:
Sandbox.java
public class Sandbox implements Paintable {
public static void main(String[] args) throws Exception {
GameWindow window = new GameWindow("Test", 800, 600);
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.setVisible(true);
boolean running = true;
while(running) {
sandbox.update();
screen.getPaintBuffer().clear();
screen.getPaintBuffer().paint(sandbox);
screen.repaint();
Thread.sleep(1000 / 60);
}
}
private int x = 0, y = 0;
public void update() {
x++;
y++;
}
public void paint(Graphics g) {
g.drawRect(x, y, 50, 50);
}
}
GameWindow.java
public class GameWindow extends JFrame {
public GameWindow(String title, int width, int height) {
setTitle(title);
setSize(width, height);
setResizable(false);
setLocationByPlatform(true);
setDefaultCloseOperation(3);
}
}
GameScreen.java
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
public void paint(Graphics g) {
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), null);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
Paintable.java
public interface Paintable {
public void paint(Graphics g);
}
ImageBuffer.java
public class ImageBuffer {
private final Image buffer;
private int width, height;
public ImageBuffer(int width, int height) {
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.width = width;
this.height = height;
}
public void paint(Paintable paintable) {
paintable.paint(buffer.getGraphics());
}
public void clear() {
buffer.getGraphics().clearRect(0, 0, width, height);
}
public Image getBuffer() {
return buffer;
}
}
将 GameScreen
的 paint
方法更改为...
- 替代
paintComponent
- 调用
super.paintComponent
以维护油漆链的合约 - 将
this
作为ImageObsever
参数传递给drawImage
例如...
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
查看 Painting in AWT and Swing and Performing Custom Painting 了解有关绘画工作原理的更多详细信息
已更新
基本问题是缩放问题...
您用来绘制的图像是 800x600
,但 GameScreen
实际上是 794x572
(在我的电脑上),这会导致图像被缩放。现在,Swing 的默认缩放比例并不漂亮,并且通常基于速度而不是质量。
现在,您可以通过多种方法来改进这一点,但一种快速的方法是应用一些更高的渲染提示,例如
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
g2d.dispose();
}
现在,我已经过火了,所以你可能想删除一些并看看有什么变化
已更新
呈现提示可能会减慢呈现过程,因此更好的解决方案可能是覆盖 GameScreen
的 getPreferredSize
方法和 return 预期大小
public static class GameScreen extends JPanel {
private ImageBuffer buffer;
private Dimension size;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
this.size = new Dimension(width, height);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
然后,而不是将大小传递给 GameWindow
,只需调用 pack
GameWindow window = new GameWindow("Test");
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.pack();
window.setVisible(true);
这样大家的尺码都一样