框架在不同时间绘画?

我的游戏中有一个非常烦人的错误,帧的底部似乎比帧的顶部更早渲染,我不确定它为什么会发生。

我正在使用一个JPanel来重绘每个游戏循环,我的游戏循环设置为60FPS。 在绘画function开始时,它将玩家X和Y设置为变量,然后用于绘制每个元素,(因为它们是相对于玩家绘制的,因为相机跟随玩家)

如果需要,我可以发布任何代码,以帮助诊断问题,但代码太多,我不知道问题的哪一部分; 因此,根据我的解释,我主要是在询问是否有人知道可能出现的问题。

我无法发布问题的video,因为它没有接收video,但是可以随意在游戏中看到它, 链接到游戏 ,并在此处进行病毒扫描

如果您下载游戏,则在打开游戏时,输入名称(或保留默认值),并在询问服务器时单击“否”。 当您使用WASD移动时,您应该在屏幕上的某处看到水平线闪烁效果。 如果游戏没有打开,请再试一次,它很可能无法打开(这是一个已知的错误,我计划很快修复它)

对不起的解释感到抱歉,我发现很难描述我的问题。 我已经坚持了几个小时,甚至在搜索互联网之后也找不到解决方案。

编辑:整个源代码: 这里

EDIT2:它需要位于此处的kryonet lib

EDIT3: Github

这是两个基本原理的演示,但基本上是一系列缓冲,旨在减少paintComponent的工作量……

一般来说,将图像BLIT到显卡更快然后就是“绘制”像素,考虑到这一点,这个例子做了两件事……

首先,它预先渲染背景地图。 此示例仅在运行时随机生成地图,但创建的地图大约是全高清的4倍。

其次,它雇佣它自己的双缓冲。 “view”有两个缓冲区,一个是active ,一个是updateactive缓冲区被绘制到屏幕上, update缓冲区是Engine用来呈现输出的当前状态的…

这很重要,因为视图的缓冲区始终与视图的大小相同,因此您永远不会渲染任何不在屏幕外显示的内容。

此示例将其他内容(如动画,特效)的渲染推送到Engine ……

我的这个例子在我的30英寸显示器上以2560×1600运行时几乎没有问题,运动增量非常小,所以我可以更快地平移,使它变大会使这些问题无效……

 import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class TestRender { public static void main(String[] args) { new TestRender(); } public TestRender() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public interface View { public BufferedImage switchBuffers(); public int getWidth(); public int getHeight(); } public enum KeyState { UP, DOWN, LEFT, RIGHT; } public class TestPane extends JPanel implements View { private Engine engine; private BufferedImage active; private BufferedImage update; private ReentrantLock lckBuffer; public TestPane() { lckBuffer = new ReentrantLock(); initBuffers(); engine = new Engine(this); engine.gameStart(); InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up_released"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down_released"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left_released"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right_released"); ActionMap am = getActionMap(); am.put("up_pressed", new AddState(engine, KeyState.UP)); am.put("up_released", new RemoveState(engine, KeyState.UP)); am.put("down_pressed", new AddState(engine, KeyState.DOWN)); am.put("down_released", new RemoveState(engine, KeyState.DOWN)); am.put("left_pressed", new AddState(engine, KeyState.LEFT)); am.put("left_released", new RemoveState(engine, KeyState.LEFT)); am.put("right_pressed", new AddState(engine, KeyState.RIGHT)); am.put("right_released", new RemoveState(engine, KeyState.RIGHT)); } protected void initBuffers() { if (getWidth() > 0 && getHeight() > 0) { try { lckBuffer.lock(); active = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); update = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); } finally { lckBuffer.unlock(); } } } @Override public void invalidate() { super.invalidate(); initBuffers(); } @Override public Dimension getPreferredSize() { return new Dimension(1920, 1080); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); try { lckBuffer.lock(); if (active != null) { g2d.drawImage(active, 0, 0, this); } } finally { lckBuffer.unlock(); } g2d.dispose(); } @Override public BufferedImage switchBuffers() { try { lckBuffer.lock(); BufferedImage tmp = active; active = update; update = tmp; repaint(); } finally { lckBuffer.unlock(); } return update; } } public static class Engine { public static final int MAP_WIDTH = 15 * 4; public static final int MAP_HEIGHT = 9 * 4; public static final int X_DELTA = 32; public static final int Y_DELTA = 32; //This value would probably be stored elsewhere. public static final double GAME_HERTZ = 60.0; //Calculate how many ns each frame should take for our target game hertz. public static final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ; //We will need the last update time. static double lastUpdateTime = System.nanoTime(); //Store the last time we rendered. static double lastRenderTime = System.nanoTime(); //If we are able to get as high as this FPS, don't render again. final static double TARGET_FPS = GAME_HERTZ; final static double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS; //Simple way of finding FPS. static int lastSecondTime = (int) (lastUpdateTime / 1000000000); public static int fps = 60; public static int frameCount = 0; private boolean isGameFinished; private BufferedImage map; private BufferedImage tiles[]; private View view; private int camX, camY; private Set keyStates; public Engine(View bufferRenderer) { keyStates = new HashSet<>(4); this.view = bufferRenderer; tiles = new BufferedImage[7]; Random rnd = new Random(); map = new BufferedImage(MAP_WIDTH * 128, MAP_HEIGHT * 128, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = map.createGraphics(); for (int row = 0; row < MAP_HEIGHT; row++) { for (int col = 0; col < MAP_WIDTH; col++) { int tile = rnd.nextInt(7); int x = col * 128; int y = row * 128; g2d.drawImage(getTile(tile), x, y, null); } } g2d.dispose(); } protected BufferedImage getTile(int tile) { BufferedImage img = tiles[tile]; if (img == null) { try { img = ImageIO.read(getClass().getResource("/" + tile + ".png")); img = img.getSubimage(0, 64, 128, 128); } catch (IOException ex) { ex.printStackTrace(); } tiles[tile] = img; } return img; } public void gameStart() { Thread gameThread = new Thread() { // Override run() to provide the running behavior of this thread. @Override public void run() { gameLoop(); } }; gameThread.setDaemon(false); // Start the thread. start() calls run(), which in turn calls gameLoop(). gameThread.start(); } public void gameLoop() { BufferedImage buffer = view.switchBuffers(); // initial buffer... while (!isGameFinished) { double now = System.nanoTime(); lastUpdateTime += TIME_BETWEEN_UPDATES; gameUpdate(buffer); renderBuffer(buffer); buffer = view.switchBuffers(); // Push the buffer back frameCount++; lastRenderTime = now; int thisSecond = (int) (lastUpdateTime / 1000000000); if (thisSecond > lastSecondTime) { fps = frameCount; frameCount = 0; lastSecondTime = thisSecond; } //Yield until it has been at least the target time between renders. This saves the CPU from hogging. while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) { //Thread.yield(); //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it. //You can remove this line and it will still work (better), your CPU just climbs on certain OSes. //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this. try { Thread.sleep(1); } catch (Exception e) { } now = System.nanoTime(); } } } protected void renderBuffer(BufferedImage buffer) { if (buffer != null) { Graphics2D g2d = buffer.createGraphics(); g2d.drawImage(map, camX, camY, null); g2d.dispose(); } } protected void gameUpdate(BufferedImage buffer) { // render transient effects here if (keyStates.contains(KeyState.DOWN)) { camY -= Y_DELTA; } else if (keyStates.contains(KeyState.UP)) { camY += Y_DELTA; } if (camY < -(map.getHeight() - view.getHeight())) { camY = -(map.getHeight() - view.getHeight()); } else if (camY > 0) { camY = 0; } if (keyStates.contains(KeyState.RIGHT)) { camX -= Y_DELTA; } else if (keyStates.contains(KeyState.LEFT)) { camX += Y_DELTA; } if (camX < -(map.getWidth() - view.getWidth())) { camX = -(map.getWidth() - view.getWidth()); } else if (camX > 0) { camX = 0; } } public void addKeyState(KeyState state) { keyStates.add(state); } public void removeKeyState(KeyState state) { keyStates.remove(state); } } public class AddState extends AbstractAction { private Engine engine; private KeyState state; public AddState(Engine engine, KeyState state) { this.engine = engine; this.state = state; } @Override public void actionPerformed(ActionEvent e) { engine.addKeyState(state); } } public class RemoveState extends AbstractAction { private Engine engine; private KeyState state; public RemoveState(Engine engine, KeyState state) { this.engine = engine; this.state = state; } @Override public void actionPerformed(ActionEvent e) { engine.removeKeyState(state); } } } 

在我的实验过程中,我注意到,如果你试图将内容“超出”缓冲区的范围(即允许地图顶部在缓冲区内滑落),你会得到令人讨厌的油漆效果,所以要小心你总是在缓冲区的可视区域内渲染……

可能还有其他需要整理的领域,但这certificate了基础……

对不起,在我之前的回答中用完了房间:P

使用VolitileImage示例进行更新

现在,在你兴奋之前,我对VolitileImage体验比创建这个例子的时间大约多20分钟,所以它可能不是最好的例子……

滚动

 import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.io.IOException; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class TestVolitile { public static void main(String[] args) { new TestVolitile(); } public TestVolitile() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new ViewPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public interface View { public VolatileImage getOffscreenBuffer(); public void show(VolatileImage img); public boolean isIncompatiable(VolatileImage img); public int getWidth(); public int getHeight(); } public enum KeyState { UP, DOWN, LEFT, RIGHT; } public class ViewPane extends JPanel implements View { private VolatileImage offscreen; private BufferedImage onscreen; private ReentrantLock lckBuffers; private Engine engine; public ViewPane() { lckBuffers = new ReentrantLock(); engine = new Engine(this); engine.gameStart(); InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right_pressed"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up_released"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down_released"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left_released"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right_released"); ActionMap am = getActionMap(); am.put("up_pressed", new AddState(engine, KeyState.UP)); am.put("up_released", new RemoveState(engine, KeyState.UP)); am.put("down_pressed", new AddState(engine, KeyState.DOWN)); am.put("down_released", new RemoveState(engine, KeyState.DOWN)); am.put("left_pressed", new AddState(engine, KeyState.LEFT)); am.put("left_released", new RemoveState(engine, KeyState.LEFT)); am.put("right_pressed", new AddState(engine, KeyState.RIGHT)); am.put("right_released", new RemoveState(engine, KeyState.RIGHT)); } @Override public void invalidate() { super.invalidate(); onscreen = null; // offscreen = null; } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); try { lckBuffers.lock(); // Make sure the buffer is okay for painting.... if (onscreen != null) { Graphics2D g2d = (Graphics2D) g.create(); g2d.drawImage(onscreen, 0, 0, this); g2d.dispose(); } } finally { lckBuffers.unlock(); } } protected VolatileImage createVolatileImage(int width, int height, int transparency) { GraphicsConfiguration gc = getGraphicsConfiguration(); VolatileImage image = null; if (gc != null && width > 0 && height > 0) { image = gc.createCompatibleVolatileImage(width, height, transparency); int valid = image.validate(gc); if (valid == VolatileImage.IMAGE_INCOMPATIBLE) { image = this.createVolatileImage(width, height, transparency); } } return image; } @Override public VolatileImage getOffscreenBuffer() { if (isIncompatiable(offscreen)) { offscreen = createVolatileImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); } return offscreen; } @Override public void show(VolatileImage img) { try { lckBuffers.lock(); GraphicsConfiguration gc = getGraphicsConfiguration(); if (gc != null) { if (onscreen == null) { onscreen = gc.createCompatibleImage(getWidth(), getHeight(), Transparency.TRANSLUCENT); } if (isOkay(img)) { Graphics2D g2d = onscreen.createGraphics(); g2d.drawImage(img, 0, 0, this); g2d.dispose(); repaint(); } } } finally { lckBuffers.unlock(); } } @Override public boolean isIncompatiable(VolatileImage offscreen) { boolean isIncompatiable = true; GraphicsConfiguration gc = getGraphicsConfiguration(); if (gc != null) { if (offscreen != null) { if (offscreen.getWidth() == getWidth() && offscreen.getHeight() == getHeight()) { if (offscreen.validate(gc) != VolatileImage.IMAGE_INCOMPATIBLE) { isIncompatiable = false; } } } } return isIncompatiable; } public boolean isOkay(VolatileImage buffer) { boolean isOkay = false; GraphicsConfiguration gc = getGraphicsConfiguration(); if (gc != null) { if (buffer != null) { if (buffer.getWidth() == getWidth() && buffer.getHeight() == getHeight()) { if (buffer.validate(gc) == VolatileImage.IMAGE_OK) { isOkay = true; } } } } return isOkay; } } public static class Engine { public static final int MAP_WIDTH = 15 * 4; public static final int MAP_HEIGHT = 9 * 4; public static final int X_DELTA = 4; public static final int Y_DELTA = 4; public boolean isGameFinished = false; //This value would probably be stored elsewhere. public static final long GAME_HERTZ = 25; //Calculate how many ns each frame should take for our target game hertz. public static final long TIME_BETWEEN_UPDATES = Math.round(1000000000 / (double) GAME_HERTZ); //We will need the last update time. static long lastUpdateTime = System.nanoTime(); //Store the last time we rendered. static long lastRenderTime = System.nanoTime(); //If we are able to get as high as this FPS, don't render again. final static long TARGET_FPS = GAME_HERTZ; final static long TARGET_TIME_BETWEEN_RENDERS = Math.round(1000000000 / (double) TARGET_FPS); //Simple way of finding FPS. static int lastSecondTime = (int) (lastUpdateTime / 1000000000); public int fps = 60; public int frameCount = 0; private View view; private int camX, camY; private Set keyStates; private BufferedImage map; private BufferedImage tiles[]; public Engine(View view) { this.view = view; keyStates = new HashSet<>(4); tiles = new BufferedImage[22]; Random rnd = new Random(); GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); map = gc.createCompatibleImage(MAP_WIDTH * 128, MAP_HEIGHT * 128, Transparency.TRANSLUCENT) Graphics2D g2d = map.createGraphics(); for (int row = 0; row < MAP_HEIGHT; row++) { for (int col = 0; col < MAP_WIDTH; col++) { int tile = rnd.nextInt(22); int x = col * 128; int y = row * 128; g2d.drawImage(getTile(tile), x, y, null); } } g2d.dispose(); } protected BufferedImage getTile(int tile) { BufferedImage img = tiles[tile]; if (img == null) { try { img = ImageIO.read(getClass().getResource("/" + tile + ".png")); img = img.getSubimage(0, 64, 128, 128); img = toCompatiableImage(img); } catch (IOException ex) { ex.printStackTrace(); } tiles[tile] = img; } return img; } public void gameStart() { Thread gameThread = new Thread() { // Override run() to provide the running behavior of this thread. @Override public void run() { gameLoop(); } }; // Start the thread. start() calls run(), which in turn calls gameLoop(). gameThread.start(); } public void gameLoop() { while (!isGameFinished) { long startTime = System.nanoTime(); lastUpdateTime += TIME_BETWEEN_UPDATES; updateGame(); renerGame(); frameCount++; lastRenderTime = startTime; long duration = System.nanoTime() - startTime; int thisSecond = (int) (lastUpdateTime / 1000000000); if (thisSecond > lastSecondTime) { fps = frameCount; frameCount = 0; lastSecondTime = thisSecond; } if (duration < TARGET_TIME_BETWEEN_RENDERS) { duration = TARGET_TIME_BETWEEN_RENDERS - duration; long milli = TimeUnit.NANOSECONDS.toMillis(duration); try { Thread.sleep(milli); } catch (InterruptedException ex) { } } } } protected void updateGame() { if (keyStates.contains(KeyState.DOWN)) { camY -= Y_DELTA; } else if (keyStates.contains(KeyState.UP)) { camY += Y_DELTA; } if (camY < -(map.getHeight() - view.getHeight())) { camY = -(map.getHeight() - view.getHeight()); } else if (camY > 0) { camY = 0; } if (keyStates.contains(KeyState.RIGHT)) { camX -= Y_DELTA; } else if (keyStates.contains(KeyState.LEFT)) { camX += Y_DELTA; } if (camX < -(map.getWidth() - view.getWidth())) { camX = -(map.getWidth() - view.getWidth()); } else if (camX > 0) { camX = 0; } } protected void renerGame() { VolatileImage buffer = view.getOffscreenBuffer(); if (buffer != null) { Graphics2D g2d = null; do { if (view.isIncompatiable(buffer)) { buffer = view.getOffscreenBuffer(); } try { g2d = buffer.createGraphics(); } finally { if (g2d != null) { g2d.drawImage(map, camX, camY, null); // Draw effects here... FontMetrics fm = g2d.getFontMetrics(); g2d.setColor(Color.RED); g2d.drawString(Integer.toString(fps), 0, fm.getAscent()); g2d.dispose(); } } } while (buffer.contentsLost()); view.show(buffer); } } public void addKeyState(KeyState state) { keyStates.add(state); } public void removeKeyState(KeyState state) { keyStates.remove(state); } protected BufferedImage toCompatiableImage(BufferedImage img) { GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); BufferedImage compImg = gc.createCompatibleImage(img.getWidth(), img.getHeight(), img.getTransparency()); Graphics2D g2d = compImg.createGraphics(); g2d.drawImage(img, 0, 0, null); g2d.dispose(); return compImg; } } public class AddState extends AbstractAction { private Engine engine; private KeyState state; public AddState(Engine engine, KeyState state) { this.engine = engine; this.state = state; } @Override public void actionPerformed(ActionEvent e) { engine.addKeyState(state); } } public class RemoveState extends AbstractAction { private Engine engine; private KeyState state; public RemoveState(Engine engine, KeyState state) { this.engine = engine; this.state = state; } @Override public void actionPerformed(ActionEvent e) { engine.removeKeyState(state); } } } 

现在,如果这仍然给你带来麻烦,你可以尝试更换……

 g2d.drawImage(map, camX, camY, null); 

与…

 BufferedImage clip = map.getSubimage(camX * -1, camY * -1, view.getWidth(), view.getHeight()); g2d.drawImage(clip, 0, 0, null); 

这减少了图形缓冲区中可能存在的任何可能的“悬垂”。 不是100%肯定它是否会产生影响,但它不会伤害。 你可以在两个例子中做到这一点……

如果你仍然发生撕裂/剪切,你可以调查试图禁用directx或opengl渲染管道(这些是命令行选项),看它是否有所作为……

看看VolatileImage , 游戏编程维基,Java:教程:VolatileImage和Java 2D:硬件加速 – 第1部分 – 易失性图像以获取更多创意。

我也改变了你的游戏循环“计时”循环一点,不知道它是否会有所作为,但Thread.sleep(1)总是吓到我…

更新

我已经更新了代码,因此只有一个VolatileImagepaintComponent使用实际的BufferedImage ,而不是对GraphicsConfiguration进行优化。 这样可以确保始终根据需要将内容绘制为唯一更新(在show方法中)。 应该有助于防止闪烁……

我还优化了所有加载的图块,以便为GraphicsConfiguration进行优化,这意味着它们的颜色模型在渲染到屏幕时不需要转换,因为它们是相同的,应该有助于节省一段时间……

有关更多详细信息,请查看toCompatiableImage方法

当进行更改时,JPanel不会等待您调用repaint()。

为了防止这种情况,我认为您可以按如下方式使用RepaintManager: –

 RepaintManager.currentManager(yourJPanel).markCompletelyClean(yourJPanel) 

还有另一种技术使用两个不同的JPanel实例。 基本的想法是这样的:

  • 设置一个可见和其他不可见。
  • 反映对不可见的更改(当不可见时,面板不会自行更新)
  • 完成游戏循环迭代后,切换其可见性并重新开始

不过,我不知道第二个会如何影响性能。

最后,一个BufferStrategy实现,这就像你要获得的硬件一样接近……

请参阅BufferStrategy

通常,当您希望完全控制绘制过程时,可以执行此操作…

 import java.awt.BufferCapabilities; import java.awt.Canvas; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.io.IOException; import java.util.HashSet; import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW; import javax.swing.JFrame; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class TestVolitile { public static void main(String[] args) { new TestVolitile(); } public TestVolitile() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new ViewPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public interface View { public int getWidth(); public int getHeight(); public BufferStrategy getBufferStrategy(); } public enum KeyState { UP, DOWN, LEFT, RIGHT; } public class ViewPane extends Canvas implements View { private VolatileImage offscreen; private BufferedImage onscreen; private Engine engine; public ViewPane() { engine = new Engine(this); engine.gameStart(); setFocusable(true); requestFocusInWindow(); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { requestFocusInWindow(); } }); addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: engine.addKeyState(KeyState.UP); break; case KeyEvent.VK_DOWN: engine.addKeyState(KeyState.DOWN); break; case KeyEvent.VK_LEFT: engine.addKeyState(KeyState.LEFT); break; case KeyEvent.VK_RIGHT: engine.addKeyState(KeyState.RIGHT); break; } } @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: engine.removeKeyState(KeyState.UP); break; case KeyEvent.VK_DOWN: engine.removeKeyState(KeyState.DOWN); break; case KeyEvent.VK_LEFT: engine.removeKeyState(KeyState.LEFT); break; case KeyEvent.VK_RIGHT: engine.removeKeyState(KeyState.RIGHT); break; } } }); } @Override public void addNotify() { super.addNotify(); createBufferStrategy(3); } @Override public void invalidate() { super.invalidate(); onscreen = null; // offscreen = null; } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } } public static class Engine { public static final int MAP_WIDTH = 15 * 4; public static final int MAP_HEIGHT = 9 * 4; public static final int X_DELTA = 4; public static final int Y_DELTA = 4; public boolean isGameFinished = false; //This value would probably be stored elsewhere. public static final long GAME_HERTZ = 25; //Calculate how many ns each frame should take for our target game hertz. public static final long TIME_BETWEEN_UPDATES = Math.round(1000000000 / (double) GAME_HERTZ); //We will need the last update time. static long lastUpdateTime = System.nanoTime(); //Store the last time we rendered. static long lastRenderTime = System.nanoTime(); //If we are able to get as high as this FPS, don't render again. final static long TARGET_FPS = GAME_HERTZ; final static long TARGET_TIME_BETWEEN_RENDERS = Math.round(1000000000 / (double) TARGET_FPS); //Simple way of finding FPS. static int lastSecondTime = (int) (lastUpdateTime / 1000000000); public int fps = 60; public int frameCount = 0; private View view; private int camX, camY; private Set keyStates; private BufferedImage map; private BufferedImage tiles[]; public Engine(View view) { this.view = view; keyStates = new HashSet<>(4); tiles = new BufferedImage[22]; Random rnd = new Random(); GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); map = gc.createCompatibleImage(MAP_WIDTH * 128, MAP_HEIGHT * 128, Transparency.TRANSLUCENT); Graphics2D g2d = map.createGraphics(); for (int row = 0; row < MAP_HEIGHT; row++) { for (int col = 0; col < MAP_WIDTH; col++) { int tile = rnd.nextInt(22); int x = col * 128; int y = row * 128; g2d.drawImage(getTile(tile), x, y, null); } } g2d.dispose(); } protected BufferedImage getTile(int tile) { BufferedImage img = tiles[tile]; if (img == null) { try { img = ImageIO.read(getClass().getResource("/" + tile + ".png")); img = img.getSubimage(0, 64, 128, 128); img = toCompatiableImage(img); } catch (IOException ex) { ex.printStackTrace(); } tiles[tile] = img; } return img; } public void gameStart() { Thread gameThread = new Thread() { // Override run() to provide the running behavior of this thread. @Override public void run() { gameLoop(); } }; // Start the thread. start() calls run(), which in turn calls gameLoop(). gameThread.start(); } public void gameLoop() { while (!isGameFinished) { long startTime = System.nanoTime(); lastUpdateTime += TIME_BETWEEN_UPDATES; updateGame(); renerGame(); frameCount++; lastRenderTime = startTime; long duration = System.nanoTime() - startTime; int thisSecond = (int) (lastUpdateTime / 1000000000); if (thisSecond > lastSecondTime) { fps = frameCount; frameCount = 0; lastSecondTime = thisSecond; } if (duration < TARGET_TIME_BETWEEN_RENDERS) { duration = TARGET_TIME_BETWEEN_RENDERS - duration; long milli = TimeUnit.NANOSECONDS.toMillis(duration); try { Thread.sleep(milli); } catch (InterruptedException ex) { } } } } protected void updateGame() { if (keyStates.contains(KeyState.DOWN)) { camY -= Y_DELTA; } else if (keyStates.contains(KeyState.UP)) { camY += Y_DELTA; } if (camY < -(map.getHeight() - view.getHeight())) { camY = -(map.getHeight() - view.getHeight()); } else if (camY > 0) { camY = 0; } if (keyStates.contains(KeyState.RIGHT)) { camX -= Y_DELTA; } else if (keyStates.contains(KeyState.LEFT)) { camX += Y_DELTA; } if (camX < -(map.getWidth() - view.getWidth())) { camX = -(map.getWidth() - view.getWidth()); } else if (camX > 0) { camX = 0; } } protected void renerGame() { BufferStrategy bs = view.getBufferStrategy(); if (bs != null) { do { Graphics2D g2d = (Graphics2D) bs.getDrawGraphics(); if (g2d != null) { g2d.drawImage(map, camX, camY, null); // Draw effects here... FontMetrics fm = g2d.getFontMetrics(); g2d.setColor(Color.RED); g2d.drawString(Integer.toString(fps), 0, fm.getAscent()); g2d.dispose(); } } while (bs.contentsLost()); bs.show(); } } public void addKeyState(KeyState state) { keyStates.add(state); } public void removeKeyState(KeyState state) { keyStates.remove(state); } protected BufferedImage toCompatiableImage(BufferedImage img) { GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); BufferedImage compImg = gc.createCompatibleImage(img.getWidth(), img.getHeight(), img.getTransparency()); Graphics2D g2d = compImg.createGraphics(); g2d.drawImage(img, 0, 0, null); g2d.dispose(); return compImg; } } }