java.lang.IllegalArgumentException: 问题和 Hitbox
java.lang.IllegalArgumentException: Problem and Hitbox
我有两个问题,一个是,如果我想用圆圈对象显示分数:
layoutV.getChildren().addAll(virus, score);
我收到以下错误:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = Pane@6661fc86[styleClass=root].
据我了解是因为Task要显示多个分数。那我是不是应该换个场景或者布局来显示分数呢?
我的另一个问题是对象的碰撞箱,每次我点击分数都会上升。我查找了鼠标事件 getTarget,但似乎我无法做到这一点,因此我的对象是使用鼠标事件的唯一目标。
public class Main extends Application {
private Stage window;
private Pane layoutV;
private Scene scene;
private Circle virus;
private int score;
private Label scores;
@Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Enemy TEST");
this.score = 0;
scores = new Label("Score "+ score);
layoutV = new Pane();
scene = new Scene(layoutV, 600, 600);
window.setScene(scene);
window.show();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Task task = new Task<Void>() {
@Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
@Override
public void run() {
drawCircles();
}
});
Thread.sleep(1000);
}
}
};
public void drawCircles() {
double x = (double)(Math.random() * ((550 - 50) + 1)) + 50;
double y = (double)(Math.random() * ((550 - 50) + 1)) + 50;
double r = (double)(Math.random() * ((30 - 10) + 1)) + 10;
virus = new Circle(x, y, r, Color.VIOLET);
layoutV.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
layoutV.getChildren().remove(e.getTarget());
this.score++;
System.out.println("score: "+ this.score);
}
});
layoutV.getChildren().addAll(virus);
scene.setRoot(layoutV);
window.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
你有很多问题,不仅仅是你问题中的那些:
虽然它会像你编码的那样工作
它,我不建议生成一个线程来画你的圈子,而是
见:
- JavaFX periodic background task
不需要在场景中设置root,在场景中设置场景
window每次画一个新的圆。
也不需要设置
每次画圆时,布局上的鼠标处理程序。
与其在布局上设置鼠标处理程序,不如在圆圈本身上设置鼠标处理程序(您可以在将它们添加到场景之前执行此操作)。
score
是一个int
,不是节点只能添加节点到场景
图。
见documentation for the scene package:
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in the children list of a Parent or as the clip of a Node. See the Node class for more details on these restrictions.
我不清楚您是如何多次添加节点的,因为您使用的代码可能与您提供的 Main class 不同。
要在顶部添加一个带分数的圆圈,请在标签中使用带有分数的 StackPane,但要使标签鼠标透明,这样它就不会记录任何点击:
Label scoreLabel = new Label(score + "");
scoreLabel.setMouseTransparent(true);
StackPane balloon = new StackPane(circle, scoreLabel);
layoutV.getChildren.add(balloon);
在气球上添加点击处理程序。
还有其他问题我没有在这里详细说明,但在提供的演示代码中已解决。
为了修复您的所有错误,我会编写如下代码。也许您可以查看它并将其与您的代码进行比较,以帮助理解创建此游戏的一种方法。
示例代码可能不完全是您正在寻找的功能(这不是它的真正目的),但它应该足以让您走上正确的轨道来实施您的应用程序。
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.ThreadLocalRandom;
public class Inoculation extends Application {
public static final int W = 600;
public static final int H = 600;
private final IntegerProperty score = new SimpleIntegerProperty(0);
private final Pane playingField = new Pane();
@Override
public void start(Stage stage) {
StackPane overlay = createOverlay();
Pane layout = new StackPane(playingField, overlay);
stage.setScene(new Scene(layout, W, H));
stage.show();
Infection infection = new Infection(playingField, score);
infection.begin();
}
private StackPane createOverlay() {
Label totalScoreLabel = new Label();
totalScoreLabel.textProperty().bind(
Bindings.concat(
"Score ", score.asString()
)
);
StackPane overlay = new StackPane(totalScoreLabel);
StackPane.setAlignment(totalScoreLabel, Pos.TOP_LEFT);
overlay.setMouseTransparent(true);
return overlay;
}
public static void main(String[] args) {
launch(args);
}
}
class Infection {
private static final Duration SPAWN_PERIOD = Duration.seconds(1);
private static final int NUM_SPAWNS = 10;
private final Timeline virusGenerator;
public Infection(Pane playingField, IntegerProperty score) {
virusGenerator = new Timeline(
new KeyFrame(
SPAWN_PERIOD,
event -> spawnVirus(
playingField,
score
)
)
);
virusGenerator.setCycleCount(NUM_SPAWNS);
}
public void begin() {
virusGenerator.play();
}
private void spawnVirus(Pane playingField, IntegerProperty score) {
Virus virus = new Virus();
virus.setOnMouseClicked(
event -> {
score.set(score.get() + virus.getVirusScore());
playingField.getChildren().remove(virus);
}
);
playingField.getChildren().add(virus);
}
}
class Virus extends StackPane {
private static final int MAX_SCORE = 3;
private static final int RADIUS_INCREMENT = 10;
private final int virusScore = nextRandInt(MAX_SCORE) + 1;
public Virus() {
double r = (MAX_SCORE + 1 - virusScore) * RADIUS_INCREMENT;
Circle circle = new Circle(
r,
Color.VIOLET
);
Text virusScoreText = new Text("" + virusScore);
virusScoreText.setBoundsType(TextBoundsType.VISUAL);
virusScoreText.setMouseTransparent(true);
getChildren().setAll(
circle,
virusScoreText
);
setLayoutX(nextRandInt((int) (Inoculation.W - circle.getRadius() * 2)));
setLayoutY(nextRandInt((int) (Inoculation.H - circle.getRadius() * 2)));
setPickOnBounds(false);
}
public int getVirusScore() {
return virusScore;
}
// next random int between 0 (inclusive) and bound (exclusive)
private int nextRandInt(int bound) {
return ThreadLocalRandom.current().nextInt(bound);
}
}
关于此实现的一些可能有用的附加说明:
总分放在 overlayPane 中,这样它就不会被添加到 playingField(包含病毒生成)的元素遮挡。
制作overlayPanemouseTransparent,这样它就不会拦截任何鼠标事件,点击会落到游戏区的项目上。
无论您是否调整 window 大小,该应用程序目前都会在固定字段大小内生成病毒。这就是它的设计和编码方式,您可以根据需要以其他方式进行编码。这样做会更麻烦。
绑定 class 用于创建字符串表达式 binding which concatenates 静态字符串“Score”,其中整数 属性 表示分数。这允许将表示分数的字符串绑定到叠加层中的分数标签文本,以便在分数更改时自动更新。
病毒生成使用 timeline 并基于以下概念:
- JavaFX periodic background task
应用程序 class 故意保持简单,主要是处理核心 application lifecycle,应用程序的实际功能被抽象为感染 class它处理病毒的产生和产生新病毒的病毒class。
此技术用于将每个病毒的分数集中在病毒上:
- how to put a text into a circle object to display it from circle's center?
病毒本身位于 StackPane. The pane has pick on bounds 设置为 false 中。要删除病毒感染,您必须单击代表病毒的圆圈,而不仅仅是堆栈窗格正方形中的任意位置。
因为圆坐标是局部坐标,圆在表示病毒的父堆栈窗格中,圆本身不需要设置 x 和 y 值,而是布局 x 和 y 值设置在封闭窗格上,以允许定位代表整个病毒的窗格。
以下技术用于使用ThreadLocalRandom生成可接受范围内的随机整数:
- How do I generate random integers within a specific range in Java?
我有两个问题,一个是,如果我想用圆圈对象显示分数:
layoutV.getChildren().addAll(virus, score);
我收到以下错误:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = Pane@6661fc86[styleClass=root].
据我了解是因为Task要显示多个分数。那我是不是应该换个场景或者布局来显示分数呢?
我的另一个问题是对象的碰撞箱,每次我点击分数都会上升。我查找了鼠标事件 getTarget,但似乎我无法做到这一点,因此我的对象是使用鼠标事件的唯一目标。
public class Main extends Application {
private Stage window;
private Pane layoutV;
private Scene scene;
private Circle virus;
private int score;
private Label scores;
@Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("Enemy TEST");
this.score = 0;
scores = new Label("Score "+ score);
layoutV = new Pane();
scene = new Scene(layoutV, 600, 600);
window.setScene(scene);
window.show();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Task task = new Task<Void>() {
@Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
@Override
public void run() {
drawCircles();
}
});
Thread.sleep(1000);
}
}
};
public void drawCircles() {
double x = (double)(Math.random() * ((550 - 50) + 1)) + 50;
double y = (double)(Math.random() * ((550 - 50) + 1)) + 50;
double r = (double)(Math.random() * ((30 - 10) + 1)) + 10;
virus = new Circle(x, y, r, Color.VIOLET);
layoutV.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
layoutV.getChildren().remove(e.getTarget());
this.score++;
System.out.println("score: "+ this.score);
}
});
layoutV.getChildren().addAll(virus);
scene.setRoot(layoutV);
window.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
你有很多问题,不仅仅是你问题中的那些:
虽然它会像你编码的那样工作 它,我不建议生成一个线程来画你的圈子,而是 见:
- JavaFX periodic background task
不需要在场景中设置root,在场景中设置场景 window每次画一个新的圆。
也不需要设置 每次画圆时,布局上的鼠标处理程序。
与其在布局上设置鼠标处理程序,不如在圆圈本身上设置鼠标处理程序(您可以在将它们添加到场景之前执行此操作)。
score
是一个int
,不是节点只能添加节点到场景 图。见documentation for the scene package:
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in the children list of a Parent or as the clip of a Node. See the Node class for more details on these restrictions.
我不清楚您是如何多次添加节点的,因为您使用的代码可能与您提供的 Main class 不同。
要在顶部添加一个带分数的圆圈,请在标签中使用带有分数的 StackPane,但要使标签鼠标透明,这样它就不会记录任何点击:
Label scoreLabel = new Label(score + ""); scoreLabel.setMouseTransparent(true); StackPane balloon = new StackPane(circle, scoreLabel); layoutV.getChildren.add(balloon);
在气球上添加点击处理程序。
还有其他问题我没有在这里详细说明,但在提供的演示代码中已解决。
为了修复您的所有错误,我会编写如下代码。也许您可以查看它并将其与您的代码进行比较,以帮助理解创建此游戏的一种方法。
示例代码可能不完全是您正在寻找的功能(这不是它的真正目的),但它应该足以让您走上正确的轨道来实施您的应用程序。
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.ThreadLocalRandom;
public class Inoculation extends Application {
public static final int W = 600;
public static final int H = 600;
private final IntegerProperty score = new SimpleIntegerProperty(0);
private final Pane playingField = new Pane();
@Override
public void start(Stage stage) {
StackPane overlay = createOverlay();
Pane layout = new StackPane(playingField, overlay);
stage.setScene(new Scene(layout, W, H));
stage.show();
Infection infection = new Infection(playingField, score);
infection.begin();
}
private StackPane createOverlay() {
Label totalScoreLabel = new Label();
totalScoreLabel.textProperty().bind(
Bindings.concat(
"Score ", score.asString()
)
);
StackPane overlay = new StackPane(totalScoreLabel);
StackPane.setAlignment(totalScoreLabel, Pos.TOP_LEFT);
overlay.setMouseTransparent(true);
return overlay;
}
public static void main(String[] args) {
launch(args);
}
}
class Infection {
private static final Duration SPAWN_PERIOD = Duration.seconds(1);
private static final int NUM_SPAWNS = 10;
private final Timeline virusGenerator;
public Infection(Pane playingField, IntegerProperty score) {
virusGenerator = new Timeline(
new KeyFrame(
SPAWN_PERIOD,
event -> spawnVirus(
playingField,
score
)
)
);
virusGenerator.setCycleCount(NUM_SPAWNS);
}
public void begin() {
virusGenerator.play();
}
private void spawnVirus(Pane playingField, IntegerProperty score) {
Virus virus = new Virus();
virus.setOnMouseClicked(
event -> {
score.set(score.get() + virus.getVirusScore());
playingField.getChildren().remove(virus);
}
);
playingField.getChildren().add(virus);
}
}
class Virus extends StackPane {
private static final int MAX_SCORE = 3;
private static final int RADIUS_INCREMENT = 10;
private final int virusScore = nextRandInt(MAX_SCORE) + 1;
public Virus() {
double r = (MAX_SCORE + 1 - virusScore) * RADIUS_INCREMENT;
Circle circle = new Circle(
r,
Color.VIOLET
);
Text virusScoreText = new Text("" + virusScore);
virusScoreText.setBoundsType(TextBoundsType.VISUAL);
virusScoreText.setMouseTransparent(true);
getChildren().setAll(
circle,
virusScoreText
);
setLayoutX(nextRandInt((int) (Inoculation.W - circle.getRadius() * 2)));
setLayoutY(nextRandInt((int) (Inoculation.H - circle.getRadius() * 2)));
setPickOnBounds(false);
}
public int getVirusScore() {
return virusScore;
}
// next random int between 0 (inclusive) and bound (exclusive)
private int nextRandInt(int bound) {
return ThreadLocalRandom.current().nextInt(bound);
}
}
关于此实现的一些可能有用的附加说明:
总分放在 overlayPane 中,这样它就不会被添加到 playingField(包含病毒生成)的元素遮挡。
制作overlayPanemouseTransparent,这样它就不会拦截任何鼠标事件,点击会落到游戏区的项目上。
无论您是否调整 window 大小,该应用程序目前都会在固定字段大小内生成病毒。这就是它的设计和编码方式,您可以根据需要以其他方式进行编码。这样做会更麻烦。
绑定 class 用于创建字符串表达式 binding which concatenates 静态字符串“Score”,其中整数 属性 表示分数。这允许将表示分数的字符串绑定到叠加层中的分数标签文本,以便在分数更改时自动更新。
病毒生成使用 timeline 并基于以下概念:
- JavaFX periodic background task
应用程序 class 故意保持简单,主要是处理核心 application lifecycle,应用程序的实际功能被抽象为感染 class它处理病毒的产生和产生新病毒的病毒class。
此技术用于将每个病毒的分数集中在病毒上:
- how to put a text into a circle object to display it from circle's center?
病毒本身位于 StackPane. The pane has pick on bounds 设置为 false 中。要删除病毒感染,您必须单击代表病毒的圆圈,而不仅仅是堆栈窗格正方形中的任意位置。
因为圆坐标是局部坐标,圆在表示病毒的父堆栈窗格中,圆本身不需要设置 x 和 y 值,而是布局 x 和 y 值设置在封闭窗格上,以允许定位代表整个病毒的窗格。
以下技术用于使用ThreadLocalRandom生成可接受范围内的随机整数:
- How do I generate random integers within a specific range in Java?