我正在使用JPanel将我的游戏画上,使用固定的游戏循环和双缓冲;但是我在屏幕上的某个地方出现了故障。毛刺是一个屏幕撕裂的视觉神器,横跨X轴延伸,高约20像素。
我已在1个类中重新创建了该问题,如下所示。要重新创建问题,您可以运行代码并使用箭头键移动方块,当方块移动到发生视觉撕裂的位置时,您应该看到效果。 (视觉撕裂的位置似乎是随机的)
我重新创建了图像撕裂的单帧,但是在运行时,它会显示闪烁效果。
package main;
import java.awt.Color;
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.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
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 Panel {
public static void main(String[] args) {
new Panel();
}
public Panel() {
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("Game!");
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;
}
@SuppressWarnings("serial")
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(800, 800);
}
@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 = 8;
public static final int Y_DELTA = 8;
//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;
public int x, y;
private boolean isGameFinished;
private View view;
private Set<KeyState> keyStates;
public Engine(View bufferRenderer) {
keyStates = new HashSet<>(4);
this.view = bufferRenderer;
}
public void gameStart() {
x = (800/2) - (60/2);
y = (800/2) - (60/2);
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;
}
while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
Thread.yield();
try { Thread.sleep(1);
} catch (Exception e) {}
now = System.nanoTime();
}
}
}
protected void renderBuffer(BufferedImage buffer) {
if (buffer != null) {
Graphics2D g2d = buffer.createGraphics();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
g2d.setColor(Color.WHITE);
g2d.fillRect(x, y, 60, 60);
g2d.setColor(Color.WHITE);
g2d.drawString("FPS: "+Engine.fps, 0, 10);
g2d.dispose();
}
}
protected void gameUpdate(BufferedImage buffer) {
if (keyStates.contains(KeyState.DOWN)) {
y = y + Y_DELTA;
} else if (keyStates.contains(KeyState.UP)) {
y = y - Y_DELTA;
}
if (keyStates.contains(KeyState.RIGHT)) {
x = x + X_DELTA;
} else if (keyStates.contains(KeyState.LEFT)) {
x = x - X_DELTA;
}
}
public void addKeyState(KeyState state) {
keyStates.add(state);
}
public void removeKeyState(KeyState state) {
keyStates.remove(state);
}
}
@SuppressWarnings("serial")
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);
}
}
@SuppressWarnings("serial")
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);
}
}
}
编辑:屏幕撕裂的可能性很小,(我相信这是因为帧与屏幕同步)
Edit2:有些人告诉我这个问题在他们的硬件上没有发生,我怎么能为我的硬件解决这个问题呢?
答案 0 :(得分:1)
这是一项测试 - 不是答案
这是我能提出的最简单的基于Swing的示例。我直接绘制到JPanel
并尝试每16毫秒(60fps)更新
我建议您使用X_DELTA
和Y_DELTA
值以及评论Toolkit.getDefaultToolkit().sync();
进出,看看它是否有所作为......
nb-这两个例子都使用&#34; direct&#34;绘画过程。也就是说,使用BufferedImage
(或其他后备缓冲区),它们直接绘制到Graphics
上下文
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
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.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class BasicAnimation {
public static void main(String[] args) {
new BasicAnimation();
}
public BasicAnimation() {
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 enum KeyState {
UP, DOWN, LEFT, RIGHT;
}
public static class TestPane extends JPanel {
private Set<KeyState> keyStates;
private Point player = new Point(200, 200);
public static final int X_DELTA = 4;
public static final int Y_DELTA = 4;
public TestPane() {
keyStates = new HashSet<>(4);
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(KeyState.UP));
am.put("up_released", new RemoveState(KeyState.UP));
am.put("down_pressed", new AddState(KeyState.DOWN));
am.put("down_released", new RemoveState(KeyState.DOWN));
am.put("left_pressed", new AddState(KeyState.LEFT));
am.put("left_released", new RemoveState(KeyState.LEFT));
am.put("right_pressed", new AddState(KeyState.RIGHT));
am.put("right_released", new RemoveState(KeyState.RIGHT));
Timer timer = new Timer(16, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateState();
repaint();
Toolkit.getDefaultToolkit().sync();
}
});
timer.start();
}
protected void updateState() {
if (keyStates.contains(KeyState.UP)) {
player.y -= Y_DELTA;
} else if (keyStates.contains(KeyState.DOWN)) {
player.y += Y_DELTA;
}
if (keyStates.contains(KeyState.LEFT)) {
player.x -= X_DELTA;
} else if (keyStates.contains(KeyState.RIGHT)) {
player.x += X_DELTA;
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.WHITE);
g2d.fillRect(player.x - 25, player.y - 25, 50, 50);
g2d.dispose();
}
public void addKeyState(KeyState state) {
keyStates.add(state);
}
public void removeKeyState(KeyState state) {
keyStates.remove(state);
}
@SuppressWarnings("serial")
public class AddState extends AbstractAction {
private final KeyState state;
public AddState(KeyState state) {
this.state = state;
}
@Override
public void actionPerformed(ActionEvent e) {
addKeyState(state);
}
}
@SuppressWarnings("serial")
public class RemoveState extends AbstractAction {
private final KeyState state;
public RemoveState(KeyState state) {
this.state = state;
}
@Override
public void actionPerformed(ActionEvent e) {
removeKeyState(state);
}
}
}
}
还包括Canvas
/ BufferStrategy
示例。有些交易,使用X/Y_Delta
值并启用/停用Toolkit.getDefaultToolkit().sync();
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class BasicAnimation {
public static void main(String[] args) {
new BasicAnimation();
}
public BasicAnimation() {
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 GameView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum KeyState {
UP, DOWN, LEFT, RIGHT;
}
public static class GameView extends Canvas {
private Set<KeyState> keyStates;
private Point player = new Point(200, 200);
public static final int X_DELTA = 4;
public static final int Y_DELTA = 4;
private GameThread gt;
public GameView() {
keyStates = new HashSet<>(4);
gt = new GameThread(this);
setFocusable(true);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
addKeyState(KeyState.UP);
break;
case KeyEvent.VK_DOWN:
addKeyState(KeyState.DOWN);
break;
case KeyEvent.VK_LEFT:
addKeyState(KeyState.LEFT);
break;
case KeyEvent.VK_RIGHT:
addKeyState(KeyState.RIGHT);
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
removeKeyState(KeyState.UP);
break;
case KeyEvent.VK_DOWN:
removeKeyState(KeyState.DOWN);
break;
case KeyEvent.VK_LEFT:
removeKeyState(KeyState.LEFT);
break;
case KeyEvent.VK_RIGHT:
removeKeyState(KeyState.RIGHT);
break;
}
}
});
}
@Override
public void addNotify() {
super.addNotify();
createBufferStrategy(2);
gt.start();
requestFocusInWindow();
}
@Override
public void removeNotify() {
gt.stop();
super.removeNotify();
}
public void updateState() {
if (keyStates.contains(KeyState.UP)) {
player.y -= Y_DELTA;
} else if (keyStates.contains(KeyState.DOWN)) {
player.y += Y_DELTA;
}
if (keyStates.contains(KeyState.LEFT)) {
player.x -= X_DELTA;
} else if (keyStates.contains(KeyState.RIGHT)) {
player.x += X_DELTA;
}
}
public void paintState(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.WHITE);
g2d.fillRect(player.x - 25, player.y - 25, 50, 50);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void addKeyState(KeyState state) {
keyStates.add(state);
}
public void removeKeyState(KeyState state) {
keyStates.remove(state);
}
@SuppressWarnings("serial")
public class AddState extends AbstractAction {
private final KeyState state;
public AddState(KeyState state) {
this.state = state;
}
@Override
public void actionPerformed(ActionEvent e) {
addKeyState(state);
}
}
@SuppressWarnings("serial")
public class RemoveState extends AbstractAction {
private final KeyState state;
public RemoveState(KeyState state) {
this.state = state;
}
@Override
public void actionPerformed(ActionEvent e) {
removeKeyState(state);
}
}
}
public static class GameThread implements Runnable {
private volatile boolean keepRunning = true;
private GameView view;
private Thread currentThread;
public GameThread(GameView view) {
this.view = view;
}
public void start() {
if (currentThread == null) {
keepRunning = true;
currentThread = new Thread(this);
currentThread.start();
}
}
public void stop() {
keepRunning = false;
if (currentThread != null) {
try {
currentThread.join();
} catch (InterruptedException ex) {
}
}
}
@Override
public void run() {
while (keepRunning) {
view.updateState();
BufferStrategy bs = view.getBufferStrategy();
do {
Graphics2D g2d = (Graphics2D) bs.getDrawGraphics();
view.paintState(g2d);
g2d.dispose();
} while (bs.contentsLost());
bs.show();
Toolkit.getDefaultToolkit().sync();
try {
Thread.sleep(16);
} catch (InterruptedException ex) {
}
}
}
}
}
答案 1 :(得分:0)
尝试从paintComponent(g)方法移动g2d.dipose()。在绘制活动图像后立即进入try {} catch()语句。