I'm making a small asteroids game, and I'm having some trouble controlling the animation speed.
For example, let's say I have 20 asteroids in my game, when I destroy an asteroid, the amount of asteroids goes down (obviously). Because there are fewer objects in the game, the fps goes up and the animation speed of the asteroids is getting faster and faster.
I fixed it by adjusting the animation speed according to the amount of asteroids I have in the game, but I'm also facing another problem with the explosions when I destroy an asteroid. I could do the same thing I did with the asteroids I suppose, but I just think it's not a very wise way to "solve" it and just seems like bad practice to me.
I thought of capping the fps, but I'm not really sure how to do it. I'd like to get some advices and what's the best way to deal with such situations.
I'll post here my main game class including the game loop, and an example of the explosion class so you'll get the general idea of the code.
Game class and loop:
import com.asteroids.view.*;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = -8921419424614180143L;
public static final int WIDTH = 1152, HEIGHT = WIDTH / 8 * 5;
private Thread thread;
private boolean isRunning;
private LoadImages loadImages = new LoadImages();
private Player player = new Player();
private AllObjects objects;
private KeyInput keyInput;
private long delay = 80;
private long currentTime = System.currentTimeMillis();
private long expectedTime = currentTime + delay;
public static BufferedImage test;
public Game() {
new Window(WIDTH, HEIGHT, "Asteroids!", this);
objects = new AllObjects();
objects.addObject(player);
for (int i = 0; i < 20; i++) {
objects.addObject(new Rock((int) (Math.random() * (Game.WIDTH - 64) + 1),
(int) (Math.random() * (Game.HEIGHT - 64) + 1)));
}
keyInput = new KeyInput(player);
this.addKeyListener(keyInput);
}
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;
// main game loop.
while (isRunning) {
adjustAsteroidsSpeed();
destroyAsteroids();
collisionLoop();
// used to set delay between every bullet(milliseconds)
currentTime = System.currentTimeMillis();
if (KeyInput.shoot && currentTime >= expectedTime) {
// calculates the accurate position of the x,y on the "circumference" of the
// player
float matchedX = player.getX() + 1 + (float) ((player.getRadius() + 32) * Math.cos(player.getRadian()));
float matchedY = player.getY() - 7 + (float) ((player.getRadius() + 32) * Math.sin(player.getRadian()));
objects.addObject(new Bullet(matchedX, matchedY, player));
expectedTime = currentTime + delay;
}
destroyBullets();
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
tick();
delta--;
}
if (isRunning)
render();
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
render();
stop();
System.exit(1);
}
private void stop() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(1);
}
private void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.drawImage(LoadImages.getbackground(), 0, 0, getWidth(), getHeight(), this);
objects.render(g);
player.render(g);
g.dispose();
bs.show();
}
private void tick() {
player.tick();
objects.tick();
}
// starting thread and game loop.
public void start() {
thread = new Thread(this);
thread.start();
isRunning = true;
}
// minimum and maximum possible position for object.
public static float Bounds(float value, float min, float max) {
if (value >= max) {
return value = max;
}
if (value <= min) {
return value = min;
} else {
return value;
}
}
// detects collision between two objects
public boolean collision(GameObject a, GameObject b) {
return (b.getX() - a.getX() + 10) * (b.getX() - a.getX() + 10)
+ (b.getY() - a.getY() + 10) * (b.getY() - a.getY() + 10) < (a.getRadius() + b.getRadius())
* (a.getRadius() + b.getRadius());
}
// destroys bullets once they go out of the screen
public void destroyBullets() {
for (int i = 0; i < objects.getSize(); i++) {
if (objects.get(i).getId() == ID.BULLET) {
GameObject bullet = objects.get(i);
if (bullet.getX() > Game.WIDTH || bullet.getX() < 0 || bullet.getY() > Game.HEIGHT
|| bullet.getY() < 0) {
objects.removeObject(bullet);
}
}
}
}
// whenever a collision between an asteroid and a bullet occurs, the asteroid and the bullets are destroyed
public void destroyAsteroids() {
GameObject bullet = null;
GameObject bigRock = null;
for (int i = 0; i < objects.getSize(); i++) {
if (objects.get(i).getId() == ID.BULLET) {
bullet = (Bullet) objects.get(i);
for (int q = 0; q < objects.getSize(); q++) {
if (objects.get(q).getId() == ID.BIGROCK) {
bigRock = objects.get(q);
if (bullet != null && bigRock != null) {
if (collision(bigRock, bullet)) {
objects.addObject(new Explosion(bigRock.getX(), bigRock.getY(), objects));
objects.removeObject(bigRock);
objects.removeObject(bullet);
}
}
}
}
}
}
}
// calculates the amount of asteroids in the game and adjust the asteroids speed
public void adjustAsteroidsSpeed() {
int rocksCount = 0;
Rock rock;
for (GameObject object : objects.link()) {
if (object.getId() == ID.BIGROCK) {
rocksCount++;
}
}
for (GameObject object : objects.link()) {
if (object.getId() == ID.BIGROCK) {
rock = (Rock) object;
rock.setAnimSpeed(rocksCount * 0.002f);
}
}
}
Explosion class:
package com.asteroids.model;
import java.awt.Graphics;
import java.awt.Image;
import com.asteroids.controller.*;
import com.asteroids.view.LoadImages;
public class Explosion extends GameObject {
private AllObjects objects;
private Image explosion;
private float frame = 0;
private float animSpeed = 0.09f;
private int frameCount = 48;
public Explosion(float x, float y, AllObjects objects) {
super(x, y, ID.EXPLOSION, 1);
this.objects = objects;
}
public void render(Graphics g) {
explosion(g);
}
public void explosion(Graphics g) {
frame += animSpeed;
if (frame > frameCount) {
frame -= frameCount;
}
explosion = LoadImages.getExplosion().getSubimage((int) frame * 256, 0, 256, 256);
g.drawImage(explosion, (int) x, (int) y, 110, 110, null);
if (frame >= 47.8f) {
objects.removeObject(this);
}
}
public void tick() {
}
public void setAnimSpeed(float animSpeed) {
this.animSpeed = animSpeed;
}
}
Your main loop is generating uneven updates. If I do nothing, I get anywhere between 7799913 and 8284754 fps, however, if I throw in a 8 millisecond delay (to simulate some work), it drops to around 115-120 fps.
Your intention is to try and get the frame rate to be as even as possible, this will ensure that the animation speed remains the same
Personally, I don't like the "free-wheeling" style of game loop, it means that the loop is been allowed to consume CPU cycles without actually doing anything, where those cycles could be been used to do more important work, like update the UI.
In most cases, I just use a Swing Timer set to something like 5 millisecond intervals and then make use of the date/time API to calculate the difference between now and the last update and make choices about what to do, but, this assumes you're using a Swing based painting path. If you're doing a direct painting path (ie BufferStrategy), you could use a similar idea with a "loop" instead...
public void run() throws InterruptedException {
int frames = 0;
Duration threashold = Duration.ofMillis(1000 / 59);
Duration cycle = Duration.ofSeconds(1);
Instant cycleStart = Instant.now();
// main game loop.
while (isRunning) {
Instant start = Instant.now();
// Some update function...
Thread.sleep(rnd.nextInt(32));
Duration processTime = Duration.between(start, Instant.now());
Duration remainingTime = threashold.minusMillis(processTime.toMillis());
long delay = remainingTime.toMillis();
if (delay > 0) {
Thread.sleep(delay);
} else {
System.out.println("Dropped frame");
}
frames++;
// Render the output
Duration cycleTime = Duration.between(cycleStart, Instant.now());
if (cycleTime.compareTo(cycle) >= 0) {
cycleStart = Instant.now();
System.out.println(frames);
frames = 0;
}
}
}
In this example, your update and paint scheduling code simply have 16 milliseconds to get there job done, otherwise it will drop frames. If the work takes less then 16 milliseconds, the loop will "wait" the remaining time in order to provide some breathing room for the CPU to give time to other threads (and not take update unnecessary time on the CPU)
In the example above, I generate a "random" delay of up to 32 milliseconds for testing. Set it back to 16 and you should get (roughly) 60fps.
Now, I know people are extraordinarily passionate about these things, so if using Thread.sleep and Duration make your skin crawl, you "could" use a "free wheeling" loop, something like the one presented in Java Main Game Loop
Below is a sample implementation, I've set the number of updates and frames per second to 60, but you can change those values to suit your needs...
public void run() throws InterruptedException {
double ups = 60;
double fps = 60;
long initialTime = System.nanoTime();
final double timeU = 1000000000 / ups;
final double timeF = 1000000000 / fps;
double deltaU = 0, deltaF = 0;
int frames = 0, ticks = 0;
long timer = System.currentTimeMillis();
while (isRunning) {
long currentTime = System.nanoTime();
deltaU += (currentTime - initialTime) / timeU;
deltaF += (currentTime - initialTime) / timeF;
initialTime = currentTime;
if (deltaU >= 1) {
Thread.sleep(rnd.nextInt(32));
//getInput();
//update();
ticks++;
deltaU--;
}
if (deltaF >= 1) {
Thread.sleep(rnd.nextInt(32));
//render();
frames++;
deltaF--;
}
if (System.currentTimeMillis() - timer > 1000) {
System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
frames = 0;
ticks = 0;
timer += 1000;
}
}
}
Again, the Thread.sleep here is just to inject a random amount of "work". Because it allows for more then 16ms of delay, you will also find it "drops" frames. Your job would be to get you work down to under 16ms per pass
This part of the program is for calculating how long it takes to face a part.
It starts with some basic info, work piece diameter, feed rate and surface speed you want to tool to operate at. Then it runs a while loop, each time the tool advances 0.010", it calculates the new rpm the piece will rotate at and calculates the time for that cut adding it all up at the end.
The problem: I need to be able to limit the rpms. As the tool gets closer to the center of the work piece the rpms will climb to a very high unattainable rpm, I want to be able to set a limit, 2000 for example.
I cannot figure out how to do that with out affecting my loop... I have searched, but I'm such a noob maybe I've stumbled across a solution that would work and never realized it, or I am not searching for the correct key words. Here is my code:
public static void main(String[] args) {
double startRadius = 6; //Radius of stock diameter
double faceFinish = 0;
double feed = .010; //Amount the tool will advance per revolution
double sfm = 200; //Surface speed of tool (Surface feet per minute)
double rpm = 0;
double totalTime = 0;
while(faceFinish < startRadius) {
startRadius -= feed; //reduces diameter by feed
rpm = (sfm * 3.82) / (startRadius * 2); //establishes new rpm per tool advance
totalTime += (feed / (feed * rpm)) * 60;
}
int hours = (int) (totalTime / 3600);
int minutes = (int) ((totalTime % 3600) / 60);
int seconds = (int) (totalTime % 60);
System.out.printf("%02d:%02d:%02d\n", hours, minutes, seconds);
}
Edit - If/else which seems to be working.
public static void main(String[] args) {
double startRadius = 6;
double faceFinish = 0;
double feed = .010;
double sfm = 200;
double rpm = 0;
double rpm2 = 0;
double total = 0;
double total2 = 0;
double totalTime = 0;
while(faceFinish < startRadius) {
startRadius -= feed;
rpm = (sfm * 3.82) / (startRadius * 2);
if(rpm > 2000) {
rpm = 2000;
total += (feed / (feed * rpm)) * 60;
}else {
total2 += (feed / (feed * rpm)) * 60;
}
totalTime = total + total2;
}
int hours = (int) (totalTime / 3600);
int minutes = (int) ((totalTime % 3600) / 60);
int seconds = (int) (totalTime % 60);
System.out.printf("%02d:%02d:%02d\n", hours, minutes, seconds);
}
Use the below code snippet for the while loop you are using
while(faceFinish < startRadius) {
startRadius -= feed; //reduces diameter by feed
rpm = ((sfm * 3.82) / (startRadius * 2))>2000?2000:((sfm * 3.82) / (startRadius * 2));
totalTime += (feed / (feed * rpm)) * 60;
}
Please try below code.
class Rpm{
double value=0.0;
final double LIMIT = 2000.0;
public void setValue(double value){
if(value < LIMIT)
this.value= value;
}
public double getValue(){
return this.value;
}
}
public class yourclassname {
public static void main(String[] args) {
double startRadius = 6; //Radius of stock diameter
double faceFinish = 0;
double feed = .010; //Amount the tool will advance per revolution
double sfm = 200; //Surface speed of tool (Surface feet per minute)
Rpm rpm = new Rpm();
double totalTime = 0;
while(faceFinish < startRadius) {
startRadius -= feed; //reduces diameter by feed
rpm.setValue( (sfm * 3.82) / (startRadius * 2)); //establishes new rpm per tool advance
totalTime += (feed / (feed * rpm.getValue())) * 60;
}
int hours = (int) (totalTime / 3600);
int minutes = (int) ((totalTime % 3600) / 60);
int seconds = (int) (totalTime % 60);
System.out.printf("%02d:%02d:%02d\n", hours, minutes, seconds);
}
}
So, I'm writing a small game. I have a JPanel inside of a JFrame and the JFrame repaints in the game engine class every frame, but the player sprite will not react to keypress events.
Any help?
Oh, I'm not entirely sure what code to post because I can't seem to find the source of the problem in any way so just tell me what code you wish to see.
Run method in the engine:
#Override
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1000000000D / 60D;
int ticks = 0;
int frames = 0;
long lastTimer = System.currentTimeMillis();
double delta = 0;
init();
while (engineRunning) {
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean shouldRender = true;
while (delta >= 1) {
ticks++;
tick();
delta -= 1;
shouldRender = true;
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (shouldRender) {
frames++;
render();
}
if (System.currentTimeMillis() - lastTimer >= 1000) {
lastTimer += 1000;
frames = 0;
ticks = 0;
}
}
}
Tick & render in engine class:
public void tick() {
for (BaseEntity e : worldEntities) {
e.tick();
}
}
public void render() {
frame.repaint();
}
Player tick:
public void tick() {
Position.X += Position.velX;
Position.Y += Position.velY;
if (Position.velX > Max_Speed)
Position.velX = Max_Speed;
if (Position.velY > Max_Speed)
Position.velY = Max_Speed;
}
Here is what I have so far
long startTime = 0;
TextView timerTextView;
final Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
#Override
public void run() {
long millis = System.currentTimeMillis() - startTime;
int seconds = (int) millis / 1000;
int minutes = (int) seconds / 60;
seconds %= 60;
int min = 40000;
int max = 49999;
long randomTime = (long) (Math.random()*(min-max) + max);
timerTextView.setText(minutes + ":" + seconds);
timerHandler.postDelayed(this, 500);
}
};
I want to know how I can use the randomTime variable to trigger my TextView to change to say Time Is Up. I found a way to create a scheduled task using the following:
timerHandler.postAtTime(Runnable r, randomTime);
The only problem is I don't know what Runnable I should use to cause the TextView to change.
First of all put the randomTime outside of the thread,
int min = 40000;
int max = 49999;
long randomTime = (long) (Math.random()*(min-max) + max);
Create a simple handler that'll run after the time specified by randomTime variable,
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// handler has reached the time
// now update the TextView using a runOnUiThread
runOnUiThread(new Runnable() {
public void run() {
// Update UI elements
// get the current time now
long millis = System.currentTimeMillis() - startTime;
int seconds = (int) millis / 1000;
int minutes = (int) seconds / 60;
seconds %= 60;
//finally update the TextView
timerTextView.setText(minutes + ":" + seconds);
}
});
}
}, randomTime);
I'm trying to make a basic timing loop, but I keep getting
java.lang.ArithmaticException / by zero error
at Timer.advanceTime(Timer.java:24)
at Game.run(Game.java:79)
at java.lang.Thread.run(Thread.java:722)
here is my code:
public class Timer {
private static final long NS_PER_SECOND = 1000000000L;
private static final long MAX_NS_PER_UPDATE = 1000000000L;
private static final int MAX_TICKS_PER_UPDATE = 100;
private float ticksPerSecond;
private long lastTime;
public int ticks;
public float a;
public float timeScale = 1.0F;
public float fps = 0.0F;
public float passedTime = 0.0F;
public Timer(float ticksPerSecond) {
this.ticksPerSecond = ticksPerSecond;
this.lastTime = System.nanoTime();
}
public void advanceTime() {
long now = System.nanoTime();
long passedNs = now - this.lastTime;
this.lastTime = now;
if (passedNs < 0L) passedNs = 0L;
if (passedNs > 1000000000L) passedNs = 1000000000L;
this.fps = (float)(1000000000L / passedNs);
this.passedTime += (float)passedNs * this.timeScale * this.ticksPerSecond / 1.0E+009F;
this.ticks = (int)this.passedTime;
if (this.ticks > 100) this.ticks = 100;
this.passedTime -= this.ticks;
this.a = this.passedTime;
}
}
From the Javadoc for System.nanoTime
This method provides nanosecond precision, but not necessarily
nanosecond resolution (that is, how frequently the value changes) - no
guarantees are made except that the resolution is at least as good as
that of currentTimeMillis()
Therefore , the two consecutive calls that you make to System.nanoTime() are likely to have the same return value - which is why dividing by the difference between them gives you the exception.
probably this will be the reason
if (passedNs < 0L) passedNs = 0L; // if so passedNs becomes Zero
if (passedNs > 1000000000L) passedNs = 1000000000L;
this.fps = (float)(1000000000L / passedNs); // if passedNs is zero the error will occur
You can't divide by value zero. It will throw an arithmetic exception. You have to avoid that by checking the 'passedNs' value is Zero or not or you have to handle that exception using a try catch block simply.
if(passedNs != 0L){
this.fps = (float)(1000000000L / passedNs);
}
or
try{
this.fps = (float)(1000000000L / passedNs);
}catch(Exception e){
//handle the exception
}