我正在用javafx编写一个java游戏,但我认为这个问题的解决方案并不是javafx独有的...
我有一个Entity类和一些子类,如Missiles,Lasers等。但是,当导弹和激光由游戏中的角色创建时,它们始终保持运行直到它们到达画布的矩形边界或当他们击中一个角色并消失时。
但是,我希望导弹/激光可以有许多其他行为:
问题是,我们如何才能实现这种时间效应? (也许是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();
}
更新[看起来不错:)):
答案 0 :(得分:3)
在评论中,您问我如何使用单个线程来管理多个实体的定时销毁。首先,我们不要在使用线程方面考虑它。我们真的想做什么?我们希望执行延时行动。
我们怎么做?嗯......
我们可以创建一个调度程序来在特定时间执行操作(或响应事件)。这些可能是一次性操作,也可能是以固定间隔重复的重复操作。您可能会在游戏中结束许多此类操作。我们怎么能实现这个呢?好吧,我们实际上并不是必须;它做了很多次,做得很好。但它的主旨是:
dueTime - currentTime
过去之前自行进入休眠状态。这种调度程序称为事件循环:它以“出队,运行,等待,重复”循环运行。在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
中,可用于“处置”某个对象。在这种情况下,我们使用它来跟踪计划的销毁行动,“处置”相当于取消该行动。在上面的示例中,这有两个目的:
destroyNow()
,这反过来取消任何先前预定的破坏(现在将是多余的)。 / LI>
destroyAfter
,如果原始计划的操作尚未发生,则会取消并阻止从跑步。游戏是一个有趣的案例,特别是关于时间。考虑:
游戏中的时间不一定以恒定的速度进行。当游戏性能不佳时,时间流量通常会相应减慢。 (通常)也可以暂停时间。
游戏可能依赖于多个“时钟”。玩家可以暂停游戏,有效地冻结“游戏时间”时钟。同时,玩家仍然可以与游戏菜单和选项屏幕进行交互,这可以根据“实际”时间(例如系统时间)进行动画制作。
游戏时间通常以一个方向流动,而系统时间则不流动。如今,大多数PC都将其系统时钟与时间服务器保持同步,因此系统时间会不断地针对“漂移”进行校正。因此,系统时钟在时间上向后跳跃并不罕见。
因为系统时间往往略有波动,所以不是。但是,在运行代码时,我们处于系统调度程序的怜悯之中。如果我们设定一个目标,即将游戏时间提前一秒'打'60秒(目标60fps),我们需要了解我们的'滴答'几乎永远不会发生完全当我们想要它们时。因此,我们应该插入:如果我们的滴答在我们预期之前或之后稍微发生,我们应该将游戏时间稍微减少或超过一个'滴答'。
这些注意事项可能会阻止您使用第三方计划程序。您可能仍然会在开发早期使用一个,但最终您需要一个根据游戏时间而不是系统时间进行的操作。 RxJava实际上有一个名为TestScheduler
的{{3}},由外部时钟控制。但是,它不是线程安全的,并且依赖于外部参与者手动推进时间,但您可以将其用作您自己的调度程序的模型。
答案 1 :(得分:2)
嗯,我不是游戏行业的专家,但这是我的建议:
private volatile boolean isDestroyed = false
; 注意:必须使用volatile!
注意:您还可以在任务中放置动画以销毁游戏对象。
编辑: 让我们试试一个例子......下面的代码不是绘制/移动形状的最佳方式,只是为了展示我的解决方案。
主要课程
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;
}
}