Java9-游戏开发高级教程-六-

59 阅读1小时+

Java9 游戏开发高级教程(六)

协议:CC BY-NC-SA 4.0

二十二、评分引擎:创建评分 UI 布局并对内容评分

现在,您已经编写了游戏板方块答案选择逻辑,并且编写了游戏板旋转和摄像机动画序列的声音效果,我们需要将另一半 Java 代码放入适当的位置,查看用户选择(点击)了什么答案,并相应地更新记分板。我们将跟踪正确和错误的答案,并使用简单而有效的评分界面实时鼓励玩家。本章中的工作过程需要我们在屏幕右侧创建一个乐谱 UI 面板(panel ),我们将使用名为scoreLayout的 StackPane 和名称也以 Score 开头的文本对象来创建它。

在这一章中,我们将实现一个单人游戏和评分模型来让你的评分用户界面就位,因为很多游戏玩家都想根据游戏内容来玩游戏,作为一种学习体验。也就是说,仍然需要为每个按钮 UI 元素编写大量代码来查看答案是否是正确答案;如果是,代码将增加“正确:”的分数,如果不是,将增加“错误:”的分数。

这意味着在你学会如何实现本章中的评分逻辑后,你仍然需要增加几百行 Java 代码。这将为你所有的答案打分,这些答案是你在前一章中学会的。

幸运的是,我们将使用最佳的“编写一次代码,然后复制、粘贴和修改”方法,因此不应该像上一章那样涉及太多的输入。真正的工作将是在你学习完如何实现评分(在当前章节中)后,创建答案(第章第二十一章)和评分逻辑(本章)。

还有一个上一章的小错误,我们将通过移动。setVisible(false)从开始游戏按钮到 JavaFX application start()方法启动序列调用问答 UI 面板,这将在游戏启动时(而不是在单击按钮时)隐藏问答 UI 面板(以及后来的乐谱 UI 面板)。

闪屏渲染错误:启动时隐藏用户界面面板

您可能已经注意到,在前一章的游戏运行➤项目测试渲染中,JavaFX 错误地渲染了游戏闪屏上方的部分问答 UI 面板,如图 22-1 的左上角所示。这个 Q&UI 面板应该真正位于你的闪屏之后,因为你已经在 root.addChildren.()addAll()方法链的节点对象参数列表序列中的 addNodesToSceneGraph()方法中指定了呈现顺序。通过添加 i3D 元素来使您的场景成为 3D(或“混合”2D+3D 场景)实体,这也可能是 Z 单位位置(方位)设置问题。因此,有两种方法可以调查并修复这个小的渲染问题。由于我们已经设置了 X、Y、Z 显示单元,并且可以有效地实现我们想要在 i3D 游戏渲染管道中实现的功能,解决这个问题最简单的方法就是在游戏启动时自动隐藏 UI 面板(因为我们在游戏启动时隐藏了它),而不是手动使用开始游戏按钮 UI 元素。这是在 JavaFX 所要求的 start()方法的顶部完成的,而不是在一个事件处理结构中完成的,该事件处理结构与开始游戏按钮 UI 元素上的初始单击相连接。

A336284_1_En_22_Fig1_HTML.jpg

图 22-1。

Let’s fix the Q&A Button pane rendering bug that affects the startup screen first before developing the scoring

首先,从 gameButton 事件处理代码中移除qaLayout.setVisible(false); Java 语句,并将其放在。start()方法,以便自动处理该隐藏。

请记住,您的 qaLayout StackPane 将在 createQAnodes()方法中创建,因此该语句需要出现在您的createQAnodes();自定义方法调用之后,也就是在该自定义方法之前调用的任何方法之后。这很好,因为这些只是设置将在游戏中使用的资源引用和对象。

这最终成为解决这个视觉缺陷的一个更快更容易的方法;因为我们已经准备在游戏启动时隐藏这个面板,所以在更早的时候(自动地)而不是在事件处理逻辑中隐藏(设置可见性为假)这个面板既创建了更干净的代码,又节省了我们弄清楚为什么会发生这种情况(显然是 3D 空间中的 Z 单位设置问题)以及如何为 qaLayout StackPane 对象添加(和调整)Z 位置代码的时间,这不会弄乱您当前的原始结果(除了在这个初始闪屏显示上)。

这个简单修改的 Java 代码在图 22-2 的中间突出显示,应该类似于下面的 Java 9 语句,现在可以在您的 public void start() core JavaFX 9 方法的第一部分找到:

A336284_1_En_22_Fig2_HTML.jpg

图 22-2。

Remove the .setVisible() call from your gameButton handler and place it in .start() after createQAnodes()

public void start()  {
   loadImageAssets();
   loadAudioAssets();
   createSpecialEffects();
   createTextAssets();
   createMaterials();
   createBoardGameNodes();
   createUInodes();
   createQAnodes();

   qaLayout.setVisible(false);
   createSceneProcessing();
   createGameBoardNodes();
   ...
}

使用图 22-3 中的运行➤项目工作流程,在你的闪屏中查看对该问题的修复。

A336284_1_En_22_Fig3_HTML.jpg

图 22-3。

Your Q&A UI panel is now hidden on startup, in the top of your .start() method

现在我们已经修复了这个小的(代码方面的)闪屏呈现问题,我们可以继续创建您的 Score UI 布局设计,从 scoreLayout StackPane 对象和包含其装饰的 Text 对象开始。

记分板 UI 设计:createScoreNodes()方法

让 NetBeans 为我们创建一个 createScoreNodes()自定义方法体,方法是在我们刚刚添加的qaLayout.setVisible(false);语句后添加一行代码,然后使用 Alt+Enter 击键组合来触发 NetBeans 9 的这一自动化方法编码。此处显示了 Java 语句,并在图 22-4 中间突出显示:

A336284_1_En_22_Fig4_HTML.jpg

图 22-4。

Create a createScoreNodes() method after the qaLayout logic and use Alt+Enter to have NetBeans code it

public void start()  {
   loadImageAssets();
   loadAudioAssets();
   createSpecialEffects();
   createTextAssets();
   createMaterials();
   createBoardGameNodes();
   createUInodes();
   createQAnodes();
   qaLayout.setVisible(false);
   createScoreNodes();

   ...
}

将类底部的方法复制到 createQAnodes()方法之后,如图 22-5 底部所示。将 qaLayout 语句从 createQAnodes()方法复制到 createScoreNodes()方法中,并将。setTranslateX()方法从-250 调用到 250,以将其镜像到显示器的另一个角落。

A336284_1_En_22_Fig5_HTML.jpg

图 22-5。

Add a StackPane named scoreLayout at the top of the class and instantiate and configure it like qaLayout

您将保持其他四个复制的 Java 语句不变(除了将 qaLayout 更改为 scoreLayout),因为除了 X 位置之外,您希望“镜像”高度、深度、背景颜色和首选 StackPane 大小。使用以下 Java 代码将此 scoreLayout 添加到 SceneGraph 中,这些代码也在图 22-6 中突出显示:

A336284_1_En_22_Fig6_HTML.jpg

图 22-6。

To render the scoreLayout StackPane , you must first add it to the root.getChildren().addAll() method chain

private void addNodesToSceneGraph() {
    root.getChildren().addAll(gameBoard, uiLayout, qaLayout, scoreLayout, spinner);
    ...
}

让我们再次使用“运行➤项目”工作流程,并测试您的乐谱 UI 面板的新代码,以确保使用. settranslatex()方法调用值将乐谱 UI 面板设计镜像到屏幕右侧足够远的位置。正如你在图 22-7 中看到的,根据我们的猜测,我们离游戏的右角还差 400 个单位。因此,我们需要将值 250 更改为 650,以将 StackPane 容器进一步向右移动,并防止 2D StackPane UI 容器对象与您的 i3D 游戏棋盘节点层次结构相交。

A336284_1_En_22_Fig7_HTML.jpg

图 22-7。

As you can see, this StackPane is intersecting with the game board and needs to move right 400 units in X

完成乐谱 UI 背景和容器的 Java 9 代码在图 22-8 中高亮显示。setTranslateX()方法调用(从 250 X 单位到 650 X 单位)应该类似于下面的代码:

A336284_1_En_22_Fig8_HTML.jpg

图 22-8。

Change the scoreLayout.setTranslateX() method call from 250 to 650 to move the Score UI panel by 400 units

scoreLayout.setTranslateX(650);

我们需要做的下一件事是将 Java 代码放在适当的位置,它将在游戏启动时隐藏分数 UI 面板,就像它隐藏问答 UI 面板一样。一旦您添加了您的scoreLayout.setVisible(false); Java 语句,您的新 start()方法代码应该看起来像下面的代码,如图 22-9 所示:

A336284_1_En_22_Fig9_HTML.jpg

图 22-9。

Add the .setVisible(false) method call off scoreLayout after the createScoreNodes() method to hide the panel on startup

public void start()  {
   loadImageAssets();
   loadAudioAssets();
   createSpecialEffects();
   createTextAssets();
   createMaterials();
   createBoardGameNodes();
   createUInodes();
   createQAnodes();
   qaLayout.setVisible(false);
   createScoreNodes();
   scoreLayout.setVisible(false);
   ...
}

正如您在图 22-10 中看到的,您仍然需要使用您的。setOnFinished(event)事件处理基础结构。这段代码已经就绪,因为我们已经在摄像机动画完成后展示了 Q & A UI 面板。因此,我们所要做的就是在cameraAnimIn.setOnFinished(event->{});结构的末尾添加scoreLayout.setVisible(true);语句,在图 22-10 的中间用浅蓝色和黄色突出显示。在能够测试您的 Score UI 面板之前,您必须将这个 Java 9 代码放在适当的位置。

A336284_1_En_22_Fig10_HTML.jpg

图 22-10。

To show the scoreLayout StackPane, add a .setVisible(true) method call in cameraAnimIn.setOnFinished()

再次,使用运行➤项目的工作流程,并确保您的游戏闪屏和游戏板旋转回到他们的“干净”的外观;然后旋转并选择游戏棋盘方格 1 来调用 cameraAnimIn 对象。setOnFinished(event)事件处理方法逻辑,此时显示两个 StackPane UI 容器。

这允许我们在摄像机角度改变后测试 Score UI 容器代码。如图 22-11 所示,我们所要做的就是通过改变。将-395(如图 22-8 所示)的 setTranslateY()方法调用为-385 的值,以获得完美镜像的乐谱 UI 面板结果。

A336284_1_En_22_Fig11_HTML.jpg

图 22-11。

Use Run ➤ Project to render the Score panel, via the .setOnFinished() event handler, showing the initial pane position

现在,我们可以使用不同颜色的文本对象来“装饰”scoreLayout StackPane 的内部,我们可以使用漂亮的大字体和暗原色(RGB)值来标记 Score UI 面板的各个部分。

添加乐谱 UI 容器设计元素:文本对象

将 scoreTitle 文本对象添加到类顶部的文本复合语句,然后将 scoreTitle 添加到 scoreLayout.addChildren()。你的 addNodesToSceneGraph()方法中的 addAll()方法链,如图 22-12 所示。Java 代码应该如下所示,同样在图 22-12 的顶部以黄色显示:

A336284_1_En_22_Fig12_HTML.jpg

图 22-12。

Add a scoreTitle Text object , instantiate it, and configure it in createScoreNodes(). Then add it to the SceneGraph

private void addNodesToSceneGraph() {
    root.getChildren().addAll(gameBoard, uiLayout, qaLayout, scoreLayout, spinner);
    gameBoard.getChildren().addAll(Q1, Q2, Q3, Q4);
    Q1.getChildren().addAll(q1, Q1S1, Q1S2, Q1S3, Q1S4, Q1S5);
    Q2.getChildren().addAll(q2, Q2S1, Q2S2, Q2S3, Q2S4, Q2S5);
    Q3.getChildren().addAll(q3, Q3S1, Q3S2, Q3S3, Q3S4, Q3S5);
    Q4.getChildren().addAll(q4, Q4S1, Q4S2, Q4S3, Q4S4, Q4S5);
    qaLayout.getChildren().addAll(a1Button, a2Button, a3Button, a4Button);
    scoreLayout.getChildren().addAll(scoreTitle);
    uiLayout.getChildren().addAll(boardGameBackPlate, logoLayer, infoOverlay, uiContainer);
    uiContainer.getChildren().addAll(gameButton, helpButton, legalButton, creditButton,
                                     scoreButton);
    infoOverlay.getChildren()addAll(platText, moreText);
}

使用设置 scoreLayout StackPane 中 scoreTitle 标题的对齐方式。用 Pos 调用 setAlignment()方法。TOP_CENTER 常量,它将这个深红色的分数标题置于 StackPane 容器的顶部和中间。有趣的是,文本对象对齐是在父 StackPane 容器中设置的。我们可以在以后使用。setTranslateX()和。setTranslateY()方法调用文本子对象来微调乐谱 UI 面板中的对齐方式,我们将在接下来的几页中充实这个设计。

在 createScoreNodes()方法的底部实例化 scoreTitle 文本对象,然后使用。setFont()方法。使用 Arial 黑色字体,在 72 磅的大字体下具有醒目的可读性。使用。setFill()方法调用并将颜色从黑色更改为深红色,以便乐谱标题可以在乐谱 UI 面板的顶部轻松查看。图 22-12 底部突出显示的 Java 代码如下所示:

private void createScoreNodes()  {
    scoreLayout = new StackPane();
    scoreLayout.setTranslateX(650);
    scoreLayout.setTranslateY(-385);
    scoreLayout.setTranslateZ(-75);
    scoreLayout.setBackground(new Background(new BackgroundFill(Color.WHITE,
                                                                CornerRadii.EMPTY,
                                                                insets.EMPTY) ) );
    scoreLayout.setPrefSize(360, 654);
    scoreLayout.setAlignment(Pos.TOP_CENTER);
    scoreTitle = new Text("SCORE");
    scoreTitle.setFont( Font.font("Arial Black", 72) );
    scoreTitle.setFill(Color.DARKRED);
}

图 22-13 显示了运行➤项目的工作流程,以预览“乐谱”标题如何在乐谱窗格中工作。

A336284_1_En_22_Fig13_HTML.jpg

图 22-13。

Use the Run ➤ Project work process to preview the Score UI panel with its new Dark Red title heading

如图 22-14 中突出显示的,我们在类的顶部声明了一个 scoreRight 文本对象,并将其添加到 scoreLayout.addChildren()中。addAll()方法链,这样就可以在我们将要做的测试渲染中看到它。

A336284_1_En_22_Fig14_HTML.jpg

图 22-14。

Add a scoreRight object at the top of the class, instantiate and configure it, and add it to the SceneGraph

我在 scoreTitle 对象后添加了 scoreRight 对象实例化,并将其配置为使用深蓝色、Arial 黑色字体,字体大小为 72 磅。我添加了 X 和 Y 坐标,最初将其定位在 scoreLayout StackPane 中的(-50,150)。图 22-14 显示了代码,如下所示:

private void createScoreNodes()  {
    ...
    scoreTitle = new Text("SCORE");
    scoreTitle.setFont( Font.font("Arial Black", 72) );
    scoreTitle.setFill(Color.DARKRED);
    scoreRight = new Text("Right:");
    scoreRight.setFont( Font.font("Arial Black", 72) );
    scoreRight.setFill(Color.DARKBLUE);
    scoreRight.setTranslateX(-50);
    scoreRight.setTranslateY(150);
}

图 22-15 显示了一个运行➤项目的工作流程,以预览右:heading 如何在配乐窗格中工作。

A336284_1_En_22_Fig15_HTML.jpg

图 22-15。

Use a Run ➤ Project work process to preview the Score UI panel with new Dark Blue “Right:” score heading

因为这个特殊的 i3D 棋盘游戏设计是为即将入学的孩子设计的,所以让我们也包括一个“错误:”分数跟踪标题,并在每个答案后添加一些鼓励,如干得好!或者再次旋转。编写这段代码的最快方法是复制并粘贴 scoreRight 代码,并将其本身放在下面,将 scoreRight 更改为 score error,同时将颜色更改为红色,并将 X,Y 位置更改为-25,300。这显示在下面的 Java 9 代码中,并在图 22-16 的底部以黄色突出显示:

A336284_1_En_22_Fig16_HTML.jpg

图 22-16。

Add scoreWrong object at the top of the class, instantiate and configure it, and add it to the SceneGraph

private void createScoreNodes()  {
    ...
    scoreLayout.setPrefSize(360, 654);
    scoreLayout.setAlignment(Pos.TOP_CENTER);
    scoreTitle = new Text("SCORE");
    scoreTitle.setFont( Font.font("Arial Black", 72) );
    scoreTitle.setFill(Color.DARKRED);
    scoreRight = new Text("Right:");
    scoreRight.setFont( Font.font("Arial Black", 72) );
    scoreRight.setFill(Color.DARKBLUE);
    scoreRight.setTranslateX(-50);
    scoreRight.setTranslateY(150);
    scoreWrong = new Text("Wrong:");
    scoreWrong.setFont( Font.font("Arial Black", 72) );
    scoreWrong.setFill(Color.RED);
    scoreWrong.setTranslateX(-25);
    scoreWrong.setTranslateY(300);
}

图 22-17 显示了一个运行➤项目的工作流程,用于测试 Java 代码中的红色错误:文本标题。

A336284_1_En_22_Fig17_HTML.jpg

图 22-17。

Use a Run ➤ Project work process to preview the Score panel and Red “Wrong:” text

接下来,让我们在类顶部的复合语句中添加一个 scoreCheer 文本对象声明。正如你在图 22-18 顶部看到的黄色,你的复合语句现在有两行,一行用于启动(闪屏)UI 文本对象,另一行用于乐谱 UI 文本对象。

A336284_1_En_22_Fig18_HTML.jpg

图 22-18。

Declare the scoreCheer Text object at the top of the class; then add it to your scoreLayout SceneGraph branch

由于已经声明了该对象,因此可以将其添加到 scoreLayout.getChildren()中。addAll()方法链,如图 22-18 所示,甚至在 NetBeans 9 中实例化它并且不创建错误之前。图 22-18 底部用浅蓝色突出显示的 Java 语句应该如下所示:

scoreLayout.getChildren().addAll(scoreTitle, scoreRight, scoreWrong, scoreCheer);

将 score error Java 语句复制并粘贴到它们下面,并将 score error 更改为 scoreCheer。将 scoreCheer 设为深绿色,并将 scoreRight 和 scoreCheer 上的字体大小分别减小到 64 磅和 56 磅,以便它们更适合 scoreLayout。记住,我们需要空间来容纳代表这些分数的数字!由于 scoreWrong 更宽(因为字体中使用的字母),我将此降低到 60 分。我将标题在 Y 维度上多间隔了 10 个单位,并通过使用-56、-44 和-2 的 X 位置将它们排列在左侧,如图 22-19 中粗体和突出显示的(至少是 scoreGrade 语句):

A336284_1_En_22_Fig19_HTML.jpg

图 22-19。

Add the scoreCheer Text object in DarkGreen and tweak the other Text object font sizes and XY locations

    scoreRight = new Text("Right:");
    scoreRight.setFont( Font.font("Arial Black", 64) );
    scoreRight.setFill(Color.DARKBLUE);
    scoreRight.setTranslateX(-56);
    scoreRight.setTranslateY(160);
    scoreWrong = new Text("Wrong:");
    scoreWrong.setFont( Font.font("Arial Black", 60) );
    scoreWrong.setFill(Color.RED);
    scoreWrong.setTranslateX(-44);
    scoreWrong.setTranslateY(310);
    scoreCheer = new Text("Great Job!");
    scoreGrade.setFont( Font.font("Arial Black", 56) );
    scoreGrade.setFill(Color.DARKGREEN);
    scoreGrade.setTranslateX(-2);
    scoreGrade.setTranslateY(460);

图 22-20 显示了用于渲染新文本对象标题及其调整后的字体大小和定位设置的运行➤项目工作流程。请注意,由于我们尚未扩散答案或评分逻辑,只有猎鹰(图 22-11 或 15 中的方块 1 选项 1)显示了代表答案选项的按钮标签。

A336284_1_En_22_Fig20_HTML.jpg

图 22-20。

Use the Run ➤ Project work process to preview the Score panel and Text object headings you have added

现在,我们准备将分数引擎逻辑添加到我们在第二十一章中放置的问答按钮元素中,并将文本答案正确和答案错误选项添加到我们的分数 UI 设计中。这将显示由 Q&UI 按钮元素生成的分数。之后,我们可以计算等级,并将其分配给字母等级的第七个文本元素。

评分引擎:计算答案分数的逻辑

让我们添加一个名为 createQAprocessing()的自定义方法来保存我们的评分引擎逻辑。在 createQAnodes()方法中创建的四个按钮元素的 setOnAction(event)事件处理。正如你在图 22-21 中看到的,这需要在我们在 createQAnodes()和 createScoreNodes()中设置 Q & A 并对 UI 设计评分之后,在我们在 createSceneProcessing()中调用这个 Q & A 事件处理之前。所以,在 scoreLayout.setVisible(false)后面加一行 Java 代码;语句和 createSceneProcessing()之前;声明,如图 22-21 中黄色高亮部分所示。使用 Alt+Enter 工作进程,通过让 NetBeans 9 编写引导方法代码和错误语句来删除红色波浪下划线,我们将很快替换这些代码和语句。这将打开一个下拉菜单,其中包含 javafxgame 中的创建方法“createQAprocessing()”。JavaFXGame 选项,双击该选项即可执行(单击将选中该单选,如图 22-21 )。

A336284_1_En_22_Fig21_HTML.jpg

图 22-21。

Add the createQAprocessing() method call after createQAnodes() and createScoreNodes() and use Alt+Enter

对这四个动作事件处理结构中的第一个进行编码的最简单的方法是进入您的。start()方法,并将您在本书前面创建的按钮事件处理结构复制并粘贴到这个新创建的 createQAprocessing()方法中。在使用 Paste 命令之前,请确保完全选择 NetBeans 引导错误语句代码行,以便 ActionEvent 处理代码替换此引导错误语句。

更改。setOnAction()方法调用从调用到 a1Button,并删除此事件处理构造中的处理语句,使其成为一个空事件处理程序,以便我们可以从头开始构建分数处理逻辑。事件处理程序的 Java 代码如下所示,如图 22-22 所示:

A336284_1_En_22_Fig22_HTML.jpg

图 22-22。

Mouse over the event handling structure, and notice NetBeans wants to convert to a lambda expression

private void createQAprocessing() {
    a1Button.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle (ActionEvent event) {
            ... // An Empty ActionEvent Handling Structure
        }
    });
}

Alt+Enter 生成的 Java 代码将是相同的空事件处理结构,使用 lambda 表达式方法,这将删除八行代码中的三行,即 37.5%的编码结构。

您的 Java 9 代码应该如下所示,您得到的 lambda 表达式如图 22-24 所示。图 22-23 显示了调用 NetBeans Alt+Enter 键后的工作过程。选择“使用 lambda 表达式”选项,该选项将在 NetBeans 9 IDE 中执行一个算法,该算法将为您重写 Java 代码,并将其转换为更短的 lambda 表达式编程格式。

A336284_1_En_22_Fig23_HTML.jpg

图 22-23。

Use the Alt+Enter keystroke and select and double-click the “Use lambda expression” option to convert

private void createQAprocessing() {
    a1Button.setOnAction(ActionEvent event) -> {

        ... // Empty ActionEvent Handling Lambda Expression Structure

    });
}

在您的空 ActionEvent 处理 lambda 表达式中,我们将为每个 Button 对象提供条件 if()结构,该结构将查看 picked Node 对象和 pickSn Random 对象,以确定我们正在处理哪个游戏棋盘方块(Q1S1 到 Q4S5)和随机数生成器值(pickS1 到 pickS20)。这将告诉我们正在查看哪些内容,然后我们的评分引擎逻辑将对该选择进行评分。

在 a1Button.setOnAction()构造中,添加一个 if(picked == Q1S1)来开始这个编码过程。请注意,NetBeans 错误突出显示了选中的节点对象,因为它当前是 createSceneProcessing()方法的本地(私有)对象,如图 22-24 所示。接下来,我们将不得不使这个选中的节点对象成为一个全局(公共)变量。

A336284_1_En_22_Fig24_HTML.jpg

图 22-24。

NetBeans error highlights picked Node in the if() statement because it is local to createSceneProcessing()

在你的类的顶部声明选中的节点,如图 22-25 中突出显示的,以消除这个错误。

A336284_1_En_22_Fig25_HTML.jpg

图 22-25。

Remove the Node declaration from createSceneProcessing(); relocate it to the top of the class, making it global

现在,我们将在 createQAprocessing()方法中查看的所有对象都已声明,因此它们对整个类都是可见的,我们可以继续使用位于 a1Button 事件处理构造中的if (picked == Q1S2 && pickS1 == 0) { ... }结构对 a1Button 事件处理进行编码。

在类的顶部声明一个 rightAnswer integer 和 rightAnswers Text 对象,作为 int 变量和 Text 对象的复合声明语句的一部分,因为我们将要编写的 Java 9 代码将利用这些。

我们在 if()构造中要做的是(如果按钮 1 包含正确答案)将 rightAnswer 整数加 1,然后通过使用. setText()方法调用将 rightAnswers 文本对象设置为这个 right answer 值。在里面。setText()方法我们将使用 String.valueOf()方法将 rightAnswer 整数转换为字符串值并使用。setText()将 scoreCheer 设置为伟大的工作!。正确答案处理的代码,在 Q1S1 选项 0(第一个答案选项)的情况下是正确的(一个正确答案),应该看起来像下面的代码,如图 22-26 中突出显示的。它被编码成两行(四行,包括 lambda 表达式),以允许 20 个 board square score 逻辑处理 Java 构造适合 createQAprocessing()方法体内的 120 行代码。

A336284_1_En_22_Fig26_HTML.jpg

图 22-26。

Code a compact if() statement evaluating Q1S1 and pickS1 to see whether the a1Button answer is a correct one

a1Button.setOnAction(ActionEvent event) -> {
    if (picked == Q1S1 && pickS1 == 0) { rightAnswer = rightAnswer + 1;
        rightAnswers.setText(String.valueOf(rightAnswer)); scoreCheer.setText("Great Job!"); }
});

为了能够显示这个 rightAnswer 整数,我们需要在 createScoreNodes()中将 rightAnswers 文本对象添加到您的 UI 设计中。这是使用复制粘贴技术完成的。将 Java 代码的 scoreRight 块直接复制到它自己的下面。设置颜色为黑色,X 位置为 96。Y 位置应该保持不变,以便对齐“右”文本对象。通过在构造函数方法中使用“0”字符串值,将初始文本值设置为零。

图 22-27 底部显示的 Java 代码应该类似于以下代码:

A336284_1_En_22_Fig27_HTML.jpg

图 22-27。

Add the rightAnswers Text object to createScoreNodes() to display the result of your integer calculation

rightAnswers = new Text("0");                            // Initializes rightAnswers to Zero
rightAnswers.setFont(Font.font("Arial Black", 64));
rightAnswers.setFill(Color.BLACK);
rightAnswers.setTranslateX(96);
rightAnswers.setTranslateY(160);

图 22-28 显示了用于渲染 rightAnswers 文本对象及其设置的运行➤项目工作流程。

A336284_1_En_22_Fig28_HTML.jpg

图 22-28。

Use a Run ➤ Project work process to preview the Score panel and the rightAnswers Text object placement

其他按钮元素使用类似的代码,只不过它们将向一个 wrongAnswer int 变量添加一个。这意味着您将复制您创建了三次的 a1Button 构造,通过 a4Button 将 a1Button 更改为 a2Button。将 rightAnswer 改为 wrongAnswer,将 rightAnswers 改为 wrongAnswers,如图 22-29 所示。

A336284_1_En_22_Fig29_HTML.jpg

图 22-29。

Copy and paste a1Button construct three times underneath itself and change the object and variable names

还要改“干得好!”“再次旋转”为了能够显示一个错误答案整数,我们需要添加一个错误答案文本对象到 createScoreNodes()。这是通过复制粘贴过程完成的。将 score error Java 代码块直接复制到其自身下面。设置颜色为黑色,X 位置为 96。Y 位置保持不变,以对齐两个文本对象。在构造函数方法中使用“0”字符串值将初始文本值设置为零。

图 22-30 底部显示的 Java 9 代码看起来应该类似于下面的代码:

A336284_1_En_22_Fig30_HTML.jpg

图 22-30。

Add the wrongAnswers Text object to createScoreNodes() to display the result of the integer calculation

wrongAnswers = new Text("0");                            // Initializes wrongAnswers to Zero
wrongAnswers.setFont(Font.font("Arial Black", 64));
wrongAnswers.setFill(Color.BLACK);
wrongAnswers.setTranslateX(96);
wrongAnswers.setTranslateY(160);

请记住,为了查看正确答案和错误答案答案结果文本对象值持有者,您必须将它们添加到 scoreLayout StackPane 内的 SceneGraph 层次结构中。getChildren()。addAll()语句。

我只用了一个屏幕截图来展示将这些文本节点添加到 StackPane 中以节省本章的空间,因为我们有许多 Java 代码要做来完成您的棋盘游戏评分和分级基础结构。一旦我们完成了对这段代码的测试,您所要做的就是在 createQAprocessing()方法体中复制其他 59 个选项的评分代码,为其他 19 个游戏棋盘方格创建评分。这将需要与您的其他 59 组答案相匹配,您将复制并粘贴这些答案,然后使用我们在第二十一章中创建的代码进行编辑。

然后你会得到你所有的答案和分数!你可以在下一章开始“防错”你的代码,以确保多个 UI 元素在需要之前不会被点击。请记住,这些是年幼的儿童,智力受到挑战,玩教育游戏,所以你需要这个用户界面的保护。scoreLayout.getChildren().addAll()方法链的 Java 代码看起来像下面的 Java 代码,如图 22-31 所示:

A336284_1_En_22_Fig31_HTML.jpg

图 22-31。

Be sure to add any new Node objects to the SceneGraph hierarchy so they will be visible at render time

scoreLayout.getChildren().addAll( scoreTitle, scoreRight, scoreWrong, scoreCheer,
                                  rightAnswers, wrongAnswers);

图 22-32 显示了用于呈现新的错误答案文本对象及其设置的运行➤项目工作流程;如您所见,这对齐了 score (integer)元素,如果数值字段向右扩展,则为更大的分数(10 和 100)留出了空间,我们将在下一节 score 代码测试中确定这一点。

A336284_1_En_22_Fig32_HTML.jpg

图 22-32。

Use your Run ➤ Project work process to preview your Score UI panel and your wrongAnswers Text object

接下来,让我们测试一下我们刚刚编写的评分代码,看看评分 UI 设计是否正确地响应了分数向两位数的递增。也就是说,数值的增加是向右扩展还是向左扩展?还是从中心向外扩展?一旦我们弄清楚了这一点,我们就可以进一步“调整”(优化)我们的评分 UI 设计。

分数 UI 测试:显示更大的整数

由于我们还没有实现“玩家验证”Java 代码,我们将在下一章中实现,这样玩家就不能在每个游戏周期中多次单击 UI 元素(3D 旋转器、游戏棋盘方块、按钮)来“游戏”系统(或导致呈现错误出现),我们目前可以多次单击按钮元素。这允许我们测试记分板 UI,以找出大于 9 的数字将如何显示,以便我们可以“调整”X 位置,并将数字(正确和错误)间隔到左侧(当前间距)、最右侧或标签中心(标题)和乐谱 UI 面板的右边缘。如图 22-33 所示,我已经点了十次正确的(猎鹰 Hawk)答案,看看数字会如何移动。正如你所看到的,数字从中心向外扩展,这可以通过比较 10 和 2 来看出,而不是向左或向右。因此,我们需要将这 120 个单位向右移动。现在你的得分值将能够扩大到两位数或三位数。

A336284_1_En_22_Fig33_HTML.jpg

图 22-33。

Use your Run ➤ Project work process and click the Button elements to increment (test) your scoring code

递增。从 96 到 120 的 rightAnswers 和 wrongAnswers 文本对象的 setTranslateX()方法调用。这将使乐谱 UI 的数字部分在标签(标题)和乐谱 UI 面板的右侧居中。您的代码现在应该看起来如下,它在图 22-34 的中间和底部突出显示:

A336284_1_En_22_Fig34_HTML.jpg

图 22-34。

Expand the X position of each numeric element 24 units to the right, from a value of 96 to a value of 120

rightAnswers = new Text("0");
rightAnswers.setFont(Font.font("Arial Black", 64));
rightAnswers.setFill(Color.BLACK);

rightAnswers.setTranslateX(120);                    // Update X position 24 units from 96 to 120
rightAnswers.setTranslateY(160);
wrongAnswers = new Text("0");
wrongAnswers.setFont(Font.font("Arial Black", 64));
wrongAnswers.setFill(Color.BLACK);

wrongAnswers.setTranslateX(120);                    // Update X position 24 units from 96 to 120
wrongAnswers.setTranslateY(160);

同样,使用图 22-35 中所示的“运行➤项目”工作流程来渲染游戏,并导航到分数 UI 面板,以查看这是否会分隔数字显示,以便 10 到 99 的分数在分数 UI 面板中显示时看起来很棒。

A336284_1_En_22_Fig35_HTML.jpg

图 22-35。

Use the Run ➤ Project work process and click the Button elements to increment (test) double-digit scores

大多数玩家不会在一次游戏中玩(旋转)游戏板数百次,所以这对这个游戏来说应该很好。但是,请注意,三位数(100 到 999)也应该合适。

然而,如果你期待这么多的游戏性,你可能想要在分数 UI 设计的左侧再多留 4 到 8 个单位的标签(标题),这样就可以轻松容纳你的三位数游戏分数。

现在你已经准备好“增殖”我们在这一章关于计分和第二十一章中写的代码来创建整个游戏基础设施。这将在我们已经创建了超过 22 章的 1000(或更多)行 Java 代码的基础上再增加 1000 行代码,以将整个 i3D 棋盘游戏基础设施放置到位。接下来就说说怎么做这个吧。这是关于 2D 内容(图像、答案选项和得分)的大量工作,但它将与我们迄今为止使用 JavaFX 9 APIs 创建的 i3D 棋盘游戏无缝集成。

完成游戏:添加答案和分数

为 60 个不同的游戏棋盘方格选项添加 4 个答案涉及 240 个不同的内容选项(和代码行),为这 60 个不同的游戏棋盘方格选项添加得分涉及另外 480 行代码,如果包括 lambda 表达式容器,可能还要多一点。这是一个相当容易无错执行的大量工作的原因是,我们已经创建了一个代码设计,可以复制并粘贴到适当的位置,并且可以创建、插入和跟踪文本值,以便游戏内容在播放时能够正确工作。也就是说,不要期望为您的专业 Java 9 游戏开发管道创建内容会比创建 Java 9 代码更容易,因为游戏设计和游戏开发涉及大量的新媒体、内容、策略和编码工作,最终会产生专业的结果。

我会一次添加一个游戏棋盘方格的答案和分数,直到所有 20 个游戏棋盘方格都就位。添加未来的游戏板广场选项可以很容易地完成。您只需使用 random.nextInt(n)变量将 pickS1 到 pickS20 变量增加 1,即可添加 4 个不同的随机图像主题到每个游戏棋盘方格中。添加额外的一轮随机内容将相当于添加 20 轮新的按钮回答(80 个回答选项)并在您的评分逻辑中对这 80 个新回答进行评分,这将相当于 160 行代码,或者每个游戏板内容深度添加大约 240 行代码。增加游戏板内容的深度意味着玩家玩游戏的时间越长,看到的重复内容就越少。如果您愿意,还可以添加代码来跟踪已使用的内容。

一旦你将剩下的内容扩展成答案和评分逻辑,你就完成了游戏设计和开发工作流程的大部分。在接下来的章节中,我们将研究防错 UI 设计,以便用户在玩游戏的过程中被迫正确使用它,以及使用新的 Java 9 NetBeans IDE 进行优化和代码分析等事情。

摘要

在第二十二章中,我们学习了如何在 i3D 棋盘游戏设计的右下角实现一个乐谱 UI 面板。我们还学习了如何使用问答面板中的按钮 UI 元素上的 ActionEvent 处理来更改记分板数字部分的分数,这是我们在上一章 21 中创建的。这基本上把我们放在一个位置,我们可以完成编码和评分的个人广场(和象限,一旦广场被选中),游戏,其中一个关于内容的视觉问题得到回答和评分。(在我开始写第二十三章之前,我必须这么做。)

这意味着这是您的另一个繁重的编码章节,因为您构造了 20 个自定义方法,setupQ1S1gameplay()到 setupQ4S5gameplay()。您还在 createQAprocessing()事件处理基础结构中为每个按钮元素放置了条件 if()结构。您仍然需要确保交叉检查所有棋盘游戏方法之间的图像素材,最后,您需要一起测试所有代码,以确保它对每个游戏棋盘方格都正常工作。

在第二十三章中,作为游戏性保护的一部分,我们当然会在回答和评分完成后反转相机动画,并以更倾斜的视角显示动画,这是查看游戏棋盘旋转的最佳方式。我们还将防止点击任何可以点击的 UI 元素,这样用户就只能选择一个主题,例如,可以旋转一次白板。我们决不会结束游戏设计工作流程!

二十三、完成游戏代码和通过玩家验证你的事件处理

既然您的 i3D UI 及其事件处理(更不用说您的大多数动画和数字音频)都已就绪并正常工作,那么是时候在您的代码库中完成加载基本级别的内容(60 幅图像、240 个答案和得分)了。我们将在这一章的第一部分做这个,所以我可以向你展示我做了什么来完成游戏,并且在整本书中没有代码连续性的损失。大部分编码都是复制粘贴,这要归功于我设置游戏设计代码的方式,以及在测试游戏时确保答案匹配且运行良好。

在本章中,我们将用与视觉效果(问题)相匹配的基于文本的答案内容填充 20 个 setupQSgameplay()方法。我们还将完成 createQAprocessing()方法,该方法保存更新评分 UI 面板的答案评分代码。玩家将使用这些来选择正确的答案,揭示该方块的视觉代表什么,并对他们的答案进行评分。这意味着在这一章中你将会增加几百行代码,在你完成之前将会增加 1750 行代码。

一旦我们完成了游戏“答案显示、选择和评分”基础设施的大部分编码,并测试了每个方块以确保其工作正常,我们就可以创建 Java 代码的防错部分了。这导致了一个专业的游戏,确保玩家正确使用它。这包括使用布尔变量(称为标志)来保存“点击”变量;一旦玩家单击了旋转器、游戏棋盘方块或回答按钮 UI 元素,elementClick 变量就会被设置为 false,这样游戏玩家就不能再次单击它并“游戏”游戏代码。

例如,您的玩家可能多次单击您的正确答案按钮 UI 元素,这将运行到记分板的“右:”部分!我称之为代码的“用户检验”或“错误检验”,这是一个相当复杂的过程(正如你将在本章中看到的),有时会深入到几个层次。例如,我们将首先保护所有游戏棋盘方格不被点击两次,然后再下一层,保护一个象限的游戏棋盘方格,这样玩家在每一轮游戏中只能选择选定的象限。

我们还将添加最终的动画,它将摄像机带回到游戏的游戏棋盘旋转视图,以便玩家可以调用随机旋转来选择下一个象限(动物、植物、矿物或地点主题)。这将通过在棋盘游戏 UI 设计的顶层添加一个亮黄色的 Let's Play Again 按钮元素来实现。在这一章中我们有大量的工作要做,所以让我们开始吧!

完成游戏:填充游戏方法

这一章的第一部分将向你展示我如何把 Java 代码放在适当的位置来完成游戏。我们将在 setupQSgameplay()方法中向四个按钮 UI 元素添加答案选项(其中有二十个,第一个已经在第二十一章中编写过,以向您展示 Java 代码是如何工作的),然后我们将在四个 Q &按钮 UI 元素的 ActionEvent 处理方法内的 createQAprocessing()方法中添加这些答案的得分。

添加答案选项:完成 setupQSgameplay()方法

在游戏内容开发的这一阶段,并不是在 setupQSgameplay()方法中复制和粘贴 Java 9 代码会占用您的大部分时间,而是确认正确答案和创建错误答案会难倒玩家并导致“错误:”答案。一旦为每个随机选择添加了四个答案选项,setupQ1S1gameplay()的方法体看起来就像图 23-1 中的 18 个 Java 语句。

A336284_1_En_23_Fig1_HTML.jpg

图 23-1。

Find and add a correct answer to a different button (and three incorrect answers) for each random image

将你对应的对错答案处理添加到我们在第二十二章创建的 createQAprocessing()评分引擎中。正确答案(正确答案和正确答案)在图 23-2 中用黄色突出显示。

A336284_1_En_23_Fig2_HTML.jpg

图 23-2。

Add matching correct or incorrect answer score processing to the createQAprocessing() method for Q1S1

非常仔细地执行这个工作过程是很重要的,这样您的 20 个 setupQSgameplay()方法中的每一个方法的正确和不正确的答案都可以与 createQAprocessing()计分算法的方法体完美匹配。为了让你的评分引擎准确地给游戏评分,这些必须完美匹配,正如你通过比较数字 23-1 和 23-2 在一个问题接一个问题和一个答案接一个答案的基础上所看到的。

你可以边走边测试,一个游戏板一个游戏板地测试,或者完成后一次性测试。或者你可以两种方式都做,我就是这么做的,尝试生成在编译时和运行时都没有错误的代码。在我写这本书的时候,有成千上万行代码和一个处于测试阶段的 Java 9(和 JavaFX 9) API,这显然不是一件容易的事情,尤其是当我每周都要上交一个完整的章节的时候。

使用运行➤项目工作流程渲染代码和 3D,并在您的第一象限测试游戏棋盘 square 1,如图 23-3 所示。我建议一次做一个方格(和一个象限),这样你就可以利用代码“模式”,这可以通过比较图 23-1 和 23-2 看出。您可以根据 Java 代码对象名和变量名直观地判断出您正在使用哪个游戏棋盘方格、游戏棋盘象限、按钮编号和随机问题选择,我专门为此目的使用了这些名称。

A336284_1_En_23_Fig3_HTML.jpg

图 23-3。

Use a Run ➤ Project work process and test your Q1S1 answers and scoring logic before moving on to Q1S2

正如你在图 23-4 中突出显示的,setupQ1S2gameplay()方法代码非常相似,除了 pickS2 随机对象和图像引用,当然还有正确和错误答案的按钮标签。

A336284_1_En_23_Fig4_HTML.jpg

图 23-4。

Add correct and incorrect answers to Q1S2 Button objects, which as you can see is similar to the Q1S1 method

正如您在游戏设计和编程的这一阶段所看到的,主要目标是选择最佳按钮答案标签,并将其正确“连接”到 setupQAprocessing()评分引擎方法,以便正确计算分数!这就是为什么我建议一次一个方块地对每个游戏板进行编码,并小心地将它们绑定到 setupQAprocessing()评分引擎方法!确保对每个游戏棋盘方格进行足够好的测试,这样您就可以确保单击正确答案按钮会将“右:”分数标签的整数文本值加 1。

如图 23-5 所示,我已经添加了评分引擎方法的 Java 代码来评估这些答案,用黄色突出显示。我选择了代码中的 Q1S2 框(正方形)对象,以突出显示对它的引用。

A336284_1_En_23_Fig5_HTML.jpg

图 23-5。

Add matching correct or incorrect answer score processing to the createQAprocessing() method for Q1S2

使用图 23-6 所示的运行➤项目工作流程,并测试 Q1S2 游戏棋盘方块逻辑,通过将“right:”score ui panel integer text 对象加 1 来查看它是否为正确答案(duckling)打分。

A336284_1_En_23_Fig6_HTML.jpg

图 23-6。

Use a Run ➤ Project work process and test your Q1S2 answers and scoring logic before moving on to Q1S3

接下来,我完成了第三个到第五个棋盘游戏设置方法,并测试了它们的代码,以确保我在 createQAprocessing()方法体中连接了正确的答案按钮计分逻辑。一旦我们完成添加评分逻辑,这个方法体中的代码将达到大约 500 行,稍后,一个变量将在选择答案后锁定按钮单击事件处理。接下来会有一些非常酷的代码,我会在本章稍后介绍,当我们编写代码来“防止玩家”在 UI 上多次点击鼠标。

正如你将在图 23-7 中看到的,前六个游戏棋盘方格(已经完成 30%)的答案评分逻辑是用三十多行代码填充按钮 1 的 IDE 屏幕,这意味着我们为所有四个按钮完成了十几行代码(144 行)。

A336284_1_En_23_Fig7_HTML.jpg

图 23-7。

Add matching correct or incorrect answer score processing to the createQAprocessing() method for Q2S1

图 23-7 底部高亮显示的代码也显示在被测图左侧的图 23-8 中。当我单击第一个按钮元素(Chard)时,评分引擎将“Right:”分数加 1,如数字从 0 增加到 1 所示。

A336284_1_En_23_Fig8_HTML.jpg

图 23-8。

Use a Run ➤ Project work process to test Q2S1 through Q2S5 answers and scoring logic before moving on

此时,您必须完成接下来的四个 Q2S2 到 Q2S5 游戏棋盘 square setupQSgameplay()方法,以及相应的 createQAprocessing()评分引擎逻辑,以便对您的棋盘游戏的一半内容完成一半。如图 23-8 右侧所示,为了节省空间,我没有展示所有这些工作(及其测试工作过程)所涉及的所有游戏棋盘旋转屏幕截图。完成这个游戏内容所需的 600 行代码需要做大量的工作(createQAprocessing()大约需要 480 行,setupQSgameplay()方法大约需要 100 行),所以这花了我大约一天的时间来编码和测试。我拍摄了一些截图,我将在本章的这一部分展示。

当您将游戏棋盘方块内容和计分逻辑添加到游戏中时,请确保经常使用“运行➤项目”工作流程来测试添加答案按钮对象的新 Java 代码,以及将这些内容连接到 createQAprocessing()计分引擎方法的代码,以查看它是否能提供您想要的游戏效果。如图 23-8 所示,第二象限的答案和评分工作正常,我可以继续做第三象限了。

正如您在图 23-9 中看到的,第三象限的答案和评分现在工作正常,我可以继续添加第四象限的答案和评分代码。此时,您的棋盘游戏应该运行得相当好,我们现在可以开始添加代码,防止游戏玩家多次单击 UI 元素。

A336284_1_En_23_Fig9_HTML.jpg

图 23-9。

Use a Run ➤ Project work process to test the Q3S1 through Q3S5 answers and scoring logic before moving on

正如你在图 23-10 中看到的,游戏内容现在已经准备好了,我们可以开始玩家验证了。

A336284_1_En_23_Fig10_HTML.jpg

图 23-10。

Use the Run ➤ Project work process to test the Q4S1 through Q4S5 answers and scoring logic to finish 20 squares

接下来,让我们通过添加布尔变量来防止重复点击,从而对您当前的代码进行“玩家验证”。

玩家验证代码:控制玩家事件的使用

游戏“理论上”已经结束了,我们可以相信玩家只需点击(一次)正确的 i3D 和 i2D UI 元素就可以玩游戏了。然而,这种特定游戏的预期观众包括未成年儿童、智障人士、残疾玩家和自闭症玩家。因此,我们将放置一些控件,确保玩家只需点击一次正确的 UI 元素就可以玩这个游戏。让我们通过声明(类的顶部)并在 rotGameBoard.setOnFinished()中添加一个设置为 true 的 squareClick 布尔变量来开始这个过程,如下图所示:

A336284_1_En_23_Fig11_HTML.jpg

图 23-11。

Add a squareClick boolean variable set to true at the end of your rotGameBoard.setOnFinished() handler

rotGameBoard.setOnFinished(event-> {
    if (quadrantLanding == 315) { populateQuadrantOne();   }
    if (quadrantLanding == 225) { populateQuadrantTwo();   }
    if (quadrantLanding == 135) { populateQuadrantThree(); }
    if (quadrantLanding == 45)  { populateQuadrantFour();  }
    spinnerAudio.stop();
    squareClick = true;
});

因此,从逻辑上讲,我们已经将游戏棋盘方块的点击“保护”连接到游戏棋盘旋转动画对象。另外,请注意,通过将所有点击保护变量初始化为没有(缺省)值的布尔值,我们实际上已经将所有点击保护设置为 false,或者“点击被锁定”,只需在 Java 类的顶部声明这些变量。这样,我们就不需要 start()方法体中的任何clickProtect = false;语句。

注意在图 23-11 中,我已经在你的类的顶部使用一个复合 Java(布尔)声明声明了 spinnerClick 和 buttonClick。这是因为一旦玩家点击了 i3D spinner UI 元素、游戏棋盘方块和 Q & A 按钮 UI 元素,我们就想要“锁定”它们。这是为了防止多次点击,从而防止多次点击回答按钮(以提高分数)。它还确保您的 i3D 动画和音频通话在每轮游戏中仅触发一次,以防止玩家看到(或听到)类似错误的内容。你真的不希望动画在预期的视觉效果中间重新开始,即使你告诉它重新开始(在播放周期中足够快,这通常是一次以上的点击所做的),所以我们将在一次点击后锁定点击!

接下来,让我们为 spinnerClick 设置锁定,从 MouseEvent 处理代码中的 if(picked == spinner)条件求值开始。我们需要将&& spinnerClick == true 添加到 if(picked == spinner)中,以评估是否允许我们在游戏中的该点单击微调器。如果是的话,我们会立即将 spinnerClick 设置为 false 值,因为 spinner 动画对象(以及象限着陆处理)也在这个代码块中启动。我们将启用单击。setOnFinished()处理程序,这将阻止玩家在旋转的时候点击你的 i3D spinner UI 元素!酷!

i3D spinner 的条件 if() Java 代码基础设施中新增的防鼠标点击功能在这里以粗体显示,并在图 23-12 的顶部以浅蓝色和黄色突出显示:

A336284_1_En_23_Fig12_HTML.jpg

图 23-12。

Add && spinnerClick == true to the if() evaluation for the spinner in your MouseEvent handling structure

if(picked == spinner && spinnerClick == true) {
    spinnerClick = false;
    ...
}

接下来,我们将为这个 rotSpinner 动画对象添加一个. setOnFinished()事件处理程序,一旦 rotSpinner 动画对象完成,它就将布尔 spinnerClick 变量设置为 false 值。这是错误的,因为我们不希望旋转器(或游戏板)再次旋转,直到玩家选择了一个方块和相应的答案按钮 UI 元素来注册(和评分)他们的答案。

但是,在微调器再次出现在屏幕上(这是 rotSpinnerIn 动画对象)后,我们确实希望打开微调器鼠标单击。为此,我们将在。setOnFinished()事件处理逻辑。再说一次,Java 编程在这里是合乎逻辑的,如果你只是想在你的游戏管道中实现什么,这并不奇怪。像大多数游戏逻辑一样,一次考虑所有的事情非常多,所以一开始可能很难做到,直到你习惯于一次考虑所有的实时游戏逻辑,因为它涉及到处理实时交互式游戏所涉及的逻辑(处理管道)。这就是为什么游戏开发被大多数人认为是困难的,因为作为一个程序员,你需要一次“把你的脑袋包起来”所有的游戏代码。

此处显示了 createSceneProcessing()方法中的新 Java 代码,以及在图 23-13 中用浅蓝色和黄色突出显示的代码块,这些代码块设置了用于 rotSpinner 和 rotSpinnerIn 的逻辑:

A336284_1_En_23_Fig13_HTML.jpg

图 23-13。

Set spinnerClick to false in rotSpinner and to true in rotSpinnerIn to control when the spinner is to be clicked

rotSpinner = new RotateTransition(Duration.seconds(5), spinner);
...
rotSpinner.setOnFinished(event-> {
    spinnerClick = false;
});

rotSpinnerIn = new RotateTransition(Duration.seconds(5), spinner);
...
rotSpinnerIn.setOnFinished(event-> {
    spinnerClick = true;
});

我们需要确定(和编码)的下一件事是什么时候我们允许按钮 UI 元素被点击。从逻辑上讲,这应该在 cameraAnimIn 动画对象的末尾,也是在。setOnFinished()事件处理程序构造,在 qaLayout 和 scoreLayout StackPane 2D UI 面板(及其内容或子面板)通过使用。setVisible(true)方法调用每个 StackPane UI 容器对象。由于 buttonClick 作为(声明)缺省值是 false,这就像使用buttonClick = true; Java 语句一样简单。

一旦单击了一个应答按钮 UI 对象,buttonClick 将再次被设置为 false,以防止任何按钮 UI 对象(即使是同一个)被单击,直到再次播放 cameraAnimIn 动画对象。接下来,我们将在 createQAprocessing()计分方法中将此 Java 代码放入每个 ActionEvent 处理结构中,该结构附加到中四个按钮对象的每一个。setOnAction()事件处理构造。

您的新 cameraAnimIn Java 9 代码现在应该如下所示,并且在图 23-14 的底部以浅蓝色和黄色突出显示:

A336284_1_En_23_Fig14_HTML.jpg

图 23-14。

Add a buttonClick = true; statement to the end of the cameraAnimIn.setOnFinished() event handler code

cameraAnimIn = new ParallelTransition( moveCameraIn, rotCameraDown, moveSpinnerOff );
cameraAnimIn.setOnFinished(event-> {
    qaLayout.setVisible(true);
    scoreLayout.setVisible(true);
    buttonClick = true;
});

现在摄像机已经在游戏板表面附近设置了动画,并且 buttonClick 布尔变量已经设置为 true 以允许单击按钮来选择答案,我们需要告诉 buttonClick 变量在一个按钮(a1Button 到 a4Button)被单击时自动关闭(false)。

为此,我们需要用一个if(buttonClick == true)条件评估层“包装”每个按钮 ActionEvent 事件处理构造的分数处理内容。这将只允许在 buttonClick 打开(true)时进行事件处理,然后在处理结束时使用简单的buttonClick = false; Java 语句关闭事件处理。这将是退出if(buttonClick == true) Java 代码构造之前的最后一条语句。

您的 Java 代码应该如下所示,这也在图 23-15 的开头和图 23-16 的结尾突出显示,因为这四个按钮 UI 对象的 ActionEvent 处理结构跨越了每个按钮的 120 多行 Java 代码。setOnAction()事件处理基础结构:

A336284_1_En_23_Fig16_HTML.jpg

图 23-16。

At the end of each if(buttonClick==true) construct, set buttonClick = false; to turn off the Button click function

A336284_1_En_23_Fig15_HTML.jpg

图 23-15。

Use a conditional if(buttonClick == true) statement at the top of each Button event processing structure

private void createQAprocessing() {
    a1Button.setOnAction( (ActionEvent event) -> {
        if (buttonClick == true) { // Evaluates if (buttonClick == true) then {not yet clicked}
            if (picked == Q1S1 && pickS1 == 0)
            ...
        buttonClick = false;   // If this Button has been clicked then set buttonClick to false
        }
    });
}

将这个相同的 if(buttonClick == true)放在每个 Button.setOnAction()构造之前,并将 buttonClick = false 在每个 Button.setOnAction()事件处理构造的末尾,如图 23-15 和图 23-16 所示。

要重新打开所有这些事件处理,我们需要一个“让我们再玩一次”按钮和。setOnAction()事件处理程序。

让我们再玩一次按钮:重置播放器事件处理

一旦玩家点击一个回答按钮 UI 对象,所有的游戏棋盘方块、旋转器和按钮 UI 对象都将被锁定!为下一轮游戏解锁的最佳方式是在游戏板中间添加一个大的黄色“让我们再玩一次”按钮(如果您需要提前查看,如图 23-23 所示),用户将单击该按钮旋转另一个时间,随机选择一个新主题和另一个图像进行识别。在本章的这一节,我们将把这个按钮元素添加到你的场景图的根中,开发按钮的代码,并完成你的播放器校对。

让我们通过将 againButton 添加到类顶部的复合按钮声明中来设置 againButton 的基础结构,然后使用. getChildren()将 againButton 添加到您的场景图根中。addAll()方法链。此处显示了执行此操作所需的 Java 代码,并且在图 23-17 的顶部用黄色突出显示:

A336284_1_En_23_Fig17_HTML.jpg

图 23-17。

Declare an againButton Button object at the top of your class and add it to your SceneGraph root object

Button ... a1Button, a2Button, a3Button, a4Button, againButton;
...

root.getChildren().addAll(gameBoard, uiLayout, qaLayout, scoreLayout, spinner, againButton);

在 createBoardGameNodes()中实例化并配置 againButton,位于 X,Y (200,-400),大小为(300,150),使用 34 号 Arial 黑色字体,如图 23-18 所示。标签为“让我们再玩一次”,因为它触发了一轮游戏。

A336284_1_En_23_Fig18_HTML.jpg

图 23-18。

Instantiate and configure againButton in the createBoardGameNodes() method and use a large size and font

因为我们不希望这个按钮在玩家选择答案后才可见,所以我们将 againButton 设置为在启动时不可见。为此,我们在 createBoardGameNodes()方法调用之后,在 start()方法的顶部使用. setVisible(false)方法 call off againButton。这看起来像下面的代码,在图 23-19 中突出显示:

A336284_1_En_23_Fig19_HTML.jpg

图 23-19。

Set your againButton visibility to false in the start() method, after the createBoardGameNodes() method

againButton.setVisible(false);

接下来,将againButton.setVisible(true); Java 语句添加到每个 Button.setOnAction()构造的末尾,以打开“让我们再玩一次”按钮的可见性,如图 23-20 中用黄色和蓝色突出显示的。

A336284_1_En_23_Fig20_HTML.jpg

图 23-20。

Call againButton.setVisible(true); at end of each answer Button event handler, after buttonClick = false;

由于我们只估计了图 23-18 中按钮的位置和大小,让我们使用运行➤项目工作流程,这样我们就可以看到按钮 UI 元素是否位于游戏板设计的四色交叉点的中心。正如你在图 23-21 中看到的,我们需要做一些调整,因为按钮在内容的象限图像上。

A336284_1_En_23_Fig21_HTML.jpg

图 23-21。

Use the Run ➤ Project work process to test the againButton code to see whether it is located and sized properly

还要注意,我们需要为按钮设置黄色背景,因为象限使用粉色、蓝色、绿色和橙色。在.``setBackground``(new``Background``(new``BackgroundFill``(``color.YELLOW``)))方法链中添加一个黄色值(不要忘记空的 CornerRadii 和 Insets),如图 23-22 中蓝色高亮显示。增加你的。将 setMinSize()设置为 300,200;将您的字体大小增加到 35;并将 X,Y,Z 重新定位到(190,-580,100)。

A336284_1_En_23_Fig22_HTML.jpg

图 23-22。

Add a Yellow background color and adjust the translate values and size values to center the againButton

使用“运行➤项目”查看最终的按钮 UI 元素样式。如图 23-23 所示,看起来很棒!

A336284_1_En_23_Fig23_HTML.jpg

图 23-23。

Use a Run ➤ Project work process to test the againButton to see if it is located, sized, and colored properly

接下来,将. setOnAction()事件处理构造添加到 againButton,以便当单击按钮时,可以关闭问答和计分(StackPane)面板,并将 buttonClick、squareClick 和 spinnerClick 变量重置为 false (off ),以便可以使用 3D 微调器、游戏板方块和回答按钮 UI 元素。这些可见性和防点击重置语句的初始 Java 代码在图 23-24 中以蓝色显示。

A336284_1_En_23_Fig24_HTML.jpg

图 23-24。

Add the .setOnAction() method call to againButton; start adding mouse click and visibility event handling

我们需要创建的下一件事是 camera animout parallel transition 动画对象,它将把 camera 对象动画显示回您的完整游戏板(旋转)视图,因为我们将调用。在 againButton.setOnAction()事件处理构造中动画对象构造的 play()方法。因此,让我们在本章下一节创建 ParallelTransition 动画对象,因为这将是一个相对复杂的任务。

相机缩小:另一个平行转换

首先,让我们创建一个与我们在本书前面创建的 rotCameraDown RotateTransition 动画对象完全相反的对象,方法是将此动画对象代码复制并粘贴到 cameraAnimIn 对象下,因为我们将要创建 cameraAnimOut 对象。除了将对象名从 rotCameraDown 改为 rotCameraBack,并在。setFromAngle()和。setToAngle()方法调用。此处显示了完成此任务的 Java 9 代码,并在图 23-25 中用黄色和蓝色突出显示:

A336284_1_En_23_Fig25_HTML.jpg

图 23-25。

Add the rotCameraBack RotateTransition object in createAnimationAssets() and instantiate and configure it for use

rotCameraBack = new RotateTransition(Duration.seconds(5), camera);
rotCameraBack.setAxis(Rotate.X_AXIS);
rotCameraBack.setCycleCount(1);
rotCameraBack.setRate(0.75);
rotCameraBack.setDelay(Duration.ONE);
rotCameraBack.setInterpolator(Interpolator.LINEAR);
rotCameraBack.setFromAngle(-60);
rotCameraBack.setToAngle(-30);

接下来,让我们创建一个与 move camera in translate transition 动画对象完全相反的对象,方法是将此动画对象代码复制并粘贴到 rotCameraBack 对象下。除了将对象名从 moveCameraIn 更改为 moveCameraOut,并将。setByZ()方法调用。这里显示了完成此任务的 Java 代码,在图 23-26 中用黄色和蓝色突出显示:

A336284_1_En_23_Fig26_HTML.jpg

图 23-26。

Create a moveCameraOut TranslateTransition Animation object and change the .setByZ() value to 175

moveCameraOut = new TranslateTransition(Duration.seconds(2), camera);
moveCameraOut.setByZ(175);
moveCameraOut.setCycleCount(1);

我们可以使用现有的 moveSpinnerOn 动画对象,该动画对象用作 spinnerAnim ParallelTransition 的组件之一,以便在 ParallelTransition 将相机带回其原始游戏板旋转位置和方向时,将微调器移回屏幕上。这将证明该动画对象可以在多个 ParallelTransition 对象中使用,这是一种编码优化,因为编码构造可以用于多个目的。在图 23-27 的顶部,您可以看到这个已经编码的动画以黄色突出显示。

A336284_1_En_23_Fig27_HTML.jpg

图 23-27。

Create cameraAnimOut ParallelTransition and reference moveCameraOut, rotCameraback, and moveSpinnerOn

因此,现在我们可以创建您的 parallel transition camera animout 对象,它将并行或完全同时播放 moveCameraOut、rotCameraBack 和 moveSpinnerOn 动画对象!这将只需要一行代码来实例化 cameraAnimOut 对象,并使用其构造函数方法来加载带有其他三个动画对象引用的对象。最后,我们将添加第二行代码,调用该对象的. setOnFinished()方法,以便在相机缩小后将 spinnerClick 布尔变量重置为 false,这样玩家就可以再次使用 i3D spinner UI 元素来随机旋转游戏板。

执行此操作的 Java 代码应该如下所示,并在图 23-27 的底部以浅蓝色和黄色突出显示:

cameraAnimOut = new ParallelTransition(moveCameraOut, rotCameraBack, moveSpinnerOn);

现在,我们可以继续完成 againButton.setOnAction()构造的代码,细化结果。

完成再次播放按钮:resetTextureMaps()

我们现在将扩展 againButton.setOnAction()事件处理基础结构中的五行代码,以便我们调用新的 camera Animation 对象和现有的 AudioClip 对象,将动画和数字音频添加到游戏的一部分,使玩家返回到缩小视图,在那里他们可以随机旋转游戏板以选择新内容来测试他们的知识库。我们还将把您的 resetTextureMaps()方法调用从 createSceneProcessing()方法中移到这个游戏重置事件处理方法中,以便在游戏结束时,就在相机从游戏板缩放回来之前(以及在播放相机缩放音频效果以匹配该动画之前),游戏板的方块和象限被重置为空白。作为此过程的一部分,我们还将隐藏 againButton 按钮 UI 元素,因为我们不希望该按钮 UI 元素覆盖我们的 i3D 旋转器和游戏板旋转以随机选择下一个象限的视图。

在 qaLayout 和 scoreLayout 可见性调用之后,在 againButton 上添加一个. setVisible(false)方法调用。接下来,添加一个 resetTextureMaps()调用和。play()在方法结束时调用 cameraAnimOut 和 cameraAudio。

用于事件处理的 Java 9 代码现在看起来应该如下所示,如图 23-28 所示:

A336284_1_En_23_Fig28_HTML.jpg

图 23-28。

Call resetTextureMaps(), cameraAnimOut.play(), and cameraAudio.play() in againButton.setOnAction()

againButton = new Button();
againButton.setText("Let's Play Again!);
againButton.setFont(Font.font( "Arial Black", 35) );
againButton.setBackground(new Background(new BackgroundFill(Color.Yellow,
                                                            CornerRadii.EMPTY, Insets.Empty);
againButton.setMinSize(300, 200);
againButton.setTranslateX(190);
againButton.setTranslateY(-580);
againButton.setTranslateZ(100);
againButton.setOnAction( (ActionEvent event) -> {
    qaLayout.setVisible(false);
    scoreLayout.setVisible(false);
    againButton.setVisible(false);

    buttonClick = false;
    squareClick = false;
    spinnerClick = false;
    resetTextureMaps();

    cameraAnimOut.play();

    cameraAudio.play();

}

现在我们可以添加。setOnFinished()事件处理构造到 cameraAnimOut 动画对象,以在相机动画返回到游戏棋盘旋转视图时自动打开 spinnerClick 功能,以便玩家可以单击 i3D spinner UI 元素重新开始游戏过程。该功能的 Java 代码在这里显示为单行代码,并在图 23-29 中以浅蓝色和黄色突出显示:

A336284_1_En_23_Fig29_HTML.jpg

图 23-29。

Add a cameraAnimOut.setOnFinsihed() event handler that sets the spinnerClick variable to a true value

cameraAnimOut = new ParallelTransition(moveCameraOut, rotCameraBack, moveSpinnerOn);
cameraAnimOut.setOnFinished( event-> { spinnerClick = true; } );

使用一个运行➤项目的工作流程,如图 23-30 所示,来测试一个完整的周期(或者两轮游戏)。

A336284_1_En_23_Fig30_HTML.jpg

图 23-30。

Use Run ➤ Project to test code; notice that clicking another quadrant square sets that quadrant image

注意图 23-30 中,我们的测试过程揭示了另一个问题!事实证明,方形点击的测试不够“深入”,不足以保证完美的游戏性!当某个象限已经“登陆”或被随机选择进行游戏时,您可以单击另一个象限的方块。这需要我们给游戏增加另一层保护,我们必须创建四个 squareClick 变量(每个象限一个)来真正彻底地保护我们的游戏。让我们在本章的下一节中修改我们的代码,使用 squareClick1 到 squareClick4 布尔变量并在每个象限的基础上进行测试来完成这个任务。

象限级保护:每象限平方点击

到目前为止,让我们更改 squareClick 代码,以适应每象限正方形检查。我们要做的第一件事是将类顶部的 squareClick 更改为 squareClick1 到 squareClick4(以匹配您的象限)。我们还需要更改 createSceneProcessing()方法中的测试,以将 squareClickN 变量与四个象限中的每一个相匹配,因此例如if(picked == Q1 S1 && squareClick1 )将被修改,而if(picked == Q2 S1 && squareClick2 )等等,如图 23-31 中突出显示的(象限 4)。这个改动的 Java 代码相当微妙,代码量很大,有些重复,这里就不一一列举了。图 23-32 显示了我提到的轻微(但重要)的修改。

A336284_1_En_23_Fig32_HTML.jpg

图 23-32。

Select and delete the squareClick = true; statement in createAnimationAssets(), as we are now moving it

A336284_1_En_23_Fig31_HTML.jpg

图 23-31。

Change your squareClick code to squareClick1 through squareClick4 to match up with the quadrant involved

从 createAnimationAssets()方法中删除 squareClick 引用,因为我们将在四个 populateQuadrant()方法中基于象限来控制方形点击,这是一个更合理的做法。

如图 23-32 所示,我选择了 createAnimationAssets()square click 语句进行删除。

正如你在图 23-33 中所看到的,我也选择了 createBoardGameNodes() squareClick = false;语句进行删除,因为我们将在你的 createSceneProcessing()方法中执行此操作。事实上,这已经显示在图 23-31 中,在屏幕截图的最右侧以黄色突出显示,这是它逻辑上的归属。

A336284_1_En_23_Fig33_HTML.jpg

图 23-33。

Select and delete the squareClick = false; statement in createBoardGameNodes(), as we’ve already moved it

您希望将四个 squareClick 变量设置为 true(允许在这个象限的正方形上单击)的地方是在每个 populateQuadrantNumber()方法调用的末尾,以完成设置。这允许单击其中一个方块,以便为该象限的主题选择内容。

squareClick1 变量位于 populateQuadrantOne()的末尾,squareClick2 变量位于 populateQuadrantTwo()的末尾,squareClick3 变量位于 populateQuadrantThree()的末尾,squareClick4 变量位于 populateQuadrantFour()的末尾。

现在有一个 squareClickN 变量与四个象限中的每一个都相关。这更好地匹配了游戏模式,因为现在我们可以有选择地只在玩家所在的游戏棋盘象限打开鼠标点击,而在其他三个象限关闭游戏棋盘方块。这将防止图 23-30 中未被发现的测试,其中未被随机数发生器选择的象限仍然可以被播放。由于这在视觉上看起来不正确(如您所见),我们将通过逐个象限地关闭方块来解决这个问题,这将解决这个问题,尽管需要更复杂的防玩家 Java 代码。

populateQuadrantOne()方法体的 Java 代码如下所示,在图 23-34 的底部用浅蓝色和黄色突出显示:

A336284_1_En_23_Fig34_HTML.jpg

图 23-34。

Add a squareClick1 = true; statement at the end of populateQuadrantOne() and the other three in the other three methods

private void populateQuadrantOne() {
    pickS1 = random.nextInt(3);
    if (pickS1 == 0){diffuse1 = new Image("/gamesquare1bird0.png", 256, 256, true, true, true);}
    if (pickS1 == 0){diffuse1 = new Image("/gamesquare1bird1.png", 256, 256, true, true, true);}
    if (pickS1 == 0){diffuse1 = new Image("/gamesquare1bird2.png", 256, 256, true, true, true);}
    Shader1.setDiffuseMap(diffuse1);
    ...
    squareClick1 = true;
}

做同样的平方 clickn = true;Java 语句来打开 squareClick 函数,一旦为当前一轮游戏选择了一个方块,该函数将被关闭。如图 23-35 所示,游戏现在可以正常运行了。您可以单击与当前象限无关的微调按钮和方块,这些单击将被忽略,就像单击第一个应答按钮之后的任何按钮一样。这种“错误检验”或“玩家检验”使得 i3D 游戏更加专业。

A336284_1_En_23_Fig35_HTML.jpg

图 23-35。

Use Run ➤ Project and test the final error-proofing code and Play Again User Interface

恭喜你,基本的游戏已经完成,我们可以看看优化和剖析。

摘要

在第二十三章中,我们学习了如何创建玩家验证逻辑来强制正确使用 i3D 旋转器 UI、游戏板方块和回答按钮 UI 元素。这包括使用大约六个布尔变量,它们被用作“标志”,以关闭玩家在每轮游戏中多次点击 UI 元素的能力。我们保护了 i3D spinner UI,按钮 UI answers,以及每个象限的游戏棋盘方块,防止它们被“误用”到游戏系统中并累积未得分数。这是 pro Java 9 游戏设计和开发的一个重要部分,以确保您的游戏逻辑以预期的方式运行。

我们还在本章的第一部分完成了游戏内容的添加,添加了近 600 行 Java 代码,并将当前的 pro Java 9 游戏开发项目增加到近 1750 行 Java 代码。

在第二十四章中,您将了解游戏优化、使用 NetBeans 9 的评估以及 NetBeans 9 Profiler。

二十四、使用 NetBeans 优化游戏素材和代码以及游戏分析

现在你的游戏运行了,玩家一次点击(回合)使用它,我们可以看看它使用了多少内存,所有这些素材有多大。我们还可以想办法将数字音频和图像素材缩小 2 到 4 倍。首先使用 GIMP 进行数据占用优化,然后使用 NetBeans 9 Profiler 对当前的 24 位图像素材和 CD 质量的 16 位 44.1KHz 音频素材进行分析。通过这种方式,我们可以看到高端多媒体素材是否占用了太多的内存和 CPU 开销,或者我的开发系统是否处理得很好,我的开发系统是沃尔玛的一款老式 4GB Win7 Acer 四核微塔式机(几年前售价 300 美元)。我一直在这个系统上使用 NetBeans 9 开发 Java 9,没有发生任何事故。目前的系统是六核或八核,内存为 8MB 或 16MB,因此 Java 9 开发可以在旧系统上轻松完成,不需要像 Unity、Android 或 Lumberyard 等其他 i3D 平台那样需要先进的系统。

在本章中,我们将转换您的数字图像资源,以使用 8 位(索引)颜色而不是 24 位“真实”颜色来创建纹理贴图,并且我们将运行 NetBeans Profiler 来查看您的 Java 代码在运行游戏时使用了多少内存和 CPU 处理。

优化纹理贴图:转换为 8 位颜色

目前,您的 source ( /src/)文件夹中的数字图像素材大约为 24MB,即 24,000,000 字节,这对于一个有 120 个不同图像的 i3D 棋盘游戏来说是相当不错的(平均每个图像大约 200KB)。然而,如果我们能把它压缩到 10MB(每张图片 84KB),这将会大大减少我们游戏发行包的大小。实现图像“重量”或大小减少 300%到 400%的方法是使用 8 位颜色(索引色)以及“抖动”或点图案,用于模拟比用于表示索引图像的最大 256 色更多的颜色。小到中等的纹理贴图,这正是我们在游戏棋盘的正方形和象限中使用的,可以很好地处理索引色。这样做的原因是因为抖动可以被放大(近距离)看到,但当图像被观看得更远时(从远处看或缩小),这种视觉效果就会消失。我将在本章的这一节向您展示这一点,我们将把所有 120 个图像素材从 24 位转换为 8 位索引颜色。

创建索引颜色纹理:在 GIMP 中更改颜色模式

让我们以这样一种方式优化我们的图像素材,即我们不必对我们的 Java 代码做任何重大的改变。为了保持我们的 Java 代码不变,我们将使用相同的文件名,并将它们放在不同的文件夹中,在/src/ called /8bit/下。因此,我们将拥有索引颜色资源的/src/8bit/ path 和 24 位高质量资源的/filename 路径。使用操作系统文件管理实用程序在当前/src 文件夹下创建一个名为/8bit/的文件夹(目录),其中包含原始的真彩色图像资源。图 24-1 显示了这个新文件夹。

A336284_1_En_24_Fig1_HTML.jpg

图 24-1。

Create the /JavaFXGame/src/8bit/ folder to hold optimized versions of your 120 texture map image assets

使用文件➤打开在 GIMP 中打开第一张 gamequad1bird0.png 纹理贴图图像,然后使用图像➤模式➤索引菜单序列将 24 位颜色空间(颜色模式)转换为 8 位,如图 24-2 所示。这将打开“索引颜色转换”对话框,允许您选择多种颜色和一种抖动算法。8 位模式将位数减少了 300%或更多,抖动算法模拟了超过 256 种颜色。

A336284_1_En_24_Fig2_HTML.jpg

图 24-2。

Use a File ➤ Open menu sequence to open a texture map and use Image ➤ Mode ➤ Indexed to convert it to 8-bit

我使用了最大允许的 256 种颜色(0 到 255),方法是选择生成最佳调色板单选按钮,如图 24-3 中最左边的对话框所示,以及正常的 Floyd-Steinberg 颜色抖动算法。然后我点击对话框右下角的转换按钮。要将 8 位图像导出到/src/8bit/文件夹,使用 GIMP 文件➤导出为菜单序列,双击 8 位文件夹(在图 24-3 的第二个面板中高亮显示),点击导出按钮(保持 24 位文件名不变,如第三个面板中高亮显示)。

A336284_1_En_24_Fig3_HTML.jpg

图 24-3。

Set the conversion to 256-color Floyd-Steinberg, convert, and save in the /src/8bit folder with the same file name

正如你在图 24-4 中看到的,如果我们将一幅图像(gamequad1bird1.png)转换成 8 位索引色后放大到第二象限,你可以清楚地看到背景、鸟嘴和钢箍中的抖动。有趣的是,当你使用图像作为纹理贴图(缩小)时,你看不到这种抖动!当我们在 Java 代码中实现这些变化时,我将在本章的后面向您展示这一点(在图 24-26 和 24-27 )。

A336284_1_En_24_Fig4_HTML.jpg

图 24-4。

Click the Magnify Glass (Zoom) Tool and zoom in 300 percent (three times) to see the color dithering algorithm

正如你在图 24-5 中看到的,你的真彩色(24 位)图像在质量上是原始的,但是使用了数倍的数据。当缩小时(用作纹理贴图),两个图像看起来几乎相同,这就是为什么我们将您的 24 位图像转换为 8 位图像,因为我们可以从 24MB 的数字图像素材增加到不到 8MB,而几乎没有损失 i3D 游戏板纹理贴图的感知质量,至少从玩家的角度来看是这样。

A336284_1_En_24_Fig5_HTML.jpg

图 24-5。

Undo the indexed color, click the Magnify Glass Tool, and again zoom in 300 percent to see the (original) true-color data

使用如图 24-6 所示的文件➤关闭对话框,在将索引图像文件保存到/src/8bit 文件夹后关闭该文件。因为您从/src 文件夹中打开了 24 位文件,所以您希望确保单击“放弃更改”,这样您将得到原始的 24 位 PNG24 文件和新导出(保存)的 8 位 PNG8 文件,它们使用相同的名称,但保存在不同的文件夹中。这一点很重要,你要注意 120 次,这样你就有 120 个 PNG24 文件和 120 个 PNG8 文件在不同的目录中。要更改这些图像的引用,您只需将/8bit/filename.png 路径更改添加到您已创建的索引颜色素材文件夹名称中,然后 i3D 游戏将使用这些较小的文件大小来纹理映射您的游戏棋盘方块和象限。

A336284_1_En_24_Fig6_HTML.jpg

图 24-6。

Click Discard Changes to keep a 24-bit version

如图 24-7 所示,第一象限完成,8 位文件大小从 76KB 到 104KB 不等。

A336284_1_En_24_Fig7_HTML.jpg

图 24-7。

The first quadrant texture maps have all been reduced more than 300 percent and still look fantastic as texture maps

正如你在图 24-8 中看到的,我们已经将象限纹理贴图数据从 4MB 减少到 1.33MB。

A336284_1_En_24_Fig8_HTML.jpg

图 24-8。

Preview data reduction in File Explorer

正如你在图 24-9 中看到的,我继续减少所有 60 个图像素材的象限纹理贴图。

A336284_1_En_24_Fig9_HTML.jpg

图 24-9。

Go into the /src/8bit folder, select all 60 images, right-click the selection, and open Properties

正如你在图 24-10 中看到的,我现在也已经为你的游戏棋盘方块完成了这些 8 位图像。

A336284_1_En_24_Fig10_HTML.jpg

图 24-10。

Go into the /src/8bit folder and select all 60 images; right-click the selection and open Properties

如图 24-7 、 24-9 和 24-10 所示,这些索引彩色图像越小,它们看起来就越像真彩色图像,尽管在许多情况下它们要小几倍(三到四倍)!在本章的第一部分,我们将介绍如何将图像优化为 8 位(索引)颜色,因为这是减少分发文件数据占用空间(代码和素材包中图像素材的大小)的有效方法。

其中一些图像,例如红色的柿子椒,将非常适合索引色,因为红色光谱、白色背景和绿色边框颜色可以非常接近于仅使用 256 种颜色和紧密匹配的颜色之间的微妙抖动来表示真彩色图像,这在放大时甚至看不到。图 24-11 显示了象限纹理贴图(左半部)和方形纹理贴图(右侧)的真彩色和索引图像结果(来自图 24-9 和 24-10 所示的选择类型)。

A336284_1_En_24_Fig11_HTML.jpg

图 24-11。

Right-click the selected square and quadrant images in both folders; use Properties to preview the optimization

我们已经将游戏棋盘象限纹理贴图的数据占用空间从 17,041,285 字节减少到 6,208,570 字节,减少了 10,832,715 字节。这意味着象限图像的数据占用空间减少了 65 %(三分之二)。这是 512 像素的正方形,对于一个 i3D 游戏来说是相当大的(高质量),所以 60 张图片的 6MB 是很好的质量,每象限图片大约 100KB,正如你在 GIMP 中已经在图 24-7 中看到的。

我们还将游戏棋盘正方形纹理贴图的数据占用空间从 4,516,845 字节减少到 1,701,334 字节,减少了 2,815,511 字节。游戏板正方形图像的数据占用空间减少了 63%。这些是 256 像素的正方形,这是 i3D 游戏的主流(高质量),因此 1.7MB 的 60 张图像是很好的质量,每个游戏棋盘正方形图像大约 28KB,或者每个游戏主题选择大约 128KB 的图像数据。

要引用这些优化的素材,只需在 Java 代码中的文件名前添加/8bit/ path,这将在我使用原始的 24 位数字图像素材和 CD 质量的数字音频素材对当前代码进行分析之后进行。始终使用最高质量的素材来分析您的代码,以便您可以看到内存和 CPU 周期是否受到过大的新媒体元素的影响(数据占用方面)。就职业 Java 游戏而言,这是 NetBeans profiler 将告诉您的大部分内容。是的,你的 Java 逻辑很重要。无限循环问题会很快在 profiler 中出现,但非优化的动画对象构造、过大的纹理贴图、过长的数字音频声音效果、没有很好优化的数字视频以及使用过多多边形(过多几何体)的 i3D 资源也会出现。这就是为什么我们在本书前三分之一的时间里研究了各种新媒体的概念和原理,因为新媒体的优化影响了游戏的玩法。

NetBeans 9 Profiler:测试内存和 CPU 使用率

要调用 NetBeans 9 Profiler,只需使用 Profile 菜单和位于该菜单顶部的 Profile 项目(JavaFXGame)选项,如图 24-12 所示。还显示了 40 个自定义方法、必需的 start()和 main()方法,以及自从创建 JavaFXGame 引导应用以来我们添加的 1700 行 Java 9 代码。NetBeans 9 分析会话可以显示程序执行期间计算机上发生的大量复杂的“幕后”操作,以及与服务器的交互,甚至 SQL 数据库访问模式。因此,在本章中,我们不会涉及 NetBeans 9 分析系统的所有功能;但是,如果您对 Java 软件概要分析感兴趣,您当然应该利用自己的时间,在各种 64 位工作站上使用您的其他 Java 9 软件开发项目,探索和试验概要分析器选项。

A336284_1_En_24_Fig12_HTML.jpg

图 24-12。

Invoke a Profiler ➤ Profile Project (JavaFXGame) menu sequence to start a NetBeans profiling session

一旦您第一次调用了分析器,您将在 IDE 中得到一个选项卡,其中包含了分析 UI 和生成的分析器数据 UI,如图 24-13 所示。JavaFXGame 选项卡有一个性能分析图标,左上角有一个配置会话下拉菜单 UI 元素,还有一个配置和启动性能分析指令序列,它将概述性能分析程序选项类型,并准确地告诉您如何选择要使用的选项。

A336284_1_En_24_Fig13_HTML.jpg

图 24-13。

Once you invoke the NetBeans 9 profiler, you’ll get a JavaFXGame Profiling tab and configuration instructions

我们将首先查看遥测分析模式,因为这向我们展示了游戏如何使用系统内存和 CPU 周期,以及线程、类和垃圾收集如何影响游戏在您的开发系统上的运行。这是大部分关键的游戏代码处理信息,我们希望首先查看这些信息,以确保您的 i3D 棋盘游戏以最佳方式(即高效地)使用 Java 9 和 JavaFX 9。

单击选项卡窗格左上角的配置会话 UI 选择器旁边的向下箭头,并选择遥测选项(在图 24-14 中以浅蓝色突出显示),以启动 NetBeans 遥测分析会话。保持默认的“使用已定义的分析点”选项处于选中状态,以允许 NetBeans 9 最初为您配置此分析会话。如果发现了异常情况,您可以在稍后的分析会话中设置定制的分析点,以进一步尝试确定 Java 游戏代码有什么问题。现在让我们希望我们在本书中对以最佳方式做事的关注得到了回报。无论哪种方式,NetBeans profiler 都会揭示这一点!

A336284_1_En_24_Fig14_HTML.jpg

图 24-14。

Drop down the Configure Session menu and select the Telemetry option to profile your memory and CPU

然后,JavaFXGame 分析窗格将显示 CPU(和垃圾收集)图形的 UI 基础结构、(系统)内存实时使用图形、垃圾收集处理图形以及线程和类图形,如图 24-15 所示。还没有收集到数据,因为还没有使用 Profile 项目图标激活(启动)概要分析,该图标显示在图的顶部,带有淡黄色的弹出描述符。

A336284_1_En_24_Fig15_HTML.jpg

图 24-15。

Once you click the Profile Project Icon or Menu Item, the JavaFXGame Profile pane will populate with empty UI elements

单击您的 Profile 项目图标,您将得到“Profiler 现在将执行您的机器和目标 JVM 的初始校准”消息,如图 24-16 所示,位于五个对话框的最左边。请记住,NetBeans Profiler 正在分析您的系统和 Java 9 JVM,因此,如果您在 8 核、12 核或 16 核计算机上进行分析(比如一个新的 AMD 锐龙 5 或 7 系统,具有 16GB 的 DDR4-2400),您将获得与我在只有 4GB DDR 3-1333 内存的旧四核 Acer AMD 3.11GHz 系统上获得的结果不同的结果。我使用这样一个旧的 Windows 7 系统的原因是为了展示 Java 9 和 NetBeans 9 有多么优化,这样你就可以使用一台不能用于 Amazon Lumberyard 或 Android Studio 3.0 或 Unity development 的计算机来开发一个专业的 JavaFX i3D 游戏。

A336284_1_En_24_Fig16_HTML.jpg

图 24-16。

Once you start a profiler, you’ll get a series of dialogs for calibrating and configuring this profiling process

如果出现 Windows 7 防火墙对话框,点击允许访问按钮,如图 24-16 第二个对话框所示。然后选择显示此校准数据的详细信息,并单击确定按钮继续。您将看到一个对话框,其中显示了一些已获得的校准数据。单击该对话框的“确定”按钮后,您将看到一个“连接到目标虚拟机”对话框,其中显示了一个进度条,指示 NetBeans 9 IDE 将游戏代码和内容加载到系统内存中,以便执行校准,并最终收集和显示性能分析数据。

A336284_1_En_24_Fig17_HTML.jpg

图 24-17。

An Output Pane will open, showing your Java 9 code being run in the NetBeans Profiler Agent Utility

如图 24-17 所示,接下来你会看到的是执行游戏 Java 代码的输出面板。

关闭输出窗格,再次显示 Profiler 遥测 UI。游戏和剖析程序现在应该共享屏幕了。您在游戏中所做的任何事情都会实时反映在这些 NetBeans Profiler 遥测面板中,如图 24-18 所示。我使用红色 Arial 文本注释接下来的五个图形,以阐明我正在测试游戏的五个主要阶段中的哪一个,您可以从 profiler UI 数据中看到 3D 动画、音频回放、纹理贴图加载(或卸载)、事件处理和 Java 代码处理在 CPU 处理(百分比)开销、系统内存使用(大部分将用于加载数字图像或保存和播放数字音频, 以及保存我们用来进行 3D 建模、3D 纹理、3D 动画和音频的 JavaFX API 类)、垃圾收集、线程使用和(单个游戏原型)类使用。

A336284_1_En_24_Fig18_HTML.jpg

图 24-18。

The Animation object moving a rotating 3D spinner UI onto the screen uses 0 to 5 percent of the CPU’s capacity

正如你在图 24-18 中看到的,将你的 i3D spinner UI 移动到屏幕上只使用了百分之几的 CPU,只有一两秒钟,所以这似乎是很好的编码。一旦 i3D 微调器“着陆”在屏幕上,点击它的用户界面,其分析数据如图 24-19 所示,就 CPU 使用而言,看起来也是高度优化的。请注意,可以在垃圾收集窗格和线程和类窗格中看到着陆的游戏棋盘象限(五个)正方形图像的数量。当您的随机数生成时,此活动会达到峰值,五个游戏方格会加载随机选择的图像素材,然后这些素材会被放入系统内存中。

A336284_1_En_24_Fig19_HTML.jpg

图 24-19。

The board spin uses garbage collection to load images into memory and threads to pick a random number

选取一个方块会调用垃圾收集来加载象限图像,当选取一个方块时,CPU 线程处理会出现峰值,代表垃圾收集、事件处理、问答和分数处理,如图 24-20 所示。

A336284_1_En_24_Fig20_HTML.jpg

图 24-20。

A square pick uses garbage collection to load imagery into memory and uses threads to display UI panels

另一方面,选择答案会调用零垃圾收集来加载影像,如图 24-21 所示。它使用很少(几乎没有)CPU 开销来增加乐谱面板和显示文本资源。

A336284_1_En_24_Fig21_HTML.jpg

图 24-21。

Picking an answer (Button) involves the least amount of overhead and just minor CPU overhead for scoring

使用“让我们再玩一次”按钮对象重置游戏,其所有处理使用的开销与 3D 棋盘旋转(图 24-19 )一样多,如图 24-22 所示。垃圾收集重置所有纹理贴图,相机动画改变游戏视角,然后事件处理锁定不需要的点击,音频回放播放相机动画音频效果,类似的处理密集型代码重置另一轮游戏。

A336284_1_En_24_Fig22_HTML.jpg

图 24-22。

Clicking a Let’s Play Again Button object invokes a second flurry of CPU and memory use for special effects such as audio and animation

考虑到这包括图像数据、CD 质量的音频效果和 JavaFX 动画(过渡)类,以及 AudioClip、图像、StackPane、按钮、3D 图元、文本、(SceneGraph)节点和实用程序(Inset、Color、Pos 等), 60MB 的内存使用率也相当不错。)用法,所有这些都利用了我们用来构建这个 i3D 专业级 Java 9 游戏的 JavaFX 类。

这些内存开销中很少一部分可以直接归因于您编写的 1,700 行 Java 代码,这些代码是为了组装这个游戏而编写的;99%的内存使用可归因于加载新的媒体素材和许多 JavaFX 9 类,这些类访问和运行这些新的媒体素材。

正如你在图 24-23 中看到的,一旦你完成了对你的 Java 9 游戏的分析,你将得到一个信息对话框,显示“被分析的应用已经完成执行”单击 OK 终止 VM,您将看到一个摘要方框(黑色),显示分配的内存堆大小(71MB)和运行性能分析会话所使用的内存总量(66MB)。如果您认为 66MB 是很大的内存,请考虑这台机器有 4,096MB 的内存,66MB 代表 1.6%的内存。许多现代智能手机、iTV 电视机、平板电脑和笔记本电脑都有 8GB 的系统内存,因此整个游戏生态系统和基础设施只占不到 1%的系统资源。在 2GB 的智能手机或(古老的)计算机系统上,这将代表大约 3%的系统资源。这对于一个动画的、交互式的 3D 棋盘游戏来说是相当不错的,所以 Java 做得非常好!

A336284_1_En_24_Fig23_HTML.jpg

图 24-23。

Once you are finished profiling, NetBeans will give you a memory used summary and Information dialog

接下来,让我们看看如何优化我们的 Java 代码,因为我用来写这本书的代码就是我所说的“原型”代码。它在技术上是正确的,但是(还)没有利用任何可能实现高级 Java 语言语法或特性(如数组或哈希表)的 Java 编码结构。这样做的原因是,我试图帮助新的游戏开发者和程序员“可视化”Java 游戏逻辑(代码)在他们头脑中正在做的事情,而最简单的方法是以一种显示代码试图做什么的方式可视化地编码它。

请注意,Java 9 的编译、构建和执行过程也将做大量的工作来“在幕后”优化这段代码,因为前面关于概要分析的部分将展示这段 Java 9 代码在没有以任何方式进行专门的“程序员优化”的情况下是如何优化的。此外,从一个程序员到下一个程序员,有许多不同的方法可以做到这一点,因此我想更多地关注 JavaFX 游戏 API、游戏设计和开发工作流程以及游戏素材开发,而不是标准的 Java 代码优化,后者在数百本其他 Apress 书籍中有很好的介绍。

在 Java 9 游戏代码优化思想这一章的后面部分,我们将会看到一些 Java 代码优化。首先,让我们在游戏中完成 8 位索引图像资源的实现,并播放它,看看索引色 PNG8 资源和真彩色 PNG24 图像资源之间是否有任何视觉差异。

实现索引颜色影像:添加路径

将图像资源从真彩色更改为索引色就像将/8 位路径添加到 populateQuadrant()方法和 setupQSgameplay 方法一样简单。对于 loadImageAssets()和 resetTextureMaps()方法,您不必这样做,因为这些方法使用没有索引的纹理贴图,因为它们已经很小(几千字节),并且可以保持为真彩色图像。这是因为它们不包含数字图像,因为这些是空白纹理,用于在每一轮游戏旋转之前使游戏板看起来空无一物。我截图了 populateQuadrantOne()方法显示添加的/8 位路径,如图 24-24 所示。

A336284_1_En_24_Fig24_HTML.jpg

图 24-24。

Add an /8bit path in front of the current image file name reference to point to your new indexed imagery

你需要把这个相同的/8 位路径添加到你的 20 种设置游戏性的方法中的数字图像引用的前面。这些是为每个象限(Q)和正方形(S)命名的,作为您的 setupQSgameplay()方法,我在自定义的 40 个方法和 2 个必需方法(start()和 main() Java 方法)的末尾留了下来。

我截取了第一个 setupQ1S1gameplay()方法的截图,以显示我已经在数字图像引用的前面安装了/8bit 路径。我这样做是为了让您的新 8 位(索引色)PNG8 数字图像将被用作棋盘游戏的纹理贴图,而不是我们一直使用的 24 位真彩色数字图像。

我们这样做是为了让我们可以使用“运行➤项目”工作流程来测试您的游戏,看看在使用 325%的小索引彩色图像而不是真彩色 24 位 PNG 图像玩 i3D 游戏时,是否有任何视觉差异。图 24-25 显示了 20 种方法中的第一种,必须通过在数字图像参考名称的前面(头部)添加一个/8 位文件夹路径来进行“路径修改”。要轻松做到这一点,只需复制/8 位路径一次,并在 populateQuadrant()方法中粘贴 60 次,在 setupQSgameplay()方法中粘贴 60 次。

A336284_1_En_24_Fig25_HTML.jpg

图 24-25。

Add an /8bit path in front of the current image file name reference to point to your new indexed imagery

接下来,让我们使用图 24-26 所示的 Run ➤项目工作流程,看看游戏板在随机旋转后看起来是否相同。正如你所看到的,它看起来和我们在整本书中使用真彩色图像时几乎一模一样。我们需要做的下一件事是放大,看看 8 位图像如何支撑。

A336284_1_En_24_Fig26_HTML.jpg

图 24-26。

Use a Run ➤ Project work process; spin the game board to see whether the new 8-bit color images look the same

点击带有颜色渐变的图像,在本例中是旧金山海湾大桥,如图 24-27 所示。这将向我们展示大象限图像,以便我们可以看到是否有任何抖动模式。

A336284_1_En_24_Fig27_HTML.jpg

图 24-27。

Select an image that will show dithering to zoom the game board in closer to see whether the image looks the same

正如你所看到的,当它被用作 i3D 游戏板上的纹理贴图时,没有可见的抖动图案(点状伪像),因此这表明我们可以在这个 i3D 游戏板上成功地将索引色用于纹理贴图,而不会遭受任何可感知的交付质量下降。这给出了一个专业的结果,每样东西看起来都像是使用了真彩色,直到每个象限中的钢圈,它看起来仍然像钢一样,没有任何可察觉的抖动。接下来,让我们看看如何优化您的 16 位数字音频素材。

优化音频:以较低的采样率使用 16 位

我们在如何使用 Audacity 2.1.3 优化数字音频方面做得很好,所以我建议使用 16 位音频采样分辨率并优化采样率(48、44、32、22、16、11 或 8kHz),直到您听到质量变化。我们已经有 16 位 44.1 kHz(目前使用)和 16 位 22.05 kHz,这是我们每个音效样本的一半数据,但听起来质量非常相似。如果您想使用更小的数字音频内存,您可以简单地在 loadAudioAssets()方法体中引用更优化的音频资源。

在这一点上,我将把这个内存与质量的决定完全留给您。如果您想回到 Audacity 并优化其他三个采样速率,如 THX (48 kHz)或 32 kHz,甚至 16 kHz,您可以聆听每个采样速率产生的 16 位音频质量,并决定每个数字音频质量级别需要使用多少系统内存。

请注意,您可以对游戏中的每个数字音频资源使用不同的采样速率。一些声音效果将保持较低的采样速率(11 和 16 kHz),而其他声音效果(音乐、声乐)可能需要较高的采样速率(22 和 32 kHz)。不过,我建议全面使用 16 位采样分辨率,因为它更适合内存,因为内存“分块”为 8 位、16 位和 32 位,而您想要无缝匹配。

Java 游戏代码优化:利用 Java 技巧

您可能已经注意到,在本书的整个过程中,当我们设计和构建我们的 pro Java 9 游戏时,我一直在使用长格式的、易于阅读(和理解)的 Java 代码。这段代码是合法的,可能会被 Java 9 编译和构建(以及执行)软件阶段优化,所以它不是“坏”代码,但是有一些优化过程和构造会使它明显更短、更精简。为一本书完成这个过程的问题是,每个 Java 程序员都有不同的首选方法,那么我应该选择哪种方法呢?还是我展示了大部分?不幸的是,这本书的页数是固定的,涵盖了新媒体素材开发、游戏设计、开发和测试,以及类似的需要掌握的广泛主题,以便成为一名专业的 Java 9 游戏开发人员。在这一章的最后一节,我将介绍一些其他的东西,你可能想自己添加到这个棋盘游戏中,用你所学到的东西进行一些练习。

让我们先看看 populateQuadrant()方法代码。populateQuadrantOne()方法以下面的 Java 序列开始,象限 1 中的五个游戏棋盘方格各有一个这样的构造:

pickS1 = random.nextInt(3);
if (pickS1 == 0) { diffuse1 = new Image("/gamesquare1bird0.png", 256, 256, true, true, true); }
if (pickS1 == 1) { diffuse1 = new Image("/gamesquare1bird1.png", 256, 256, true, true, true); }
if (pickS1 == 2) { diffuse1 = new Image("/gamesquare1bird2.png", 256, 256, true, true, true); }
Shader1.setDiffuseMap(diffuse1);

您将通过用以下代码替换它来简化这段代码(一旦您知道它可以工作,在原型化之后),这也消除了条件 if() CPU 处理和内存开销:

pickS1 = random.nextInt(3);
diffuse1 = new Image("/gamesquare1bird" + pickS1 + ".png", 256, 256, true, true, true);
Shader1.setDiffuseMap(diffuse1);

的确,这使得更难看到你在游戏代码中做了什么,但是代码以相同的方式运行,并且几乎是代码行数的一半,允许你将 populateQuadrantN()方法从 26 行代码减少到 16 行代码,这减少了 38%的代码,或者所有四种方法减少了 40 行代码。

接下来,考虑第十九章中的以下代码块,它可能需要 17 到 25 行代码来编写:

if (picked == spinner) {
    int spin = random.nextInt(4);
    if (spin == 0) {
        rotGameBoard.setByAngle(1080); rotSpinner.setByAngle(-1080); spinDeg += 1080;
    }
    if (spin == 1) {
        rotGameBoard.setByAngle(1170); rotSpinner.setByAngle(-1170); spinDeg += 1170;
    }
    if (spin == 2) {
        rotGameBoard.setByAngle(1260); rotSpinner.setByAngle(-1260); spinDeg += 1260;
    }
    if (spin == 3) {
        rotGameBoard.setByAngle(1350); rotSpinner.setByAngle(-1350); spinDeg += 1350;
    }
    rotGameBoard.play();
    rotSpinner.play();
    calculateQuadrantLanding();
}

下面的 Java 代码块是基于数组的,相当于前面的代码。它要短得多,只有 10 行 Java 9 代码,减少了 41%到 60%(取决于如何在 if (spin == n) { … })中编写代码):

if (picked == spinner) {
    int spin = random.nextInt(4);
    double[] angles = { 1080, 1170, 1260, 1350 };

    rotGameBoard.setByAngle(angles[spin]);
    rotSpinner.setByAngle(-angles[spin]);
    spinDeg += angles[spin];
    rotGameBoard.play();
    rotSpinner.play();
    calculateQuadrantLanding();
}

在计算出您的随机旋转值之后,这个代码片段声明了一个双精度值的四元素数组,它表示象限着陆角度。然后,我使用 spin 值(random.nextInt(4)输出四个随机象限值中的一个,范围从 0 到 3)来访问一个角度值(通过 angles[spin]),该角度值被传递给 setByAngle,并且也被添加到 spinDeg 变量中。

请注意,如果 spinDeg 是 int (32 位整数)类型,则必须在赋值前将 double angle 值转换为(int ),否则将面临 Java 编译器错误。在这种情况下,您可以用 Java 9 代码spinDeg += (int) angle[spin];替换spinDeg += angle[spin];来避免这个 Java 编译器错误。

如果您不想三次指定 angles[spin],也可以将该值存储在一个 angle 变量中,并使用这个 double angle 变量,如下面的 Java 代码所示:

if (picked == spinner) {
    int spin = random.nextInt(4);
    double[] angles = { 1080, 1170, 1260, 1350 };
    double angle = angles[spin];

    rotGameBoard.setByAngle(angle);
    rotSpinner.setByAngle(-angle);
    spinDeg += angle;
    rotGameBoard.play();
    rotSpinner.play();
    calculateQuadrantLanding();
}

正如您所看到的,有许多方法可以为这个游戏编写 Java 代码,这将减少使用的代码行,甚至可能稍微减少游戏使用的 CPU 的几个百分点,如本章的 NetBeans 9 性能分析部分所示。由于每个人都有自己的编码优化风格和方法,我将把 Java 9 代码优化留给您,并利用书中材料的(较长的)原型代码。这将让您更好地了解我在游戏设计和开发工作流程中对新媒体素材所做的工作,并将本书内容集中在使用 Java 9 及其强大的 JavaFX 9 API 进行专业游戏设计和开发上。

最后,让我们在本章中增加一个章节,看看我们还可以利用哪些 JavaFX 9 API 类来进一步扩展这个 i3D 游戏,就像你最终会做的那样。您可以从第三方导入程序包中导入 i3D 模型(不幸的是,这还不是 JavaFX 的“原生”部分,所以我在本书中坚持使用 JavaFX i3D APIs)并添加数字视频素材,只要您仔细优化它们。因为 JavaFX 9 模块(分发包)还没有完全完成(离 Java 9 发布还有一个月,或者更久)。一旦 Oracle 发布了该书,包含 JavaFX 9 模块(游戏分发包)的附录将作为该书可下载源代码的一部分提供。要下载这本书的源代码,导航到 www.apress.com/9781484209745 并点击下载源代码按钮。

未来扩展:添加数字视频和 3D 模型

通过使用第三方网站 InteractiveMesh.org 的 i3D 导入软件,以及添加使用 Black Magic Design 的 DaVinci Resolve 14 等创建的数字视频资源,您可以向您的 i3D 棋盘游戏添加更复杂的新媒体。你可以使用一些专业的东西来优化你的视频,比如 Sorenson Media 的 Squeeze Desktop Pro 11。这将为您提供更多使用 JavaFX 9 更高级的数字视频和 3D APIs 的经验。

我下一步要做的事情之一是为指令、信用、法律信息等提炼 2D 启动代码。既然一个游戏已经有了原型,那么重温一下闪屏图形可能也是一个好主意。请记住,Pro Java 9 游戏开发,尤其是 i3D 游戏,是一个改进的过程,因为组成 i2D 和 i3D 游戏素材的数百个新媒体组件通常经过改进,以使游戏符合游戏开发者 artisan 的愿景。

一旦你完成了游戏原型,你就可以像我们之前提到的那样进行代码优化,如果有必要的话,甚至可以为不同的特性或功能创建不同的类。我的技术编辑同意我的观点,这个游戏不需要额外的类,因为我试图使用 JavaFX API 中已经编码的类来创建一个 i3D 棋盘游戏。事实上,我正在导入(使用)44 个 Java 或 JavaFX 类来创建这个游戏,所以仅仅因为我有一个 JavaFXGame 主类将所有的东西绑在一起,实际上就有 45 个类创建了这个游戏。其中 44 个已经由 Sun Microsystems 创建、编码和优化,后来由 Oracle 在收购 Sun 后创建、编码和优化。我在本书中试图做的是展示如何创建一个专业的 Java 9 游戏,通过简单地优化使用 JavaFX API 类来利用这些公司在过去十年中的所有工作,并最大限度地减少开发人员创建一个 i3D 棋盘游戏的实际工作量。随着 Oracle 继续改进这些类,JavaFX 9 将继续成为一个更加强大和令人印象深刻的游戏引擎,理想情况下,iOS 和 Android 8 支持将继续发展和改进。

摘要

在最后的第二十四章中,我们讨论了各种素材(数字图像和数字音频)的优化,以及 Java 代码的优化。我们学习了 NetBeans Profiler,以及如何查看运行我们的游戏使用了多少系统内存。我们还查看了有多少百分比的 CPU 被用来处理我们的 Java 代码,以及垃圾收集何时将我们的纹理贴图加载到系统内存中。我们还研究了线程何时被用于处理内存位置、指令、循环、随机数生成以及类似的 Java 代码指令。

我们还研究了其他一些我们可以在游戏中改进并在未来添加到游戏中的东西,以便进一步利用 JavaFX 9 类。只是要确保使用 Profiler 来监视系统内存和 CPU 的使用情况。我使用我的一个“弱”(4GB,四核 AMD 3.11GHz 宏基)工作站,所以我在一台“次主流”计算机上测试代码,同时我有足够的能力和内存来流畅地运行 NetBeans 9、Java 9 和 JavaFX 9。这证明了 NetBeans 9、Java 9 和 JavaFX 的效率。

我希望您喜欢这二十几章,它们涵盖了新媒体素材开发以及 Java 9 和 JavaFX 9 游戏开发,重点是 JavaFX 9 API 的 i3D 部分,因为我开始的 Java 8 游戏开发标题侧重于 JavaFX API 的 i2D 部分。我在 Apress ( www.apress.com )也有几本新媒体素材(内容)创作书籍,涵盖数字图像合成、数字音频编辑、数字视频编辑、数字插图(SVG)矢量编辑、数字绘画、视觉特效(VFX)创作,使用 Fusion 8。所有这些书都使用免费的商业用专业开源软件包,如 GIMP、Inkscape、Audacity 和 Fusion。一旦 Black Magic Design 完成 DaVinci Resolve 14(一个非线性编辑套件),我会将它添加到我的开源内容制作套件中,我会在我为 JavaFX 9、Android 8 或 HTML 5.1 内容制作设置的每个工作站上安装该套件。对于新硬件,我正在看 AMD 的新锐龙 7 1700,它只使用 65W 的功率以 3.0GHz(超频的话是 3.7GHZ)运行 16 个 64 位线程;它拥有镭龙 7000 GPU,并在大多数主板上支持 64GB DDR 4-2400 MHz 内存(四个插槽)、USB 3.1、24 位音频、M2 SSD 卡、超高速硬盘访问等。装有 Windows 10 的全负荷系统售价不到 1000 美元。游戏编码快乐!