I've posted a video on Youtube, where you can see the problem. I've slowed the video down, so you really can see the jumps.
LINK: https://www.youtube.com/watch?v=17Wftj2-MRM&feature=youtu.be
The problem is most visible in the last part of the video. You can clearly see the frame jumps, it's like it skipped a frame or two..
I have none problems with lag or performance, as the computer i'm running the games on got the latest tech..
I thought it may had something to do with the game loop, like a rounding error or something, but I can't seem to find any bug in the code.. I've looked for a similar problem online, but can't find it..
So here's my code The 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) {
}
}
}
}
I suppose that everything you will need is in the GUI class, but I've included the other classes in case you need anything from them.
The Game class
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();
}
}
And finally the Map class (handles loading of .map files and such)
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;
}
}
}
I'm aware that there probably is a some bugs in the code, and everything is probably not optimal, but this project is VERY early development, and I'm a very inexperienced game developer.. I would appreciate if you would focus on the posted problem, and not my other crap code. :-D
I would really like if you would comment, if you have any suggestions :-D Thank you
EDIT: I updated the post, as the problem was unclear. So i uploaded a video, where you can view the bug. I've also cleared some other things :-)
EDIT 2: Made a new thread on gamedev.stackexchange. It may help you solve the problem: https://gamedev.stackexchange.com/questions/77998/java-game-loop-frame-jumps
SOLUTION: After a ton of research i came to the conclusion, that it had something to do with the screens refresh rate, and my loops refresh rate.. My screen was simply skipping frames, which was very noticeable!
If you want a indepth solution, google made a video, where they described the problem: https://www.youtube.com/watch?v=hAzhayTnhEI
There are two ways (according to my research) to get rid of this problem:
Either you have to enable VSync through OpenGL, as java doesn't have the option to do so..
Or you have to use a mixture of swing and awt in java (the option i chose).. Here's a thread describing how to do: Smooth Drawing using Java2d without the Opengl or Direct3d Pipelines?
Thank you for all responses!