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

你有很多问题,不仅仅是你问题中的那些:

  1. 虽然它会像你编码的那样工作 它,我不建议生成一个线程来画你的圈子,而是 见:

    • JavaFX periodic background task
  2. 不需要在场景中设置root,在场景中设置场景 window每次画一个新的圆。

  3. 也不需要设置 每次画圆时,布局上的鼠标处理程序。

  4. 与其在布局上设置鼠标处理程序,不如在圆圈本身上设置鼠标处理程序(您可以在将它们添加到场景之前执行此操作)。

  5. score是一个int,不是节点只能添加节点到场景 图。

  6. 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 不同。

  7. 要在顶部添加一个带分数的圆圈,请在标签中使用带有分数的 StackPane,但要使标签鼠标透明,这样它就不会记录任何点击:

    Label scoreLabel = new Label(score + "");  
    scoreLabel.setMouseTransparent(true);  
    StackPane balloon = new StackPane(circle, scoreLabel);
    layoutV.getChildren.add(balloon); 
    

    在气球上添加点击处理程序。

  8. 还有其他问题我没有在这里详细说明,但在提供的演示代码中已解决。

为了修复您的所有错误,我会编写如下代码。也许您可以查看它并将其与您的代码进行比较,以帮助理解创建此游戏的一种方法。

示例代码可能不完全是您正在寻找的功能(这不是它的真正目的),但它应该足以让您走上正确的轨道来实施您的应用程序。

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

关于此实现的一些可能有用的附加说明:

  1. 总分放在 overlayPane 中,这样它就不会被添加到 playingField(包含病毒生成)的元素遮挡。

  2. 制作overlayPanemouseTransparent,这样它就不会拦截任何鼠标事件,点击会落到游戏区的项目上。

  3. 无论您是否调整 window 大小,该应用程序目前都会在固定字段大小内生成病毒。这就是它的设计和编码方式,您可以根据需要以其他方式进行编码。这样做会更麻烦。

  4. 绑定 class 用于创建字符串表达式 binding which concatenates 静态字符串“Score”,其中整数 属性 表示分数。这允许将表示分数的字符串绑定到叠加层中的分数标签文本,以便在分数更改时自动更新。

  5. 病毒生成使用 timeline 并基于以下概念:

    • JavaFX periodic background task
  6. 应用程序 class 故意保持简单,主要是处理核心 application lifecycle,应用程序的实际功能被抽象为感染 class它处理病毒的产生和产生新病毒的病毒class。

  7. 此技术用于将每个病毒的分数集中在病毒上:

    • how to put a text into a circle object to display it from circle's center?
  8. 病毒本身位于 StackPane. The pane has pick on bounds 设置为 false 中。要删除病毒感染,您必须单击代表病毒的圆圈,而不仅仅是堆栈窗格正方形中的任意位置。

  9. 因为圆坐标是局部坐标,圆在表示病毒的父堆栈窗格中,圆本身不需要设置 x 和 y 值,而是布局 x 和 y 值设置在封闭窗格上,以允许定位代表整个病毒的窗格。

  10. 以下技术用于使用ThreadLocalRandom生成可接受范围内的随机整数:

    • How do I generate random integers within a specific range in Java?