JavaFX 3D子场景在2D场景中的应用

3

我正在为一个游戏开发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布局

编辑:我发现调整子场景的大小后,可以看到FXML元素。显然它们被子场景“覆盖”了。有没有人知道如何将FXML元素放在子场景的上面而不是像现在这样相反的情况呢? 子场景覆盖了我创建的FXML布局


1
请参阅Z-Order in JavaFX。恐怕您没有提供足够的信息让我更具体地指导您。如果您需要更多帮助,请考虑提供[mre]。您可能还想查看StackPane。此外,从您对pane.setCenter(subScene)的调用中可以推断出pane是一个BorderPane。如果是正确的,那么您应该删除对pane.getChildren().add(subScene)的调用。对于像BorderPane这样的布局,您不应直接与子元素列表交互。 - Slaw
确保您的FXML根节点是HBoxBorderPane之一。 BorderPane可能更受欢迎。将Subcene设置为centerright - SedJ601
显然我编辑了问题,但有些编辑没有显示出来,所以代码是两者之间的混合,也让我感到困惑。我已经修复了它。目前我的FXML根节点是Anchor Pane;这是Sedrick的问题吗?我试图将其更改为StackPane,但结果似乎相同。 - anonymflaco
BorderPane不会起作用。从您的图像中,我以为您在左侧有一个面板。 - SedJ601
2个回答

5
尽管您已经找到了解决方案,但我还是希望通过回答来更清楚地说明为什么在SubScene上调用toBack()的原因。
Z-Order 每个Parent(所有布局都继承自它)都可以有一个或多个子元素。如果两个(或更多)子元素占据Parent中的同一空间,则一个子元素将呈现在另一个子元素之上。哪个子元素被绘制在另一个子元素之上取决于两件事:
  1. Parentchildren list中子元素的顺序。
  2. (自JavaFX 9以来)每个子元素相对于同一Parent中的其他子元素的viewOrder属性的值。

JavaFX中的Z-Order问答提供了更详细的信息。

你代码中的问题

你的FXML文件描述了一个只有一个StackPane作为子元素的AnchorPane。这意味着以下内容:

AnchorPane pane = loader.load();

此方法会为您提供一个带有一个StackPaneAnchorPane,并将其添加到子项列表中的第零个索引位置。然后,您可以立即使用以下方法添加您的SubScene

pane.getChildren().add(subScene);

add方法将元素添加到列表的末尾。这意味着AnchorPane的子列表顺序如下:

  1. StackPane
  2. SubScene

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

您的解决方案

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

将此Node在z轴上移动到其同级节点的后面。这是通过将此Node移动到其父项的内容ObservableList中的第一个位置来实现的。如果此Node不是组的一部分,则此函数无效。

换句话说,在调用该方法后,AnchorPane的子节点列表顺序变为:
  1. SubScene
  2. StackPane
这就是为什么SubScene现在被渲染在StackPane下面的原因,因为你改变了子节点的顺序。
请注意,toBack()方法是Node类的一部分,而不是SubScene。后者继承自前者。我提出这一点是为了指出你遇到的问题并不特定于SubScene或混合2D和3D场景图。你的StackPaneSubScene都是2D Scene的一部分(即没有深度缓冲),这意味着它们的Z顺序仅由上述内容决定*SubScene是3D的(即启用深度缓冲)这一事实与手头的问题无关;这个事实只影响了所述SubScene后代

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

其他解决方案

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

  • Add the SubScene to the start of the AnchorPane's children list:

    pane.getChildren().add(0, subScene);
    
  • If you're using JavaFX 9+, set the viewOrder property of the SubScene to something less than that of the AnchorPane's property (by default, that property's value is 0):

    subScene.setViewOrder(-1);
    
  • Define your SubScene in the FXML file before the StackPane (note this approach requires the use of 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>
    

3

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

final result

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接