Better game loop/ engine [closed] - java

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I'm fairly new to game programming in Java and I was wondering if someone could help me out with my game loop. I'm trying to avoid using premade engine libraries (I want to see if I can do it myself). Currently, my game loop is giving me a really low FPS, sometimes even in the single digits. Would this be a case where I have to use multithreading? Or is there a better fix to this problem? Any help is much appreciated!
Here is the code which runs the game loop. I don't include all of the code (because it would be a lot of files), but I think this might be enough, tell me if you need more. I also include a screenshot of the game that I'm trying to create. Thanks in advance!
package com.Farthorn.game;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferStrategy;
import javax.swing.ImageIcon;
public class Game extends Canvas implements Runnable
{
private static final long serialVersionUID = -4431078110090264106L;
public final static int WIDTH = 640;
public final static int HEIGHT = WIDTH / 12 * 9;
private Thread thread;
private boolean running = false;
private CharacterHandler cHandler;
private ObjectHandler oHandler;
public int change = 0;
private String mapBackgroundTop = "/Users/Ahhblala/Desktop/FarthornPics/background_top.png";
private String mapBackgroundBottom = "/Users/Ahhblala/Desktop/FarthornPics/background_bottom.png";
private Image backgroundTop;
private Image backgroundBottom;
public Game()
{
ImageIcon i = new ImageIcon(mapBackgroundTop);
backgroundTop = i.getImage();
i = new ImageIcon(mapBackgroundBottom);
backgroundBottom = i.getImage();
cHandler = new CharacterHandler();
cHandler.addCharacter(new Player(ID.Player, WIDTH/2-32, HEIGHT/2+64));
cHandler.addCharacter(new PlantEnemy(ID.PlantEnemy, WIDTH+180, HEIGHT/2+104, (Player) cHandler.characters.getLast()));
cHandler.addCharacter(new PlantEnemy(ID.PlantEnemy, WIDTH+195, HEIGHT/2+104, (Player) cHandler.characters.get(0)));
cHandler.addCharacter(new PlantEnemy(ID.PlantEnemy, WIDTH+300, HEIGHT/2+104, (Player) cHandler.characters.get(0)));
cHandler.addCharacter(new PlantEnemy(ID.PlantEnemy, WIDTH+315, HEIGHT/2+104, (Player) cHandler.characters.get(0)));
oHandler = new ObjectHandler();
oHandler.addObject(new HUD(10, 10, cHandler));
this.addKeyListener(new KeyInput(cHandler));
this.addMouseListener(new MouseInput(((Player) cHandler.characters.get(0))));
FarthornWindow window = new FarthornWindow(WIDTH, HEIGHT, this);
}
public synchronized void start()
{
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop()
{
try
{
thread.join();
running = false;
}
catch(Exception e)
{
e.printStackTrace();
}
}
public void run()
{
this.requestFocus();
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while (running)
{
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1)
{
tick();
delta--;
}
if (running)
{
render();
}
frames++;
if(System.currentTimeMillis() - timer > 1000)
{
timer += 1000;
System.out.println("FPS " + frames);
frames = 0;
}
}
stop();
}
private void tick()
{
int deltaX = 0;
int afterX = 0;
deltaX = cHandler.characters.get(0).getX();
oHandler.tick();
cHandler.tick();
deltaX = cHandler.characters.get(0).getX();
if (deltaX != afterX)
change = (afterX-deltaX);
else
change = 0;
render();
}
private void render()
{
BufferStrategy bs = this.getBufferStrategy();
if (bs == null)
{
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.drawImage(backgroundTop, 0+change, 0, null);
g.drawImage(backgroundBottom, 0+change, 364, null);
oHandler.render(g);
cHandler.render(g);
g.dispose();
bs.show();
}
public static int clamp(int var, int min, int max)
{
if (var >= max)
{
return var = max;
}
else if (var <= min){
return var = min;
}
else
return var;
}
}
As you might be able to tell, I am rendering a lot of outside images.

There's probably something inefficient in your rendering causing it to drop below your target of 60hz. That aside, there's definitely a problem in your "catch up" loop:
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1)
{
tick();
delta--;
}
Since your tick() routine also calls render(), tick() is effectively a frame in itself, but you are not incrementing your frame counter. Your tick() should be causing all the parts to update, but not render the image, render should only be called once per frame. You probably want to remove the render() line from your tick() routine.

Related

Bouncing rectangle (Graphics g)

I'm playing around with graphics in Java. At the moment I have a rectangle that moves from left to right. I want it to start moving left once it hits the right side of the Canvas and left when it hits the right side, i have included a game loop as this will eventually turn into my first very basic game. Thanks.
P.S - I followed some tutorials for different parts of this code hence why it might be a bit messy, I'm working on it :)
Main Class:
public class Game extends JFrame implements Runnable {
private Canvas canvas = new Canvas();
private RenderHandler renderer;
private boolean running = true;
public static int WIDTH = 1200, HEIGHT = WIDTH / 12*9;
public static int moveX =WIDTH/2;
public Game() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(WIDTH, HEIGHT);
setLocationRelativeTo(null);
setLocationRelativeTo(null);
add(canvas);
setVisible(true);
canvas.createBufferStrategy(3);
renderer = new RenderHandler(getWidth(), getHeight());
}
public void update() {
}
public void render() {
BufferStrategy bufferStrategy = canvas.getBufferStrategy();
Graphics graphics = bufferStrategy.getDrawGraphics();
super.paint(graphics);
renderer.render(graphics);
graphics.dispose();
bufferStrategy.show();
}
public void run() {
BufferStrategy bufferStrategy = canvas.getBufferStrategy();
int FRAMES = 0;
int TICKS = 0;
long lastTime = System.nanoTime();
double unprocessed = 0;
double nsPerSecs = 1000000000 /60.0;
long Timer = System.currentTimeMillis();
while(running) {
long now = System.nanoTime();
unprocessed += (now - lastTime) / nsPerSecs;
lastTime = now;
if(unprocessed >= 1) {
TICKS ++;
update();
unprocessed -= 1;
}
try
{
Thread.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
FRAMES++;
render();
if(System.currentTimeMillis() - Timer > 1000) {
System.out.println("Ticks: " + TICKS + " FPS: " + FRAMES);
TICKS = 0;
FRAMES = 0;
Timer += 1000;
}
}
}
public static void main(String[] args) {
Game game = new Game();
Thread gameThread = new Thread(game);
gameThread.start();
}
}
Class drawing the graphics:
public class RenderHandler {
public RenderHandler(int width, int height) {
}
public void render(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
g.setColor(Color.RED);
g.fillRect(Game.moveX, Game.HEIGHT/2, 50, 50);
if (Game.moveX >= Game.WIDTH) {
Game.moveX ++;
} else if (Game.moveX <= 0) {
Game.moveX --;
}else { Game.moveX++;
}
}
}
If you know how to draw on the screen and how stuffs work, I would think that this is more about getting the logic down.
This code slice I so brutally tore from your question is right next to where the rendering takes place (a problem because I view it as rather unorganized; I would recommend game logic and rendering to take place in two different functions). It basically says that it will move right if it is beyond the right of the screen, if not, it will move left if it is beyond the left of the screen, and finally, if not, it will just move left.
if (Game.moveX >= Game.WIDTH) {
Game.moveX ++;
} else if (Game.moveX <= 0) {
Game.moveX --;
}else { Game.moveX++;
}
If you want it to bounce, you will have to use a boolean to keep track of its moving state, or, if you want more versatility, use a pair of floats or doubles (floats are typically used in Java game design) to keep track of its position, and another for its velocity. I'm in a tight squeeze right now, I will return.
Add this to render handler instead of the current if statement in render
bool rol = true; // initialize this outside the method
If(Game.movex + 50 >= Game.width)
rol = false;
Else if(Game.movex <= 0)
rol = true;
If(rol)
Game.movex++;
Else
Game.movex--;
You need to store current moving direction somewhere, so add this to Game class:
public static int deltaX = 1;
And replace condition in render() with
if (Game.moveX >= Game.WIDTH-50) {
Game.deltaX =-1;
} else if (Game.moveX <= 0) {
Game.deltaX =1;
}
Game.moveX += Game.deltaX;

Lags in game loop

It's my game loop code:
public void run() {
running = true;
boolean renderCheck = false;
double firstTime = 0;
double lastTime = System.nanoTime() / 1000000000.0;
double passedTime = 0;
double unprocessedTime = 0;
double frameTime = 0;
int frames = 0;
int fps = 0;
while (running) {
firstTime = System.nanoTime() / 1000000000.0;
passedTime = firstTime - lastTime;
lastTime = firstTime;
unprocessedTime += passedTime;
frameTime += passedTime;
while (unprocessedTime >= UPDATE_CAP) {
tick();
unprocessedTime -= UPDATE_CAP;
renderCheck = true;
}
if (frameTime >= 1.0) {
frameTime = 0;
fps = frames;
frames = 0;
System.out.println(fps);
}
if (renderCheck) {
render();
frames++;
renderCheck = false;
} else {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
It's a render zone:
private void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics graphics = bs.getDrawGraphics();
graphics.setColor(Color.black);
graphics.fillRect(0, 0, WIDTH, HEIGHT);
handler.render(graphics);
graphics.dispose();
bs.show();
}
And here is a tick part(it's not necessary to show other code for handler because it'll be so much to read):
private void tick() {
handler.tick();
}
So the main problem is in the next thing. When i press the button my character has to start moving. And, actually, it does but with some delays which make this process look terrible. Than after a second, all is going perfectly. (I've already checked CPU loading - all goes normal)
This problem is happening only on linux PC. I've checked it on Windows 10 in exe format and all was working fine! That's a bit weird.
This lazy initialization might be your problem:
private void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
...
Did you consider the fact on the first call of your render() function the getBufferStrategy() will return null at which point
you will first create it (that is ok)
then return without any action
(that is suspicious)
Subsequent calls of render would actually perform rendering ... later. If you know for sure that you will need that bufferStrategy anyway, it would make sense to create it straight away when initializing your system.
Soo, finally i found a solution for fixing this issue!
Just set system properties to java2d in your static main method by writing this code:
public static void main(String argsp[]) {
System.setProperty("sun.java2d.opengl", "true");
new Game();
}

Why no speedup with more than one thread?

I'm creating a toy program in java using synchronized block. I have n "Pixelator" threads which pick a random pixel in a 1000x1000 image and assign it to the color of the Pixelator. Each pixel can only be assigned once. I write to a bufferedImage using a wrapper class that uses a synchronized method to write to the image. However, when I test with more than 1 thread, I do not see a speedup. Do you have a hint as to why that would be?
Relavant Code:
import java.awt.Color;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import java.util.ArrayList;
import java.util.Random;
public class q2 {
// The image constructed
public static BufferedImage img;
// Image dimensions; you could also retrieve these from the img object.
public static int width;
public static int height;
// simplified method for stack overflow example
public static int rgbFromN(int n) {
return -16755216;
}
public static void main(String[] args) {
Random r = new Random();
try {
// arg 0 is the width
width = 1000;
// arg 1 is the height
height = 1000;
// arg 2 is the number of threads
int nt = 1;
// create an image and initialize it to all 0's
img = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
synchronizedIMG simg = new synchronizedIMG(img);
for (int i=0;i<width;i++) {
for (int j=0;j<height;j++) {
img.setRGB(i,j,0);
}
}
Thread[] threads = new Thread[nt];
long startTime = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Pixelator(rgbFromN(i),width,height,((width*height)/nt),simg));
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
long endTime = System.currentTimeMillis();
System.out.println("Time(ms): " + (endTime-startTime));
// Write out the image
File outputfile = new File("outputimage.png");
ImageIO.write(img, "png", outputfile);
} catch (Exception e) {
System.out.println("ERROR " +e);
e.printStackTrace();
}
}
}
class Pixelator implements Runnable {
int color;
int width;
int height;
int numPixels;
int currentPixels = 0;
synchronizedIMG simg;
public Pixelator(int color, int width, int height,int numPixels, synchronizedIMG simg){
this.color = color;
this.width = width;
this.height = height;
this.numPixels = numPixels;
this.simg = simg;
}
public void run() {
int randomX = 0;
int randomY = 0;
boolean success = false;
while(currentPixels < numPixels){
randomX = 0 + (int)(Math.random() * (width));
randomY = 0 + (int)(Math.random() * (height));
success = simg.setColor(color, randomX, randomY);
if(success){
currentPixels++;
}
}
return;
}
}
class synchronizedIMG{
BufferedImage img;
public synchronizedIMG(BufferedImage img){
this.img = img;
}
public synchronized boolean setColor(int color, int x, int y){
if(img.getRGB(x, y) == 0){
img.setRGB(x, y, color);
return true;
} else{
return false;
}
}
}
It requires a certain amount of time to the machine to manage the threads. In image processing, use two threads instead of one, does not reduce the processing time by 50%, but between 30 % to 40 according to the processing (empirical estimation with the multi-threaded classes of my own java library).
Moreover in your case, you don't do any major processing just simple computation. So it's longer to manage the threads than doing the processing on a single thread. Try to do a big convolution.
The biggest problem you face is that adding more threads will not increase the memory bandwidth of your system.
Your threads do nothing except compute random numbers and write them out to memory. Adding more threads potentially increases the speed with which you can compute the random numbers, but that probably was pretty fast to begin with. Math.random() is not a crypto-quality random number generator. It probably is very fast.
Unfortunately, your job isn't done until all of the bytes have been written out to memory. Your system has only one memory bus, and it can only go so fast. All of the threads have to contend for that resource.

Using timer to repaint

I have a maze where robots can move around and explore. I'm trying to use the timer to repaint as the robot move but timer's not kicking in for some reason. It's not delaying the program so I can't see the repainting process. Here's my code:
public void updateDrawing(Maze maze) {
// 1000 millisecond delay
Timer t = new Timer(1000, new TimerListener(maze));
t.start();
}
private class TimerListener implements ActionListener {
private Maze maze;
public TimerListener(Maze maze) {
super();
this.maze = maze;
}
public void actionPerformed(ActionEvent e) {
maze.repaint();
}
}
public void explore (int id, Maze maze) {
visited.add(maze.getCell(row, col));
//Loop until we find the cavern
outerloop: //Label the outerloop for breaking purposes
while(!foundCavern){
//Move to a Cell
Cell validCell = chooseValidCell(maze);
//If validCell is null then we have to backtrack till it's not
if(validCell == null){
while(chooseValidCell(maze) == null){
//Go back in route till we find a valid cell
Cell lastCell = route.pollLast();
if(lastCell == null){ //Implies we didn't find cavern, leave route empty
break outerloop;
}
this.row = lastCell.getRow();
this.col = lastCell.getCol();
updateDrawing(maze); // <- this calls repaint using timer
}
//Add back the current location to the route
route.add(maze.getCell(row, col));
validCell = chooseValidCell(maze);
}
this.row = validCell.getRow();
this.col = validCell.getCol();
updateDrawing(maze); // <- this calls repaint using timer
//Add to the route
route.add(validCell);
//Add to visited
visited.add(validCell);
//Check if we're at the cavern
if(row == targetCavern.getRow() && col == targetCavern.getCol()){
foundCavern = true;
}
}
}
Can anyone tell me why? Thank you!
try use not ** updateDrawing(maze)** but this method:
void updateMaze() {
EventQueue.invokeLater(()->updateDrawing(maze));
}
Here's how to make a basic timer.
All you need to do to calculate the time to display, is to record the time that the timer started:
long startTime = System.currentTimeMillis();
Later, when you want to display the amount of time, you just subtract this from the current time.
long elapsedTime = System.currentTimeMillis() - startTime;
long elapsedSeconds = elapsedTime / 1000;
long secondsDisplay = elapsedSeconds % 60;
long elapsedMinutes = elapsedSeconds / 60;
//put here code to format and display the values
You can make your program wait until elapsedSeconds == whatever value you want.
From Make a simple timer in Java

Unable to use my method with Slick library

So I made a java game with jumping some time ago and I used this method for all the moving:
double height = 0, speed = 4;
public static final double gravity = 9.81;
double x = 25;
int a;
int y = (int) (500-(height*100));
boolean left = false, right = false, up = false;
public void the_jump() {
long previous = 0, start = 0;
while(true){
start = System.nanoTime();
if(previous != 0 && up){
double delta = start - previous;
height = (height + (delta/1000000000) * speed);
speed -= (delta/1000000000) * gravity;
y = (int) (500-(height * 100));
}
if(left){
x-= 3;
}
if(right){
x+= 3;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(height < 0){
height = 0;
speed = 4;
up = false;
}
previous = start;
}
}
Now It was okay when I did it all with just JComponents and such, but now when I want to implement it in a Slick enviroment, it fails.
The problem is in the while(true){} loop. If I change it against for(int i = 0; i < 1; i++) loop, then moving left and right will work. But this will not work for the jumping. I could increase the i < 1 to i < 5 and then the jump will work, but at the cost of a lot of performance.
So how would people implement this in slick? Right now I am calling the the_jump(); out in my public void update(GameContainer gc, int t) throws SlickException method, and if I use the while loop, the game will crash.
Slick already loop on update(GameContainer gc, int delta), you have to put all the code located in your while loop into the update method.
Moreover, you get the delta time between two update as parameter, and so not have to calculate it.
Feel free to ask me more question ;)
Off Topic, do you know if Slick2d is still maintain ? I switch to libGDX a few month ago, and I really advice you to test it, it's soooo fun :)

Categories