I am writing a java game in javafx, but I think the solution to this question isn't unique to javafx...
I have a Entity class and a bunch of its subclasses such as Missiles, Lasers, etc. However, when the Missiles and Lasers are created by characters in the game, they always keep running until they hit the rectangular boundary of the canvas or when they hit a character and disappear.
However, I expect that there are many other behaviors that the missiles/lasers can have:
The question is, how can we achieve this timed effect? (Maybe propertyChangeListener?) Or should I add stuff to the Entity itself, or should I consider altering my Controller Class? Here are the codes I have:
public abstract class Entity implements Collidable{
private double x;
private double y;
private int z;
private double velocityX;
private double velocityY;
private Image img;
private boolean ally;
protected double width;
protected double height;
public Entity(int x,int y,int z,boolean b,Image hihi)
{
setX(x);
setY(y);
setZ(z);
ally=b;
setVelocityX(0);
setVelocityY(0);
img= hihi;
}
public void move()
{
x+=getVelocityX();
y+=getVelocityY();
}
...
...
}
public class Controller {
private List<BattleShip> bs;
private List<Missile> m;
private Rectangle2D rect;
public Controller()
{
bs= new ArrayList<BattleShip>();
m= new ArrayList<Missile>();
rect= new Rectangle2D(-300, -300, 1300, 1050);
}
public void update()
{
for(int i = bs.size() - 1; i >= 0; i --) {
bs.get(i).move();
if (!rect.contains(bs.get(i).getRect())) {
bs.remove(i);
}
}
for(int i = m.size() - 1; i >= 0; i --) {
m.get(i).move();
if (!rect.contains(m.get(i).getRect())) {
m.remove(i);
}
}
collide();
}
Update[ Looks good : ) ] :

In a comment, you asked me how you might use a single thread to manage the timed destruction of multiple entities. First, let's not think about it in terms of using threads. What do we really want to do? We want to perform time-delayed actions.
How might we do this? Well...
We can create a scheduler to perform actions (or respond to events) at a specific time. These might be one-time actions, or recurring actions that repeat at a fixed interval. You will likely end up with many such actions in your game. How might we implement this? Well, we don't actually have to; it's been done many times, and done quite well. But the jist of it is this:
dueTime - currentTime has elapsed.This kind of scheduler is known as an event loop: it runs in a "dequeue, run, wait, repeat" loop. A good example can be found in RxJava. You could use it like this:
import io.reactivex.Scheduler;
import io.reactivex.disposables.SerialDisposable;
public final class GameSchedulers {
private static final Scheduler EVENT_LOOP =
io.reactivex.schedulers.Schedulers.single();
public static Scheduler eventLoop() {
return EVENT_LOOP;
}
}
public abstract class Entity implements Collidable {
private final SerialDisposable scheduledDestruction = new SerialDisposable();
private volatile boolean isDestroyed;
public void destroyNow() {
this.isDestroyed = true;
this.scheduledDestruction.dispose();
}
public void destroyAfter(long delay, TimeUnit unit) {
scheduledDestruction.set(
GameSchedulers.eventLoop()
.scheduleDirect(this::destroyNow, delay, unit)
);
}
/* (rest of class omitted) */
}
To schedule an entity to be destroyed after 4 seconds, you would call entity.destroyAfter(4L, TimeUnit.SECONDS). That call would schedule the destroyNow() method to be called after a 4 second delay. The scheduled action is stored in a SerialDisposable, which can be used to 'dispose' of some object. In this case, we use it to track the scheduled destruction action, and the 'disposal' amounts to a cancellation of that action. In the example above, this serves two purposes:
destroyNow(), which in turn cancels any previously scheduled destruction (which would now be redundant).destroyAfter a second time, and if the originally scheduled action hasn't occurred yet, it will be canceled and prevented from running.Games are an interesting case, specifically with regards to time. Consider:
Time in a game does not necessarily proceed at a constant rate. When a game experiences poor performance, the flow of time generally slows accordingly. It's also (usually) possible to pause time.
A game may rely on more than one 'clock'. The player may pause gameplay, effectively freezing the 'game time' clock. Meanwhile, the player may still interact with game menus and options screens, which might be animated according to 'real' time (e.g. system time).
Game time usually flows in one direction, while system time does not. Most PCs these days keep their system clock synchronized with a time server, so the system time is constantly being corrected for 'drift'. Thus, it is not unusual for the system clock to jump backward in time.
Because system time tends to fluctuate slightly, it's not smooth. However, we're at the system scheduler's mercy when it comes to running our code. If we set a goal of advancing game time by one 'tick' 60 times a second (to target 60fps), we need to understand that our 'ticks' will almost never happening exactly when we want them to. Thus, we ought to interpolate: if our tick occurred slightly before or after we expected it, we should advance our game time by slightly less or more than one 'tick'.
These kinds of considerations may prevent you from using a third-party scheduler. You might still use one early in development, but eventually you'll need one that proceeds according to game time and not system time. RxJava actually has a scheduler implementation called TestScheduler that is controlled by an external clock. However, it is not thread safe, and relies on an external actor manually advancing time, but you could use it as a model for your own scheduler.
Well, I am not expert in the game industry but this is what I suggest :
private volatile boolean isDestroyed = false;Note: the volatile is necessary!
Note: You can also put an animation for the destruction of the game object inside the Task.
Edit : So let's try an example.. the code below its not the optimal way of drawing/moving shapes it's just to show my solution.
Main class
import java.util.ArrayList;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
Group root = new Group();
Scene theScene = new Scene(root);
stage.setScene(theScene);
Canvas canvas = new Canvas(512, 820);
root.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
ArrayList<Lasser> allLassers = new ArrayList<>();
Random randGen = new Random();
for (int i = 0; i < 10; i++) {
// create 10 lessers with different self-destruction time
// on random places
allLassers.add(new Lasser(randGen.nextInt(500) + 10, 800, i * 1000));
}
new AnimationTimer() {
public void handle(long currentNanoTime) {
// Clear the canvas
gc.clearRect(0, 0, 512, 820);
for (Lasser l : allLassers) {
// if the current object is still ok
if (!l.isDestroyed()) {
// draw it
gc.fillRect(l.getxPos(), l.getyPos(), l.getWidth(), l.getHeight());
}
}
// remove all destroyed object
for (int i = allLassers.size() - 1; i >= 0; i--) {
if (allLassers.get(i).isDestroyed()) {
allLassers.remove(i);
}
}
}
}.start();
stage.show();
}
}
Lesser class
import javafx.concurrent.Task;
import javafx.scene.shape.Rectangle;
public class Lasser extends Rectangle {
private volatile boolean isDestroyed = false;
private double xPos;
private double yPos;
public Lasser(double x, double y, long time) {
super(x, y, 5, 20);
this.xPos = x;
this.yPos = y;
startSelfDestruct(time);
}
private void startSelfDestruct(long time) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
}
return null;
}
};
task.setOnSucceeded(e -> {
isDestroyed = true;
});
new Thread(task).start();
}
public void move(double x, double y) {
this.xPos = x;
this.yPos = y;
}
public boolean isDestroyed() {
return isDestroyed;
}
public double getxPos() {
return xPos;
}
public double getyPos() {
this.yPos -= 1;
return yPos;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With