Search code examples
javajavafxtimergame-physics2d-games

java game how to construct timed effect/fixed distance effect?


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:

  • A missile can keep flying until it disappears 4 seconds later
  • A missile can keep flying until it dies out after traveling for 600 pixels(this is similar to the previous one)
  • A laser can exist in a rectangular zone or move with the character until it dies out after 4 seconds.

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 : ) ] :

enter image description here


Solution

  • Well, I am not expert in the game industry but this is what I suggest :

    1. Have a variable ( boolean) showing the state of the object.
      • private volatile boolean isDestroyed = false;

    Note: the volatile is necessary!

    1. If the game object has a time limit ( self-destruct ) then upon creation inside its constructor ( or generally inside its class ) start a Task sleeping for the duration you want and at the end of the task update the isDestroyed variable to true.

    Note: You can also put an animation for the destruction of the game object inside the Task.

    1. In the main update() where you are doing all the rendering operations, you can either ignore rendering the game object or remove it from the list ( Be careful to avoid ConcurrentModificationException )

    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;
        }
    
    }