2d 场景中的 JavaFX 3d 子场景

JavaFX 3d SubScene in 2d Scene

我正在为游戏开发 GUI,我想在 JavaFX 中混合 3D SubScene 和 2D Pane。我有一个名为 root3D 的组,其中包含我已正确设置的所有 3d 对象,然后我使用通过 JavaFX Scene Builder 设置的 FXML 文件创建一个 Pane。但是没有任何显示,我只能看到我的 3D 对象。

       PerspectiveCamera camera = new PerspectiveCamera(true);
       camera.setTranslateZ(-30);
       Group root3D = new Group(model1,model2,model3); //various 3d models I imported
       SubScene subScene = new SubScene(root3D, 1280, 700, true,SceneAntialiasing.BALANCED);
       subScene.setCamera(camera);
       FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/file.fxml"));
       AnchorPane pane = loader.load();
       pane.getChildren().add(subScene);
       Scene scene = new Scene(pane);
       primaryStage.setScene(scene);
       primaryStage.setResizable(false);
       primaryStage.show();

FXML 文件:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>

<AnchorPane prefHeight="700.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <StackPane prefHeight="150.0" prefWidth="200.0">
         <children>
            <ImageView fitHeight="214.0" fitWidth="169.0" pickOnBounds="true" preserveRatio="true">
               <image>
                  <Image url="@../Sprite/Cards/Small/podium-characters-Poseidon.png" />
               </image>
            </ImageView>
            <TextField prefHeight="62.0" prefWidth="274.0" text="APOLLO">
               <font>
                  <Font name="Trebuchet MS Bold Italic" size="22.0" />
               </font>
            </TextField>
         </children>
      </StackPane>
   </children>
</AnchorPane>

编辑:我发现调整子场景的大小,我可以看到 FXML 元素。显然他们正在被子场景"covered"。有谁知道如何将 FXML 元素放在子场景之上而不是像现在这样相反?

正如@Slaw 间接指出的那样,我需要的是设置 Subscene.toBack() 以在我的场景中设置正确的 Z 顺序。非常感谢你!在阅读有关 Subscenes 的内容时,我没有找到任何相关信息,所以我希望这可以帮助将来的一些 3d 初学者。

虽然您已经找到了解决方案,但我发布了一个答案,希望能够更清楚地说明为什么 在您的 toBack() 上调用 toBack() SubScene 有效。

Z-Order

所有布局继承的每个Parent可以有一个或多个children。如果两个(或更多)children 在 Parent 中占据相同的 space,那么一个将被渲染在另一个之上。哪个 child 被绘制在另一个之上取决于两件事:

  1. children在Parentchildren list中的顺序。
  2. (自 JavaFX 9 起)每个 child 的 viewOrder 属性 相对于同一 Parent 中的其他 children 的值。

Z-Order in JavaFX 问答更详细。

你的代码中的问题

您的 FXML 文件描述了一个 AnchorPane,它只有一个 StackPane child。这意味着以下内容:

AnchorPane pane = loader.load();

给你一个 AnchorPane,在它的 children 列表中,在第零个索引处有一个 StackPane。然后你立即添加你的 SubScene 使用:

pane.getChildren().add(subScene);

您使用的 add 方法 将元素追加 到列表的末尾。所以这意味着 AnchorPane 的 children 列表的顺序是:

  1. StackPane
  2. SubScene

由于 SubSceneStackPane 之后,前者呈现在后者之上。

你的解决方案

您选择的解决方案是在将 SubScene 添加到 AnchorPane 后调用 toBack()。这是该方法的 the documentation

Moves this Node to the back of its sibling nodes in terms of z-order. This is accomplished by moving this Node to the first position in its parent's content ObservableList. This function has no effect if this Node is not part of a group.

换句话说,调用该方法后 AnchorPane 的 children 列表的顺序变为:

  1. SubScene
  2. StackPane

这就是 SubScene 现在呈现在 StackPane 下方的原因,因为您更改了 children 的顺序。

请注意 toBack() 方法是 Node class 的一部分,而不是 SubScene。后者继承于前者。我提出这个问题是为了指出您遇到的问题并非特定于 SubScene 或什至将 2D 和 3D 场景图混合在一起。您的 StackPaneSubScene 都是 2D Scene 的一部分(即没有深度缓冲),这意味着它们的 z-order 完全受上面讨论的内容约束 *SubScene 是 3D(即启用深度缓冲)这一事实与手头的问题无关;该事实仅影响所述 SubScene.

后代

* 在 3D 场景中,节点的 z-coordinate 变得相关。

其他解决方案

以下是一些可用于解决您的问题的其他方法:

  • SubScene 添加到 AnchorPane 的 children 列表的开头:

    pane.getChildren().add(0, subScene);
    
  • 如果您使用的是 JavaFX 9+,请将 SubSceneviewOrder 属性 设置为小于 AnchorPane 的值的 属性(默认情况下,属性 的值为 0):

    subScene.setViewOrder(-1);
    
  • StackPane 之前的 FXML 文件中定义您的 SubScene(注意此方法需要使用 an FXML controller):

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.TextField?>
    <?import javafx.scene.Group?>
    <?import javafx.scene.image.Image?>
    <?import javafx.scene.image.ImageView?>
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.layout.StackPane?>
    <?import javafx.scene.SceneAntialiasing?>
    <?import javafx.scene.SubScene?>
    <?import javafx.scene.text.Font?>
    
    <AnchorPane prefHeight="700.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/11.0.1"
                xmlns:fx="http://javafx.com/fxml/1">
      <children>
    
        <!-- Define the SubScene before the StackPane -->
        <SubScene fx:id="subScene" width="1280" height="700" depthBuffer="true">
          <antiAliasing>
            <SceneAntialiasing fx:constant="BALANCED"/>
          </antiAliasing>
    
          <!-- 
            Unfortunately, as far as I can tell, you can't set your PerspectiveCamera in FXML because you
            want 'fixedEyeAtCameraZero' to be true. That property can only be set during construction but
            the constructor with that parameter does not annotate said parameter with @NamedArg, thus the
            FXMLLoader can't see it. And the no-arg constructor sets the value to false, not true. This
            means you have to inject the SubScene into your controller and add the PerspectiveCamera in
            code.
           -->
    
          <root>
            <!-- Inject the root into the controller in order to add your models to it in code -->
            <Group fx:id="root3D"/>
          </root>
        </SubScene>
    
        <StackPane prefHeight="150.0" prefWidth="200.0">
          <children>
            <ImageView fitHeight="214.0" fitWidth="169.0" pickOnBounds="true" preserveRatio="true">
              <image>
                <Image url="@../Sprite/Cards/Small/podium-characters-Poseidon.png"/>
              </image>
            </ImageView>
            <TextField prefHeight="62.0" prefWidth="274.0" text="APOLLO">
              <font>
                <Font name="Trebuchet MS Bold Italic" size="22.0"/>
              </font>
            </TextField>
          </children>
        </StackPane>
    
      </children>
    </AnchorPane>