我的游戏中有一个非常烦人的错误,框架的底部似乎比框架的顶部更早渲染,我不确定它为什么会发生。
我正在使用JPanel,它会重新绘制每个游戏循环,我的游戏循环设置为60FPS。 在绘画功能的开始,它将玩家X和Y设置为变量,然后用于绘制每个元素,(因为它们是相对于玩家绘制的,因为相机跟随玩家)
我可以根据需要发布任何代码,以帮助诊断问题,但代码太多,我不知道问题的哪一部分;因此,根据我的解释,我主要是在询问是否有人知道可能出现的问题。
我无法发布问题视频,因为它不会播放视频,但您可以自由地在游戏中看到它link to game和病毒扫描here < / p>
如果您下载游戏,则在打开游戏时,输入名称(或保留默认值),并在询问服务器时单击“否”。当您使用WASD移动时,您应该在屏幕上的某处看到水平线闪烁效果。如果游戏没有打开,请再试一次,它很可能无法打开(这是一个已知的错误,我计划很快修复它)
对不起的解释感到抱歉,我发现很难描述我的问题。我已经坚持了好几个小时,即使在网上搜索一个也无法找到解决方案。
编辑:整个源代码:Here
EDIT2:它需要位于here
的kryonet libEDIT3:Github
答案 0 :(得分:3)
这是两个基本原则的演示,但基本上是一系列缓冲,旨在减少paintComponent
所做的工作量......
一般来说,将BLIT图像放到显卡上要快得多,然后才能“#”绘制&#34;像素,考虑到这一点,这个例子做了两件事......
首先,它预先渲染背景地图。这个例子只是在它运行时随机生成地图,但创建的地图大约是全高清的4倍。
其次,它雇用它自己的双缓冲。 &#34;视图&#34;有两个缓冲区,active
和update
。 active
缓冲区被绘制到屏幕上,update
缓冲区是Engine
用于呈现输出当前状态的内容...
这很重要,因为视图的缓冲区总是与视图大小相同,因此您永远不会渲染任何不会出现在屏幕外的内容。
此示例将其他内容(如动画,特效)的呈现推送到Engine
...
我有这个例子在我的30&#34;监控在2560x1600,几乎没有问题,运动增量非常小,所以我可以更快地平移,使它变大会使这些问题无效......
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<KeyState> 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);
}
}
}
在我的实验过程中,我注意到,如果你试图渲染内容&#34;超过&#34;缓冲区的范围(即允许地图顶部在缓冲区内滑落),你会得到令人讨厌的油漆效果,所以要注意你总是在缓冲区的可视区域内渲染......
可能还有其他领域需要整理,但这证明了基础......
答案 1 :(得分:1)
抱歉,在我之前的回答中用尽了空间: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<KeyState> 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
,The Game Programming Wiki, Java:Tutorials:VolatileImage和Java 2D: Hardware Accelerating - Part 1 - Volatile Images以获取更多想法。
我也改变了你的游戏循环“计时”循环,不知道它是否会有所作为,但是Thread.sleep(1)
总是吓到我......
<强>更新强>
我已更新代码,因此只有一个VolatileImage
。 paintComponent
使用实际BufferedImage
,而是针对GraphicsConfiguration
优化。这可确保内容始终根据需要绘制为唯一更新(在show
方法内)。应该有助于防止闪烁......
我还优化了加载后的所有图块,以便对GraphicsConfiguration
进行优化,这意味着当它们渲染到屏幕时,不需要转换它们的颜色模型是一样的,应该有助于节省一些时间......
查看toCompatiableImage
方法了解更多详情
答案 2 :(得分:0)
当进行更改时,JPanel不会等你调用repaint()。
为了防止这种情况,我认为您可以按如下方式使用RepaintManager: -
RepaintManager.currentManager(yourJPanel).markCompletelyClean(yourJPanel)
还有另一种技术使用两个不同的JPanel实例。基本思路是:
我不知道第二个会如何影响表现。
答案 3 :(得分:0)
最后,一个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<KeyState> 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;
}
}
}