java游戏如何构建定时效果/固定距离效果?

时间:2017-10-15 21:07:26

标签: java javafx timer game-physics 2d-games

我正在用javafx编写一个java游戏,但我认为这个问题的解决方案并不是javafx独有的...

我有一个Entity类和一些子类,如Missiles,Lasers等。但是,当导弹和激光由游戏中的角色创建时,它们始终保持运行直到它们到达画布的矩形边界或当他们击中一个角色并消失时。

但是,我希望导弹/激光可以有许多其他行为:

  • 导弹可以继续飞行直到它在4秒后消失
  • 导弹可以继续飞行,直到它行进600像素后消失(这与前一个类似)
  • 激光可以存在于矩形区域中,也可以与角色一起移动,直到它在4秒后消失。

问题是,我们如何才能实现这种时间效应? (也许是propertyChangeListener?)或者我应该向实体本身添加内容,还是应该考虑更改我的Controller类? 以下是我的代码:

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

更新[看起来不错:)):

enter image description here

2 个答案:

答案 0 :(得分:3)

在评论中,您问我如何使用单个线程来管理多个实体的定时销毁。首先,我们不要在使用线程方面考虑它。我们真的想做什么?我们希望执行延时行动。

我们怎么做?嗯......

事件循环调度简介

我们可以创建一个调度程序来在特定时间执行操作(或响应事件)。这些可能是一次性操作,也可能是以固定间隔重复的重复操作。您可能会在游戏中结束许多此类操作。我们怎么能实现这个呢?好吧,我们实际上并不是必须;它做了很多次,做得很好。但它的主旨是:

  1. 创建一个队列来保存计划的工作项。这应该是一个优先级队列,排序使得最接近时间的工作项最接近队列的前面。
  2. 启动等待队列的处理线程。
  3. 各种游戏组件通过将工作项插入队列(以线程安全的方式)来调度工作项。插入后,它们会通知处理线程唤醒并检查队列。
  4. 处理线程在唤醒时检查队列中是否有工作项。如果找到一个,它会查看其完成时间并将其与当前游戏时间进行比较。
    • 如果是时候运行工作项,那就是这样。如果有的话,请对队列中的下一个项目重复步骤4.
    • 如果时间来运行工作项,则线程会在dueTime - currentTime过去之前自行进入休眠状态。
    • 或者,如果队列为空,则无限期地睡眠;我们会在安排下一个工作项目时醒来。
  5. 这种调度程序称为事件循环:它以“出队,运行,等待,重复”循环运行。在RxJava中可以找到一个很好的例子。您可以像这样使用它:

    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) */
    }
    

    要在4秒后安排要销毁的实体,请拨打entity.destroyAfter(4L, TimeUnit.SECONDS)。该调用将安排在4秒延迟后调用destroyNow()方法。计划的操作存储在SerialDisposable中,可用于“处置”某个对象。在这种情况下,我们使用它来跟踪计划的销毁行动,“处置”相当于取消该行动。在上面的示例中,这有两个目的:

    1. 如果实体被其他方式摧毁,例如,玩家射击并摧毁导弹,你可以简单地调用destroyNow(),这反过来取消任何先前预定的破坏(现在将是多余的)。 / LI>
    2. 如果您想更改销毁时间,您可以再次拨打destroyAfter,如果原始计划的操作尚未发生,则会取消并阻止从跑步。
    3. 注意事项

      游戏是一个有趣的案例,特别是关于时间。考虑:

      1. 游戏中的时间不一定以恒定的速度进行。当游戏性能不佳时,时间流量通常会相应减慢。 (通常)也可以暂停时间。

      2. 游戏可能依赖于多个“时钟”。玩家可以暂停游戏,有效地冻结“游戏时间”时钟。同时,玩家仍然可以与游戏菜单和选项屏幕进行交互,这可以根据“实际”时间(例如系统时间)进行动画制作。

      3. 游戏时间通常以一个方向流动,而系统时间则不流动。如今,大多数PC都将其系统时钟与时间服务器保持同步,因此系统时间会不断地针对“漂移”进行校正。因此,系统时钟在时间上向后跳跃并不罕见。

      4. 因为系统时间往往略有波动,所以不是。但是,在运行代码时,我们处于系统调度程序的怜悯之中。如果我们设定一个目标,即将游戏时间提前一秒'打'60秒(目标60fps),我们需要了解我们的'滴答'几乎永远不会发生完全当我们想要它们时。因此,我们应该插入:如果我们的滴答在我们预期之前或之后稍微发生,我们应该将游戏时间稍微减少或超过一个'滴答'。

      5. 这些注意事项可能会阻止您使用第三方计划程序。您可能仍然会在开发早期使用一个,但最终您需要一个根据游戏时间而不是系统时间进行的操作。 RxJava实际上有一个名为TestScheduler的{​​{3}},由外部时钟控制。但是,它不是线程安全的,并且依赖于外部参与者手动推进时间,但您可以将其用作您自己的调度程序的模型。

答案 1 :(得分:2)

嗯,我不是游戏行业的专家,但这是我的建议:

  1. 有一个变量(布尔值)显示对象的状态。
    • private volatile boolean isDestroyed = false;
  2. 注意:必须使用volatile!

    1. 如果游戏对象有时间限制(自毁),那么在其构造函数内部(或通常在其类中)创建时,启动一个任务休眠所需的持续时间,并在任务结束时将isDestroyed变量更新为真正。
    2. 注意:您还可以在任务中放置动画以销毁游戏对象。

      1. 在您正在进行所有渲染操作的主更新()中,您可以忽略渲染游戏对象或从列表中删除它(小心避免ConcurrentModificationException)
      2. 编辑: 让我们试试一个例子......下面的代码不是绘制/移动形状的最佳方式,只是为了展示我的解决方案。

        主要课程

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

        次级

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