当前位置: 首页 > news >正文

软件网站开发公司名字山东省工程建设招标信息网站

软件网站开发公司名字,山东省工程建设招标信息网站,高仿服装网站建设,广州快速建站公司推荐协议#xff1a;CC BY-NC-SA 4.0 五、游戏设计介绍#xff1a;概念、多媒体和使用场景生成器 在本章中#xff0c;您将通过学习在 JavaFX 中使用场景图范例的最佳方式#xff0c;了解 JavaFX Scene Builder 工具和 FXML#xff0c;以及为什么(或为什么不)在某些类型的 Ja… 协议CC BY-NC-SA 4.0 五、游戏设计介绍概念、多媒体和使用场景生成器 在本章中您将通过学习在 JavaFX 中使用场景图范例的最佳方式了解 JavaFX Scene Builder 工具和 FXML以及为什么(或为什么不)在某些类型的 Java 游戏开发场景中使用这些工具来建立您对 JavaFX 多媒体引擎的了解。您还将研究基本的游戏设计优化概念、游戏类型以及适用于 Java 平台的游戏引擎包括物理引擎如 JBox2D 和 Dyn4J以及 3D 游戏引擎如 LWJGL 和 JMonkey。最后你将考虑新媒体的概念你将需要了解整合数字图像数字音频数字视频和动画到你的游戏制作管道。我们还将看看一些免费的开源多媒体制作工具它们是你在第一章中安装的现在可以用来制作 Java 8 游戏。 首先你将重温静态(固定)与动态(实时)的基本概念这在第三章(常量与变量)和第四章(脉冲)中有所涉及也是游戏优化的基本原则之一。这一点很重要因为您会希望您的游戏能够在用于玩游戏的所有不同平台和设备上流畅运行即使设备只使用单处理器(这在当今实际上很少见大多数设备都采用双核(双处理器)或四核(四处理器)CPU)。 接下来你将学习游戏设计的概念、技术和术语包括精灵、碰撞检测、物理模拟、背景板、动画、图层、关卡、逻辑和人工智能。你还将研究可以设计的不同类型的游戏以及它们之间的区别。 然后您将探索多媒体资产在当今视觉(和听觉)令人印象深刻的游戏中所扮演的角色。您将了解数字成像、数字视频、动画以及数字音频的原理因为您将在本书的课程中使用许多这些新媒体资产类型并且需要这些基础知识才能使用它们。 最后您将深入了解您在第二章中生成的引导 JavaFX 应用代码以及 Java。main()方法和 JavaFX。start()方法使用 Stage()构造函数方法创建 primaryStage Stage 对象并在其中使用 scene()构造函数方法创建一个名为 Scene 的场景对象。您将了解如何使用 Stage 类中的方法来设置场景、标题舞台和显示舞台以及如何创建和使用 StackPane 和 Button 类(对象)以及如何向按钮添加 EventHandler。 高级概念:静态与动态 我想从一个高层次的概念开始涉及到我将在本章中谈到的一切从您可以创建的游戏类型到游戏优化到 JavaFX 场景生成器和 JavaFX 场景图。不管你是否意识到在探索 Java 常量的概念时你已经在第三章回顾了这个概念Java 常量是固定的或静态的不会改变而 Java 变量是动态的会实时改变。类似地JavaFX 场景图中的 UI 设计可以是静态的(固定的和不可移动的)或动态的(动画的、可拖动的或可换肤的这意味着您可以更改 UI 外观以适应您的个人喜好)。 这些概念在游戏设计和开发中非常重要的原因是您设计用来运行或渲染游戏的游戏引擎必须不断检查其动态部分以查看它们是否发生了变化并需要响应(更新分数、移动精灵位置、播放动画帧、更改游戏角色的状态、计算碰撞检测、计算物理等等)。这种对每个帧更新的检查(以及随后的处理)(在 JavaFX 中称为脉冲参见第四章)以确保你所有的变量、位置、状态、动画、碰撞、物理等都符合你的 Java 游戏引擎逻辑真的可以加起来而且在某些时候做所有这些工作的处理器可能会过载这会降低它的速度 这种增强游戏动态性的所有实时、逐帧检查过载的结果是游戏运行的帧速率将会降低。没错像数字视频和动画一样Java 8 游戏也有帧率但是 Java 8 游戏帧率是基于你的编程逻辑的效率。你游戏的帧率越低游戏玩起来就越不流畅至少对于动态的、实时的游戏来说是这样比如街机游戏一款游戏玩起来有多流畅关系到玩家的用户体验有多流畅。 出于这个原因静态与动态的概念对于游戏设计的每个方面都非常重要并且使某些类型的游戏比其他类型的游戏更容易实现出色的用户体验。我将在本章后面讨论不同类型的游戏(参见“游戏类型:谜题、棋盘游戏、街机游戏、混合游戏”一节)但是正如你可能想象的棋盘游戏本质上更静态街机游戏更动态。也就是说有一些优化方法可以让游戏保持动态也就是说看起来好像有很多事情在进行而从处理的角度来看实际发生的事情是很容易管理的。这是游戏设计的众多技巧之一说到底这是关于优化的。 Android (Java)编程中最重要的静态与动态设计问题之一是使用 XML 的 UI 设计(静态设计)与使用 Java 的 UI 设计(动态设计)。Android 平台允许使用 XML 而不是 Java 来完成 UI 设计这样非程序员(设计者)可以为应用进行前端设计。JavaFX 允许使用 FXML 做完全相同的事情。要做到这一点你必须创建一个 FXML JavaFX 应用正如你在第二章中看到的那样(参见图 2-4右侧第三个选项“JavaFX FXML 应用”)。这样做会将 javafx.fxml 包和类添加到应用中让您设计 UI使用 fxml然后让您的 Java 编程逻辑“膨胀”它们以便设计由 JavaFX UI 对象组成。 值得注意的是使用 FXML 会给应用开发和编译过程增加另一层其中包含 FXML 标记及其翻译和处理。我将在本章后面演示如何做到这一点以防您的设计团队希望使用 FXML 而不是 Java 进行 UI 设计工作流程(参见“JavaFX Scene Builder:使用 FXML 进行 UI 设计”一节)。我这样做是因为我想涵盖 JavaFX 中的所有设计选项包括 FXML以确保这本书完整地涵盖了使用 Java 8 和 JavaFX 8.0 可以做些什么。然而归根结底这是一个 Java 8 编程的题目所以我在本书中的主要焦点将是使用 Java 8而不是 FXML。 在任何情况下我关于使用 XML(或 FXML)创建 UI 设计的观点是这种方法可以被视为静态的因为设计是预先使用 XML 创建的并且在编译时使用 Java“膨胀”。Java 膨胀方法使用设计者提供的 FXML 结构来创建场景图该场景图基于使用 FXML 创建的 UI 设计结构(层次结构)填充有 JavaFX UI(类)对象。我将在本章的后面向您概述这是如何工作的这样您就可以了解这是如何工作的(参见“JavaFX Scene Builder:使用 FXML 进行 UI 设计”一节)。 游戏优化:平衡静态元素与动态元素 游戏优化归结为平衡不需要实时处理的静态元素和需要持续处理的动态元素。过多的动态处理尤其是在不需要的时候会让你的游戏变得不稳定。这就是为什么游戏编程是一种艺术形式:它需要平衡以及伟大的人物故事线创造力幻觉预期准确性最后优化。 表 5-1 中列出了动态游戏中一些不同的游戏组件优化考虑事项。如您所见游戏的许多方面都可以进行优化使处理器的工作负载明显不那么“繁忙”如果这些主要的动态游戏处理区域中有一个因为处理器每帧的宝贵周期而“失控”这将极大地影响游戏的用户体验。我将在本章的下一节介绍游戏术语(精灵、碰撞检测、物理模拟等等)。 表 5-1。 Aspects of Game Play That Can Be Optimized to Minimize System Memory and Processor Cycle Usage 游戏方面基本优化原则精灵位置(移动)移动精灵尽可能多的像素以实现屏幕上的平滑移动。冲突检出仅在必要时检查屏幕上对象之间的碰撞(非常接近)。物理模拟尽量减少场景中需要进行物理计算的对象数量。精灵动画最大限度地减少需要循环的帧数以创建流畅的动画效果。背景动画最小化有动画的背景区域使整个背景看起来有动画但实际上没有。游戏逻辑尽可能高效地编程游戏逻辑(模拟或人工智能)。记分板更新仅在得分时更新记分牌并将得分更新最小化至每秒最多一次。用户界面设计使用静态 UI 设计以便脉冲事件不用于 UI 元素定位或 CSS3。 考虑到所有这些游戏编程领域使得游戏编程成为一项极其棘手的工作 值得注意的是这些方面中的一些共同作用为玩家创造了一个特定的幻觉。例如精灵动画将创建角色奔跑、跳跃或飞行的幻觉但如果不将该代码与精灵定位(运动)代码相结合将无法实现幻觉的真实性。为了微调一个错觉动画的速度(帧速率)和移动的距离(每帧像素)可能需要调整(我喜欢称之为微调)以获得最真实的结果。我们将在第一章 3 节中做这件事。 如果你可以移动游戏元素(主要玩家精灵目标精灵敌人精灵背景)更多的像素和更少的次数你将节省处理周期。花费处理时间的是移动的部分而不是距离(移动了多少像素)。类似地对于动画实现令人信服的动画所需的帧越少保存这些帧所需的内存就越少。请记住您正在优化内存使用以及处理周期。检测碰撞是游戏编程逻辑的主要部分重要的是不要检查游戏元素之间的冲突这些元素不是“在玩”(在屏幕上)也不是活动的并且彼此不靠近。 自然力(物理模拟)和游戏逻辑(如果没有很好地编码(优化)是处理器最密集的方面。这些都是我将在本书的后面部分涉及的主题当你更高级的时候(见第十六章和第十七章)。 游戏设计概念:精灵物理碰撞 让我们来看看构建游戏需要了解的各种游戏设计组件以及可以用来实现游戏的这些方面的 Java 8(或 JavaFX)包和类我喜欢称之为游戏组件。这些可以包括游戏元素本身(通常称为精灵)以及处理引擎您可以自己编写代码或者导入预先存在的 Java 代码库如物理模拟和碰撞检测。 精灵是游戏的基础定义你的主角用来伤害主角的投射物以及发射这些投射物的敌人。精灵是 2D 图形元素可以是静态的(固定的单个图像)或动态的(动画几个图像的无缝循环)。精灵将基于编程逻辑在屏幕上移动这决定了游戏如何运行。精灵需要与背景图像和其他游戏元素以及其他精灵合成因此用于创建精灵的图形需要支持透明背景。 在第四章中我向你介绍了阿尔法通道和透明度的概念。你需要用你的精灵来达到同样的最终效果来创造一个无缝的游戏视觉体验。游戏的下一个最重要的方面是碰撞检测因为如果你的精灵只是在屏幕上从彼此身边飞过并且当他们相互接触或“相交”时从来没有做过任何酷的事情那么你真的不会有一个很好的游戏一旦你添加了一个碰撞检测引擎(由交叉逻辑处理例程组成),你的游戏就可以确定任何两个精灵何时接触(边缘)或彼此重叠。碰撞检测将调用(触发)其他逻辑处理例程这些例程将确定当任何两个给定的精灵(如投射物和主要角色)相交时会发生什么。例如当投射物与主角相交时伤害点可能会增加生命力指数可能会降低或者死亡动画可能会开始。相比之下如果一件宝物与主角相交(被主角捡到)则可能会产生能量或能力点生命力指数可能会增加或者可能会启动“我找到了”的庆祝动画。正如你所看到的除了精灵(角色、投射物、宝物、敌人、障碍等等)本身游戏的碰撞检测是游戏的基本设计元素之一这就是为什么我在本书的早期就介绍了这个概念。 对你的游戏来说下一个重要的概念是真实世界的物理模拟。像重力这样的东西的加入摩擦弹跳拖加速度运动曲线如 JavaFX 插值器类提供的和高度精确的碰撞检测的基础上增加了额外的真实感。 最后添加到游戏中的最专有的属性或逻辑结构(Java 代码)是自定义游戏逻辑它使您的游戏在市场中真正独一无二。这个逻辑应该保存在它自己的 Java 类或方法中与物理模拟和碰撞检测代码分开。毕竟如果您学习了 OOP 概念并将它们应用到您的编程逻辑中Java 8 会使您的代码模块化结构良好 当你开始把所有这些游戏组件加在一起时它们开始让游戏变得更可信也更专业。一个伟大游戏的关键目标之一是暂停信念这意味着你的玩家完全相信前提、角色、目标和游戏。这是任何内容制作人无论是电影制作人、电视制作人、作家、歌曲作者、Java 8 游戏程序员还是应用开发人员都追求的目标。如今游戏的创收能力与任何其他内容分发类型一样如果不是更多的话。 接下来让我们看看可以创建的不同类型的游戏以及这些游戏在应用精灵、碰撞检测、物理模拟和游戏逻辑等核心游戏组件方面有何不同。 游戏类型:谜题、棋盘游戏、街机游戏、混合游戏 就像我在这一章中谈到的其他东西一样游戏本身可以通过使用静态和动态的分类方法来分类。静态游戏不受处理器限制因为它们本质上倾向于基于回合而不是基于手眼协调因此在某种意义上它们更容易流畅地工作只有游戏规则的编程逻辑和吸引人的图形需要被放置和调试。对于开发新类型的游戏类型来说也存在一个重要的机会这种游戏类型以前所未见的创造性的新方式使用静态和动态游戏玩法的混合组合。我自己正在研究其中的一些 因为这是一个 Java 8 编程的标题所以我将从这个角度来处理所有事情这恰好是将游戏分成不同类别(静态、动态、混合)的一个好方法所以让我们先讨论静态(固定图形)、回合制游戏。这些游戏包括棋类游戏、益智游戏、知识游戏、记忆游戏和策略游戏所有这些游戏的受欢迎程度和可销售性都不容小觑。 静态游戏的酷之处在于它们可以像动态游戏一样有趣而且处理开销明显更少因为它们不必达到 60FPS 的实时处理目标就可以流畅、专业地玩游戏。这是因为游戏的本质根本不是基于移动而是基于做出正确的战略移动但只有当轮到你这样做的时候。 在静态游戏中可能涉及一些形式的碰撞检测关于哪个游戏棋子已经移动到游戏棋盘或游戏面上的给定位置然而没有碰撞检测使处理器过载的危险因为游戏板的其余部分是静态的除了在特定玩家的回合中被有策略地移动的一个棋子。 策略游戏的处理逻辑更多的是基于策略逻辑的编程在给定正确的移动顺序的情况下允许玩家获得最终的胜利而动态游戏编程逻辑更多地关注游戏精灵之间发生的冲突。动态游戏的重点是分数躲避弹丸寻找宝藏完成水平目标杀死敌人。 具有大量相关规则的复杂策略游戏如国际象棋可能比动态游戏有更多的编程逻辑例程。然而因为代码的执行对时间不敏感所以无论平台和 CPU 有多强大最终的游戏都将是流畅的。当然游戏规则集逻辑必须完美无缺这种类型的游戏才能真正做到专业所以最终静态和动态游戏都很难编码尽管原因不同。 动态游戏可以被称为动作游戏或街机游戏并且包括显示屏上的大量运动。这些高度动态的游戏几乎总是涉及射击例如在第一人称射击游戏(例如DoomHalf-Life)以及第三人称射击游戏(生化危机侠盗猎车手)类型中或者偷东西或者躲避东西。还有就是障碍赛导航范式常见于平台游戏比如大金刚超级马里奥。 值得注意的是任何类型的游戏都可以使用 2D 或 3D 图形和资源甚至是 2D 和 3D 资源的组合来制作正如我在第四章中指出的这是 JavaFX 允许的。 有这么多流行的游戏类型总是有机会通过使用静态(策略)游戏类型和动态(动作)游戏类型的混合方法来创建一种全新的游戏类型。 游戏设计资产:新媒体内容概念 让你的游戏变得高度专业并让买家满意的最强大的工具之一是你在第一章中下载并安装的多媒体制作软件。在我继续讲下去之前我需要花一些时间向您提供关于 Java 8 中支持的四种主要新媒体资产类型的基础知识使用 JavaFX 8 多媒体引擎:数字图像用于精灵、背景图像和 2D 动画向量形状用于 2D 插图、碰撞检测、3D 对象、路径和曲线数字音频用于音效、旁白和背景音乐数字视频在游戏中用于动画背景循环(天空中飞翔的鸟飘动的云等等)曾经高度优化。如图 5-1 所示这四个主要流派或者区域都是通过 JavaFX 场景图安装的使用了我在第四章中描述的包。将使用的一些主要类是 ImageView、AudioClip、Media、MediaView、MediaPlayer、Line、Arc、Path、Circle、Rectangle、Box、Sphere、Cylinder、Shape3D、Mesh 和 MeshView。 图 5-1。 How new media assets are implemented, using Scene Graph through the JavaFX API in Java 8 via NetBeans 因为在 Java 8 游戏设计和编程管道中使用这些新媒体元素之前您需要有一定的技术基础所以我将从数字图像和矢量插图开始介绍这四个新媒体领域的基本概念。 数字成像概念:分辨率、色深、Alpha、图层 JavaFX(因此 Java8)支持大量流行的数字图像文件(数据)格式这给了游戏设计者极大的灵活性。其中一些已经存在很久了例如 CompuServe 的图形交换格式(GIF)和联合图像专家组(JPEG)格式。有些 JavaFX 图形文件格式更现代比如可移植网络图形(PNG 发音为“ping”)这是您将在游戏中使用的文件格式因为它可以产生最高的质量水平并支持图像合成。Java 支持的所有这些主流数字图像文件格式在 HTML5 浏览器中也受支持并且因为 Java 应用可以与 HTML 应用和网站一起使用这确实是一个非常合乎逻辑的协同作用 最古老的 CompuServe GIF 格式是无损数字图像文件格式。之所以称为无损压缩是因为它不会丢弃图像数据来获得更好的压缩结果。GIF 压缩算法不像 PNG 格式的压缩算法那样精细(强大), GIF 只支持索引颜色这是它获得压缩(较小文件大小)的方式。如果您的游戏图像资产已经以 GIF 格式创建您将能够毫无问题地在 Java 8 游戏应用中使用它们(除了效率较低的图像压缩算法和没有合成功能)。 Java 8 (JavaFX)支持的最流行的数字图像文件格式是 JPEG它使用“真彩色”色深而不是索引色深以及所谓的有损数字图像压缩其中压缩算法“丢弃”图像数据以便它可以实现更小的文件大小(图像数据将永远丢失除非您聪明地保存您的原始图像). 如果您在压缩后放大 JPEG 图像您将看到一个变色区域(效果),这在原始图像中是不存在的。图像中的一个或多个退化区域通常被称为压缩伪影。这只会发生在有损图像压缩中在 JPEG(和运动图像专家组[MPEG])压缩中很常见。 Tip 我建议您在 Java 8 游戏中使用 PNG 数字图像格式。这是一种专业的图像合成格式你的游戏本质上是一个实时精灵合成引擎所以你需要使用 PNG32 图像。 PNG 有两个真彩色文件版本:PNG24(不能用于图像合成)和 PNG32(带有用于定义透明度的 alpha 通道)。 我为你的游戏推荐 PNG因为它有一个不错的图像压缩算法并且是一种无损图像格式。这意味着 PNG 有很好的图像质量以及合理的数据压缩效率这将使你的游戏发行文件更小。PNG32 格式的真正力量在于它能够使用透明度和抗锯齿(通过其 alpha 通道)与其他游戏图像合成。 数字图像分辨率和纵横比:定义图像大小和形状 您可能知道数字图像是由 2D(二维)像素阵列组成的(“像素”代表图片[pix]元素[el])。图像的清晰度由其分辨率表示分辨率是图像宽度(或 W有时称为 x 轴)和高度(或 H有时称为 y 轴)维度中的像素数量。图像的像素越多分辨率越高。这类似于数码相机的工作原理因为图像捕捉设备(称为相机 CCD)的像素越多可以实现的图像质量就越高。 要计算图像像素的总数请将宽度像素乘以高度像素。例如一个宽视频图形阵列(VGA) 800 × 480 图像包含 384000 个像素正好是八分之三兆字节。这就是你如何找到你的图像的大小包括使用的千字节(或兆字节)和显示屏上的高度和宽度。 使用图像纵横比来指定数字图像资产的形状。纵横比是数字图像的宽高比定义了正方形(1:1 纵横比)或矩形(也称为宽屏)数字图像形状。具有 2:1(宽屏)宽高比的显示器例如 2160 × 1080 分辨率现在已经上市。 1:1 纵横比的显示器(或图像)总是完美的正方形2:2 或 3:3 纵横比的图像也是如此。例如你可能会在智能手表上看到这个长宽比。值得注意的是定义图像或屏幕形状的是这两个宽度和高度(x 和 y)变量之间的比率而不是实际的数字本身。 纵横比应该始终表示为冒号两边可以达到(减少)的最小数字对。如果你在高中时注意学习最小公分母那么长宽比对你来说很容易计算。我通常通过继续将冒号的每一边一分为二来计算长宽比。比如你拿 SXGA 1280×1024 分辨率来说1280×1024 的一半是 640 × 512640 × 512 的一半是 320×256320 × 256 的一半是 160 × 128再一半是 80 × 64再一半是 40 × 32再一半是 20×1620 × 16 的一半是 10 × 8其中的一半给出了 SXGA 的 5 × 4 纵横比这可以通过在两个数字之间使用冒号来表示例如纵横比为 5:4。 数字图像色彩理论和色彩深度:定义精确的图像像素颜色 每个数字图像像素的颜色值可以由红、绿和蓝(RGB)三种不同颜色的量来定义这三种颜色在每个像素中以不同的量存在。消费电子显示屏利用加色其中每个 RGB 颜色通道的光波长相加在一起产生 1680 万种不同的颜色值。加色用于液晶显示器(LCD)、发光二极管(LED)和有机发光二极管(有机发光二极管)显示器。它与印刷中使用的减色法相反。为了向您展示不同的结果在减色模式下将红色与绿色(油墨)混合将产生紫色而在加色模式下将红色与绿色(浅色)混合将产生鲜艳的黄色。加色可以提供比减色更宽的颜色范围。 为每个像素保存的每个红色、绿色和蓝色值有 256 个亮度级别。这允许您为每个红色、绿色和蓝色值设置 8 位值控制颜色亮度变化从最小值 0 (#00 或关闭全暗或黑色)到最大值 255 (#FF 或全开最大颜色贡献)。用于表示数字图像像素颜色的位数被称为图像的色深。 数字成像行业中使用的常见色深包括 8 位、16 位、24 位和 32 位。我将在这里概述这些以及它们的格式。最低色深存在于 8 位索引的彩色图像中。这些具有最多 256 个颜色值并使用 GIF 和 PNG8 图像格式来保存这种索引颜色类型的数据。 中等色深图像具有 16 位色深因此包含 65536 种颜色(计算为 256 × 256)。它受 TARGA (TGA)和标记图像文件格式(TIFF)数字图像格式支持。如果您想在 Java 8 游戏中使用 GIF、JPEG 和 PNG 之外的数字图像格式请导入第三方 ImageJ 库。 真彩色颜色深度图像将具有 24 位颜色深度因此将包含超过 1600 万种颜色。这被计算为 256 × 256 × 256产生 16777216 种颜色。支持 24 位颜色深度的文件格式包括 JPEG(或 JPG)、PNG、BMP、XCF、PSD、TGA、TIFF 和 WebP。JavaFX 支持其中的三种:JPG、PNG24 (24 位)和 PNG32 (32 位)。使用 24 位色深将为您提供最高的质量水平。这就是为什么我推荐你的 Java 游戏使用 PNG24 或 PNG32。接下来让我们看看如何通过 alpha 通道表示图像像素透明度值以及如何在 Java 8 游戏中使用这些值实时合成数字图像 数字图像合成:在图层中使用 Alpha 通道和透明度 合成是将多层数字影像无缝融合在一起的过程。正如你所想象的这对于游戏设计和开发来说是一个极其重要的概念。当您想要在显示器上创建一个看起来像是单个图像(或动画)的图像而实际上它是两个或多个合成图像层的无缝集合时合成非常有用。您想要设置图像或动画合成的一个主要原因是通过将它们放在不同的层上允许对这些图像中的各种元素进行编程控制。 要实现这一点您需要一个 alpha 通道透明度值该值可用于控制给定像素与其他层(在其上方和下方)上的另一个像素(在相同的 xy 图像位置)的混合量的精度。 像其他 RGB 通道一样alpha 通道有 256 个透明度级别。在 Java 编程中alpha 通道由# AARRGGBB 数据值的十六进制表示中的前两个槽来表示(我将在下一节中详细介绍)。Alpha 通道 ARGB 数据值使用八个数据槽(32 位)而不是 24 位图像中使用的六个数据槽(#RRGGBB)24 位图像实际上是一个没有 alpha 通道数据的 32 位图像。 因此24 位(PNG24)图像没有 alpha 通道不会用于合成除非它是合成层堆栈中的底部图像板。相比之下PNG32 图像将用作 PNG24(背景板)或 PNG32(较低的 z 顺序合成层)之上的合成层这将需要此 alpha 通道功能来显示(通过 alpha 通道透明度值)图像合成中的某些像素位置。 数字图像 alpha 通道和图像合成的概念是如何影响 Java 游戏设计的主要的优点是能够将游戏画面以及画面中的精灵、投射物和背景图形元素分解成多个组件层。这样做的原因是为了能够将 Java 8 编程逻辑(或 JavaFX 类)应用于单独的图形图像元素以控制游戏屏幕的某些部分否则如果它是一个单独的图像您将无法单独控制这些部分。 图像合成的另一部分称为混合模式也是专业图像合成功能的重要因素。JavaFX 混合模式通过使用 Blend 类和 javafx.scene.effect 子包中的 BlendMode 常量值来应用(参见第四章)。这个 JavaFX 混合效果包为 Java 游戏开发人员提供了许多与 Photoshop(和 GIMP)为数字图像制作人员提供的相同的图像合成模式。这将 Java 8(通过 JavaFX)变成了一个强大的图像合成引擎就像 Photoshop 一样混合算法可以在非常灵活的水平上进行控制使用自定义的 Java 8 代码。JavaFX 混合模式常量包括 ADD、SCREEN、OVERLAY、DARKEN、LIGHT、MULTIPLY、DIFFERENCE、EXCLUSION、SRC_ATOP、SRC_OVER、SOFT_LIGHT、HARD_LIGHT、COLOR_BURN 和 COLOR_DODGE。 用 Java 8 游戏代码表示颜色和 Alpha:使用十六进制记数法 现在您已经知道了什么是色深和 alpha 通道以及在任何给定的数字图像中使用四种不同的图像通道(alpha、红色、绿色和蓝色[ARGB])的组合来表示颜色和透明度重要的是理解作为程序员您应该如何在 Java 8 和 JavaFX 中表示这四种 ARGB 图像颜色和透明度通道值。 在 Java 编程语言中颜色和 alpha 不仅用于 2D 数字图像(通常称为位图图像)还用于 2D 插图(通常称为矢量图像)。颜色和透明度值也经常在许多不同的颜色设置选项中使用。正如您已经看到的 Stage 对象和 Scene 对象您可以为舞台、场景、布局容器(StackPane)或 UI 控件等设置背景色(或透明度值)。 在 Java 中(JavaFX 也是如此),不同级别的 ARGB 颜色强度值用十六进制表示。十六进制(或 hex)是基于原始的 16 进制计算机记数法。这在很久以前被用来表示 16 位数据值。与更常见的从 0 到 9 计数的 Base10 不同Base16 记数法从 0 到 F 计数其中 F 表示 15 的 Base10 值(0 到 15 产生 16 个数据值)。 Java 中的十六进制值总是以 0 和 x 开头就像这样:0xFFFFFF。这个十六进制颜色值表示颜色。白色常数不使用 alpha 通道。在这种 24 位十六进制表示中六个槽中的每一个都代表一个 16 进制值因此要获得每种 RGB 颜色所需的 256 个值需要两个槽即 16 × 16 256。因此要使用十六进制表示法表示 24 位图像您需要在井号后有六个槽来保存六个十六进制数据值(每个数据对表示 256 个级别的值)。如果您乘以 16 × 16 × 16 × 16 × 16 × 16您将获得 16777216 种颜色这在使用 24 位真彩色图像数据时是可能的。 十六进制数据槽以下列格式表示 RGB 值:0xRRGGBB。对于 Java 常量颜色。白色十六进制颜色数据值表示中的所有红色、绿色和蓝色通道都处于全亮度(最大颜色值)设置。如果你把所有这些颜色加在一起你会得到白光。 黄色表示红色和绿色通道打开蓝色通道关闭因此颜色是十六进制表示。因此黄色为 0xFFFF00其中红色和绿色通道槽完全打开(FF 或 255 Base10 数据值)蓝色通道槽完全关闭(00 或 0 值)。 ARGB 值的八个十六进制数据槽将使用以下格式保存数据:0xAARRGGBB。因此对于颜色。白色十六进制颜色数据值表示中的所有阿尔法、红色、绿色和蓝色通道将处于它们的最大亮度(或不透明度)并且阿尔法通道将完全不透明即不透明如 FF 值所表示的。因此颜色的十六进制值。白色常数将是 0xFFFFFFFF。 100%透明的 alpha 通道可以通过将 alpha 槽设置为 0 来表示正如您在创建一个无窗口的 Java 8 应用时所观察到的那样(参见第四章)。因此您可以使用 0x00000000 和 0x00FFFFFF 之间的任何值来表示透明图像像素值。重要的是要注意如果 alpha 通道值等于完全透明那么可以包含在其他六个(RGB)十六进制数据值槽中的 16777216 个颜色值将完全无关紧要因为该像素是透明的将被评估为不存在因此不会合成在最终图像或动画合成图像中。 数字图像遮罩:使用 Alpha 通道创建游戏精灵 alpha 通道在游戏设计中的主要应用之一是遮蔽图像或动画(一系列图像)的区域以便它可以在游戏图像合成场景中用作游戏精灵。蒙版是使用 alpha 通道透明度值从数字图像中剪切出主题的过程以便主题可以放置在它自己的虚拟层上。这是使用数字成像软件包完成的例如 GIMP。 数字图像-合成软件包如 Photoshop 和 GIMP包含用于遮罩和图像合成的工具。如果不进行有效的遮罩就无法进行有效的图像合成因此对于希望将图形元素(如图像精灵和精灵动画)集成到游戏设计中的游戏设计人员来说这是一个需要掌握的重要领域。数字图像蒙版技术已经存在很长时间了 遮罩可以自动为您完成使用专业的蓝屏(或绿屏)背景以及可以自动提取那些精确颜色值的计算机软件来创建遮罩遮罩被转换为 alpha 通道(透明度)信息(数据)。也可以使用数字图像软件通过算法选择工具之一结合各种锐化和模糊算法手动进行遮罩。 在本书的过程中你会学到很多关于这个工作过程的知识使用常见的开源软件包比如 GIMP。掩蔽可能是一个复杂的工作过程。这一章的目的是让你接触到基础知识这些知识是你阅读这本书的过程的基础。 遮罩过程中的一个关键考虑因素是在被遮罩的对象(主题)周围获得平滑、清晰的边缘。这是为了当你把一个被遮罩的物体(在这种情况下是一个游戏精灵)放到新的背景图像上时它看起来就像是在第一个地方被拍摄的一样。成功做到这一点的关键在于你的选择工作过程这需要以适当的方式使用数字图像软件选择工具如 GIMP 中的剪刀工具或 Photoshop 中的魔棒工具。选择正确的工作流程至关重要 例如如果您想要遮罩的对象周围有统一颜色的区域(可能您是对着蓝色屏幕拍摄的)您将使用具有适当阈值设置的魔棒工具来选择除对象之外的所有内容。然后反转选择这将为您提供包含该对象的选择集。通常正确的工作流程需要以相反的方式进行。其他选择工具包含复杂的算法可以查看像素之间的颜色变化。这些对于边缘检测非常有用您可以将其用于其他选择方法。 平滑数字图像合成:使用抗锯齿平滑图像边缘 抗锯齿是一种流行的数字图像合成技术在这种技术中数字图像中的两种相邻颜色沿着两种颜色区域的边界混合在一起。当图像缩小时这会欺骗观众的眼睛看到更平滑(更少锯齿)的边缘从而消除所谓的图像锯齿。抗锯齿通过使用平均颜色值(一个颜色范围是两种颜色结合在一起的中间部分)提供了令人印象深刻的结果沿着边缘只有几个需要平滑的彩色像素。 让我们看一个例子看看我在说什么。图 5-2 显示了一个图层上看起来非常清晰的红色圆圈覆盖了一个背景图层上的黄色填充颜色。我放大了红色圆圈的边缘然后制作了另一个截图放在缩小的圆圈的右边。此屏幕截图显示了一系列抗锯齿颜色值(黄色-橙色、橙色到橙色、红色-橙色),正好位于红色和黄色的边缘即圆形与背景的交界处。 图 5-2。 A red circle composited on a yellow background (left) and a zoomed-in view (right) showing antialiasing 值得注意的是JavaFX 引擎将使用 Java2D 软件渲染器或使用 Prism 引擎渲染的硬件(可以使用 OpenGL 或 DirectX ),针对所有背景颜色和背景图像对 2D 形状和 3D 对象进行抗锯齿处理。您仍将负责正确合成即使用每个图像的 alpha 通道为多层图像提供抗锯齿。 数字图像优化:使用压缩、索引颜色和抖动 影响数字图像压缩的因素有很多您可以使用一些基本的技术以较小的数据占用量获得较高质量的结果。这是优化数字图像的主要目标为您的应用(在本例中为游戏)获得尽可能小的数据占用空间同时获得最高质量的视觉效果。让我们从对数据占用空间影响最大的几个方面入手研究一下对于任何给定的数字图像来说这些方面是如何对数据占用空间优化产生影响的。有趣的是它们的重要性顺序与我迄今为止提出的数字成像概念的顺序相似。 影响最终数字图像资产文件大小的最关键因素是我所称的数据足迹即数字图像的像素数量或分辨率。这是合乎逻辑的因为需要存储每个像素以及包含在它们的三个(24 位)或四个(32 位)通道中的颜色和 alpha 值。在保持图像清晰的同时分辨率越小生成的文件也就越小。 对于 24 位 RBG 图像原始(或未压缩)图像的大小计算为宽×高× 3对于 32 位 ARGB 图像为宽×高× 4。例如未压缩的 24 位真彩色 VGA 图像将具有 640 × 480 × 3相当于 921600B 的原始(raw)未压缩数字图像数据。要确定此原始 VGA 图像中的千字节数您需要进行如下划分:921600 ÷ 1024这是一千字节中的字节数在 truecolor VGA 图像中会有 900KB 的数据。 通过优化数字影像分辨率来优化原始(未压缩)图像大小非常重要。这是因为一旦图像从游戏应用文件解压缩到系统内存中这就是它将要占用的内存量因为图像将使用 24 位(RGB)或 32 位(ARGB)表示法逐个像素地存储在内存中。这也是我游戏开发用 PNG24 和 PNG32而不是索引色(GIF 或 PNG8)的原因之一如果操作系统要将颜色转换到 24 位色彩空间那么出于质量原因您应该使用 24 位色彩空间并处理(接受)稍大的应用文件大小。 图像颜色深度是压缩图像的数据足迹的第二个最重要的因素因为图像中的像素数乘以 1 (8 位)、2 (16 位)、3 (24 位)或 4 (32 位)颜色数据通道。这种小文件大小是 8 位索引彩色图像仍然被广泛使用的原因尤其是 GIF 图像格式。 如果用于构成图像的颜色变化不太大索引色图像可以模拟真彩色图像。索引彩色影像仅使用 8 位数据(256 种颜色)来定义图像像素颜色使用多达 256 种最佳选择颜色的调色板而不是 3 个 RGB 颜色通道或 4 个 ARGB 颜色通道每个通道包含 256 种颜色级别。同样需要注意的是一旦您使用 GIF 或 PNG8 编解码器将 24 位图像压缩为 8 位图像您就只能使用原始的 16777216 种颜色中的(最多)256 种。这就是为什么我提倡使用 PNG24 或 PNG32 图像而不是 GIF 或 PNG1 (2 色)、PNG2 (4 色)、PNG4 (16 色)或 PNG8 (256 色)图像JavaFX 也支持这些图像。 根据任何给定的 24 位源图像中使用的颜色数量使用 256 种颜色来表示最初包含 16777216 种颜色的图像可能会导致一种称为条带的效果。这是由于(通过压缩)得到的 256 色(或更少)调色板中相邻颜色之间的转换不是渐变的因此看起来不是平滑的颜色渐变。索引的彩色图像有一个视觉校正条带的选项称为抖动。 抖动是一种算法过程它沿着图像中任何相邻颜色之间的边缘制作点图案以欺骗眼睛看到第三种颜色。抖动会给你一个最大的颜色感知数量(65536256 × 256)但只有当这 256 种颜色中的每一种都与其他 256 种颜色中的每一种相邻时才会出现这种情况。尽管如此您仍然可以看到创建额外颜色的潜力并且您会对索引颜色格式在某些压缩场景中(对于某些图像)可以达到的效果感到惊讶。 让我们拍摄一张真彩色图像如图 5-3 所示并将其保存为 PNG5 索引彩色图像格式向您展示这种抖动效果。需要注意的是虽然 PNG5 在 Android 和 HTML5 中受支持但在 JavaFX 中不受支持所以如果您自己做这个练习请选择 2 色、4 色、16 色或 256 色选项该图展示了奥迪 3D 图像中驾驶员侧后挡泥板上的抖动效果因为它包含灰色渐变。 图 5-3。 A truecolor PNG24 image created with Autodesk 3ds Max, which you are going to compress as PNG5 有趣的是在 8 位索引彩色图像中使用小于 256 色的最大值是允许的。这通常是为了进一步减少影像的数据足迹。例如仅使用 32 种颜色就可以获得良好效果的图像实际上是一个 5 位图像从技术上来说它被称为 PNG5尽管这种格式本身通常被称为用于索引颜色使用级别的 PNG8。 我已经设置了这张索引色 PNG 图像如图 5-4 所示使用 5 位颜色(32 色或 PNG5)来清晰地说明这种抖动效果。正如您在图像预览区域中看到的那样在图的左侧抖动算法在相邻的颜色之间制作点图案以创建额外的颜色。 另外请注意您可以设置使用抖动的百分比。我经常选择 0%或 100%的设置但是您可以在这两个极端值之间的任何地方微调抖动效果。您也可以在抖动算法之间进行选择因为正如您可能已经猜到的那样抖动效果是使用抖动算法创建的抖动算法是索引文件格式(在本例中为 PNG8)压缩例程的一部分。 我使用扩散抖动这给了不规则形状的梯度一个平滑的效果就像在汽车挡泥板上看到的那样。您也可以使用更随机的噪波选项或者不太随机的模式选项。扩散选项通常给出最好的结果这就是为什么我在使用索引色时选择它(这并不常见)。 正如你所想象的抖动给图像增加了更难压缩的数据模式。这是因为对于压缩算法来说图像中的平滑区域(如梯度)比尖锐过渡(边缘)或随机像素模式(如相机 CCD 的抖动或“噪声”)更容易压缩。 因此应用抖动选项总是会增加几个百分点的数据占用空间。请务必检查应用和不应用抖动(在“导出”对话框中选择)的结果文件大小以查看它是否值得提供更好的视觉效果。请注意索引彩色 PNG 图像也有一个透明度选项(复选框),但 PNG8 图像中使用的 alpha 通道只有 1 位(开/关),而不是 PNG32 中的 8 位。 图 5-4。 Setting dithering to the diffusion algorithm and 32 colors (5 bit), with 100 percent dithering for PNG5 output 您还可以通过添加 alpha 通道来定义合成的透明度从而增加图像的数据足迹。这是因为通过添加一个 alpha 通道你将在被压缩的图像中添加另一个 8 位颜色通道(或者透明通道)。如果您需要 alpha 通道来定义图像的透明度以支持未来的合成要求例如将图像用作游戏精灵那么除了包含 alpha 通道数据之外没有太多选择。 如果您的 alpha 通道包含全零(或使用全黑填充颜色)这将定义您的图像为完全透明或者包含全 FF 值(或使用全白填充颜色)这将定义您的图像为完全不透明您将基本上(实际上)定义一个不包含任何有用的 alpha 数据值的 alpha。因此需要移除透明图像并且需要将不透明图像定义为 PNG24 而不是 PNG32。 最后大多数用于遮罩数字图像 RGB 层中的对象的 alpha 通道应该能够很好地压缩。这是因为 alpha 通道主要是白色(不透明)和黑色(透明)的区域在这两种颜色之间的边缘有一些中灰度值来反走样蒙版(见图 5-2 )。这些灰色区域包含 alpha 通道中的抗锯齿值并将在图像的 RGB 层中的对象与任何背景颜色或可能在它后面使用的背景图像之间提供视觉上平滑的边缘过渡。 其原因是在 alpha 通道图像蒙版中8 位透明度渐变(从白到黑)定义了透明度级别这可以被认为是每像素混合(不透明度)强度。因此蒙版(包含在 alpha 通道中)中每个对象的边缘上的中灰度值将基本上用于平均对象边缘和任何目标背景的颜色无论它可能包含什么颜色(或图像)值。这为任何可能使用的目标背景(包括动画背景)提供了实时抗锯齿。 数字视频和动画:帧、速率、循环、方向 有趣的是我刚刚谈到的所有关于数字图像的概念同样适用于数字视频和动画因为这两种格式都使用数字图像作为其内容的基础。数字视频和动画通过使用帧将数字成像扩展到第四维(时间)。这两种格式由有序的帧序列组成随时间快速显示。 术语“帧”来自电影工业在电影工业中即使在今天电影帧也以每秒 24 帧(24FPS)的速度通过电影放映机这产生了运动的幻觉。因为数字视频和动画都是由包含数字图像的帧的集合组成的所以当涉及到内存数据占用优化工作过程(对于动画资产)以及数字视频文件大小数据占用优化工作过程时以每秒帧数表示的帧速率的概念也是非常重要的。如前所述在 JavaFX 中动画的这个属性存储在动画对象速率变量中(见第四章)。 关于动画对象或数字视频资源中的帧的优化概念与关于图像中的像素(数字图像的分辨率)的优化概念非常相似:使用的越少越好这是因为动画或视频中的帧数会使每一帧所使用的系统内存和文件大小数据占用空间都成倍增加。在数码视频中不仅每帧(图像)的分辨率帧速率(在“压缩设置”对话框中指定)也会影响文件大小。在本章的前面您已经了解到如果您将图像中的像素数乘以其颜色通道数您将获得图像的原始数据足迹。对于动画和数字视频您现在将再次将该数字乘以需要用于创建运动错觉的帧数。 因此如果您的游戏有一个动画 VGA (RGB)背景板(请记住每帧为 900KB ),它使用五帧来创建运动的幻觉那么您正在使用 900KB × 5 或 4500KB (4.5MB)的系统内存来保存该动画。当然这对于一个背景来说占用了太多的内存这也是为什么你将使用静态背景和精灵覆盖来在不到一兆字节的空间内达到完全相同的效果。数字视频的计算有点不同因为它有数百或数千帧。对于数字视频您可以将原始图像数据大小乘以数字视频设置为回放的每秒帧数(帧速率)(此帧速率值在压缩过程中指定)然后将结果乘以视频文件中包含的内容持续时间的总秒数。 继续 VGA 示例您知道 24 位 VGA 图像有 900KB。这使得将这带到下一个级别的计算变得容易。数字视频传统上以 30FPS 运行因此 1 秒钟的标准清晰度原始(未压缩)数字视频将是 30 个图像帧每个图像帧为 900KB总数据量为 27000KB您可以看到为什么 MPEG-4 H.264 AVC 等视频压缩文件格式非常重要它可以压缩数字视频所产生的大量原始数据。JavaFX 媒体包使用最令人印象深刻的视频压缩编解码器之一(“codec”代表 code-decode)它在 HTML5 和 Android 中也受支持即前面提到的 MPEG-4 H.264 AVC(高级视频编解码器)。这对于开发人员资产优化非常方便因为一个数字视频资产可以跨 JavaFX、HTML5 和 Android 应用使用。万一你想在你的游戏背景中使用数字视频(我不推荐)接下来我将讲述数字视频压缩和优化的基础知识。 数字视频压缩概念:比特率、数据流、标清、高清、UHD 让我们从商业视频中使用的主要或标准分辨率开始。这些也是常见的设备屏幕分辨率可能是因为如果屏幕像素分辨率与屏幕上全屏播放的视频像素分辨率匹配将会出现零缩放这可能会导致缩放伪像。在高清晰度出现之前视频是标准清晰度(SD)使用 480 像素的垂直分辨率。VGA 是标清分辨率720 × 480 可以称为宽标清分辨率。高清(HD)视频有两种分辨率1280 × 720我称之为伪高清1920×1080业界称之为真高清。这两种高清分辨率都具有 16:9 的宽高比用于电视和独立电视、智能手机、平板电脑、电子书阅读器和游戏控制台。现在还有一种超高清(UHD)分辨率具有 4096 × 2160 像素。 视频流是一个比分辨率更复杂的概念因为它涉及到在大范围内播放视频数据例如 Java 8 游戏应用和远程视频数据服务器之间的视频数据远程视频数据服务器将保存您潜在的大量数字视频资产。此外运行 Java 游戏应用的设备将与远程数据服务器实时通信在视频播放时接收视频数据包(之所以称为流是因为视频从视频服务器通过互联网流入硬件设备)。MPEG-4 H.264 AVC 格式编解码器(编码器-解码器对)支持视频流。 你需要理解的最后一个概念是比特率。比特率是视频压缩过程中使用的关键设置因为比特率代表您的目标带宽或每秒钟能够容纳一定数量的比特流的数据管道大小。比特率设置还应该考虑任何给定的支持 Java 的设备中存在的 CPU 处理能力使您的数字视频的数据优化更具挑战性。幸运的是如今大多数设备都配备了双核或四核 CPU 一旦比特通过数据管道它们也需要被处理并显示在设备屏幕上。因此数字视频资产的比特率不仅要针对带宽进行优化还要考虑到 CPU 处理能力的变化。一些单核 CPU 可能无法在不丢帧的情况下解码高分辨率、高比特率的数字视频资产因此如果您打算将较旧或较便宜的消费电子设备作为目标请确保优化低比特率视频资产。 数字视频数据足迹优化:使用编解码器及其设置 如前所述您的数字视频资产将被压缩使用称为编解码器的软件工具。视频编解码器有两个“方面”:一方面编码视频数据流另一方面解码视频数据流。视频解码器将是使用它的操作系统、平台(JavaFX)或浏览器的一部分。解码器主要针对速度进行优化因为回放的平滑度是一个关键问题而编码器则针对减少其生成的数字视频资产的数据占用量进行了优化。因此编码过程可能需要很长时间这取决于工作站包含多少个处理核心。大多数数字视频内容制作工作站应该支持八个处理器内核比如我的 64 位 AMD 八核工作站。 编解码器(编码器端)类似于插件因为它们可以安装到不同的数字视频编辑软件包中使它们能够编码不同的数字视频资源文件格式。因为 Java 和 JavaFX 8 支持 MPEG-4 H.264 AVC 格式所以您需要确保您使用的数字视频软件包之一支持使用这种数字视频文件格式对数字视频数据进行编码。不止一家软件制造商生产 MPEG-4 编码软件因此在编码速度和文件大小方面会有不同的 MPEG-4 AVC 编解码器产生不同的(更好或更差的)结果。如果你想制作专业的数字视频我强烈推荐你使用的专业解决方案是 Sorenson Squeeze Pro。 还有一个名为 EditShare LightWorks 12 的开源解决方案计划到 2014 年原生支持输出到 MPEG4 编解码器。优化(设置压缩设置)数字视频数据文件大小时有许多变量会直接影响数字视频数据的占用空间。我将按照对视频文件大小的影响从最重要到最不重要的顺序来讨论这些参数以便您知道调整哪些参数来获得您想要的结果。 与数字图像压缩一样视频每帧的分辨率或像素数是开始优化过程的最佳位置。如果您的用户使用 800 × 480 或 1280 × 720 的智能手机、电子阅读器或平板电脑那么您不需要使用真正的高清 1920 × 1080 分辨率来获得数字视频资产的良好视觉效果。有了超高密度(小点距)显示器你可以将 1280 的视频放大 33 %,看起来相当不错。这种情况的例外可能是高清或 UHD(通常称为 4K 独立电视)游戏的目标是独立电视对于这些巨大的 55 至 75 英寸(屏幕)场景您可能希望使用行业标准的真正高清 1920 × 1080 分辨率。 假设数字视频本身的实际秒数无法缩短下一个优化级别将是每秒视频使用的帧数(或 FPS)。如前所述这就是所谓的帧速率与其设置视频标准 30FPS 帧速率不如考虑使用电影标准 24FPS 帧速率甚至多媒体标准 20FPS 帧速率。您甚至可以使用 15FPS 的帧速率这是视频标准的一半具体取决于内容中的移动量(和速度)。请注意15FPS 的数据量是 30FPS 的一半(编码数据减少了 100%)。对于某些视频内容这将与 30FPS 内容一样回放(看起来)。对此进行测试的唯一方法是在编码过程中尝试帧速率设置。 获得较小数据占用空间的下一个最佳设置是您为编解码器设置的比特率。比特率等于所应用的压缩量因此设定了数字视频数据的质量水平。值得注意的是您可以简单地使用 30FPS、1920 分辨率的高清视频并指定低比特率上限。如果您这样做结果将不会像您尝试使用较低的帧速率和分辨率以及较高(质量)的比特率设置那样好看。对此没有固定的规则因为每个数字视频资源都包含完全唯一的数据(从编解码器的角度来看)。 获得较小数据占用空间的第二个最有效的设置是编解码器用来对数字视频进行采样的关键帧的数量。视频编解码器通过查看每一帧然后在接下来的几帧中仅对变化或偏移进行编码来应用压缩从而编解码器算法不必对视频数据流中的每一帧进行编码。这就是为什么会说话的头部视频比每个像素都在每帧上移动的视频(如使用快速摄像机平移或快速视野[FOV]缩放的视频)编码更好。 关键帧是编解码器中的一项设置它强制编解码器不时对您的视频数据资源进行新的采样。关键帧通常有一个自动设置它允许编解码器决定采样多少关键帧还有一个手动设置它允许您指定关键帧采样的频率通常是每秒特定次数或整个视频的持续时间(总帧数)。 大多数编解码器通常具有质量或清晰度设置(滑块)用于控制压缩前应用到视频帧的模糊量。如果您不熟悉这个技巧对图像或视频应用轻微的模糊(这通常是不可取的)可以实现更好的压缩因为图像中的尖锐过渡(边缘)比柔和过渡更难编码需要更多的数据来再现。也就是说我会将质量(或清晰度)滑块保持在 80%到 100%之间并尝试使用我在这里讨论的其他变量之一来减少您的数据占用量例如降低分辨率、帧速率或比特率。 最终对于任何给定的数字视频数据资产您都需要微调许多不同的变量以实现最佳的数据占用空间优化。重要的是要记住对于数字视频编解码器来说每个视频资源看起来都是不同的(数学上)。由于这个原因不可能有可以被开发来实现任何给定压缩结果的标准设置。也就是说随着时间的推移调整各种设置的经验最终会让您感受到为了获得所需的最终结果您必须根据不同的压缩参数来更改各种设置。 数字音频概念:振幅、频率、样本 你们这些音响发烧友知道声音是通过在空气中发送声波脉冲而产生的。数字音频很复杂这种复杂性的一部分来自于需要将使用扬声器纸盆创建的模拟音频技术与数字音频编解码器连接起来。模拟扬声器通过脉冲产生声波。我们的耳朵以完全相反的方式接收模拟音频捕捉和接收那些空气脉冲或不同波长的振动然后将它们转换回我们大脑可以处理的数据。这就是我们“听到”声波的方式然后我们的大脑将不同的音频声波频率解释为不同的音符或音调。 声波产生各种音调这取决于声波的频率。宽的或不常见的(长的)波产生低音而更频繁的(短的)波长产生高音。有趣的是不同频率的光会产生不同的颜色所以模拟声音(音频)和模拟光(颜色)有着密切的相关性。你很快就会看到数字图像(和视频)之间还有许多其他相似之处这些相似之处也将贯穿到你的数字新媒体内容制作中。 声波的音量将由其振幅或波的高度(或大小)决定。因此如果你在 2D 观察声波的频率等于声波在 x 轴上的间距振幅等于声波在 y 轴上的高度。 声波可以独特地成形允许它们“搭载”各种声音效果。一种“纯”或基线类型的声波称为正弦波(您在高中三角学中学过使用正弦、余弦和正切数学函数)。熟悉音频合成的人都知道其他类型的声波也用于声音设计例如锯齿波它看起来像锯子的边缘(因此得名)以及脉冲波它仅使用直角进行整形产生即时的开和关声音转换为音频脉冲(或突发)。 甚至在声音设计中使用随机波形(如噪声)来获得尖锐的声音效果。通过使用最近获得的数据足迹优化知识您可能已经确定声波(以及一般的新媒体数据)中出现的“混乱”或噪声越多编解码器就越难压缩。因此由于数据中的混乱更复杂的声波将导致更大的数字音频文件。 将模拟音频转换为数字音频数据:采样、精度、高清音频 将模拟音频(声波)转换为数字音频数据的过程称为采样。如果你在音乐行业工作你可能听说过一种叫做采样器的键盘(甚至是架装式设备)。采样是将模拟音频波分割成片段的过程以便您可以使用数码音频格式将波形存储为数码音频数据。这就把无限精确的模拟声波变成了离散的数字数据也就是说变成了 0 和 1。使用的 0 和 1 越多无限精确(原始)模拟声波的再现就越精确。 采样的音频声波的每个数字段称为样本因为它在精确的时间点对声波进行采样。您想要的采样精度(分辨率)将决定用于再现模拟声波的 0 和 1 的数量因此采样精度由用于定义每个波切片高度的数据量决定。与数字成像一样这种精度被称为分辨率或者更准确地说(没有双关语)样本分辨率。采样分辨率通常用 8 位、12 位、16 位、24 位或 32 位分辨率来定义。游戏大多利用 8 位分辨率来实现爆炸等效果清晰度在这些效果中并不重要12 位分辨率用于清晰的口语对话和更重要的音频元素背景音乐可能是 16 位分辨率。 在数字成像和数字视频中分辨率由像素数来量化而在数字音频中则由用于定义每个模拟音频样本的数据位数来量化。同样与数字成像一样像素越多质量越好而数字音频的样本分辨率越高声音再现越好。因此更高的采样分辨率使用更多的数据来再现给定的声波样本将产生更高质量的音频回放代价是更大的数据足迹。这就是 16 位音频(通常称为 CD 音质音频)听起来比 8 位音频更好的原因。根据所涉及的音频12 位音频可能是一个很好的折衷方案。 在数字音频领域有一种新型的音频样本在消费电子行业被称为高清音频。HD 数字音频广播电台使用 24 位样本分辨率因此每个音频样本或声波片段包含 16777216 位样本分辨率。一些较新的硬件设备现在支持高清音频如你看到的智能手机广告中的“高清音频”这意味着它们有 24 位音频硬件。如今笔记本电脑(包括 PC)以及游戏机和 ITV 也标配了 24 位音频播放硬件。 需要注意的是对于 Java 8 游戏来说HD 音频可能不是必需的除非您的游戏是面向音乐的并且使用了高质量的音乐在这种情况下您可以通过 WAVE 文件格式使用 HD 音频样本。 另一个考虑因素是数字音频采样频率(也称为采样速率)它衡量在 1 秒的采样时间帧内以特定采样分辨率采样的数量。就数字图像编辑而言采样频率类似于数字图像中包含的颜色数量。您可能很熟悉“CD 音质音频”这个术语它被定义为使用 16 位采样分辨率和 44.1kHz 采样速率(采集 44100 个样本每个样本具有 16 位采样分辨率即 65536 位音频数据)。您可以通过将采样比特率乘以采样频率再乘以音频片段中的秒数来确定音频文件中的原始数据量。显然这可能是一个巨大的数字音频编解码器在优化数据方面非常出色数据占用空间非常小音质损失非常小。 因此数字图像和数字视频中存在的完全相同的权衡也发生在数字音频中:包含的数据越多结果质量越高但总是以更大的数据足迹为代价。在视觉媒体中数据覆盖区的大小是使用色深、像素来定义的在数字视频和动画的情况下还使用帧来定义。在听觉媒体中它是通过采样分辨率结合采样率来定义的。数字音频行业目前最常见的采样率包括 8kHz、22kHz、32kHz、44.1kHz、48kHz、96KHz、192kHz甚至 384kHz。 较低的采样率如 8kHz、11kHz 和 22kHz是您将在游戏中使用的采样率因为经过精心优化这些采样率可以产生高质量的音效和街机音乐。这些速率对于采样任何基于语音的数字音频也是最佳的例如电影对白或电子书旁白轨道。较高的音频采样速率(如 44.1kHz)更适合音乐需要高动态范围(高保真)的声音效果(如隆隆雷声)可以使用 48kHz。更高的采样率将允许音频再现展示电影院(THX)的声音质量但这不是大多数游戏所要求的。 数字音频流:专属音频与流音频 与数字视频数据一样数字音频数据既可以包含在应用分发文件(对于 Java是. JAR 文件)中也可以使用远程数据服务器进行流式传输。与数字视频类似流式数字音频数据的优势在于它可以减少应用文件的数据占用量。缺点是可靠性。许多相同的概念同样适用于音频和视频。流式音频将节省数据空间因为您不必在中包含所有繁重的新媒体数字音频数据。JAR 文件。所以如果你计划编写一个自动点唱机应用你可能想考虑流式传输你的数字音频数据否则请尝试优化您的数字音频数据以便您可以将它们(受控)包含在中。JAR 文件。这样当应用的用户需要时数据总是可用的 流式数字音频的缺点是如果用户的连接(或音频数据服务器)中断您的数字音频文件可能并不总是呈现给最终用户使用您的游戏应用播放和收听数字音频数据的可靠性和可用性是流传输和捕获之间权衡的另一个关键因素。这同样适用于数字视频资产。 同样与数字视频一样数字音频流的一个主要概念是数字音频数据的比特率。正如您在上一节中了解到的比特率是在压缩过程中定义的。需要支持较低比特率带宽的数字音频文件将对音频数据进行更多的压缩这将导致较低的质量。这些将在更多的设备上更平滑地传输(回放),因为更少的比特可以更容易地快速传输和处理。 JavaFX 中的数字音频:支持的数字音频编解码器和数据格式 JavaFX 中的数字音频编解码器比数字视频编解码器多得多因为只有一种视频编解码器即 MPEG-4 H.264 AVC。Android 音频支持包括. MP3 (MPEG-3)文件Windows WAVE(脉码调制[PCM]音频)。WAV 文件、. MP4(或. M4A) MPEG-4 AAC(高级音频编码)音频和 Apple 的 AIFF (PCM)文件格式。JavaFX(以及 Java 8)支持的最常见的格式是流行的. MP3 数字音频文件格式。由于音乐下载网站如 Napster 或 Soundcloud你们大多数人都熟悉 MP3 数字音频文件我们大多数人收集这种格式的歌曲用于 MP3 播放器和基于 CD-ROM 或 DVD-ROM 的音乐收藏。MP3 数字音频文件格式很受欢迎因为它具有相当好的压缩比和质量并得到广泛支持。 在 Java 8 应用中MP3 是一种可以接受的格式只要使用最佳的编码工作流程尽可能获得最高的质量水平。值得注意的是像 JPEG(用于图像)一样MP3 是一种有损音频文件格式其中一些音频数据(以及质量)在压缩过程中被丢弃并且无法恢复。 JavaFX 有两种无损音频压缩编解码器AIFF 和 WAVE。你们中的许多人对这些都很熟悉因为它们分别是苹果 Macintosh 和微软 Windows 操作系统使用的原始音频格式。这些文件使用 PCM 音频这是无损的(在这种情况下因为没有应用任何压缩).“PCM”如上所述代表“脉冲编码调制”指的是它所保存的数据格式。 PCM 音频通常用于 CD-ROM 内容以及电话应用。这是因为 PCM WAVE audio 是一种未压缩的数字音频格式没有对数据流应用 CPU 密集型压缩算法。因此解码(CPU 数据处理)对于电话设备或 CD 播放器来说不是问题。 因此当您开始将数字音频资产压缩成各种文件格式时您可以使用 PCM 作为基准文件格式。它允许您查看 PCM (WAVE)与 MP3 和 MP4 音频压缩结果之间的差异以了解您的 JAR 文件获得了多少数据占用优化更重要的是你还可以看到你的采样分辨率和采样频率优化将如何影响游戏音频效果所使用的系统内存。即使您使用 MP3 或 MP4 格式在音频资产可以与 AudioClip 类一起使用并在 Java 8 游戏中用作声音效果之前仍然必须将其解压缩到内存中。 因为 WAVE 或 AIFF 文件不会有任何质量损失(因为也不需要解压缩)PCM 数据可以直接从 JAR 文件放入系统内存这使得 PCM 音频非常适合持续时间短(0.1 到 1 秒)的游戏音效并且可以高度优化使用 8 位和 12 位采样分辨率以及 8kHz、22kHz 或 32kHz 采样频率。最终对于任何给定的数字音频数据要找出 JavaFX 支持的哪种音频格式具有最佳的数字音频压缩结果唯一真正的方法是使用您知道受支持且高效的主要编解码器对您的数字音频进行编码。稍后我将概述这一工作过程当您向游戏添加音频时您将使用相同的源音频样本观察不同格式之间的相对数据足迹结果(参见第一章 5)。然后您将听取音频播放质量这样您就可以做出关于质量和文件大小之间最佳平衡的最终决定。这是开发 JavaFX 数字音频资产以在 Java 8 游戏中使用的工作流程。 JavaFX 还支持流行的 MPEG-4 AAC 编解码器。这些数字音频数据可以包含在 MPEG4 容器(. mp4、. m4a、. m4v)或文件扩展名中并且可以使用任何操作系统回放。值得注意的是JavaFX 不包含 MPEG-4 解码器而是支持所谓的多媒体容器这意味着 JavaFX 使用操作系统的 MPEG-4 解码器。 出于这个原因并且因为在线听力研究已经得出结论MP3 格式比 MP4 具有更好的质量(对于音乐)您将通过 Media 和 MediaPlayer 类使用 MP3 格式来获得更长形式的音频(游戏背景音乐循环)。您将通过 JavaFX 慷慨提供的 audio clip digital audio sequencing engine(class)使用 PCM WAVE audio 格式的短格式(1 秒或更短)音频(游戏音效如枪声、铃声、叫喊声、咕哝声、笑声、欢呼声和其他此类数字音频资产)。 数字音频优化:从 CD 质量的音频开始然后反向工作 优化您的数字音频资产以便在市场上最广泛的硬件设备上播放将比优化您的数字视频或数字图像(以及动画)更容易。这是因为目标屏幕分辨率和显示器宽高比之间的差异比硬件设备之间的数字音频播放硬件支持类型之间的差异大得多(具有 24 位高清音频播放硬件兼容性的新硬件可能除外)。所有硬件都可以很好地播放数字音频资产因此音频优化是一个“一个音频资产影响所有设备”的场景而对于视觉(视频、图像、动画)部分您可以看到大到 4096 × 2160 像素(4K iTV 电视机)和小到 320 × 320 像素(翻盖手机和智能手表)的显示屏。 重要的是要记住用户的耳朵不能感知数字音频的质量差异而用户的眼睛可以感知数字图像、2D 动画和数字视频的质量差异。一般来说对于 Java 游戏音频的支持在所有硬件设备上有三个主要的数字音频支持“最佳点”。 通过使用 8kHZ 或 22kHz 的采样速率以及 8 位或 12 位的采样分辨率较低质量的音频(如短旁白轨道、人物感叹词和短时声音效果)可以获得非常高的质量。中等质量的音频如长旁白轨道、持续时间较长的声音效果和循环背景(环境)音频通过使用 22kHz 或 32kHz 采样速率以及 12 位或 16 位采样分辨率可以达到非常高的质量水平。 高质量的音频资产如音乐应进行优化以接近 CD 质量的音频并将使用 32kHz 或 44.1kHz 的采样速率以及 16 位数据采样分辨率。对于处于音频频谱超高端的高清质量音频您可以使用 48kHz 采样速率和 24 位数字音频数据采样分辨率。还有一种未命名的“中间某处”高端音频规格使用 48kHz 采样率以及 16 位数据采样分辨率这恰好是杜比 THX 过去在电影院使用的高端音频体验技术(过去)。 最终它归结为数字音频数据足迹优化工作流程中出现的质量-文件大小平衡结果这可能是惊人的。因此在所有这些硬件设备上优化数字音频资产的初始工作流程是创建 44.1kHz 或 48kHz 的基准 16 位资产然后使用 JavaFX 支持的不同格式对其进行优化(压缩)。一旦工作流程完成您就可以看到哪些数字音频资产提供了最小的数据占用空间以及最高质量的数字音频回放。之后您可以将 44.1KHz 或 48kHz 数据降低到 32kHz 并保存下来首先使用 16 位分辨率然后使用 12 位分辨率。接下来重新打开原始的 48kHz 数据下采样至 22kHz 采样频率并使用 16 位和 12 位分辨率导出该数据依此类推。稍后当您将数字音频资产添加到 Java 8 游戏中时您将执行此工作流程因此您将看到整个流程(参见第一章 5)。 接下来我们来看看 JavaFX Scene Builder以及它是如何使用 FXML 来让设计人员可视化地设计 JavaFX 应用的。在本书的课程中我不会使用 Scene Builder 或 FXML(只是 Java 8 代码和 JavaFX 类),所以请注意 JavaFX 场景生成器:使用 FXML 进行 UI 设计 JavaFX Scene Builder 是一个可视化设计工具可生成 FXML (JavaFX 标记语言)UI 场景图结构以定义 JavaFX 应用的前端设计。然后可以在 Java 8 中“膨胀”这个 FXML UI 定义以创建应用的 JavaFX 场景图、节点、组和 SubScene 对象其中填充了定义 UI 设计的 javafx.scene.control 包类(对象)。Oracle 提供 Scene Builder 可视化开发工具和 FXML 的目的是允许非程序员(表面上是 UI 设计师)为他们的 Java 8 应用设计前端 UI以便 Java 程序员可以专注于后端功能应用任务处理逻辑。 因为 FXML 和 Scene Builder 针对 UI 设计进行了优化(排列控件如按钮、文本输入字段、单选按钮、复选框等)所以在本书的整个过程中我不打算使用 Scene Builder 和 FXML。不过我将在本章中介绍它以便您知道如何在其他 JavaFX 应用中使用它。我的理由是除了最初的游戏闪屏它包含一些 UI 按钮对象显示游戏说明列出贡献者跟踪高分保存当前游戏状态并开始玩游戏UI 设计不会是本书的主要焦点。 要使用 FXML在使用 Scene Builder 可视化 UI 设计工具后不久您必须创建一种特殊的 FXML 应用正如您在第二章(见图 2-4)中创建 JavaFX 游戏时所学。创建 FXML 应用会导入 javafx.fxml 包和类。这允许 Java 8 代码膨胀由 UI 设计者创建的 FXML 结构以便程序员可以使用它们将 Java 逻辑附加到各种 UI 控件。Android 操作系统也是这样做的使用基本的 XML但是在 Android 中这种方法不是可选的这是做事方式的一部分。在 JavaFX 8 中(如图 2-4 所示)它只是一个选项。如果你想进一步研究基于 XML 的 Android UI 设计可以看看我的书 Pro Android UI (Apress2014)。 为您编写 FXML UI 设计构造的 Scene Builder 可视化布局工具是一个所见即所得的拖放界面设计工具。设计者所要做的就是将任何 JavaFX UI 控件从包含 javafx.scene.control 包中每个控件类(对象)的 UI 控制面板拖放到编辑屏幕上(参见第四章)。这个场景生成器集成到 NetBeans 8.0 中以便于访问和与 JavaFX 集成以防程序员也需要使用它来快速为他们的客户端设计 UI 原型。对于不想在 NetBeans IDE 中工作的设计人员还有一个独立版本的场景构建器工具版本 2.0。 您可以在 FXML 编辑和预览之间实时切换查看 UI 设计和布局的变化而无需编译 Java 应用。您还可以将所有 CSS 样式实时应用于场景构建器工具和 FXML 结构并查看这些代码更改的结果同样无需任何 Java 编译此外您可以使用第三方 JAR 文件或 FXML 定义将自定义 UI 控件添加到 UI 控制面板库中。 场景构建器工具包 API 是开源的。这使您可以自定义 Scene Builder 的 UI 面板和控件的集成允许您将 Scene Builder 集成到其他 ide 中如 Eclipse 或 IntelliJ IDEA。GUI 组件(控件)库中最近添加了一个富文本 TextFlow 容器提供了富文本编辑功能。有了这些新功能您可以构建多节点、富文本结构将其他 UI 元素或新的媒体元素类型与 TextFlow 元素集成在一起。 对于 3D“发烧友”来说Scene Builder 可视化设计编辑器和 FXML 也完全支持 3D。可以使用场景构建器工具加载甚至保存 3D 对象并且可以使用检查器面板实时编辑(和查看)对象的所有属性。现在还不能使用 Scene Builder 从头开始创建 3D 对象您也不能在此时分配或编辑复杂的网格或材质属性但我相信这些功能会随着计划添加到 JavaFX 8 中的高级 3D OpenGL ES 功能一起出现。 接下来让我们深入了解一下 FXML 标记语言。之后您将检查一个实际的 FXML UI 定义结构并且您将确切地看到当前 JavaFX 应用的 UI 设计是如何使用 FXML UI 定义构造的。正如您将看到的FXML 使 UI 设计变得更加容易 FXML 定义:XML UI 定义结构的剖析 FXML 结构基于 JavaFX 类(对象)及其属性FXML 标记和参数结构(您可以轻松创建)允许您使用标记语言更轻松地“模拟”前端 UI。FXML 结构使您可以更容易地构建场景图层次FXML 标记及其参数(您将在下一节中看到)与 JavaFX API 类 1:1 匹配。 一旦创建了 UI 设计Java 程序员就可以使用 javafx.fxml 类和方法根据 Java 对象将 UI 布局容器和 UI 控件排列扩展到 javafx 场景和场景图结构中。然后可以在应用 Java 代码中使用 UI 设计。如前所述FXML 对于设计包含大量按钮、表单、复选框等的复杂、静态(固定)UI 设计布局非常有用。 Hello World UI FXML 定义:使用 FXML 复制当前的 UI 设计 在 FXML 结构中定义的第一件事是 FXML 处理指令。每个处理指令都以小于号、问号序列() and ends with the reversal of that sequence (question mark, greater-than sign [?)开始。第一个处理指令声明了 XML 语言的用法、版本(1.0)和要使用的文本字符集语言编码格式(在本例中是 UTF-8[通用字符集转换格式8 位])。因为它是 8 位的所以在这个国际字符集中有 256 个字符它被设计成跨越许多基于日耳曼字符的语言即使用 A 到 Z 字母表(包括重音字符)的语言。 XML 语言和字符集声明之后是处理指令。它们导入 Java 语言、实用程序和 javafx.scene 包以及 javafx.scene.layout 和 javafx.scene.control 包这些包用于设计 UI 布局和布局包含的 UI 控件。 例如您在当前应用中使用的 StackPane UI 布局容器位于 javafx.scene.layout 包中而 button UI 控件元素位于 javafx.scene.control 包中。因为 FXML UI 布局容器是这个结构中的父元素所以它首先出现或者在您将要创建的嵌套 FXML UI 定义结构之外。 在中您将嵌套 StackPane 类(object)的子类使用子类标签(XML 标签使用箭头括号编码)。嵌套在这些子标签中的是 UI 控件元素(在本例中是一个按钮控件所以您将使用按钮标签)。请注意在箭头括号内使用了类(对象)的专有名称来创建 FXML 标签因此这是非常符合逻辑的应该很容易学习并融入到您的 UI 设计工作过程中: ?xmlversion1.0 encodingUTF-8 ? import java.lang.* ?? import java.util.* ?? import javafx.scene.* ?? import javafx.scene.layout.* ?? import javafx.scene.control.* ?StackPaneidroot prefHeight250 prefWidth300 childrenButtonidbtn textSay Hello World layoutX125 layoutY116 /children/StackPane接下来让我们看一下标签和参数语法以便您总是知道如何构造 FXML UI 布局和控件定义文件。一个没有子元素的 UI 元素比如之前的 UI 控件将使用一个简写的标签开闭语法使用类名开始标签和正斜杠大于号结束标签(/ 像这样: Buttonidbtn textSay Hello World layoutX125 layoutY116 请注意配置标记的参数(等同于对象的属性(或创建对象的类中的变量))嵌套在标记本身中并使用变量名和等于运算符以及引号中指定的数据值如前面的代码所示。 嵌套了对象的 FXML 标签将使用这个不同的类名开始标签。在这个标签内(后)列出的嵌套标签之后是一个 /ClassName 结束标签。这允许标记语法指定(成为)其内部的子标记的容器正如您在这里的示例中看到的其中开始和结束 FXML 标记根据它们的嵌套(内部)层次结构进行排序: StackPaneidroot prefHeight250 prefWidth300 childrenButtonidbtn textSay Hello World layoutX125 layoutY116 /children/StackPane如您所见可以将参数放在父标记的开始标记中方法是将它们放在开始标记的 ClassName 部分和大于号之间。如果需要的话这就是为任何参数配置父标记的方式就像指定 StackPane 大小和名称(在 FXML 中称为 id)一样。 摘要 在第五章中您仔细了解了一些更重要的游戏设计和新媒体概念您将在 Java 8 游戏开发工作流程中使用这些概念这样您就拥有了创建游戏所必需的基础知识。您还学习了 JavaFX Scene Builder 和 FXML只是为了掌握这些概念因为我将使用 Java 8 代码和 JavaFX 类来完成本书中的所有工作以满足我的 Android 书籍读者的要求(“我们如何仅使用 Java 代码来完成这些工作我们不想使用 XML 来创建我们的应用”是我这几天不断听到的口头禅)。 首先您研究了静态与动态的关键概念以及这对游戏设计和游戏优化的重要性因为如果游戏优化不是游戏设计、开发和优化过程中的一个持续考虑因素过多的动态会使旧的单核甚至双核 CPU 过载。 接下来您探索了游戏设计(和开发)的一些关键组件如精灵、碰撞检测、物理模拟、背景动画、UI 设计、评分引擎和游戏逻辑。您看了一下这些如何应用于静态游戏或没有连续运动的游戏如策略游戏、棋盘游戏、谜题、知识游戏、记忆游戏和动态游戏以及使用连续运动的游戏如平台游戏、街机游戏、第一人称射击游戏、第三人称射击游戏、驾驶游戏等。 您对新媒体资产类型以及数字图像、动画、数字视频和数字音频的概念和术语进行了高水平的技术概述。你学习了像素决议以及纵横比如何定义图像、动画或视频的形状以及颜色深度和 alpha 通道透明度以及如何使用十六进制表示法定义这些内容。然后您研究了时间的第四维度了解了帧、帧速率和比特率并查看了数字音频、采样频率和采样分辨率。最后您学习了 JavaFX 场景图和 FXML 如何工作以及如何在您当前的游戏中使用它们。 在下一章中您将研究 JavaFX 场景图并为 Java 8 游戏应用创建基础设施包括闪屏(游戏的主屏幕和主 UI)。 六、游戏设计的基础:JavaFX 场景图和InvinciBagel游戏基础设施 在这一章中你将开始从用户界面(UI)和用户体验的角度以及从“引擎盖下”游戏引擎、精灵引擎、碰撞引擎和物理引擎的角度来设计你的 InvinciBagel 游戏的基础设施。你将记住优化因为你必须通过这本书的其余部分工作所以你不会得到一个如此广泛或复杂的场景图脉冲系统不能有效地更新一切。这意味着保持主要的游戏 UI 屏幕(场景或子场景节点)最少(三个或四个)确保 3D 和媒体引擎(数字音频和数字视频)使用自己的线程并检查驱动游戏的功能性“引擎”都是逻辑编码的使用它们自己的类和适当的 Java 8 编程约定、结构、变量、常数和修饰符(见第三章)。 首先您将了解游戏将为用户提供的顶级、正面 UI 屏幕设计包括用户在启动应用时看到的 InvinciBagel“品牌”闪屏。该屏幕上有按钮控件用于访问其他信息屏幕您可能希望尽量减少这些信息屏幕的数量因为它们可能是场景节点(主游戏屏幕)或 ImageView 节点(其他信息屏幕)。这些游戏支持屏幕将包含用户为了有效地玩游戏而需要知道的东西例如游戏说明和高分屏幕。您还将包括一个法律免责声明屏幕(以使您的法律部门满意)其中还将包含为创建游戏引擎和游戏资产而工作的各种程序员和新媒体工匠的学分。 您将开发的 InvinciBagel 游戏设计基础的下一个级别是 InvinciBagel 游戏的底层或背面(游戏用户看不到)游戏引擎组件 Java 类设计方面。其中包括一个游戏引擎它将使用一个 Java FX . animation . animation timer 类来控制对游戏界面屏幕的游戏更新一个精灵引擎它将使用 Java 列表数组和集合来管理游戏精灵碰撞引擎当两个精灵之间发生碰撞时它将进行检测并做出响应一个物理引擎它将把力和类似的物理模拟应用到游戏中以便精灵加速并逼真地对重力做出反应和一个演员引擎它将管理 InvinciBagel 游戏中每个演员的特征。 最后您将修改现有的 InvinciBagel.java 应用子类为游戏播放屏幕和其他三个功能信息屏幕实现一个新的闪屏和按钮为 InvinciBagel 游戏应用提供这些顶级 UI 特性和基础 UI 屏幕基础结构。这将最终让你进入一些 Java 和 JavaFX 编程因为你为游戏创造了基础。 游戏设计基础:主要功能屏幕 你要设计的第一件事就是你的游戏用户将与之交互的顶级或者最高级别的用户界面。这些都将通过包含在主要 InvinciBagel 船级社代码中的 InvinciBagel splash(品牌)屏幕进行访问。如前所述此 Java 代码将扩展 javafx.application.Application 类并将启动应用显示其闪屏以及查看指令、玩游戏、查看高分或查看游戏的法律免责声明和游戏创作者(程序员、艺术家、作家、作曲家、声音设计师等)的选项。在图 6-1 中可以看到一个显示游戏的高级图表从顶部的功能 UI 屏幕开始向下发展到操作系统级别。 图 6-1。 Primary game functional screens and how they are implemented through Java and JavaFX API, using a JVM 这将需要向 StackPane 布局容器父节点添加另外三个按钮节点并为闪屏背景图像容器添加一个 ImageView 节点。必须首先将 ImageView 节点添加到 StackPane 中使其成为 StackPane 中的第一个子节点(z-order 0)因为这个 ImageView 包含我称之为闪屏 UI 设计的背景板。因为它在背景中所以图像需要在按钮 UI 控件元素的后面这些元素的 z 顺序值为 1 到 4。 这意味着您将在应用的场景图中使用六个节点对象(一个父节点和五个子节点)来创建 InvinciBagel 闪屏说明和信用屏幕将使用另一个 ImageView 节点因此您已经有了六个节点高分屏幕可能会使用另外两个(ImageView 和 TableView)节点因此在您考虑为游戏屏幕添加节点之前您可能会在场景图中有八个以上的节点来创建游戏支持基础结构当然这是您希望游戏获得最佳性能的地方。 如果你考虑一下这真的没有那么糟糕因为这些屏幕都是静态的不需要更新也就是说它们包含的(UI)元素是固定的不需要使用 脉冲系统更新所以你应该仍然有 JavaFX 脉冲引擎的 99%的能力来处理 InvinciBagel 游戏 GamePlayLoop 引擎。事实上随着 Java 8 和 JavaFX 8 继续提高其平台 API 和类的效率您实际上可能会有更多的处理能力用于游戏(精灵运动、碰撞、物理、动画等)因此将处于良好的状态。 GamePlayLoop 将使用 javafx.animation 包及其 AnimationTimer 类为您处理游戏代码。你总是需要知道你要求脉冲引擎处理多少场景图节点对象因为如果这个数字变得太大它将开始影响游戏的性能。 Java 类结构设计:游戏引擎支持 接下来让我们来看看 InvinciBagel 游戏的功能结构将如何被整合也就是说在你的 Java 8 游戏编程代码中这就是本书的全部内容正面 UI 屏幕的外观和底层编程逻辑的外观之间确实没有关联因为大多数编程代码都将用于在游戏屏幕上创建游戏体验。游戏说明和法律和信用屏幕将只是图像(ImageView)并将在图像中嵌入文本(导致使用更少的场景图形节点)或在 ImageView 的顶部合成一个透明的 TextView。高分屏幕将需要一点编程逻辑这将在游戏开发的最后完成因为游戏逻辑必须被创建和播放才能在第一时间产生高分(参见第十七章) 图 6-2 显示了完成 InvinciBagel 游戏所需的主要功能区组件。该图显示了位于层次顶部的 InvinciBagel 应用子类它创建了顶层和场景以及包含在它下面(或里面)的场景图。 图 6-2。 Primary game functional classes and how they are implemented under the Scene and Scene Graph levels 在 InvinciBagel 场景对象(实际上是在 InvinciBagel 应用子类中创建的)下面是功能类的更广泛的结构设计您将需要在本书的剩余部分中编写代码。图中所示的引擎(类)将创建您的游戏功能如游戏引擎(游戏循环)、逻辑引擎(游戏播放逻辑)、精灵引擎(演员管理)、演员引擎(演员属性)、得分引擎(游戏得分逻辑)、动画引擎(动画逻辑)、碰撞检测和物理模拟。您将必须创建所有这些 Java 类函数来完全实现 InvinciBagel 游戏的全面的 2D 游戏引擎。 我称之为 GamePlayLoop 类的游戏引擎是创建 AnimationTimer 对象的主要类该对象调用连续处理游戏循环的脉冲事件。如您所知这个循环将调用。handle()方法该方法又包含方法调用这些方法调用最终将访问您将创建的用于管理 actors (sprite 引擎)的其他类在屏幕上移动它们(演员引擎)检测任何碰撞(碰撞引擎)检测到碰撞后应用游戏逻辑(逻辑引擎)并应用物理的力量来为游戏提供逼真的效果如重力和加速度(物理引擎)。 从第七章开始你将构建这些不同的引擎它们将被用来创造游戏体验。我将根据每个引擎和它们需要做什么来对章节主题进行分层这样从学习和编码的角度来看一切都是有逻辑的。 JavaFX 场景图设计:最小化 UI 节点 最小化场景图的技巧是使用尽可能少的节点来实现一个完整的设计如图 6-3 所示这可以通过一个 StackPane 根节点、一个 VBox 分支(父)节点和七个叶(子)节点(一个 TableView、两个 ImageView 和四个按钮 UI 控件)来实现。当你接下来开始编码场景图时(最后)您将仅使用 14 个对象仅导入 12 个类来使您在上一节中设计的 InvinciBagel 游戏的整个顶层成为现实。TableView 将覆盖 ImageView 组合其中包含设计的信息屏幕层。这个 TableView 对象将在游戏设计的后期添加。ImageView 背板将包含 InvinciBagel 艺术品ImageView 合成层将包含三个不同的透明图像根据 ActionEvents(按钮控件的点击)无缝覆盖背板图像VBox 父 UI 布局容器将包含四个按钮控件。您还将创建一个 Insets 对象来保存填充值以微调按钮库对齐方式。 图 6-3。 Primary splash screen Scene Graph node hierarchy, the objects it contains, and the assets it references 因为按钮对象不能单独定位所以我不得不使用 HBox 类以及 Insets 类和 Pos 类来包含和定位按钮控件。在这一章中我将介绍你将用于这个高级设计的类这样你就可以对你将要添加到 InvinciBagel 类中来创建这个顶级 UI 设计的每个类有一个大概的了解。 我为匹配四个不同按钮所需的四个不同屏幕优化场景图形使用的方法是使用一个 ImageView 作为背板来包含 InvinciBagel 闪屏插图然后再使用一个 ImageView 来包含使用透明度(alpha 通道)的不同合成图像(覆盖)。这样您可以仅使用两个 ImageView 场景图形节点对象来模拟四个不同的屏幕。 最后TableView 场景图节点将包含高分表的表结构。这将通过分数引擎创建你将在最后创建在你完成整个游戏设计和编程。现在你将离开高分和玩游戏按钮代码未实现。 场景图代码:优化你当前的 InvinciBagel 类 我知道您渴望在 InvinciBagel 类代码上工作所以让我们清理、组织和优化现有的 Java 代码来实现这个顶级 UI 屏幕设计。首先将对象声明和命名 Java 代码放在 InvinciBagel 类的顶部。这更有组织性你的类中的所有方法将能够看到和引用这些对象而不需要使用 Java 修饰关键字。正如你在图 6-4 中看到的这些包括你现有的场景 scene 对象根 StackPane 对象和 btn 按钮对象(我把它重命名为 gameButton)。我添加了另外三个按钮对象分别名为 helpButton、scoreButton 和 legalButton它们都是使用一行 Java 代码声明和命名的还添加了两个 ImageView 对象分别名为 splashScreenbackplate 和 splashScreenTextArea。您还需要创建四个 Image 对象来保存数字图像资产这些资产将显示在 ImageView 节点中我已经用一个复合 Java 语句将它们命名为 splashScreen、instructionLayer、legalLayer 和 scoresLayer 并声明了它们。最后声明并命名 buttonContainer VBox 对象和 buttonContainerPadding Insets 对象。只要您使用 AltEnter 快捷键选择正确的 javafx 包和类路径NetBeans 就会为您编写导入语句。进口显示在图的顶部。 图 6-4。 Declaring and naming the 14 objects that will make up your Scene Graph hierarchy at the top of the class 在本章中您将详细了解所有这些 JavaFX 类以便了解它们的用途以及它们能为您的 Java 应用做些什么。 场景图设计:简化现有的。start()方法 现在您可以优化。start()方法这样它只有一二十行代码。首先将场景图节点创建 Java 例程模块化到它们自己的 createSplashScreenNodes()方法中该方法将在。start()方法如图 6-5 所示。在此方法中创建所有节点后创建一个 addNodesToStackPane()方法将节点添加到 StackPane 根节点然后让三行 primaryStage 代码配置和管理 Stage 对象最后是 ActionEvent 处理代码例程这些例程将按钮 UI 控件“连接”到单击按钮时要执行的 Java 代码。 图 6-5。 Organize the .start() method with the createSplashScreenNodes() and addNodesToStackPane() methods 如您所见在复制了。对于每个按钮对象当您折叠 EventHandler 例程时您有九行代码:一行用于创建节点一行用于向根添加节点三行用于 Stage 对象设置四行用于 UI 按钮事件处理。如果你考虑到你在游戏结构顶层增加的功能数量(游戏、指令、法律、积分、记分牌)这是非常紧凑的。 按照正确的顺序做事很重要因为一些 Java 代码是基于其他 Java 代码的。因此对象声明排在第一位然后在。start()方法创建(实例化)节点。一旦声明、命名和实例化(创建)了它们就可以将它们添加到 StackPane 根节点然后配置(使用。setTitle()方法)并将场景 scene 对象添加到 primaryStage Stage 对象中。setScene()方法。在你的对象进入系统内存之后你才能够处理 ActionEvent 处理例程这些例程被附加到你的四个按钮 UI 控件上。接下来让我们确保将在 createSplashScreenNodes()方法中引用的数字图像资产位于正确的 NetBeans 文件夹中。 场景图形资源:在项目中安装 ImageView 的图像资源 要在 Java 代码中引用 JAR 文件中的数字图像资产必须在文件名前插入一个正斜杠。但是在引用文件之前您必须将这些图像文件从图书存储库中复制到计算机/计算机名/用户/用户/my documents/netbeans projects/InvinciBagel/src 文件夹中如图 6-6 的左侧(和顶部)所示。您还可以看到这些数字图像资产是如何合成的因为 invincibagelsplash PNG24 的背景板上有一个位置可以覆盖其他三个 PNG32 图像。复合 ImageView 资产中看到的白色区域实际上是透明的现在你准备好了 图 6-6。 Windows 7 Explorer file management utility, showing a PNG24 splash screen and three PNG32 overlays JavaFX UI 类:HBox、Pos、Insets 和 ImageView 让我们从编码中休息一下深入了解一些新的类您将使用它们来完成您的顶级游戏应用 UI 设计。其中包括 Pos 类(定位)Insets 类(填充)HBox 类(UI 布局容器)图像类(数字图像容器)ImageView 类(数字图像显示)以及 TableView 类(表格数据显示)您将在这里学习该类但在游戏完全完成后您将在游戏开发的后续代码中实现该类。您将按照从最简单(Pos)到最复杂(TableView)的顺序检查这些然后编写。createSplashScreenNodes()和。addNodesToStackPane()方法它们使用这些新的类(对象)。 JavaFX Pos 类:通用屏幕位置常量 Pos 类是一个 Enum 类代表“枚举”这个类包含一个常量列表这些常量被转换成整数值以在代码中使用。常量值(在本例中是定位常量如 TOP、CENTER 和 BASELINE)使程序员更容易在代码中使用这些值。 Pos 类的 Java 类扩展层次结构从 java.lang.Object masterclass 开始逐步发展到 java.lang.Enum 类最后以 javafx.geometry.Pos 类结束。如图 6-4 所示(l. 6)Pos 位于 JavaFX geometry 包中并使用以下子类层次结构: java.lang.Object java.lang.EnumPosjavafx.geometry. Pos Pos 类有一组常量用于提供通用的水平和垂直定位和对齐(见表 6-1 )。正如您将在下一节中看到的您将不得不使用 Insets 类和对象来获得您想要的像素精确定位。您将使用 BOTTOM_LEFT 常量将按钮控件组定位在初始屏幕的左下角。 表 6-1。 Pos Class Enum Constants That Can Be Used for Positioning and Alignment in JavaFX 位置常数定位结果(对象)基线 _ 中心在基线上垂直地在中心水平地基线 _ 左侧在基线上垂直地在左边水平地基线 _ 右侧在基线上垂直地在右边水平地底部中心在底部垂直地在中心水平地左下角在底部垂直地在左边水平地右下在底部垂直地在右边水平地中心在中心垂直和水平中央 _ 左侧在中心垂直地在左边水平地中间 _ 右侧在中心垂直地在右边水平地顶部 _ 中间在顶部垂直地在中心水平地左上角在顶部垂直地在左边水平地右上方在顶部垂直地在右边水平地 因为 Pos 类提供了一般化的定位所以它应该与 Insets 类结合使用以实现像素级的精确定位。接下来让我们看看 Insets 类因为它也在 javafx.geometry 包中。 JavaFX Insets 类:为用户界面提供填充值 insets 类是一个公共类它直接扩展了 java.lang.Object masterclass这意味着 Insets 类是从头开始编写的以提供矩形区域内的 Insets 或 offsets。想象一个相框在里面放一块垫子或者在相框外面和里面的图片之间放一个漂亮的边框。这就是 insets 类用两个构造函数方法做的事情:一个提供相等或均匀的 insets另一个提供不相等或不均匀的 Insets。 您将使用提供不相等 insets 值的构造函数如果您正在构建一幅图片这将看起来非常不专业Insets 类的 Java 类层次结构从 java.lang.Object 主类开始并使用该类创建 javafx.geometry.Insets 类。如图 6-4 所示(l. 5)Insets 包含在 JavaFX geometry 包中就像 Pos 类一样并使用以下类层次结构: java.lang.Objectjavafx.geometry. Insets Insets 类提供了一组四个双偏移值用于指定矩形的四个边(上、右、下、左),在构造函数方法中应该按照这个顺序指定。您将使用 Insets 类(对象)来微调按钮控件组的位置您将使用 HBox 布局容器来创建该控件组。将这些 Insets 对象视为在另一个框内绘制一个框的方式它显示了您希望矩形内的对象围绕其边缘“尊重”的间距。Insets 对象的简单构造函数将使用以下格式: Insets(doubletopRightBottomLeft 此构造函数对所有间距边使用单个值(topRightBottomLeft)重载的构造函数允许您分别指定每个值如下所示: Insets(doubletop, doubleright, doublebottom, doubleleft) 这些值需要按此顺序指定。记住这一点的简单方法是使用模拟时钟。钟的顶部有“12”右边有“3”底部有“6”左边有“9”。所以从正午开始(对于你们这些西方流派的爱好者来说)总是顺时针工作就像指针绕着钟面移动一样你将有一个很好的方法来记住如何在“不均匀值”构造方法中指定 Insets 值。您将很快使用 insets 类来定位按钮控件组该控件组最初“卡”在初始屏幕设计的左下角远离屏幕的左侧和底部使用这四个 Insets 定位参数中的两个。 JavaFX HBox 类:在设计中使用布局容器 因为按钮对象不容易定位所以我将把这四个按钮对象放在 javafx.scene.layout 包中的一个布局容器中该包名为 HBox代表水平框。这个公共类将事情安排在一行中因为您希望按钮在闪屏的底部对齐所以您使用四个按钮控件节点的父节点这些节点将成为这个 HBox 分支节点的子节点(叶节点)。这将创建一组 UI 按钮它们可以作为闪屏设计的一个单元一起定位(移动)。 HBox 类是一个公共类它直接扩展 javafx.layout.Pane 超类后者又扩展 javafx.layout.Region 超类。javafx.layout.Region 超类扩展了 javafx.scene.parent 超类后者又扩展了 javafx.scene.Node 超类后者扩展了 java.lang.Object 主类。如图 6-4 所示(l. 11)HBox 包含在 javafx.scene.layout 包中就像 StackPane 类一样它使用如下的类层次结构: java.lang.Object javafx.scene.Node javafx.scene.Parent javafx.scene.layout.Region javafx.scene.layout.Panejavafx.scene.layout. HBox 如果 HBox 指定了边框或填充值HBox 布局容器的内容将遵循该边框或填充规范。填充值是使用 Insets 类指定的您将在这个微调的 UI 控件库应用中使用它。 您将使用 HBox 类(object ),以及 Pos 类常量和 Insets 类(object ),将 UI 按钮对象分组在一起然后微调它们作为按钮控件库的位置。因此这个 HBox 布局容器将成为按钮 UI 控件(或叶节点)的父节点(或分支节点)。 可以把 HBox 对象想象成一种水平排列子对象的方式。这些可能是您的图像资产它将使用基本的 HBox 构造函数(零间距)或者 UI 控件如按钮它们排列在一起但有间距使用重载的构造函数之一。创建 HBox 对象的最简单的构造函数将使用下面的空构造函数方法调用格式: HBox()您将用于创建 HBox 对象的重载构造函数将使用一个间距值在 HBox 内的子按钮对象之间留出一些空间使用以下构造函数方法调用格式: HBox(doublespacing 还有另外两种重载构造函数方法调用格式。这将允许您在构造函数方法调用本身中指定子节点对象(在本例中为按钮对象)如下所示: HBox(doublespacing, Nodes...children) - or, withzero spacing HBox(Nodes...children 你将会使用“长表格”。getChildren()。addAll()方法链但是您也可以通过使用以下构造函数来声明 HBox 及其按钮节点对象: HBox buttonContainer new HBox(12, gameButton, helpButton, scoreButton, legalButton);如果子对象被设置为可调整大小HBox 布局容器将基于不同的屏幕大小、纵横比和物理分辨率来控制子元素的大小调整。如果 HBox 区域将容纳子对象的首选宽度它们将被设置为该值。此外fillHeight 属性(布尔变量)设置为 true 作为默认值指定子对象是否应该填充(放大)HBox 高度值。 HBox 的对齐是由 Alignment 属性(属性或变量)控制的该属性默认为 Pos 类(Pos)中的 TOP_LEFT 常量。TOP_LEFT)。如果 HBox 的大小超过了其指定的宽度子对象将使用它们的首选宽度值多余的空间不会被使用。值得注意的是HBox 布局引擎将对托管子元素进行布局而不考虑它们的可见性属性(属性或变量)设置。 现在我已经讨论了 JavaFX geometry 和 layout 类您将使用它们来创建 UI(一组按钮对象)设计让我们来看看 javafx.scene.image 包中与数字图像相关的类它将允许您实现数字图像合成管道您将在 HBox UI 布局容器对象中保存的这四个 JavaFX 按钮 UI 控件元素对象的后面放置该管道。 JavaFX Image 类:在设计中引用数字图像 Image 类是一个公共类它直接扩展了 java.lang.Object masterclass这意味着 Image 类也是从头开始编写的以提供图像加载(引用)和缩放(调整大小)。您可以锁定缩放的纵横比并指定缩放算法(质量)。支持 java.net.URL 类支持的所有 URL。这意味着你可以从网上加载图片(www.servername.com/image.png)从 OS(文件:image . png)或者从 JAR 文件中使用正斜杠(/image.png)。 Image 类的 Java 类层次结构从 java.lang.Object 主类开始并使用该类创建 javafx.scene.image.Image 类。如图 6-4 所示(l. 9)Image 包含在 JavaFX image 包中就像 ImageView 类一样使用如下的类层次结构: java.lang.Objectjavafx.scene.image. Image Image 类提供了六种不同的(重载的)Image()构造函数方法。这些函数从简单的 URL 到一组指定 URL、宽度、高度、比例、平滑和预加载选项的参数值。当您使用所有构造函数方法中最复杂的方法编写 Image()构造函数时您将很快看到这些方法应该在构造函数方法中按此顺序指定其格式如下: Image(Stringurl, doublerequestedWidth, doublerequestedHeight, booleanpreserveRatio,booleansmooth,booleanbackgroundLoading) Image 对象的简单构造函数仅指定 URL并使用以下格式: Image(Stringurl 如果您要加载图像并让构造函数方法将图像缩放到不同的宽度和高度(通常较小以节省内存)同时使用最高质量的重新采样(平滑像素缩放)锁定(保留)纵横比则该图像对象构造函数使用以下格式: Image(Stringurl, doublescaleWidth, doublescaleHeight, booleanpreserveAspect, booleansmooth) 如果希望在后台(异步)加载图像使用其“本机”或物理分辨率和本机纵横比image()构造函数使用以下格式: Image(Stringurl, booleanbackgroundLoading 两个 Image()构造函数方法也使用 java.io.InputStream 类该类向 Image()构造函数方法提供输入数据的实时流(类似于视频或音频流只针对 java 应用进行了定制)。这两种图像对象构造器格式采用以下格式(简单和复杂): Image(InputStreamis Image(InputStreamis,doublenewWidth, doublenewHeight, booleanpreserveAspect, booleansmooth) 因此Image 类(对象)用于准备数字图像资产以供使用即从 URL 读取其数据如果有必要调整它们的大小(使用你喜欢的平滑和纵横比锁定)并异步加载它们而应用中的其他事情正在进行。需要注意的是图像类(或对象)并不显示图像资产:图像类只是加载它如果需要缩放它并将它放在系统内存中供您的应用使用。 要显示图像对象您需要使用第二个类(对象)称为 ImageView 类。ImageView 对象可以用作场景图和引用上的节点然后将图像对象数据“绘制”到布局容器上该容器保存 ImageView 节点(在这种情况下是叶 ImageView 节点的 StackPane 场景图根和父节点)。我将在下一节介绍 ImageView 类。 从数字图像合成的角度来看StackPane 类(对象)是图像合成引擎也可以称为层管理器每个 ImageView 对象代表层堆栈中的一个层。图像对象包含图像视图层中的数字图像数据或者如果需要包含多个图像视图中的数字图像数据因为图像对象和图像视图对象是分离的并且彼此独立存在。 JavaFX ImageView 类:在设计中显示数字图像 ImageView 类是一个公共类它直接扩展 javafx.scene.Node 超类后者是 java.lang.Object 的扩展(参见第四章)。因此ImageView 对象是 JavaFX 场景图中的一种节点对象用于使用 Image 对象中包含的数据绘制视图。该类具有允许图像重采样(调整大小)的方法并且与 image 类一样您可以锁定缩放的纵横比以及指定重采样算法(平滑质量)。 ImageView 类的 Java 类层次结构从 java.lang.Object 主类开始并使用该类创建 javafx.scene.Node 类然后使用该类创建 ImageView 节点子类。如图 6-4 所示(l. 10)和 Image 类一样ImageView 包含在 JavaFX image 包中。ImageView 类使用以下 Java 类继承层次结构: java.lang.Object javafx.scene.Nodejavafx.scene.image. ImageView ImageView 类提供了三种不同的(重载的)ImageView()构造函数方法。这些范围从一个空的构造函数(这是您稍后将在代码中使用的一个构造函数)转换为一个将图像对象作为其参数的函数转换为以 URL 字符串对象作为参数并自动创建图像对象的方法。要创建 ImageView 对象简单(空)ImageView()构造函数方法使用以下格式: ImageView()您将使用这个构造函数方法这样我就可以向您展示如何使用setImage()方法调用将图像对象加载到 ImageView 对象中。如果您想避免使用。setImage()方法调用时可以使用重载的构造函数方法其格式如下: ImageView(Imageimage 因此要“显式”设置一个 ImageView 并将其连接到 Image 对象如下所示: splashScreenBackplate new splashScreenBackplate.setImage(splashScreen 您可以使用重载的构造函数方法将其压缩成一行代码结构如下: splashScreenBackplate newImageView(splashScreen 如果您想绕过创建和加载图像对象的过程也有一个构造函数方法它使用以下格式: ImageView(Stringurl 要在后台(异步)加载图像使用其本机(默认)分辨率和本机纵横比image()构造函数使用以下格式: splashScreen new Image(/invincibagelsplash.png, 640, 400, true, false, true); splashScreenBackplate new ImageView();splashScreenBackplate.setImage(splashScreen 如果不想指定图像尺寸、背景图像加载和平滑缩放或者不想锁定任何缩放的纵横比可以将前面三行 Java 代码压缩到以下构造函数中: splashScreenBackplate new ImageView(/invincibagel.png); // uses third constructor method至少在开始时(出于学习目的)我会用长时间的方式来做这件事我会一直使用 Image()构造函数方法显式地加载图像对象这样您就可以指定所有不同的属性并看到您在这个 Java 编程逻辑中使用的所有不同的图像资产。我想在这里向您展示快捷方式代码因为一旦您开始使用 ImageViews 作为精灵您将在本书的后面使用这种方法(参见第八章)。你可以对你的精灵使用这种快捷方式因为你不需要缩放它们也因为它们已经高度优化不需要后台加载。 接下来让我们快速看一下 TableView 类它将保存高分表。虽然你不会在这里实现它但我会介绍这个类因为它是你在本章中创建和实现的顶级 UI 设计的一部分。 JavaFX TableView 类:在设计中显示数据表 TableView 类是直接扩展 javafx.scene.control.Control 超类的公共类javafx.scene.layout.Region 是 javafx.scene.layout.Region 的扩展javafx.scene.Parent 是 javafx.scene.Node 场景图超类的扩展(见第四章)。因此TableView S 对象是一种 UI 控件(表格)和 JavaFX 场景图中的节点对象用于使用 S 对象构建表格每个对象都包含要在表格中显示的数据。在本书的后面部分你将使用这些 S 对象将数据写入一个 TableView S 对象在得分超过当前列表中的得分之后。 TableView 类的 Java 类层次结构从 java.lang.Object 主类开始并使用该类创建 javafx.scene.Node 类然后使用该类创建父类。这用于创建一个区域类该区域类又创建一个控件类该控件类用于创建 TableView 类。TableView 类具有以下 Java 类继承层次结构: java.lang.Object javafx.scene.Node javafx.scene.Parent javafx.scene.layout.Region javafx.scene.control.Controljavafx.scene.control. TableViewS TableView 类提供了两种不同的(重载的)TableView()构造函数方法一种是空的构造函数另一种是接受 ObservableList 对象的构造函数该对象以表数据项作为参数。创建空 TableView 对象的简单(空)TableView()构造函数方法将使用以下格式: TableView()第二种构造函数类型使用 javafx.collections 包中的 ObservableList 类(object ),这是一种列表类型它允许数据更改事件侦听器跟踪列表中发生的任何更改。此 TableView 对象构造函数方法调用使用以下格式: TableView(ObservableListSitems 我认为现在已经有足够的类背景信息了所以让我们开始为你的第一个类编写代码。createSplashScreenNodes()方法该方法将实例化并设置场景图形的所有节点对象 场景图形节点:。createSplashScreenNodes() 使用 createSplashScreenNodes()方法要做的第一件事是编写空方法结构的代码并添加已经存在于引导代码中的节点对象创建代码引导代码是 NetBeans 在第二章中为您生成的。这包括按钮节点的节点对象、StackPane 根节点和名为 Scene 的场景对象。您将在。start()方法因为该对象是使用。start(Stage primaryStage)构造函数方法调用。Button 对象已经被重命名为 gameButton(原来是 btn)所以您有三行对象实例化代码和一行配置代码如下所示: root new StackPane(); scene new Scene(root gameButton new Button();gameButton.setText(PLAY GAME);需要注意的是因为在场景 scene 对象的构造函数方法调用中使用了根 StackPane 对象所以这一行代码需要放在前面(在使用它之前必须先创建您的根对象).接下来您需要创建的是 HBox 布局容器对象它将保存您的四个按钮 UI 控件。您还将为 HBox 设置对齐属性添加一个 Insets 对象以包含填充值然后使用这四行 Java 代码将这些填充添加到 4 HBox 对象中: buttonContainer new HBox(12 buttonContainer.setAlignment(Pos.BOTTOM_LEFT buttonContainerPadding new Insets(0, 0, 10, 16); buttonContainer.setpadding(buttonContainerPadding 接下来让我们采用一种方便的程序员快捷方式将两行 gameButton(实例化和配置)代码复制并粘贴到 HBox 代码下面(因为按钮在 HBox 内部这只是为了视觉组织而不是为了使代码工作)然后在单独的行上再复制并粘贴三次。这将允许您通过创建以下四个按钮 Java 代码分别将游戏更改为帮助、得分和合法: gameButton new Button();gameButton.setText(PLAY GAME);helpButton new Button();helpButton.setText(INSTRUCTIONS);scoreButton new Button();scoreButton.setText(HIGH SCORES);legalButton new Button();legalButton.setText(LEGAL CREDITS);现在您已经创建了 HBox 按钮 UI 控件布局容器和按钮您还需要再编写一行代码使用。getChildren()。addAll()方法链像这样: buttonContainer.getChildren().addAll 接下来让我们添加您的图像合成节点对象(image 和 ImageView ),以便您可以为 InvinciBagel 闪屏添加插图以及装饰您的说明、法律免责声明和制作人员名单的面板覆盖图并为您的(最终)游戏高分表添加背景和屏幕标题。我使用两个 ImageView 对象来包含这两层让我们首先设置最底部的背板图像层方法是使用以下 Java 代码实例化 image 对象然后实例化 ImageView 对象并将它们连接在一起: splashScreen new Image(/invincibagelsplash.png, 640, 400, true, false, true);splashScreenBackplate new ImageView();splashScreenBackplate.setImage(splashScreen); // this Java statement connects the two objects最后让我们对图像合成板做同样的事情也就是说ImageView 将保存包含 alpha 通道(透明度)值的不同图像对象这些图像对象将为 InvinciBagel 闪屏插图(由才华横溢的 2D 艺术家 Patrick Harrington 创建)创建面板图像的覆盖图: instructionLayer new Image(/invincibagelinstruct.png, 640, 400, true, false, true);splashScreenTextArea new ImageView();splashScreenTextArea.setImage(instructionLayer); // this Java statement connects the two objects如图 6-7 所示你的场景图节点创建(见 InvinciBagel 类的顶部)和节点对象实例化和配置(见 createSplashScreenNodes()方法)已经就绪并且没有错误。您仍然需要为其他两个屏幕添加图像对象但是这里有足够的代码能够使用 addNodesToStackPane()方法将这些节点对象添加到场景图中然后测试代码以确保它能够工作。根据 NetBeans IDE此代码没有错误 图 6-7。 Coding the createSplashScreenNodes() method; instantiating and configuring the nodes in the Scene Graph 接下来让我们在 addNodesToStackPane()方法中将节点对象添加到 StackPane 场景图形根对象中。 向场景图添加节点:。addStackPaneNodes() 最后您必须创建一个方法将您已经创建的节点对象添加到场景图形根在本例中是一个 StackPane 对象。您将使用。getChildren()。add()方法链将子节点对象添加到父 StackPane 根场景图节点。这是通过三行简单的 Java 代码完成的如下所示: root.getChildren().add(splashScreenBackplate);root.getChildren().add(splashScreenTextArea);root.getChildren().add(buttonContainer);正如你在图 6-8 中看到的Java 代码是没有错误的根对象在类的顶部看到了它的声明。单击代码中的根对象会创建这种突出显示它通过代码跟踪对象的使用。这是一个非常酷的 NetBeans 8.0 技巧当您想要跟踪代码中的对象时应该使用它。 图 6-8。 Coding the addNodesToStackPane() method, using the .getChildren() method chained to the .add() method 这里要注意的重要事情是节点对象添加到 StackPane 根场景图形对象的顺序。这会影响图像合成的合成层顺序以及这些数字图像元素之上的 UI 元素合成。添加到 StackPane 的第一个节点将位于层堆栈的底部这需要是 splashScreenBackplate ImageView 节点对象如图所示。 下一个要添加的节点将是 splashScreenTextArea ImageView 节点对象因为带有面板覆盖的透明图像需要放在 Pat Harrington 的 InvinciBagel 闪屏 2D 作品的正上方。之后您可以放置 UI 设计在本例中可以使用 buttonContainer HBox 节点对象一次性完成该节点对象包含所有的按钮对象。请注意您不必向这个 StackPane 根场景图形对象添加按钮因为您已经使用了。getChildren()。addAll()方法链用于将按钮 UI 控件添加到 HBox(父对象)节点分支对象下的场景图形层次中。现在您可以开始测试了 测试 InvinciBagel 应用:脉动场景图形 单击 NetBeans IDE 顶部的绿色播放箭头然后运行项目。这将弹出如图 6-9 所示的窗口(我已经删除了在第四章中添加的 Java 代码演示如何创建一个无窗口应用)。所以你又有了 windows“chrome ”,至少现在是这样。正如您所看到的您只使用了十几条 import 语句(外部类)、几十行 Java 代码和场景图根(StackPane)对象下的六个子节点就获得了非常好的结果。如您所见JavaFX 在将背板、合成图像覆盖和按钮库覆盖合成为一个无缝、专业的结果方面做得非常好 图 6-9。 Run the InvinciBagel application, and make sure that the StackPane compositing class is working correctly 因为您只复制和粘贴了每个按钮的 EventHandler 例程并且更改了按钮对象的名称而没有更改这些例程中的代码所以按钮对象仍然可以正常工作(将文本写入控制台)并且不会导致编译器错误。但是他们不会做您希望他们做的事情即更改图像覆盖以便设计左侧的面板包含您希望它向用户显示的标题和文本。 这将通过调用。setImage()方法该方法将根据用户单击的按钮 UI 控件将 splashScreenTextArea ImageView 对象设置为 instructionLayer、scoresLayer 或 legalLayer Image 对象。在添加最后两个 Image 对象实例之前您不能实现这个事件处理代码 完成 InvinciBagel UI 屏幕设计:添加图像 让我们以 createSplashScreenNodes()方法结束在该方法的末尾再添加两行以添加两个图像对象引用 invincibagelcreds.png 和 invincibagelscores.png 的 32 位 PNG32 数字图像资产。这是通过使用下面两行使用 new 关键字的 Java 对象实例化代码来实现的: legalLayer new Image( /invincibagelcreds.png, 640, 400, true, false, true );scoresLayer new Image( /invincibagelscores.png, 640, 400, true, false, true );如图 6-10 所示代码是没有错误的因为你已经将四个 PNG 文件复制到你的项目的/src 文件夹中。您不需要其他代码行(ImageView 对象实例化。setImage())因为您将使用 splashScreenTextArea ImageView 对象来保存这最后两个图像对象。因此您可以节省所使用的场景图节点对象因为您使用单个 ImageView 场景图节点对象来基于按钮事件显示三个不同的图像对象(覆盖)。 图 6-10。 Adding legalLayer and scoresLayer Image object instantiations to add the other image composite plates 这意味着您将对 splashScreenTextArea ImageView 对象进行的 splashScreenTextArea.setImage()方法调用将被放置在三个按钮对象的 ActionEvent EventHandler 编程构造中这三个按钮对象在被单击时会触发图像合成覆盖。第四个按钮对象将启动游戏所以现在在按钮事件处理结构中只有一个 Java 注释使它成为一个“空”逻辑结构。现在让我们看看如何完成这些 EventHandler 构造的编码这样您就可以完成这个 UI 设计并继续创建前面提到的游戏引擎。 交互性:连接 InvinciBagel 按钮以供使用 在所有这些重复的按钮事件处理结构中您需要用对。setImage()方法以便您可以将图像合成板 ImageView 设置为 Image 对象该对象包含您想要覆盖由 Pat Harrington 创建的 InvinciBagel 背板作品的数字图像资产。您已经在 createSplashScreenNodes()方法中编写了两次这个代码结构所以如果您想要一个快捷方式您可以将代码行直接复制到您刚刚编写的两个 Image 对象实例化之上。 那个。因此setOnAction()事件处理 Java 代码结构如下所示: helpButton .setOnAction(new EventHandlerActionEvent() { Overridepublic void handle(ActionEvent event) {splashScreenTextArea.setImage(instructionLayer }});scoreButton .setOnAction(new EventHandlerActionEvent() { Overridepublic void handle(ActionEvent event) {splashScreenTextArea.setImage(scoresLayer }});legalButton .setOnAction(new EventHandlerActionEvent() { Overridepublic void handle(ActionEvent event) {splashScreenTextArea.setImage(legalLayer }});如图 6-11 所示您的事件处理代码没有错误您已经准备好再次运行和测试了 图 6-11。 Modify the body of the .handle() method for each of four Button controls to complete the infrastructure 如您所见您暂时将 gameButton.setOnAction()事件处理结构保留为空在下一章中您将创建主要的游戏界面和一个脉冲事件处理引擎(结构),该引擎将通过调用您将在本书过程中编写的各种功能引擎来运行该游戏。 您现在也将高分屏幕的底部保留为空白以便您可以在场景图形根堆栈窗格中用 TableView 节点对象覆盖两个 ImageView 层。在开发完 Java 8 游戏之后您将完成高分按钮 UI 元素的复合。 现在是时候对游戏应用的顶层 UI 部分进行最终测试了以确保所有的 UI 按钮元素(对象)都能正常工作并按照您的设计(编码)完成它们的功能。之后您将再次运行 NetBeans 8.0 Profiler以确保您刚刚创建的场景图形层次结构确实为您将从现在开始创建的游戏引擎留出了 99%的可用 CPU 处理能力。 测试最终的 InvinciBagel UI 设计 再次单击 NetBeans IDE 8.0 顶部的绿色播放箭头然后运行您的项目。这将调出如图 6-12 所示的窗口。正如您所看到的当您单击“法律和学分”按钮 UI 元素时该覆盖图与 InvinciBagel artwork 背板无缝合成如图左侧所示当您单击“高分”按钮 UI 元素(控件)时高分表格背景将就位如图右侧所示。如您所见javafx.image 包中的类提供了关于合成的原始结果 图 6-12。 The other two Image objects shown composited, using the background plate and compositing ImageViews 接下来让我们来看看你在本章中编写的场景图实现占用了多少 CPU 周期因为你想确保 100%静态的顶级 UI 设计以便游戏中使用的唯一动态元素是游戏引擎(和相关引擎)本身。因为脉冲分辨率引擎遍历场景图层次可能会变得“昂贵”所以这里需要非常小心 请记住您的主要目标是创建一个顶级的 UI 设计用于启动游戏播放屏幕和循环同时还实现一个允许您的用户显示说明、法律免责声明和制作人员名单的 UI并负责设置一个用于显示高分表的区域。同时您的任务是节省 99%的处理能力供以后使用通过 JavaFX 脉冲引擎处理游戏逻辑、精灵运动、精灵动画、碰撞检测、得分和物理。 剖析 InvinciBagel 场景图的脉冲效率 重要的是游戏 UI 设计不要从 CPU 中取走任何处理能力因为游戏引擎将需要所有的处理能力。如图 6-13 所示您可以使用 ProfileProfile Project(InvinciBagel)菜单序列来运行 Profiler并对当前(顶层 UI)应用的 CPU 统计数据进行截图。 图 6-13。 Profiling the Scene Graph UI design thus far to make sure that it does not use any perceptible CPU cycles 正如您在图的右侧的 Total Time 列中所看到的createSplashScreenNodes()方法需要 279 毫秒或者大约十分之三秒的时间来执行并且您的场景图被创建。执行 addNodesToStackPane()方法大约需要 3 毫秒即千分之三秒。 如果您查看线程分析输出并单击 UI 按钮元素您将会看到线程上出现一个彩色点显示了按钮单击的处理开销正如您所看到的每次单击不到十分之一秒(查看最右边的调用列了解我测试了多少次按钮单击函数)。我突出显示了 threads 视图在那里我单击了 High Scores然后是 Legal 和 Credits 按钮 UI 元素(参见图 6-14 )。正如您在这个视图中看到的当前的设计使用了最少的资源。 图 6-14。 Profiling the Scene Graph UI design thus far to make sure that it does not use any perceptible thread overhead Java 8 及其 JavaFX 引擎衍生了近十几个线程所以你的游戏应用已经是高度多线程的了甚至不需要在这个时间点Oracle 的团队正在努力使 JavaFX 成为首屈一指的游戏引擎所以性能会越来越好这对 Java 8 游戏开发者来说是个好消息 摘要 在第六章中您已经开始着手为您的游戏进行实际的顶级 UI 设计概述底层游戏引擎组件设计并找出最有效的场景图节点设计。然后您回到 Java 8 游戏编程并重新设计了您现有的引导 Java 8 代码这些代码最初是由 NetBeans 8.0 创建的。 因为 NetBeans 生成的 Java 代码设计并不适合您的目的所以您完全重写了它使它更有条理。这是通过创建两个自定义 Java 方法来实现的。createSplashScreenNodes()和。addNodesToStackPane()用于模块化场景图节点创建过程以及将三个父(和叶)节点对象添加到场景图根(在本例中是 StackPane 对象它用于其多层 UI 对象合成功能)。 接下来您从 javafx.geometry 包中了解了一些用于实现这些新方法的 JavaFX 类包括 Pos 类和 Insets 类javafx.scene.image 包中的 Image 和 ImageView 类HBox 类来自 javafx.scene.layout 包以及 javafx.scene.control 包中的 TableView 类。你编写了新的。createSplashScreenNodes()方法该方法使用 Insets 对象、Image 和 ImageView 对象以及四个 Button 对象来实例化和配置 HBox 对象。一旦实例化和配置了所有这些场景图节点您就可以编写一个。addNodesToStackPane()方法将节点对象添加到 StackPane 根对象以便它们可以由 Stage 对象显示Stage 对象引用场景图形的根对象。接下来您测试了您的顶级游戏应用 UI 设计。然后添加最后几个图像对象并添加 ActionEvent EventHandler 程序逻辑。最后您对应用进行了概要分析以确保它是高效的。 在下一章中我将介绍 JavaFX 脉冲引擎和 AnimationTimer 类以便您可以为 Java 8 游戏引擎创建基础结构该引擎将实时处理您的游戏事件。 七、游戏循环的基础JavaFX 脉冲系统和游戏处理架构 现在您已经为您的用户创建了学习如何玩游戏、开始游戏、查看高分以及查看法律免责声明和 Ira H. Harrison Rubin 的 InvinciBagel 知识产权游戏制作致谢名单所需的顶级 UI 屏幕让我们进入正题为您的 InvinciBagel 游戏创建游戏播放计时循环。从用户体验的角度来看这是最重要的并且对于您将在本书剩余部分创建的不同游戏引擎的正常运行也是至关重要的包括精灵引擎、碰撞检测引擎、动画引擎、评分引擎和物理引擎。你将永远记住游戏的流畅度JavaFX 脉冲系统的高效、优化实现在游戏的这个阶段是至关重要的(没有双关语)。为此我将在本章中详细介绍 javafx.animation 包以及它的所有函数类之间的区别。 首先您将探索 javafx.animation 包中的两个动画超类:Animation 和 AnimationTimer。之后您将了解动画、时间轴和过渡以及这些类及其任何子类(如 PathAnimation 和 TranslateAnimation)如何允许您访问 JavaFX 脉冲事件计时系统。现在你需要使用脉冲如果你想创建一个面向行动的街机类型的 Java 8 游戏 您还将仔细查看整个 javafx.animation 包的整体结构因为您需要在 Java 8 游戏循环中使用其中一个类。您将通过使用整个包的图表来完成这个任务这样您就可以对它的所有类是如何相互关联的有一个总体的了解。然后您将详细检查所有 JavaFX 动画类之间的类层次结构。除了 AnimationTimer、Interpolator、KeyFrame 和 KeyValue所有这些 javafx.animation 包类都使用 JavaFX Animation 超类进行子类化(使用 Java extends 关键字)。 最后您将把新的 GamePlayLoop 类添加到 invincibagel 包中该包将作为 invincibagel 应用子类中的 GamePlayLoop 对象创建实现定时循环。这个 GamePlayLoop 类将包含一个. handle()方法以及一个. start()方法和一个. stop()方法这将允许您在 GamePlayLoop 运行时控制 GamePlayLoop 计时事件并确定它何时处于潜伏状态(停止或暂停)。 我将创建一个图表显示这个 InvinciBagel 游戏的类和对象层次结构这样您就可以开始想象您正在编写的这些类和您正在创建的对象是如何组合在一起的。这就好像使用 Java 8 和 JavaFX 编写游戏代码本身就是一个(益智)游戏很酷的东西。 游戏循环处理:利用 JavaFX 脉冲 即使在开发团队中的 Oracle 员工中一个主要问题是实现游戏计时循环引擎的哪种设计方法应该与 JavaFX 动画包(类套件)中包含的类一起使用。事实上这正是本章的全部内容:使用 javafx.animation 包及其类这些类利用了 JavaFX 脉冲事件计时引擎。在包的类层次结构的顶层如图 7-1 所示AnimationTimer 和 Animation 类提供了获取这些脉冲事件的主要方法以便它们为您进行实时处理。在这一部分你将看到它们的不同之处以及它们的设计用途包括它们应该用于的游戏种类。除了插值器(运动曲线应用)、关键帧(关键帧定义)和 KeyValue(关键帧自定义)之外javafx.animation 包中的所有类都可以用于控制脉冲事件。 图 7-1。 Javafx.animation package subclass hierarchy; top level classes all coded from scratch with java.lang.Object 有四种基本方法来实现(访问)JavaFX 脉冲事件计时系统以创建游戏计时循环。这些不同的方法适用于不同类型的游戏我之前已经讨论过了(见第五章)。这些游戏类型从需要使用 脉冲事件引擎来实现特殊效果(过渡子类)或自定义动画(时间轴类结合关键帧类和可能的 KeyValue 类)的静态游戏(棋盘游戏、益智游戏)到需要以每秒 60 次的游戏播放刷新率对 脉冲事件系统进行必要的核心访问的高度动态游戏(AnimationTimer 类)。 最高级别(视觉上最低级别显示在图的左下方)是使用 javafx.animation 包中预先构建的 Transition 子类如 PathTransition 类(或对象)用于游戏精灵或投射物的路径或 TranslateTransition用于翻译(移动)屏幕上的东西。所有这些过渡子类都是为你编码的你所要做的就是使用它们这就是为什么在这个特殊的讨论中我把它标为最高的功能级别。这种高水平的预建功能带来了内存和处理价格这是因为正如你从 Java 继承中所了解到的(参见第三章), path transition 类包含了它所有的方法、变量和常量以及在类层次结构中位于它之上的所有类的方法、变量和常量。 这意味着整个 PathTransition 类、Transition 超类、Animation 超类和 java.lang.Object masterclass 都包含在该类的内存占用中并且还可能包含处理开销这取决于如何使用该类实现对象。这是一个需要考虑的问题因为你在 JavaFX 动画包中的位置越低它就越昂贵你对为你编写的代码的控制就越多而不是你自己编写的定制代码。 编码自定义游戏循环的下一个最高级别的方法是子类化 javafx.animation.Transition 类以创建您自己的自定义过渡子类。这一级和前一级都被认为是顶级方法最适用于静态但有动画效果的游戏或者动态性较差的游戏。 中级解决方案是使用 Timeline 类及其相关的 KeyFrame 和 KeyValue 类它们非常适合于实现拖放工具(如 Flash)中基于时间轴的动画类型。您会发现如果您在网上查看 JavaFX 游戏引擎讨论这是一种流行的方法因为许多动画都是通过创建单个关键帧对象然后使用时间轴对象来处理脉冲事件来实现的。 使用时间轴对象方法允许您指定处理游戏循环的帧速率例如 30FPS。这将适用于可以使用较低帧速率的动态性较低的游戏因为它们不涉及大量的帧间游戏处理如精灵移动、精灵动画、碰撞检测或物理计算。请务必注意如果使用 Timeline 对象(类)您将在系统内存中为帧速率和至少一个关键帧对象引用(这些是 Timeline 类定义的一部分)定义变量以及从 Animation 超类继承的属性(变量)如 status、duration、delay、cycleCount、cycleDuration、autoReverse、currentRate、currentTime 和 on finished(action event)object property。 如果您熟悉创建动画您会看到时间轴以及至少一个关键帧对象和存储在每个关键帧对象中的潜在的大量 KeyValue 对象显然是为创建基于时间轴的动画而设计的(优化的)。虽然这是一个非常强大的功能但它也意味着使用时间轴和关键帧对象进行游戏循环处理将会创建近十几个内存分配区域这些区域甚至可能不会在您的游戏中使用或者可能不会针对您的游戏设计实现进行优化设计(编码)。 幸运的是还有另一个与 javafx.animation 包计时相关的类它没有这种预构建类的开销所以我称之为最底层的方法在这种方法中您必须在一个简单的。handle()函数它在每次传递时访问 JavaFX 脉冲引擎。 低级的解决方案包括使用 AnimationTimer 类这样命名是因为 Java (Swing)已经有了一个 Timer 类(javax.swing.Timer)Java 的实用程序类(java.util.Timer)也是如此如果您是一个足够高级的程序员也可以使用它来处理所有线程同步问题(和编码)。 因为这是一本初学者级别的书所以您将坚持使用 Java 8 游戏引擎(JavaFX 8)循环您的游戏。JavaFX 在 javafx.animation 包中有自己的 Timer 类称为 AnimationTimer以免与 Swing GUI Toolkit 的 Timer 类混淆(由于遗留代码的原因它仍然受支持)。许多新开发人员对这个类名的“动画”部分感到困惑不要假设这个定时器类是用于动画的它的核心是为了计时。就访问 javafx 脉冲计时系统而言该类是 javafx.animation 包中最低级别的类本质上仅用于访问脉冲计时系统。其他的都被剥离了。 因此AnimationTimer 类是为您实现提供最少系统开销(使用的内存)的类。在全速 60FPS 时它将具有最高的性能假设。handle()方法得到了很好的优化。这是用于快速、高动态游戏的类例如街机游戏或射击游戏。出于这个原因这是您将在游戏中使用的类因为您可以继续构建游戏引擎框架并添加功能而不会耗尽电量。 在本书中您将使用最底层的方法以防您将 Java 8 游戏开发推向极限并且正在创建一个高度动态的、充满动作的游戏。JavaFX AnimationTimer 超类非常适合这种类型的游戏应用因为它处理它的。每个 JavaFX 脉冲事件的 handle()方法。脉冲事件目前被限制在 60FPS这是专业动作游戏的标准帧速率(也称为刷新率)。您将从 AnimationTimer 超类中派生出 GamePlayLoop.java 类的子类。 有趣的是大多数现代 iTV LCD、有机发光二极管和 LED 显示屏产品也以这种精确的刷新率(60Hz)更新尽管较新的显示器将以两倍于此的速率(120Hz)更新。具有 240Hz 刷新率的显示器也即将问世但因为这些 120Hz 和 240Hz 刷新率显示器使用 60Hz 的偶数倍(2 倍或 4 倍)所以 60FPS 是为当今的消费电子设备开发游戏的合理帧速率。接下来让我们在你的游戏中实现 GamePlayLoop.java 类它将子类化 AnimationTimer 来访问脉冲。 创建一个新的 Java 类:GamePlayLoop.java 让我们使用 javafx.animation 包中的 AnimationTimer 超类来创建一个自定义的 GamePlayLoop 类(以及最终的对象)和所需的。handle()方法来处理您的游戏进行计算。如图 7-2 所示在 NetBeans 8.0 中右键单击项目层次结构窗格中的 invincibagel 包文件夹即可完成此操作。这将向 NetBeans 显示在创建新的 Java 类后您希望将它放在哪里。 图 7-2。 Right-click the invincibagel package folder, and use the New ➤ Java Class menu sequence 点击新建➤ Java 类将打开新建 Java 类对话框如图 7-3 所示。将该类命名为 GamePlayLoop保留 NetBeans 设置的其他默认值具体取决于您右键单击 invincibagel 包文件夹然后单击 Finish。 图 7-3。 Name the new Java class GamePlayLoop, and let NetBeans set up the other fields NetBeans 将为 GamePlayLoop.java 类创建一个引导基础结构带有一个包和一个类声明如图 7-4 所示。现在添加一个 extends 关键字和 AnimationTimer。 图 7-4。 NetBeans creates a GamePlayLoop.java class and opens it in an editing tab in the IDE, for you to edit 鼠标悬停在错误上按 AltEnter选择添加导入如图 7-5 所示。 图 7-5。 Subclass an AnimationTimer superclass with an extends keyword: press AltEnter, and select Add import 一旦 NetBeans 添加了import javafx.animation.AnimationTimer;编程语句您就可以开始创建这个类了它将为您利用 JavaFX 脉冲引擎并包含您所有的核心游戏循环处理或者对将执行各种类型处理的类和方法的调用例如精灵移动、精灵动画、碰撞检测、物理模拟、游戏逻辑、音频处理、AI、记分牌更新等等。 创建 GamePlayLoop 类结构:实现你的。handle()方法 请注意一旦 NetBeans 为您编写了 import 语句GamePlayLoop 类名下方就会出现另一个红色波浪状错误高亮显示。将鼠标放在上面查看与这个最新错误相关的错误消息。如图 7-6 所示。每个 AnimationTimer 子类所需的 handle()方法还没有在这个 GamePlayLoop.java 类中实现(也称为 overridden ),所以接下来必须这样做。也许你甚至可以让 NetBeans 帮你写代码让我们来看看看看 图 7-6。 Once you extend and import AnimationTimer, NetBeans throws an error: class does not implement the .handle() 正如您在弹出的错误消息的左下方看到的您可以使用 AltEnter 组合键来打开一个帮助器对话框它将为您提供几个解决方案其中一个将实际编写未实现的。handle()方法。选择实现所有抽象方法如图 7-7 所示以蓝色突出显示。双击此选项后NetBeans 将为您编写此方法结构: Overridepublic void handle (longnow throw new UnsupportedOperationException(Not supported yet.);}请注意an Override 关键字位于公共 void 句柄方法访问关键字、返回类型关键字和方法名称之前。这告诉 Java 编译器。handle()方法将替换(覆盖)AnimationTimer 的。handle()方法这就是为什么该错误指示您必须重写抽象方法。手柄(长)。 你肯定不希望你的。handle()方法在游戏循环中每秒抛出 60 个 UnsupportedOperationException()错误但是您现在将把它留在这里以便您可以看到它的作用并了解更多关于 NetBeans 错误控制台的信息。 图 7-7。 Take a coding shortcut: press AltEnter to bring up a helper dialog, and select Implement all abstract methods 如图 7-8 所示一旦选择了实现所有抽象方法选项Java 代码就没有错误了类的基本包-导入-类-方法结构也就就位了。现在您应该能够使用该类创建一个 GamePlayLoop 对象所以让我们换个方式在 InvinciBagel Java 类中进行一些编程在该类中您创建一个 GamePlayLoop 对象然后分析应用以查看它做了什么。 图 7-8。 NetBeans creates a public void handle(long now) bootstrap method with UnsupportedOperationException 创建 GamePlayLoop 对象:添加脉冲控制 接下来您需要声明、命名和实例化一个名为 gamePlayLoop 的 GamePlayLoop 对象使用您创建的新类结合 Java new 关键字。点击 InvinciBagel.java 选项卡如图 7-9 所示在声明 gamePlayLoop 对象的 Insets 对象声明下添加一行代码命名为 GamePlayLoop如下所示: GamePlayLoopgamePlayLoop 图 7-9。 Click the InvinciBagel.java editing tab, and declare a GamePlayLoop object named gamePlayLoop at the top 正如您所看到的代码是没有错误的因为 NetBeans 已经找到了您的 GamePlayLoop 类它包含了被覆盖的。handle()方法其父 AnimationTimer 类有一个构造函数方法可以使用 GamePlayLoop 类创建 AnimationTimer(类型)对象扩展 AnimationTimer。 现在您必须使用 Java new 关键字在内存中实例化或创建 GamePlayLoop 对象的实例。这在游戏第一次开始时完成一次这意味着实例需要放入。start()方法。 您可以在创建所有其他场景图节点对象和 ActionEvent EventHandler 对象后使用以下 Java 代码行来完成此操作(另请参见图 7-10 ): gamePlayLoop new 这个代码放置的逻辑(在最后)是根据创建和配置来设置所有静态对象然后在最后创建动态对象该对象将处理脉冲相关的逻辑。 图 7-10。 At the end of the .start() method, instantiate the gamePlayLoop object by using the Java new keyword 分析 GamePlayLoop 对象:运行 NetBeans Profiler 让我们使用配置文件➤项目配置文件菜单序列来运行 NetBeans Profiler以确定您是否可以在任何配置文件视图中看到您创建的 GamePlayLoop 对象。如图 7-11 所示GamePlayLoop init 调用用了不到 2 毫秒的时间在内存中设置 GamePlayLoop 对象供您使用使用的开销很小。 图 7-11。 Use a Profile ➤ Profile Project menu sequence to start the Profiler and look at GamePlayLoop memory use 接下来让我们通过向下滚动 Profiler 选项卡来研究 threads analysis 窗格如图 7-11 的左上角所示。查找线程图标 NetBeans 会询问您是否要启动线程分析工具一旦你同意它将打开螺纹标签(见图 7-12 )。 图 7-12。 Click the Threads icon, seen at the left of the screen, and open the Threads tab; the same eleven threads are running 如果您想知道为什么在图 7-12 所示的线程对象中看不到任何“信号”,就像您在上一章中单击按钮对象时所做的那样您的假设是正确的您应该在该图的某个地方看到 JavaFX 脉冲 engine 计时事件所有的线程栏都是纯色的因此没有动作或脉冲事件触发。我将让您根据需要经常使用 NetBeans profiling 实用程序以便熟悉它因为许多开发人员都避免使用该工具因为他们还不习惯使用它。 你没有看到任何事件的原因是仅仅创建游戏循环对象是不够的。它内部的 handle()方法来抓取脉冲事件。因为它是一个定时器对象(确切地说是一个动画定时器)像任何定时器一样它需要启动和停止。接下来让我们为游戏循环创建这些方法。 控制你的游戏循环:。开始( )和。停止( ) 因为 AnimationTimer 超类具有。开始()和。stop()方法控制类(对象)何时(使用. start()方法调用)和何时(不使用. stop()方法调用)处理脉冲事件您只需在方法代码中使用 Java super 关键字将这些函数“向上”传递给 AnimationTimer 超类。您将重写。使用 Java Override 关键字启动()方法然后使用以下方法编程结构将方法调用功能传递给 AnimationTimer 超类: Overridepublic void start() {super .start(); }的。stop()方法结构将被覆盖方法功能以完全相同的方式传递给超类使用下面的 Java 方法编程结构: Overridepublic void stop() {super .stop(); }如图 7-13 所示GamePlayLoop 类代码是无错误的现在您可以在 InvinciBagel 类中编写启动 GamePlayLoop AnimationTimer 对象的代码这样您就可以在分析应用时看到 脉冲对象。 图 7-13。 Adding .start() and .stop() methods to the GamePlayLoop class and using the Java super keyword properly 你需要打这个电话。start()方法关闭名为 gamePlayLoop 的 GamePlayLoop 对象。您刚刚创建的 start()方法。点击 InvinciBagel.java 选项卡如图 7-14 所示在 GamePlayLoop 对象实例化下面添加一行代码调用。start()方法关闭名为 gamePlayLoop 的 GamePlayLoop 对象如下所示: gamePlayLoop.start 如您所见方法调用已经就绪Java 代码没有错误因为 NetBeans 现在可以找到。GamePlayLoop 类中的 start()方法。接下来让我们使用运行➤项目序列并测试一两个脉冲以确定现在将会发生什么GamePlayLoop AnimationTimer 子类已经使用。start()方法调用。看看在。handle()方法就可以了 图 7-14。 Call a .start() method off the gamePlayLoop object to start GamePlayLoop AnimationTimer 如图 7-15 所示您会得到与中的内容相关的重复错误。handle()方法。 图 7-15。 Click Run ➤ Project, and open the Output pane to see errors being generated in .handle() 显然NetBeans 8.0 并不总是为它为我们编写的引导方法编写最佳代码所以让我们删除代码的throw new UnsupportedOperationException(Not implemented yet.);行(参见图 7-13 )。在它的位置你将插入一个 Java 注释这会创建一个空方法如图 7-16 所示。这将允许您的游戏应用运行。虽然游戏应用窗口启动时抛出了错误但是场景图形的组件没有写入场景只能看到默认的白色背景色。如果您在 NetBeans 中继续学习您将会观察到这一点。 图 7-16。 Replace throw new UnsupportedOperationException(); with a comment, creating an empty method 现在让我们再次使用“剖析➤剖析项目”( InvinciBagel)工作流程看看 NetBeans 的“实时结果”和“线程”选项卡中是否出现了新内容。点击图 7-17 左侧所示的实时结果图标并在选项卡中启动实时结果分析器。注意GamePlayLoop 对象是使用 init 创建的而 AnimationTimer 是使用 invincibagel 启动的。分析器输出中的 GamePlayLoop.start()条目。 如您所见初始化每个事件队列只需要几分之一毫秒包括 脉冲事件和所有四个 ActionEvent EventHandler 事件处理队列。这与我们的最大游戏优化方法一致使用静态场景图节点并且不在 GamePlayLoop 内做任何事情这些事情会消耗更多的系统资源(内存和处理周期),而不是在创建充满动作的街机游戏时完成各种任务所绝对需要的资源。 现在您已经创建并启动了 GamePlayLoop 对象让我们来看看线程档案器 图 7-17。 Use a Profile ➤ Profile Project menu sequence to start the Profiler, and look at GamePlayLoop memory use 再次向下滚动图 7-17 左上角所示的 Profiler 选项卡找到图 7-18 左上角显示的 Threads 图标。NetBeans 将询问您是否要启动线程分析工具一旦你同意它将打开线程标签。如图 7-18 所示脉冲引擎正在运行显示线程 6 的几个脉冲事件。有趣的是一旦 JavaFX 确定。handle()方法为空脉冲事件系统不会继续处理这个空。handle()方法并使用不必要的脉冲事件这表明 JavaFX 脉冲事件系统具有一定的智能。 图 7-18。 Click the Threads icon, seen at the left side of the screen, and open a Threads tab; AnimationTimer pulses can be observed on Thread-6 InvinciBagel 图:包、类和对象 接下来让我们以图表的形式看看你当前的包、类和对象层次结构(见图 7-19 )看看你在创建你的游戏引擎方面处于什么位置。在图的右侧是 InvinciBagel 类它保存场景图以及 Stage、Scene 和 StackPane 对象这些对象保存并显示您的闪屏 UI 设计。图的左边是 gamePlayLoop 类它将包含游戏处理逻辑调用并在 InvinciBagel 类中声明和实例化为 GamePlayLoop 对象但不是场景图形层次的一部分。很快您将开始构建图表中显示的其他功能区域以便您可以控制您的精灵检测精灵之间的碰撞并模拟真实世界的物理力使游戏更加逼真。随着您阅读本书并创建您的 Java 8 游戏您将会看到该图的补充。 图 7-19。 Current invincibagel package, class, and object hierarchy, after addition of the GamePlayLoop 接下来在进入 GamePlayLoop AnimationTimer 类和对象之前您将在当前空的中放置一些相对简单的 Java 代码。handle()方法。您将这样做以确保脉冲引擎正在处理并看看 60FPS 有多快(我不得不承认我的好奇心占了上风). 测试 GamePlayLoop:制作 UI 容器动画 让我们围绕 InvinciBagel 闪屏逆时针移动一个现有的场景图节点例如 HBox 布局容器父(分支)节点它包含四个 UI 按钮控件元素。您将通过使用一个简单的 if-else Java 循环控制编程结构来读取(使用. get()方法)和控制(使用. set()方法)控制(在本例中)屏幕位置角的 Pos 常量。 首先在 GamePlayLoop 类的顶部声明一个名为 location 的 Pos 对象。然后单击突出显示的错误消息按 AltEnter并选择“导入 Pos 类”选项以便 NetBeans 为您编写导入语句。接下来在。handle()方法添加一个 if-else 条件语句该语句计算这个名为 location 的 Pos 对象并将其与表示显示屏四个角的四个 Pos 类常量进行比较这四个角包括 BOTTOM_LEFT、BOTTOM_RIGHT、TOP_RIGHT 和 TOP_LEFT。您的 Java 代码应该类似于下面的 if-else 条件语句 Java 程序结构(参见图 7-20 ): Poslocation Overridepublic void handle(long now) {location InvinciBagel.buttonContainer.getAlignment() if (location Pos.BOTTOM_LEFT) { InvinciBagel.buttonContainer.setAlignment(Pos.BOTTOM_RIGHT);}else if InvinciBagel.buttonContainer.setAlignment(Pos.TOP_RIGHT);}else if InvinciBagel.buttonContainer.setAlignment(Pos.TOP_LEFT);}else if InvinciBagel.buttonContainer.setAlignment(Pos.BOTTOM_LEFT);}}如图所示您的代码没有错误您已经准备好使用“运行➤项目”工作流程并观看 60FPS 的焰火了准备好享受炫目的速度吧 图 7-20。 Create an if-else loop that moves the HBox UI counterclockwise around the four corners of a splash screen 接下来让我们最后一次运行实时结果分析器和线程分析器看看您的脉冲引擎是否正在启动一旦你这样做了你就会知道你已经成功地为你的游戏实现了你的 GamePlayLoop 计时引擎然后你就可以把你的注意力转移到开发你的游戏精灵碰撞检测物理和逻辑上了 剖析游戏循环:脉冲引擎 现在让我们最后一次使用“剖析➤剖析项目”( invincibagel)工作流程看看 NetBeans 的“实时结果”和“线程”选项卡中是否出现了新内容。点击图 7-21 左侧所示的实时结果图标并在选项卡中启动实时结果分析器。注意GamePlayLoop 对象是使用 init 创建的使用 invincibagel 启动了一个动画定时器。分析器输出中的 GamePlayLoop.start()条目有一个不可战胜的怪物。GamePlayLoop.handle(long)条目这意味着您的游戏计时循环正在被处理。 正如您所看到的调用列显示了有多少脉冲访问了。GamePlayLoop 中的 handle()方法。处理 3532 个脉冲只需要 40.1 毫秒因此使用新的 Java 8 计时分辨率每个脉冲相当于 0.0114 毫秒即 114 纳秒。因此您当前用于测试脉冲的代码或者至少是 JavaFX 脉冲引擎是高效运行的。 图 7-21。 Run the Live Results Profiler 当然您需要从。handle()方法然后进入下一章在下一章中您将开始处理这个方法中的游戏资产和逻辑。 接下来让我们最后一次向下滚动图 7-21 左上角显示的 Profiler 选项卡点击图 7-22 左上角显示的 Threads 图标打开 Threads 选项卡。正如您所看到的脉冲引擎正在运行可以看到在 Thread-6 和 JavaFX 应用线程中处理脉冲事件。 鉴于空虚。handle()方法处理来自 Thread-6 中的 GamePlayLoop 对象(见图 7-18 )可以假设 Thread-6 中的脉冲事件来自 GamePlayLoop AnimationTimer 子类。这意味着 JavaFX 应用线程中显示的脉冲事件显示了。handle()方法正在访问 InvinciBagel 类中 stackPane 场景图形根中包含的 buttonContainer HBox 对象。 图 7-22。 Run the Threads Profiler 现在您已经有了一个低开销、速度极快的游戏处理循环您可以开始创建您的其他(精灵、碰撞、物理、得分、逻辑等等)游戏引擎了一个搞定了还有一大堆要做 摘要 在第七章中您编写了在本书的课程中将设计和编码的许多游戏引擎中的第一个GamePlayLoop 游戏播放计时类和对象它们允许您利用强大的 JavaFX 脉冲事件处理系统。 首先您研究了 javafx.animation 包中的不同类以及使用 animation、Transition、Timeline 和 AnimationTimer 类来利用 JavaFX 脉冲事件处理系统的不同方法。 之后您学习了如何在 NetBeans 中创建新的 Java 类然后扩展了 AnimationTimer 超类以创建 GamePlayLoop 子类它将以 60FPS 的速度处理您的游戏逻辑。您看到了如何使用 NetBeans 来帮助您编写这个新子类的大部分代码包括 package 和 class 语句、import 语句和 bootstrap。handle()方法。 接下来您进入 InvinciBagel.java 类使用您创建的新类声明并命名了一个新的 gamePlayLoop GamePlayLoop 对象。然后您测试了代码并对其进行了概要分析以查看 Threads Live Results 选项卡中是否出现了任何新条目。您还测试了。NetBeans 为您编码的 handle()方法并将其更改为空方法以消除由脉冲事件引擎引发的重复错误。接下来您实现了。开始()和。stop()方法使用 Java super 关键字这样您就可以控制对 脉冲引擎的使用如果您想要添加额外的 Java 语句比如保存游戏状态稍后当 脉冲引擎启动和停止时。您再次测试并分析了应用以观察您的进展。最后您将一些测试代码放在。handle()方法这样您就可以再次测试和分析应用以确保脉冲事件引擎快速一致地处理您放在。handle()方法。 在下一章你将会看到如何创建和实现抽象类这些抽象类将会被用来创建你的游戏精灵。一旦我们有了这些它将允许我们在后面的章节中实时地在你的新游戏循环引擎中的显示屏上显示它们制作它们的动画并处理它们的运动。 八、创建你的演员引擎为你的游戏设计角色并定义他们的能力 既然我们已经在第七章的中创建了游戏计时循环让我们在第八章的中进入一些临时代码并创建公共抽象类框架我们可以用它来创建我们将在无敌百吉游戏中使用的不同类型的精灵。这实质上等同于你的游戏的“角色引擎”因为你将定义和设计你的游戏将包括的各种类型的游戏组件作为角色这两个类将用于创建所有其他类这些类将用于创建你的游戏中的对象(组件)。这些将包括诸如无敌面包圈本身(面包圈类)、他的对手(敌人类)、他在游戏中寻找的所有宝藏(宝藏类)、射向他的东西(抛射体类)、他在上面和周围导航的东西(道具类)所有这些都提供了无敌面包圈必须尝试和实现的游戏目标。 在本章中我们将创建两个公共抽象类结构。第一个Actorclass将是另一个Hero 子类的超类。这两个抽象类可以在书中用来创建我们的固定精灵它们是不动的精灵(障碍和宝藏)使用 Actor 超类和在屏幕上移动的精灵使用 Actor 类的 Hero 子类。英雄类将为运动精灵(超级英雄和他在多人游戏版本中的主要敌人)提供额外的方法和变量。这些将跟踪像碰撞和物理特性以及演员动画(运动状态)。在屏幕上有大量的动作会让游戏玩起来更有趣并且让我们让游戏对玩家来说更有挑战性。 预先创建 Actor 引擎将使您获得创建公共抽象类的经验。正如你在第三章中所回忆的公共抽象类在 Java 中被用来创建其他类(和对象)结构但并不直接用在实际的游戏编程逻辑中。这就是为什么我称创建这两个“蓝图”类为创建演员引擎因为我们本质上定义了游戏的最低级别在这一章中的“演员”。 随着本书中游戏设计的进展我们将使用 Actor (fixed sprite)类创建 Treasure 子类用于在游戏过程中 InvinciBagel 将获得的“固定”宝藏。我们还将使用这个 Actor 超类创建 Prop 类用于 InvinciBagel 必须成功地向上、向上、向下、绕过或通过的游戏中的障碍。我们还将使用 Actor 超类的 Hero 子类创建在屏幕上移动的精灵比如 Bagel 类。我们最终会创造一个敌人职业和抛射职业。 除了在本章中设计两个关键的公共抽象演员类我们还将使用不到 10 个 PNG32 数字图像资源来定义我们的主要无敌角色的数字图像状态。我们将在本章中这样做以便在我们想要在本书的下一章中使用这些类和精灵图像状态之前我们将在下一章中查看事件处理以便玩家可以控制 InvinciBagel 在屏幕上的位置以及什么状态(站立、奔跑、跳跃、飞跃、飞行、着陆、错过、蹲伏等)。)他用它来导航他世界中的障碍。 游戏角色设计:预先定义属性 任何流行游戏的基础都是角色——英雄和他的主要敌人——以及游戏的障碍、武器库(投射物)和宝藏。这些“角色”中的每一个都需要使用 Java 中的变量来定义属性这些属性使用系统内存区域来实时跟踪每个角色在游戏过程中发生了什么。我将尝试在第一次就正确地做到这一点就像您希望在第一次定义数据库时定义一个数据库记录结构来保存将来需要的数据一样。这可能是你游戏开发的一个具有挑战性的阶段因为你需要展望未来确定你希望你的游戏和它的演员有什么样的功能然后把这些放到你的演员的能力(变量和方法)前面。图 8-1 让你了解在本章的过程中我们将为游戏角色安装的 24 个属性中的一些因为我们创建了一百多行代码来实现我们的游戏角色引擎。 图 8-1。 Design a public abstract Actor superclass and a public abstract Hero subclass to use to create sprite classes 如你所见我试图得到一个平衡的变量数量在这种情况下在固定精灵演员类和运动精灵英雄类之间每个大约有 12 个。正如你从第三章中所知道的因为我们将要创建的 Hero 子类扩展了 Actor 超类它实际上有 24 个属性或特征因为它假设了所有的超类变量除了有自己的。一个设计上的挑战是将尽可能多的这些属性放在 Actor 超类中这样固定的精灵就有尽可能多的灵活性。一个很好的例子是在第一轮设计中我在 Hero 类中有枢轴点(pX 和 pY 变量),但后来我想了想“如果我想稍后旋转固定精灵(障碍和宝藏)以获得更高的设计效率会怎么样”,所以我将这些变量放在 Actor 超类中将这种枢轴(旋转)功能赋予固定和运动精灵。 我在 Hero 类中“上移”到 Actor 超类的另一个变量是 List 属性。在这个设计过程中我对自己说“如果出于某种原因我希望我的固定精灵有不止一个图像状态呢”我还将 Actor 类从使用简单的 Rectangle Shape 对象升级为使用 SVGPath Shape 子类这样我就可以使用比矩形更复杂的形状来定义碰撞几何体(这就是 spriteBounds 变量的含义),以支持游戏后期更复杂的高级障碍结构。 还要注意我在 Actor 类中有 spriteFrame ImageView它保存 sprite 图像资产因为固定和运动 sprite 都使用图像所以我可以将 ImageView 放入 Actor 超类中。我在 Actor 超类中使用 imageStates 列表,这样固定精灵就可以像运动精灵一样访问不同的“视觉状态”。正如您可能已经猜到的List 是一个填充了 JavaFX Image 对象的 Java List 对象。Actor 类中的 iX 和 iY 变量是图像(或初始)位置 X 和 Y它们在游戏级别布局上放置一个固定的 sprite但当由 Hero 子类假定时也将保持运动 sprite 的当前 sprite 位置。其他变量保存布尔状态(活/死等。)和寿命、损坏、偏移、碰撞或我们稍后需要的物理数据。 无敌精灵图像:视觉动作状态 除了设计用于实现游戏中的角色、宝藏和障碍的最佳演员引擎类之外另一个要优化的重要内容是游戏的主要角色以及角色根据玩家的角色移动而在动画的不同状态之间移动。从内存优化的角度来看我们能够完成所有这些的图像帧越少越好。正如你在图 8-2 中看到的我将只使用九种不同的精灵图像资源来提供所有的无敌角色运动状态其中一些可以以多种方式使用:例如通过使用 pX 和 pY 变量这将允许我们围绕我们选择的任何枢轴点旋转这些 sprite 帧。这方面的一个例子是飞行状态的中心轴点放置如图 8-2 中间所示通过将该图像顺时针旋转 50 度(水平方向)到 100 度(倾斜向下飞而不是向上飞)我们就可以起飞(向上飞)、飞行和着陆(向下飞)。 图 8-2。 The nine primary character motion sprites for the InvinciBagel character that will be used during the game 尽管我们在 sprite Actor 引擎抽象类中提供了偏移和枢轴点功能但这并不意味着我们不应该确保我们的运动 sprite 图像状态彼此之间很好地同步。这使得我们不必经常使用这些旋转或偏移功能来获得良好的视觉效果。这就是我所说的子画面注册包括不同的子画面状态相对于彼此的最佳定位。 在图 8-3 中可以看到一些将相互使用的子画面帧之间的子画面注册的例子。例如开始运行 imageStates[1]精灵应该以与站立(或等待)imageStates[0]精灵相同的脚位置开始其运行周期如图 8-3 左侧所示。此外相对于开始运行 sprite 的 imageStates[1],运行的 imageStates[2] sprite 应该尽可能保持其主体部分不动。一个准备着陆的 imageStates[6] sprite 应该相对于着陆的 imageStates[7] sprite 真实地改变脚的位置。 图 8-3。 Sprite registration (alignment) to make sure the transition motion is smooth 相对于所有其他精灵您想要做的是优化精灵注册将所有数字图像精灵放入相同的正方形 1:1 纵横比分辨率图像格式并将它们全部放在数字图像合成软件包(如 GIMP 或 Photoshop)的层中。然后使用移动工具和轻推(使用键盘上的箭头键移动单个像素)每个精灵到适当的位置相对于您打开可见性(使用每个层左侧的眼睛图标打开/关闭)的两个层。结果如图 8-3 所示。 创建执行元超类:修复执行元属性 让我们开始编写我们的公共抽象演员类这将是我们在本书中为游戏创建的所有精灵的基础。我不会重新讨论如何在 NetBeans 中创建新类(参见图 7-2 ),因为您已经在第七章中了解到了这一点所以创建一个 Actor.java 类使用公共抽象类 Actor 声明它并将前五行代码放在类的顶部声明一个名为 imageStates 的 List Image ,创建一个新的 ArrayList 对象以及一个名为 spriteFrame 的 ImageView、一个名为 spriteBound 的 SVGPath 和双变量 iX 和 iY。对所有这些进行保护这样任何子类都可以访问它们如图 8-4 所示。对于与 List 类(对象)、ArrayList 类(对象)、Image 类(对象)、ImageView 类(对象)和 SVGPath 类(对象)所需的导入语句相关的红色错误下划线您需要使用 Alt-Enter 工作流程。一旦 NetBeans 为您编写了这些代码声明 List Image ArrayList、spriteFrame ImageView、SVGPath collision Shape 对象以及包含 sprite 的 X 和 Y 位置的 double 变量的十几行代码应该类似于下面的 Java 类结构: package invincibagel;import java.util.ArrayList;import java.util.List;import javafx.scene.image.Image;import javafx.scene.image.ImageView;import javafx.scene.shape.SVGPath;publicabstract protected ListImageimageStates protected ImageViewspriteFrame protected SVGPathspriteBound protected doubleiX protected doubleiY }图 8-4。 Create a New Class in NetBeans, name it public abstract class Actor, and add in the primary Actor variables 这五个变量或属性持有任何 sprite 的“核心”属性spriteFrame ImageView 和它保存的图像资产(一个到多个可见状态)的 List ArrayList(这定义了子画面的外观)、spriteBound 碰撞形状区域(定义了被认为与子画面相交的区域)以及子画面在显示屏上的 XY 位置。 这五个变量也将在稍后使用 Actor()构造函数方法和 Hero()构造函数方法进行配置。首先我们将创建 Actor()构造函数之后我们将添加所有其他变量我们需要每个 Actor 子类都包含这些变量。 在我们为 Actor 类创建了所有其他变量(这些变量不是使用 Actor()构造函数方法设置的)之后我们将初始化这些变量以在构造函数方法中保存它们的默认值最后我们将让 NetBeans 创建。get()和。使用一个你会喜欢的自动编码函数为我们的变量设置()方法。 我们将编码并传递给这个 Actor()构造函数的参数将包括名为 SVGdata 的 String 对象它将包含一个定义 SVGPath 冲突形状的文本字符串以及 sprite XY 位置和一个逗号分隔的图像对象列表。SVGPath 类有一个. setContent()方法可以读取或“解析”原始 SVG 数据字符串因此我们将使用它将字符串 SVG data 变量转换为 SVGPath 碰撞形状对象。 我们将不会在本章或下一章中实现碰撞代码或 SVGPath Shape 对象但我们需要将它们放在适当的位置这样我们可以在后面的第十六章碰撞检测处理以及如何使用 GIMP 和 PhysEd (PhysicsEditor)软件包创建碰撞多边形数据中使用它们。 我们将创建的 Actor 构造函数方法将遵循以下构造函数方法格式: public Actor(StringSVGdata, doublexLocation, doubleyLocation, Image...spriteCels) 稍后如果我们需要创建更复杂的 Actor()构造函数方法我们可以通过添加其他更高级的参数来“重载”该方法例如 pivot point pX 和 pY或者 isFlipH 或 isFlipV 布尔值以允许我们水平或垂直镜像固定的 sprite 图像。您的 Java 代码将如下所示: public Actor(StringSVGdata, doublexLocation, doubleyLocation, Image...spriteCels) { spriteBound new SVGPath();spriteBound.setContent(SVGdata spriteFrame new ImageView(spriteCels[0] imageStates.addAll(Arrays.asList(spriteCels iX xLocation iY yLocation }请注意使用 Java new 关键字调用的 ImageView 构造函数通过使用 spriteCels[0]注释使用逗号分隔的列表传递您正在传递的 List ArrayList 数据的第一帧(Image)。如果您要创建一个允许您设置轴心点数据的重载方法它可能如下所示: public Actor(StringSVG, doublexLoc, doubleyLoc, doublexPivot, doubleyPivot, Image...Cels){ spriteBound new SVGPath();spriteBound.setContent(SVG spriteFrame new ImageView(Cels[0] imageStates.addAll(Arrays.asList(Cels iX xLoc iY yLoc pX xPivot pY yPivot }如图 8-5 所示您需要使用 Alt-Enter 工作流程并让 NetBeans 为您的 Arrays 类编写导入语句。一旦你这样做了你的代码就不会有错误。 图 8-5。 Create a constructor method to set up fixed Actor sprite subclasses with collision shape, Image list, location 接下来让我们编写这个类的另一个关键方法抽象方法。update()方法然后我们可以添加我们将需要的 Actor 类的其余固定 sprite 属性。之后我们可以初始化 Actor()构造函数方法中的附加变量。最后我们将学习如何为 Actor 类创建“getter 和 setter”方法然后继续使用这个新的自定义 Actor 超类来创建我们的另一个 Hero motion sprites 子类。 创建一个。update()方法:连接到 GamePlayLoop 引擎 对于任何 sprite 类来说除了创建它的构造函数方法之外最重要的方法是。update()方法。那个。update()方法将包含 Java 8 代码告诉 sprite 在 GamePlayLoop 的每个脉冲上做什么。因为这个原因这个。update()方法将用于将使用我们的 Actor 超类和 Hero 子类创建的 Actor sprite 子类“连接”到我们在第七章中创建的 GamePlayLoop 计时引擎中。 因为我们需要一个。update()方法作为游戏中每个 Actor 对象(actor sprite)的一部分我们需要包含一个“空的”(目前)抽象。我们当前正在编写的 Actor 超类中的 update()方法。 正如您在第三章中了解到的这个公共抽象方法在 Actor 超类中是空的或者更准确地说是未实现的但是需要在任何 Actor 子类中实现(也就是说需要被实现)(或者再次声明为抽象方法)包括我们稍后将要编码的 Hero 子类。 该方法被声明为 public abstract void因为它不返回任何值(它只是在每个 JavaFX 脉冲事件上执行)并且不包含{…}花括号因为它里面(还)没有任何代码体声明公共抽象(空的或未实现的)方法的单行代码应该如下所示: publicabstractvoidupdate 正如你在图 8-6 中看到的这个方法实现起来非常简单一旦你在你的 Actor()构造函数方法下添加了这个新方法你的 Java 8 代码再次没有错误你就可以准备添加更多的代码了。 图 8-6。 Add an Arrays import statement to support constructor method; add a public abstract .update() method 接下来我们将为我们的固定精灵演员超类添加其余的属性(或变量)这需要我们提前考虑在创建这个游戏期间我们希望能够用我们的精灵完成什么。 向 Actor 类添加 Sprite 控件和定义变量 从编码的角度来看这个过程的下一部分很简单因为我们将在 Actor 类的顶部声明更多的变量。然而从设计的角度来看这更加困难因为它要求我们尽可能地提前思考并推测我们的精灵演员(固定精灵和运动精灵)需要什么样的可变数据以便能够在游戏的构建和游戏过程中做我们想做的一切。 在 iX 和 iY 变量之后我要声明的第一个附加变量是 pX 和 pY 枢轴点变量。我最初将它们放在 Hero 子类中一旦我们完成了这个 Actor 超类的创建接下来我们将创建它。我将这些“升级”到演员超类级别的原因是因为我想拥有旋转固定精灵(宝藏和障碍)以及运动精灵的灵活性。在关卡和场景设计方面这给了我更多的权力和灵活性。这些支点 X 和 Y 变量将被声明为受保护的 double 数据变量并使用下面两行 Java 代码来完成: protected doublepX protected doublepY 接下来我们需要在 Actor 类(对象)定义中添加一些布尔“标志”。这些将指示关于所讨论的精灵对象的某些事情例如它是活的(对于固定精灵这将总是假的)还是死的或者它是固定的(对于固定精灵这将总是真的对于不在运动中的运动精灵也是真的)还是移动的或者奖励对象指示它们的捕获(碰撞)的附加点(或寿命)或者有价值的指示它们的获取(碰撞)的附加能力(或寿命)。最后我定义了一个水平翻转和垂直翻转标志与没有这些标志的情况相比使用(固定的或运动的)精灵图像资源给了我四倍的灵活性。 由于 JavaFX 可以在 X 或 Y 轴上翻转或镜像图像这意味着我可以使用 FlipV 反转精灵方向(向左或向右),或使用 FlipH 反转方向(向上或向下)。 这六个额外的布尔标志固定(演员)sprite 属性将通过使用受保护的布尔数据变量来声明使用以下六行 Java 8 代码如图 8-7 所示(没有错误没有减少): protected booleanisAlive protected booleanisFixed protected booleanisBonus protected booleanhasValu protected booleanisFlipV protected booleanisFlipH 图 8-7。 Add the rest of the variables needed to support rotation (pivot point), and sprite definition states 接下来我们将在 Actor()构造函数方法中初始化这些变量。如果您想使用参数列表将这些布尔标志的设置传递给 Actor()构造函数方法请记住您可以创建任意多的重载构造函数方法格式只要每个格式的参数列表都是 100%唯一的。在本书的后面部分我们可能会这样做例如如果我们需要一个构造函数方法来为布局设计的目的旋转我们的固定精灵或者一个围绕给定的轴翻转它例如为了相同的确切目的或者一个两者都做的方法这将给我们一个九参数 Actor()构造函数方法调用。 在 Actor 构造函数方法中初始化 Sprite 控件和定义变量 现在我们将把轴心点 pX 和 pY 初始化为 0(左上角原点),所有布尔标志的值都设为 false只有 isFixed 变量除外对于固定的 sprite它的值总是设为 true。我们将在当前 Actor()构造函数方法中使用以下八行 Java 代码并在该方法中处理使用方法参数配置 Actor 对象的前四行代码的下面使用这些代码: pX 0;pY 0;isAlive false;isFixed true;isBonus false;hasValu false;isFlipV false;isFlipH false;我们也可以使用复合初始化语句来实现这一点。这将把代码减少到三行: px pY 0; isFixed true;isAlive isBonus hasValu isFlipV isFlipH false; 正如你在图 8-8 中看到的我们现在已经编写了近 30 行无错误的 Java 8 代码我们准备创建剩下的。get()和。set()方法将组成公共抽象 Actor 超类。 图 8-8。 Add initialization values to the eight new fixed sprite pivot and state definition variables you just declared Actor 类中的其余方法通常称为“getter”和“setter”方法因为这些方法提供了对类内部数据变量的访问。使用 getter 和 setter 方法是正确的做法因为这样做实现了 Java 封装的概念(和优势)它允许 Java 对象成为对象属性(可变数据值)和行为(方法)的自包含容器。 访问参与者变量:创建 Getter 和 Setter 方法 NetBeans 的一个真正强大的(并且节省时间的)功能是它将编写您的所有。get()和。自动为每个对象和数据变量设置()方法。我们将在本书中尽可能使用这一便利的特性因此您可以习惯于使用这一节省时间的特性来为您编写大量 Java 8 代码加速您的 Java 8 游戏代码生产输出您可以通过使用源菜单及其插入代码子菜单来访问该自动编码功能如图 8-9 所示。可以看到还有一个键盘快捷键(Alt-Insert)使用其中任何一个都会调出浮动生成菜单该菜单在图 8-9 的底部中央以红色高亮显示。 图 8-9。 Use Source ➤ Insert Code menu (or AltInsert) to bring up a Generate Getter and Setter dialog and select all 点击在生成浮动菜单中间高亮显示的 Getter 和 Setter 选项将出现一个生成 Getter 和 Setter 对话框如图 8-9 右侧所示。确保层次结构是打开的并且 Actor 旁边的复选框被选中这将自动选择该类中的所有变量在这种情况下在图 8-9 的右侧也显示了十几个被选中的变量。 一旦所有这些都被选中点击对话框底部的 Generate 按钮生成 24。get()和。如果 NetBeans 8.0 没有提供这一便利的 IDE 功能则必须手动键入 set()方法。 这些。get()和。由 NetBeans 8.0 源代码➤插入代码➤生成➤ Getters 和 Setters 菜单序列生成的 set()方法将为您提供以下 24 个 Java 方法代码构造相当于我们在公共抽象 Actor 类中定义的 12 个变量中的每一个都有两个方法: public ListImagegetImageStates() return imageStates;}public voidsetImageStates(ListImage imageStates) this.imageStates imageStates;}public ImageViewgetSpriteFrame() return spriteFrame;}public voidsetSpriteFrame(ImageView spriteFrame) this.spriteFrame spriteFrame;}public SVGPathgetSpriteBound() return spriteBound;}public voidsetSpriteBound(SVGPath spriteBound) this.spriteBound spriteBound;}public doublegetiX() return iX;}public voidsetiX(double iX) this.iX iX;}public doublegetiY() return iY;}public voidsetiY(double iY) this.iY iY;}public doublegetpX() return pX;}public voidsetpX(double pX) this.pX pX;}public doublegetpY() return pY;}public voidsetpY(double pY) this.pY pY;}public booleanisAlive() return isAlive;}public voidsetIsAlive(boolean isAlive) this.isAlive isAlive;}public booleanisFixed() return isFixed;}public voidsetIsFixed(boolean isFixed) this.isFixed isFixed;}public booleanisBonus() return isBonus;}public voidsetIsBonus(boolean isBonus) this.isBonus isBonus;}public booleanhasValu() return hasValu;}public voidsetHasValu(boolean hasValu) this.hasValu hasValu;}public booleanisFlipV() return isFlipV;}public voidsetIsFlipV(boolean isFlipV) this.isFlipV isFlipV;}public booleanisFlipH() return isFlipH;}public voidsetIsFlipH(boo lean isFlipH) this.isFlipH isFlipH;}注意除了。get()和。set()方法生成对于布尔变量还有一个附加的。是()方法它是代替。get()方法。因为我已经使用“is”前缀命名了布尔标志所以我将删除第二个“Is ”,以便这些“double is”方法更具可读性。我还将对 hasValu 方法做同样的事情这样在方法调用中查询布尔设置就更自然了例如。hasValu()、isFlipV()、isBonus()、isFixed()或。例如 isFlipH()。为了可读性我建议您对代码进行同样的编辑。 现在我们准备创建我们的 Hero 子类它将向我们在 Actor 类中创建的 13 个属性添加另外 11 个属性使总数达到 24 个。Hero 类中的这 11 个附加属性将用于可以在屏幕上移动的可移动精灵(我喜欢称之为运动精灵)。在我们游戏的单人版本中我们的无敌英雄角色将是主要的英雄角色对象在未来的多人版本中这将包括无敌英雄角色对象和敌人英雄角色对象。 创建英雄超类:动作演员属性 接下来让我们创建我们的公共抽象英雄类这个类将是我们在本书中为游戏创建动作精灵的基础。在 NetBeans 中创建您的 Hero.java 类并将其声明为public abstract class Hero extends Actor。由于我们已经在 Actor 类中完成了许多“繁重的工作”,所以您不必创建 ImageView 来保存 sprite 图像资产也不必创建 List Image ArrayList 对象该对象加载了一个由 Image 对象填充的 List 对象或者创建一个 SVGPath Shape 对象来保存碰撞形状 SVG polyline(或多边形)路径数据。 由于我们不必声明任何主属性因为这些属性是从 Actor 超类继承的所以我们要做的第一件事是创建一个 Hero()构造函数方法。这将包含字符串对象中的碰撞形状数据sprite XY 位置以及将加载到 List ArrayList 对象中的图像对象。在我们创建了一个基本的 Hero()构造函数方法之后我们将完成计算你的运动精灵需要包含的其他属性(或变量),就像我们在设计 Actor 超类时所做的一样。 请记住您已经有了使用 Actor()方法在 Actor 类中构造的 spriteBound SVGPath Shape 对象、imageStates List ArrayList 对象、SpriteFrames Image 对象以及 iX 和 iY 变量。为了能够编写我们的 Hero()构造函数方法我们还需要这些。由于这些都已经就绪由于 Hero 类声明中的 java extends 关键字我们所要做的就是使用 super()构造函数方法调用并将这些变量从 Hero()构造函数向上传递给 Actor()构造函数。这将使用 Java super 关键字自动将这些变量传递给 Hero 类供我们使用。 因此我们已经具备了编写核心 Hero()构造函数方法所需的一切现在让我们开始吧。Hero()构造函数将接受与 Actor()构造函数相同数量的复杂参数。这些包括碰撞形状数据包含在名为 SVGdata 的字符串对象中子画面的“初始位置”X 和 Y 位置以及子画面的图像对象(cels 或 frames)的逗号分隔列表我将其命名为 Image…斯普里特塞尔。这张照片…designation 需要在参数列表的末尾因为它是“开放式的”这意味着参数列表将传入一个或多个图像对象。您的代码将如下所示: public void Hero(StringSVGdata, doublexLocation, doubleyLocation, Image...spriteCels) { super(SVGdata,xLocation,yLocation,spriteCels); }通过使用 super()将核心构造函数传递到 Actor 超类 Actor()构造函数方法您之前编写的代码(在 Actor()构造函数内部)将使用 Java new 关键字和 SVGPath Shape 子类创建 spriteBound SVGPath Shape 对象并将使用 SVGPath 类。setContent()方法以便加载 SVGPath Shape 对象以及要用于 sprite 图像状态的碰撞形状。设置了 iX 和 iY 的初始位置imageStates 列表数组加载了从参数列表末尾传入的 sprite 图像对象。 值得注意的是因为我们是这样设置的所以 Hero 类可以访问 Actor 类所拥有的一切(十三个强大的属性)。实际上反过来看可能更“突出”演员(固定精灵)类拥有英雄(运动精灵)类的所有能力。这种能力应该用于关卡设计 wow factor包括多图像状态(List Array)、自定义 SVGPath 碰撞形状功能、自定义枢轴点位置以及围绕 X 轴(FlipV true)或 Y 轴(FlipH true)或两个轴(FlipH FlipV true)翻转(镜像)精灵图像的能力。将这些功能放入你的 Actor 引擎(Actor 和 Hero 抽象超类)只是第一步在你的游戏设计和编程中出色地使用它们随着时间的推移你会继续构建和完善游戏这是本章中奠定基础的最终目标。正如你在图 8-10 中看到的我们的基本(核心)构造函数代码是没有错误的。 图 8-10。 创建一个公共抽象类 Hero extends Actor 并添加一个构造函数方法和一个 super()构造函数 添加更新和冲突方法:。更新()和。碰撞() 现在我们有了一个基本的构造函数方法稍后我们会添加它让我们添加所需的抽象。update()方法以及. collide()方法因为运动精灵正在移动因此可能会与物体发生碰撞首先让我们添加public abstract void .update();方法因为它是我们的 Actor 超类所需要的。这样做实质上是向下传递(或者向上传递如果您愿意的话)了实现需求。update()方法从 Actor 超类到 Hero 子类并继续到 Hero 的任何未来子类(这将使 Hero 成为一个超类并更好地反映其名称)。未来的非抽象(函数)类将实现这一点。update()方法该方法将用于完成游戏编程逻辑的所有繁重工作。正如你在图 8-11 中看到的运动精灵(Hero 子类)也需要有一个碰撞检测方法我称之为。collide()因为这是一个更短的名字至少现在除了返回一个布尔值 false(这里没有冲突老板)布尔数据值。的 Java 代码。collide()方法结构将把一个 Actor 对象作为它的参数因为这是你的 Hero 对象将与之碰撞的对象应该如下所示: public booleancollide(Actor object returnfalse }图 8-11。 Add the Override public abstract void .update() and public boolean .collide(Actor object) methods 接下来我们再给这个英雄类增加十一个变量。这些将保存适用于运动精灵的数据值这些精灵必须处理与物体的碰撞并遵守物理定律。我们还需要一些东西比如寿命变量以及一个保存累积伤害(点数)的变量如果敌人互相射击伤害就会累积。我们将添加受保护的变量如 X 和 Y 速度X 和 Y 偏移(用于微调物体相对于精灵的位置)碰撞形状旋转和缩放因子最后是摩擦重力和反弹因子。 向 Hero 类添加 Sprite 控件和定义变量 我们需要做的下一件事是确保我们需要保存运动精灵数据的所有变量都在 Hero 类的顶部定义如图 8-12 所示。NetBeans 将使用这些信息为 Hero 类创建 getter 和 setter 方法。Java 代码应该是这样的: protected doublevX protected doublevY protected doublelifeSpan protected doubledamage protected doubleoffsetX protected doubleoffsetY protected doubleboundScale protected doubleboundRot protected doublefriction protected doublegravity protected doublebounce 图 8-12。 Add eleven variables at the top of Hero class defining velocity, lifespan, damage, physics, collision 在我们添加所有 22 个 getter 和 setter 方法之前总共是 11 个。get()和 11。set()方法为了匹配我们新的 Hero 类变量让我们回过头来完成我们的 Hero()构造函数方法并初始化我们刚刚在 Hero 类顶部添加的这十一个变量。 在 Hero 构造函数中初始化 Sprite 控件和定义变量 让我们给我们的英雄演员对象(运动精灵)1000 个单位的寿命并设置其他变量为零你可以看到我已经使用复合初始化语句来节省八行代码。如图 8-13 所示代码没有错误Java 编程语句应该采用以下格式: lifespan 1000 vX vY damage offsetX offsetY 0 boundScale boundRot friction gravity bounce 0 图 8-13。 Add initialization for your eleven variables inside of your constructor method using compound statements 在我们生成 getter 和 setter 方法之前让我们看看如何使用复合变量声明语句的组合以及如果我们不显式地指定它们Java 将为我们的变量设置哪些默认变量类型值以减少编写整个 Hero 类所需的代码量从 25 行代码(如果不使用复合变量初始化语句则为 33 行代码)减少到 14 行代码。 如果不计算带一个花括号(三个)的代码行我们说的是不到十几行 Java 语句包括包、类和导入声明来编码整个公共抽象类。这是相当令人印象深刻的考虑到核心类给了我们多少运动精灵的力量和能力。当然在我们添加了 22 个 getter 和 setter 方法(每个方法有 3 行代码)后我们将有大约 80 行代码没有空格。值得注意的是NetBeans 将为我们编写超过 75%的此类代码相当酷。 通过复合语句和缺省变量值优化 Hero 类 在让 NetBeans 为我们编写 getter 和 setter 方法之前我将做两件主要的事情来减少 Hero 类的主要部分的代码量。第一种方法是对所有相似的数据类型使用复合声明首先声明受保护的 double 和受保护的 float 修饰符和关键字然后在它们后面列出所有变量用逗号分隔这在编程术语中称为“逗号分隔”。11 个 Hero 类变量声明的 Java 代码现在将如下所示: protected double vX, vY, lifeSpan, damage, offsetX, offsetY;protected float boundScale, boundRot, friction, gravity, bounce;正如你在图 8-13 和 8-14 中看到的我们为初始化做了相同类型的复合语句: lifeSpan 1000;vX vY damage offsetX offsetY 0;boundScale boundRot friction gravity bounce 0;如果您碰巧正在 HDTV 显示屏上进行编辑也可以只用两行代码来完成: lifeSpan 1000;vX vY damage offsetX offsetY boundScale boundRot friction gravity bounce 0;接下来如果我们依靠 Java 编译器将变量初始化为零因为如果没有指定初始化值double 和 float 变量将被初始化为我们可以将这两行代码减少为一行代码: lifeSpan 1000;现在我们已经完成了 Hero()构造函数方法的“核心”,让 NetBeans 编写一些代码吧 图 8-14。 Optimize your Java code by using compound declarations, and leveraging default initialization values 访问英雄变量:创建 Getter 和 Setter 方法 在你的。collide()方法并将光标放在那里这将向 NetBeans 显示您希望它放置将要生成的代码的位置。这在图 8-15 中由源菜单后面的浅蓝色阴影线显示。使用源插入代码菜单序列或 Alt-Insert 击键组合当生成浮动弹出菜单出现在这条蓝线下时(这显示了选中的代码行)选择 Getter 和 Setter 选项在图 8-15 中高亮显示并选择所有的英雄职业。确保选择了所有的英雄类变量或者通过使用英雄类主选择复选框或者通过使用每个变量的复选框 UI 元素如图 8-15 的右侧所示。 图 8-15。 Use the Source ➤ Insert Code ➤ Generate ➤ Getter and Setter menu sequence and select all class variables 单击“生成 Getters 和 Setters”对话框底部的“生成”按钮后您将看到 22 个新方法它们都是 NetBeans 为您编写的全新方法。这些方法如下所示: public doublegetvX() return vX;}public voidsetvX(double vX) this.vX vX;}public doublegetvY() return vY;}public voidsetvY(double vY) this.vY vY;}public doublegetLifeSpan() return lifeSpan;}public voidsetLifeSpan(double lifeSpan) this.lifeSpan lifeSpan;}public doublegetDamage() return damage;}public voidsetDamage(double damage) this.damage damage;}public doublegetOffsetX() return offsetX;}public voidsetOffsetX(double offsetX) this.offsetX offsetX;}public doublegetOffsetY() return offsetY;}public voidsetOffsetY(double offsetY) this.offsetY offsetY;}public floatgetBoundScale() return boundScale;}public voidsetBoundScale(float boundScale) this.boundScale boundScale;}public floatgetBoundRot() return boundRot;}public voidsetBoundRot(float boundRot) this.boundRot boundRot;}public floatgetFriction() return friction;}public voidsetFriction(float friction) this.friction friction;}public floatgetGravity() return gravity;}public voidsetGravity(float gravity) this.gravity gravity;}public floatgetBounce() return bounce;}public voidsetBounce(float bounce) this.bounce bounce;}值得注意的是使用 Hero 类创建的对象也可以访问我们之前为 Actor 类生成的 getter 和 setter 方法。如果你想知道 Java 关键字在所有这些中意味着什么。set()方法它引用的是使用 Actor 或 Hero 类构造函数方法创建的当前对象。因此如果您调用。iBagel Bagel 对象(我们将在第十章中创建)的 setBounce()方法这个关键字指的是这个(iBagel) Bagel 对象实例。因此如果我们想要设置 50%的反弹因子我们将使用我们的新。setBounce() setter 方法: iBagel.setBounce(0.50 接下来让我们看看这些 sprite Actor 类是如何与我们在本书中编写的其他类相适应的。在那之后我们将总结我们在这一章中学到的东西我们可以进入这本书的未来章节并使用这些类为我们的游戏创建精灵就像我们学习如何在游戏中使用精灵一样。 更新游戏设计:演员或英雄如何融入 让我们更新一下我在第七章(图 7-19)中介绍的图表以包括 Actor.java 和 Hero.java 类。正如你在图 8-16 中看到的我不得不切换。更新()物理和。collide()图的冲突部分因为 Actor 类只包括。update()方法而 Hero 类包含了这两种方法。自从。collide()方法将在。update()方法我也用 chrome 球体连接了图的这两个部分。 那个。GamePlayLoop 对象中的 handle()方法将调用这些。update()方法所以这里也有联系。Actor 和 Hero 类与 InvinciBagel 类之间存在联系因为使用这些抽象类创建的所有游戏 sprite 对象都将在该类的方法中声明和实例化。 我们在开发我们的游戏引擎框架方面取得了很大的进展同时我们也看到了 Java 8 编程语言的一些核心特性是如何为我们所用的。在下一章的事件处理中我们将会看到强大的 Java 8 lambda expressions 新特性所以关于 Java 8 前沿特性的更多知识将会在游戏中出现。希望你和我一样激动 图 8-16。 The current invincibagel package class (object) hierarchy, now that we have added Actor and Hero classes 摘要 在第八章中我们写了第二轮的游戏引擎我们将在本书中设计和编码演员(固定精灵)超类和它的英雄(运动精灵)子类。一旦我们在第十章和后续章节开始创建游戏精灵英雄职业也将成为超职业。从本质上讲在这一章中你学习了如何创建公共抽象类这些类将在本书中用来定义我们的 sprite 对象。这相当于为我们游戏中的所有演员(精灵)做了所有繁重的工作(精灵设计和编码工作)使我们从现在开始为我们的游戏创建强大的固定和运动精灵变得更加容易。我们正在首先建立我们的知识库和我们的游戏引擎框架 我们首先看一下这些演员和英雄类将如何设计以及我们将使用它们创建什么类型的实际精灵类。我们查看了九个 sprite 图像资源以及这些资源如何通过仅使用九个资源来覆盖广泛的运动并查看了如何相对于彼此“注册”sprite“状态”。 接下来我们设计并创建了我们的 Actor 超类以处理固定的精灵如 props 和 treasure创建了基本的 List 、ImageView、SVGPath、iX 和 iY 变量以及一个构造函数方法该方法使用这些来定义固定的精灵外观、位置和碰撞边界。然后我们添加了一些额外的变量我们将需要在未来的游戏设计方面并了解如何让 NetBeans 写。get()和。set()方法。 接下来我们设计并创建了我们的 Hero 子类它扩展了 Actor 来处理运动精灵例如不可战胜的妖怪自己和他的敌人以及投射物和移动挑战。我们创建了基本的构造函数方法来设置 Actor 超类中的变量这次是为了定义运动精灵图像、初始位置和碰撞边界。然后我们添加了一些额外的变量这将是我们在未来的游戏设计方面需要的并再次看到了 NetBeans 将如何编写我们的。get()和。为我们设置()方法看起来总是很有趣 最后我们看了一下更新的 invincibagel 包、类和对象结构图看看在本书的前八章中我们已经取得了多大的进步。这越来越令人兴奋了 在下一章中我们将看看如何控制游戏精灵我们将使用本章中创建的演员引擎来创建游戏精灵。接下来的第九章将涵盖 Java 8 和 JavaFX 事件处理这将允许我们的游戏玩家使用事件处理来操纵(控制)这些演员精灵。
文章转载自:
http://www.morning.qzfjl.cn.gov.cn.qzfjl.cn
http://www.morning.jtmql.cn.gov.cn.jtmql.cn
http://www.morning.ccpnz.cn.gov.cn.ccpnz.cn
http://www.morning.ryxdf.cn.gov.cn.ryxdf.cn
http://www.morning.dtrz.cn.gov.cn.dtrz.cn
http://www.morning.bkylg.cn.gov.cn.bkylg.cn
http://www.morning.hmbxd.cn.gov.cn.hmbxd.cn
http://www.morning.xkpjl.cn.gov.cn.xkpjl.cn
http://www.morning.jsrnf.cn.gov.cn.jsrnf.cn
http://www.morning.buyid.com.cn.gov.cn.buyid.com.cn
http://www.morning.mzwqt.cn.gov.cn.mzwqt.cn
http://www.morning.slysg.cn.gov.cn.slysg.cn
http://www.morning.wcghr.cn.gov.cn.wcghr.cn
http://www.morning.txgjx.cn.gov.cn.txgjx.cn
http://www.morning.wcqxj.cn.gov.cn.wcqxj.cn
http://www.morning.ysnbq.cn.gov.cn.ysnbq.cn
http://www.morning.lffrh.cn.gov.cn.lffrh.cn
http://www.morning.zxrtt.cn.gov.cn.zxrtt.cn
http://www.morning.qwmpn.cn.gov.cn.qwmpn.cn
http://www.morning.nqgjn.cn.gov.cn.nqgjn.cn
http://www.morning.mrkbz.cn.gov.cn.mrkbz.cn
http://www.morning.zwwhq.cn.gov.cn.zwwhq.cn
http://www.morning.tturfsoc.com.gov.cn.tturfsoc.com
http://www.morning.xgjhy.cn.gov.cn.xgjhy.cn
http://www.morning.lsgjf.cn.gov.cn.lsgjf.cn
http://www.morning.ykswq.cn.gov.cn.ykswq.cn
http://www.morning.ltpph.cn.gov.cn.ltpph.cn
http://www.morning.sgbk.cn.gov.cn.sgbk.cn
http://www.morning.xplng.cn.gov.cn.xplng.cn
http://www.morning.lwlnw.cn.gov.cn.lwlnw.cn
http://www.morning.sxcwc.cn.gov.cn.sxcwc.cn
http://www.morning.nhzxd.cn.gov.cn.nhzxd.cn
http://www.morning.youyouling.cn.gov.cn.youyouling.cn
http://www.morning.dtgjt.cn.gov.cn.dtgjt.cn
http://www.morning.gbxxh.cn.gov.cn.gbxxh.cn
http://www.morning.bnqcm.cn.gov.cn.bnqcm.cn
http://www.morning.xcszl.cn.gov.cn.xcszl.cn
http://www.morning.xbmwh.cn.gov.cn.xbmwh.cn
http://www.morning.gnwpg.cn.gov.cn.gnwpg.cn
http://www.morning.ryjl.cn.gov.cn.ryjl.cn
http://www.morning.zdfrg.cn.gov.cn.zdfrg.cn
http://www.morning.spxsm.cn.gov.cn.spxsm.cn
http://www.morning.trjr.cn.gov.cn.trjr.cn
http://www.morning.qqhmg.cn.gov.cn.qqhmg.cn
http://www.morning.nqpy.cn.gov.cn.nqpy.cn
http://www.morning.zcwwb.cn.gov.cn.zcwwb.cn
http://www.morning.cbnxq.cn.gov.cn.cbnxq.cn
http://www.morning.hjssh.cn.gov.cn.hjssh.cn
http://www.morning.fdfsh.cn.gov.cn.fdfsh.cn
http://www.morning.gybnk.cn.gov.cn.gybnk.cn
http://www.morning.nffwl.cn.gov.cn.nffwl.cn
http://www.morning.fthcn.cn.gov.cn.fthcn.cn
http://www.morning.krkwp.cn.gov.cn.krkwp.cn
http://www.morning.fkrzx.cn.gov.cn.fkrzx.cn
http://www.morning.zhiheliuxue.com.gov.cn.zhiheliuxue.com
http://www.morning.mdrnn.cn.gov.cn.mdrnn.cn
http://www.morning.dtlqc.cn.gov.cn.dtlqc.cn
http://www.morning.rjkfj.cn.gov.cn.rjkfj.cn
http://www.morning.sjwiki.com.gov.cn.sjwiki.com
http://www.morning.jwtwf.cn.gov.cn.jwtwf.cn
http://www.morning.wjwfj.cn.gov.cn.wjwfj.cn
http://www.morning.jjnql.cn.gov.cn.jjnql.cn
http://www.morning.sskhm.cn.gov.cn.sskhm.cn
http://www.morning.nzhzt.cn.gov.cn.nzhzt.cn
http://www.morning.wjlbb.cn.gov.cn.wjlbb.cn
http://www.morning.ggnfy.cn.gov.cn.ggnfy.cn
http://www.morning.fynkt.cn.gov.cn.fynkt.cn
http://www.morning.tnfyj.cn.gov.cn.tnfyj.cn
http://www.morning.fwllb.cn.gov.cn.fwllb.cn
http://www.morning.dzgmj.cn.gov.cn.dzgmj.cn
http://www.morning.fmznd.cn.gov.cn.fmznd.cn
http://www.morning.ktdqu.cn.gov.cn.ktdqu.cn
http://www.morning.fjmfq.cn.gov.cn.fjmfq.cn
http://www.morning.rzbgn.cn.gov.cn.rzbgn.cn
http://www.morning.yknsr.cn.gov.cn.yknsr.cn
http://www.morning.paoers.com.gov.cn.paoers.com
http://www.morning.wfttq.cn.gov.cn.wfttq.cn
http://www.morning.pyxtn.cn.gov.cn.pyxtn.cn
http://www.morning.spsqr.cn.gov.cn.spsqr.cn
http://www.morning.dlwzm.cn.gov.cn.dlwzm.cn
http://www.tj-hxxt.cn/news/249037.html

相关文章:

  • 国外的ui设计思想网站烟台企业做网站
  • 汕头中文建站模板淮北网站建设公司
  • 十堰做网站的深圳防疫今天最新规定
  • wordpress更新的文章编辑器不好用上海seo
  • 做网站p图工具郑州seo公司哪家好
  • wordpress多站点命名石家庄logo标志设计
  • 空间建设网站房山营销型网站建设
  • 建一个多用户团购网站需要多少钱pc手机模板网站建设
  • 做哪些网站流量最大最有创意的logo设计
  • 广州门户网站开发做网站的公司有
  • 微盟属于营销型手机网站wordpress 坐标
  • 有哪些网站做明星周边微网站建设要多少钱
  • 个人网页制作模板免费网站seo诊断分析报告
  • 用python做的大型网站黑客收徒网站建设
  • 正邦网站建设win优化大师
  • 中山企业网站建设方案广州建网站兴田德润信任
  • 做公益筹集项目的网站wordpress用什么数据库连接
  • 会计做帐模板网站点开文字进入网站是怎么做的
  • 校友会网站建设的目的用ps制作海报教程方法步骤
  • 网站建设年终总结怎么写惠州有做网站的吗
  • 社交网站建设教程顺企网下载安装
  • 编辑网站的软件手机软件唯尚广告联盟app下载
  • 做网站全屏尺寸是多少钱济南网站建设外包公司排名
  • 丹阳火车站片区规划郑州做网站远辰
  • 网站定制开发 团队义乌市住房和城乡建设局网站
  • 简单手机网站页游在线玩
  • 邢台网站优化服务平台wordpress在这个站点注册
  • 最大郑州网站建设公司建设企业网站有什么好处
  • 深南花园裙楼 网站建设懂网络维护和网站建设的专业
  • 电子商务网站建设方面的论文外贸公司经营范围