框架在不同时间绘画?

时间:2014-08-21 06:42:36

标签: java swing

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

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

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

我无法发布问题视频,因为它不会播放视频,但您可以自由地在游戏中看到它link to game和病毒扫描here < / p>

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

对不起的解释感到抱歉,我发现很难描述我的问题。我已经坚持了好几个小时,即使在网上搜索一个也无法找到解决方案。

编辑:整个源代码:Here

EDIT2:它需要位于here

的kryonet lib

EDIT3:Github

4 个答案:

答案 0 :(得分:3)

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

一般来说,将BLIT图像放到显卡上要快得多,然后才能“#”绘制&#34;像素,考虑到这一点,这个例子做了两件事......

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

其次,它雇用它自己的双缓冲。 &#34;视图&#34;有两个缓冲区,activeupdateactive缓冲区被绘制到屏幕上,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分钟,所以它可能不是最好的例子......

Scrolling

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渲染管道(这些是命令行选项)以查看它是否有所作为......

请查看VolatileImageThe Game Programming Wiki, Java:Tutorials:VolatileImageJava 2D: Hardware Accelerating - Part 1 - Volatile Images以获取更多想法。

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

<强>更新

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

我还优化了加载后的所有图块,以便对GraphicsConfiguration进行优化,这意味着当它们渲染到屏幕时,不需要转换它们的颜色模型是一样的,应该有助于节省一些时间......

查看toCompatiableImage方法了解更多详情

答案 2 :(得分:0)

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

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

RepaintManager.currentManager(yourJPanel).markCompletelyClean(yourJPanel)

还有另一种技术使用两个不同的JPanel实例。基本思路是:

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

我不知道第二个会如何影响表现。

答案 3 :(得分:0)

最后,一个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<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;
        }

    }

}