Java游戏循环:帧跳跃

时间:2014-06-10 19:12:35

标签: java game-engine 2d-games game-loop

我在Youtube上发布了一个视频,你可以在那里看到问题所在。我放慢了视频速度,所以你真的可以看到跳跃。

LINK:https://www.youtube.com/watch?v=17Wftj2-MRM&feature=youtu.be

问题在视频的最后部分最为明显。你可以清楚地看到帧跳跃,就像跳过一两帧......

我没有延迟或性能方面的问题,因为我正在运行游戏的计算机获得了最新技术..

我认为这可能与游戏循环有关,比如舍入错误或其他什么,但我似乎无法在代码中找到任何错误。 我在网上找了类似的问题,但找不到它..

所以这是我的代码 GUI

import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;

public class Gui extends JFrame implements Runnable {

private static final long serialVersionUID = 1L;
public static final int FRAMES_PR_SEC = 60;
public static final int TIME_PR_FRAME = 1000 / FRAMES_PR_SEC;
public static final int MARGIN_X = 3;
public static final int MARGIN_Y = 32;
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static int TILE_SIZE = 32;
public static int SPEED = 5;

public static void main(final String[] args) {
    new Gui();
}

private Game _game;

private Container _cp;
private BufferedImage _img;

private Graphics2D _g;

public Gui() {
    super("Tile Based Game");
    setSize(WIDTH + MARGIN_X + 3, HEIGHT + MARGIN_Y - 4);
    setResizable(false);
    setVisible(true);
    setLocationRelativeTo(null);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    _cp = getContentPane();
    _game = new Game();
    newGraphics();
    addMouseListener(new MouseAdapter() {

        @Override
        public void mousePressed(final MouseEvent arg0) {
            _game.mP(arg0.getX() - MARGIN_X, arg0.getY() - MARGIN_Y);
        }

        @Override
        public void mouseReleased(final MouseEvent arg0) {
            _game.mR(arg0.getX() - MARGIN_X, arg0.getY() - MARGIN_Y);
        }
    });
    addKeyListener(new KeyAdapter() {

        @Override
        public void keyPressed(final KeyEvent arg0) {
            _game.keyP(arg0.getKeyCode());
        }

        @Override
        public void keyReleased(final KeyEvent arg0) {
            _game.keyR(arg0.getKeyCode());
        }
    });
    new Thread(this).start();
}

private void newGraphics() {
    _img = getGraphicsConfiguration().createCompatibleImage(WIDTH, HEIGHT);
    _g = _img.createGraphics();
}

@Override
public void run() {
    while (true) {
        long before = System.currentTimeMillis();
        _game.update();
        newGraphics();
        _game.draw(_g);
        _cp.getGraphics().drawImage(_img, 0, 0, null);
        long after = System.currentTimeMillis();
        long sleep = TIME_PR_FRAME - (after - before);
        System.out.println(sleep);
        if (sleep < 5) {
            sleep = 5;
        }
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
        }
    }
}
}

我想你需要的一切都在GUI类中,但我已经包含了其他类,以防你需要任何东西。

游戏课

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;

public class Game {
private Map[] _maps;
private String[] _mapNames;
private int _currentMap;

private boolean _r;
private boolean _l;
private boolean _u;
private boolean _d;

public Game() {
    _mapNames = new String[0];
    _maps = new Map[0];
    addMap("test.map");

}

public void addMap(final String s) {
    String[] stringTemp = _mapNames;
    _mapNames = new String[stringTemp.length + 1];
    for (int i = 0; i < stringTemp.length; i++) {
        _mapNames[i] = stringTemp[i];
    }
    _mapNames[_mapNames.length - 1] = s.replace(".map", "");
    Map[] mapTemp = _maps;
    _maps = new Map[mapTemp.length + 1];
    for (int i = 0; i < mapTemp.length; i++) {
        _maps[i] = mapTemp[i];
    }
    _maps[_maps.length - 1] = new Map(s);
}

public void draw(final Graphics2D _g) {
    _maps[_currentMap].draw(_g);
    _g.setColor(Color.WHITE);
    _g.drawString(_mapNames[_currentMap], 5, 595);
}

public void keyP(final int keyCode) {
    if (keyCode == KeyEvent.VK_RIGHT) {
        _r = true;
    }
    if (keyCode == KeyEvent.VK_LEFT) {
        _l = true;
    }

    if (keyCode == KeyEvent.VK_UP) {
        _u = true;
    }
    if (keyCode == KeyEvent.VK_DOWN) {
        _d = true;
    }
}

public void keyR(final int keyCode) {
    if (keyCode == KeyEvent.VK_SPACE) {
        if (_currentMap < _maps.length - 1) {
            _currentMap++;
        } else {
            _currentMap = 0;
        }
    }
    if (keyCode == KeyEvent.VK_RIGHT) {
        _r = false;
    }
    if (keyCode == KeyEvent.VK_LEFT) {
        _l = false;
    }

    if (keyCode == KeyEvent.VK_UP) {
        _u = false;
    }
    if (keyCode == KeyEvent.VK_DOWN) {
        _d = false;
    }
}

public void mP(final int i, final int j) {

}

public void mR(final int i, final int j) {

}

public void update() {

    if (_r) {
        _maps[_currentMap].incOffsetX(Gui.SPEED);
    }
    if (_l) {
        _maps[_currentMap].decOffsetX(Gui.SPEED);
    }
    if (_u) {
        _maps[_currentMap].decOffsetY(Gui.SPEED);
    }
    if (_d) {
        _maps[_currentMap].incOffsetY(Gui.SPEED);
    }
    _maps[_currentMap].update();
}
}

最后是Map类(处理.map文件的加载等)

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Map {

private class Tile {
    private Image _img;
    private int _x;
    private int _y;

    public Tile(final Image img) {
        _img = img;
    }

    public Image getImg() {
        return _img;
    }

    public int getX() {
        return _x;
    }

    public int getY() {
        return _y;
    }

    public void setLoc(final int x, final int y) {
        _x = x * Gui.TILE_SIZE;
        _y = y * Gui.TILE_SIZE;
    }
}

private int _offsetX;
private int _offsetY;

private String[][] _map;

private Tile[][] _tileMap;

private BufferedImage _terrain;

private Color _waterColor;

public Map(final String s) {
    _terrain = loadImg("terrain.png");

    _waterColor = new Color(21, 108, 153);

    _map = loadMap(s);

    _offsetX = _map[0].length * Gui.TILE_SIZE / 2 - Gui.WIDTH / 2;
    _offsetY = _map.length * Gui.TILE_SIZE / 2 - Gui.HEIGHT / 2;

    _tileMap = new Tile[_map.length][_map[0].length];

    for (int i = 0; _map.length > i; i++) {
        for (int j = 0; _map[0].length > j; j++) {
            Image img;
            switch (_map[i][j]) {
            case "o":
                img = getSubimage(_terrain, 0, 0, 3, 6);
                break;
            case "0":
                img = getSubimage(_terrain, 0, 1, 3, 6);
                break;
            case "a":
                img = getSubimage(_terrain, 1, 0, 3, 6);
                break;
            case "b":
                img = getSubimage(_terrain, 2, 0, 3, 6);
                break;
            case "c":
                img = getSubimage(_terrain, 1, 1, 3, 6);
                break;
            case "d":
                img = getSubimage(_terrain, 2, 1, 3, 6);
                break;
            case "1":
                img = getSubimage(_terrain, 0, 2, 3, 6);
                break;
            case "2":
                img = getSubimage(_terrain, 1, 2, 3, 6);
                break;
            case "3":
                img = getSubimage(_terrain, 2, 2, 3, 6);
                break;
            case "4":
                img = getSubimage(_terrain, 0, 3, 3, 6);
                break;
            case "5":
                img = getSubimage(_terrain, 1, 3, 3, 6);
                break;
            case "6":
                img = getSubimage(_terrain, 2, 3, 3, 6);
                break;
            case "7":
                img = getSubimage(_terrain, 0, 4, 3, 6);
                break;
            case "8":
                img = getSubimage(_terrain, 1, 4, 3, 6);
                break;
            case "9":
                img = getSubimage(_terrain, 2, 4, 3, 6);
                break;
            case "#":
                img = getSubimage(_terrain, 0, 5, 3, 6);
                break;
            case "&":
                img = getSubimage(_terrain, 1, 5, 3, 6);
                break;
            case "%":
                img = getSubimage(_terrain, 2, 5, 3, 6);
                break;
            default:
                img = null;
                break;
            }
            Tile tile = new Tile(img);
            int x = j;
            int y = i;
            tile.setLoc(x, y);
            _tileMap[i][j] = tile;
        }
    }
}

public void decOffsetX(final int i) {
    _offsetX -= i;
}

public void decOffsetY(final int i) {
    _offsetY -= i;
}

public void draw(final Graphics2D _g) {
    for (int i = 0; _tileMap.length > i; i++) {
        for (int j = 0; _tileMap[0].length > j; j++) {
            if (_tileMap[i][j].getImg() != null
                    && isVisible(_tileMap[i][j])) {
                _g.drawImage(_tileMap[i][j].getImg(), _tileMap[i][j].getX()
                        - _offsetX, _tileMap[i][j].getY() - _offsetY, null);
            } else if (_tileMap[i][j].getImg() == null
                    && isVisible(_tileMap[i][j])) {
                _g.setColor(_waterColor);
                _g.fillRect(_tileMap[i][j].getX() - _offsetX,
                        _tileMap[i][j].getY() - _offsetY, Gui.TILE_SIZE,
                        Gui.TILE_SIZE);
            }
        }
    }
}

private Image getSubimage(final BufferedImage img, final int x,
        final int y, final int w, final int h) {
    return img.getSubimage(x * Gui.TILE_SIZE, y * Gui.TILE_SIZE,
            Gui.TILE_SIZE, Gui.TILE_SIZE);

}

public void incOffsetX(final int i) {
    _offsetX += i;
}

public void incOffsetY(final int i) {
    _offsetY += i;
}

public boolean isVisible(final Tile tile) {
    if (tile.getX() - _offsetX < 0 - Gui.TILE_SIZE) {
        return false;
    } else if (tile.getX() - _offsetX > Gui.WIDTH) {
        return false;
    }
    if (tile.getY() - _offsetY < 0 - Gui.TILE_SIZE) {
        return false;
    } else if (tile.getY() - _offsetY > Gui.HEIGHT) {
        return false;
    }
    return true;
}

private BufferedImage loadImg(final String s) {
    BufferedImage img = null;
    try {
        img = ImageIO.read(new File("res/" + s));
    } catch (IOException e) {
    }
    return img;
}

private String[][] loadMap(final String s) {
    String _s = null;
    String string = null;
    int appear = 0;
    try {
        BufferedReader br;
        br = new BufferedReader(new FileReader("res/maps/" + s));
        while ((_s = br.readLine()) != null) {
            string = string + _s + ",";
            appear++;
        }
        br.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    String[] ss = string.replaceAll("null", "").split(",");
    String[][] items = new String[appear][];
    for (int i = 0; i < items.length; i++) {
        items[i] = ss[i].split(" ");
    }

    return items;
}

public void update() {
    if (_offsetX < 0) {
        _offsetX += Gui.SPEED;
    } else if (_offsetX > _map[0].length * Gui.TILE_SIZE - Gui.WIDTH
            - Gui.SPEED) {
        _offsetX -= Gui.SPEED;
    }
    if (_offsetY < 0) {
        _offsetY += Gui.SPEED;
    } else if (_offsetY > _map.length * Gui.TILE_SIZE - Gui.HEIGHT
            - Gui.SPEED) {
        _offsetY -= Gui.SPEED;
    }
}
}

我知道代码中可能存在一些错误,而且一切都可能不是最佳的,但是这个项目很早就开发了,我是一个非常缺乏经验的游戏开发者......如果你愿意,我将不胜感激专注于发布的问题,而不是我的其他垃圾代码。 :-D

如果你有任何建议,我真的很想你评论:-D 谢谢

编辑: 我更新了帖子,因为问题不明确。所以我上传了一个视频,你可以在那里查看错误。我还清除了其他一些东西: - )

编辑2: 在gamedev.stackexchange上创建了一个新主题。它可以帮助您解决问题:https://gamedev.stackexchange.com/questions/77998/java-game-loop-frame-jumps

解决方案: 经过大量的研究后我得出结论,它与屏幕刷新率和我的循环刷新率有关。我的屏幕只是跳帧,这非常引人注目!

如果你想要一个深入的解决方案,谷歌制作了一个视频,他们在那里描述了问题:https://www.youtube.com/watch?v=hAzhayTnhEI

有两种方法(根据我的研究)摆脱这个问题:

您必须通过OpenGL启用VSync,因为java没有这样做的选项。

或者你必须在java中使用swing和awt的混合(我选择的选项)..这是一个描述如何做的线程:Smooth Drawing using Java2d without the Opengl or Direct3d Pipelines?

感谢您的回复!

1 个答案:

答案 0 :(得分:0)

首先,一个想法是将BufferStrategy作为一种机制来创建一个更强大和更高效的游戏循环。其次,您的游戏循环在不同的值上休眠,具体取决于您的更新/渲染所需的时间。这很好,但是 update 方法也应该反映这一点,而不是以恒定的速度移动。您希望实体相对于自上一个循环周期( lastTime - currentTime )以来经过的 delta 时间移动。

你的一些常量不应该是整数。例如,TIME_PR_FRAME不应该是16,而是16.67,所以将它设为double,或者至少是float。您的if(sleep < 5) sleep = 5;代码毫无意义。如果渲染和更新需要很长时间,你不应该浪费时间睡觉,基本上你是故意或不在这些情况下引入抖动。

您是否调试了代码以查看sleep变量的平均值是什么,以及它是否出现峰值?如果没有,我也会这样做。