一、Java 的不同面貌:创建一个 Java 9 开发工作站
欢迎来到专业 Java 9 游戏开发书。在第一章中,我将讨论 Java 的各种版本,这些版本目前仍被用于为 Android 等开源平台以及基于 WebKit 的开源浏览器(如 Google Chrome、Mozilla Firefox、Apple Safari 和 Opera)开发软件应用。在了解了从 JDK 1.6(也称为 Java 6)到 JDK 1.9(最近发布为 Java 9)的 Java 版本之后,需要使用哪些版本来开发这些流行平台的各种版本。我们还需要详细了解如何创建一个专业的 Java 9 软件开发工作站,以便在本书的剩余部分使用。这将包括其他软件,如新媒体内容制作软件包,这些软件包可以与您的 Java 软件开发包一起用于创建游戏和物联网(IoT)应用。
工作站的核心将是 Java 8 SDK 或软件开发工具包,也称为 JDK 或 Java 开发工具包,或者是 2017 年推出的新 Java 9 JDK,比 Java 8 更加模块化,但具有相同的类和方法来创建游戏或物联网用户体验。这个事实将允许我们在本书的过程中安全地关注 Java 8 和 Java 9。这是因为,就我们的目的而言,这些基本上是相同的,允许我们关注最新的 Java APIs,而不是您正在使用的 Java 版本。事实上,由于我们将重点讨论 Java 的多媒体 API,通常称为 JavaFX,所以您在本书中学到的内容也可以用 Java 7 编码!Android 最近升级到 Java 7 和 Java 8 兼容(从 Java 6)。
我们还将为您设置 NetBeans 9.0 IDE 或集成开发环境,这将使 Java 8 或 9 游戏的编码变得更加容易。预计在 2017 年第四季度 Java 9 发布后会使用 NetBeans 9,因为 NetBeans 9 IDE 将进行重大升级,以适应 Java 9 的新模块化特性,并允许您混合功能模块,为任何类型的应用开发创建自定义 Java 包集合(API 版本)。
配置好 Java JDK 和 NetBeans IDE 后,我们将为您安装最新的开源新媒体内容创建软件包,包括用于数字成像的 GIMP、用于数字插图的 InkScape、用于数字视频编辑或特殊效果的 DaVinci Resolve、用于数字音频编辑的 Audacity、用于特殊效果和 3D 的 Fusion、用于业务和项目管理的 Open Office 4 Suite、用于 3D 建模、纹理、动画、渲染、粒子系统、流体动力学或特殊效果的 Blender,以及用于虚拟星球的 Terragen 4。
在本章的最后,我甚至会推荐一些其他的专业级软件包,你应该考虑将它们添加到我们将在本章中创建的专业游戏开发工作站中。这样,当我们完成第一章的时候,你将拥有一个对你的企业非常有价值的生产资源。希望仅这第一章就值得你为这整本书所付出的,因为你可以花 500 美元购买一个强大的 64 位工作站,并在几个小时内使它价值五位数!
我们还将讨论新的 Java 9 内容生产工作站的一些硬件要求和注意事项。最后,请注意,本书中的 Java 代码在 Java 8 IDE(或集成开发环境)中也能很好地工作,所以本书可以很容易地被称为专业 Java 8 游戏开发书!
Java 二分法:在各种平台上使用的版本
有许多不同版本的 Java 仍然被广泛用于许多不同流行平台的开发,包括用于 32 位 Android 的 Java 6(Android 的版本 1.x、2.x、3.x 和 4.x 是 32 位的),以及用于早期 64 位 Android 版本(5.0、5.1 和 6.0)的 Java 7,用于最新 Android 版本(7.0、7.1.2、8.0)的 Java 8,以及用于 Windows 10 操作系统、Ubuntu Linux 操作系统(和其他 Linux)的 Java 9
值得注意的是,Java 有三个主要版本;Java ME 或 Micro Edition 针对嵌入式设备进行了优化,Java SE 或 Standard Edition 用于“客户端”以及移动消费电子设备和 iTV 设备,Java EE 或 Enterprise Edition 可被视为“服务器端”范例,因为大型企业计算环境通常是基于服务器的,而不是“对等的”(纯客户端,除了客户端-服务器交互之外,还可能有客户端-客户端交互)。
于 2006 年 12 月发布的 Java 6(十多年前)仍然被广泛用于与 Eclipse IDE 一起为所有 32 位版本的 Android 开发应用,从版本 1.0 到版本 4.4。这是因为这是谷歌最初指定用于开发 32 位 Android 应用的 Java 版本,当时 Android 1.0 于 2008 年 9 月发布。值得注意的是,Google 使用 Open Java 项目创建了 Java 6 的自定义版本,但这不会影响编程 API,因为如果您在 NetBeans IDE 或 IntelliJ IDEA 中使用 Java 6,而不是使用 Eclipse IDE,这些类、方法和接口的功能仍然相同。
当谷歌将 Android 升级到 64 位 Linux 内核时,在 Android 5.x 中,使用了基于 IntelliJ 的 Android Studio IDEA,他们升级到使用 Java 7,Java 7 也有 64 位版本。Java 7 于 2011 年 7 月发布。因此,如果您正在为高级平台开发 Android 5-6 应用,如 Android Wear,这在 Apress 的 my Pro Android Wearables (2015)标题中有所介绍,或者 Android TV 或 Android Auto,在 Apress 的 Android Apps for Absolute 初学者(2017)标题中有所介绍,您将希望利用 Java 7。JavaFX 8 和 JavaFX 9 中的 JavaFX 8 引擎也被移植到 Java 7 中;然而 Java 7 今年退役了。Android 还在用 Java 6,7,8。
在撰写本书时,Java 8 是 Java SE 的最新版本,此外,它还具有强大的 JavaFX 8.0 多媒体引擎,该引擎也与 Java 7 兼容,尽管 JavaFX 8.0 APIs 在 Android APIs 中还没有得到本机支持。然而,开发 JavaFX 8 或 9 应用在 Android 操作系统和 iOS 平台上运行是可能的,这使得这本书对我们的读者更有价值!Java 8 支持所有流行的浏览器,Android 7、7.1.2 和 8.0,以及所有四种流行的操作系统,包括 Windows 7、8.1 和 10,所有 Linux 发行版、Macintosh OS/X 和 Oracle 的 Open Solaris。Java 8 于 2014 年 3 月发布,增加了一个名为 Lambda Expressions 的强大新功能,我们将在本书中介绍,因为这是一种编写更紧凑代码的方式,通常也更高效地处理多处理器(和多线程)。
Java 9 是 Java 的下一个主要版本。Java 9 于 2017 年 9 月 22 日发布。Java 语言开发人员正在改造的 Java 9 中的主要新特性是使 Java 9 语言 API 模块化。这将允许 Java 9 开发者在“模块”(代码库)中“混合和匹配”特性,并创建他们自己的定制的、优化的 Java 版本。对于定制开发环境或定制应用,这些定制 Java 版本将完全按照开发人员需要的方式工作。截至本书发布时,NetBeans 9 仍在开发中。
作为游戏开发人员,或者物联网开发人员,这意味着您可以创建几个游戏开发定制 Java 版本级别,或者几个定制物联网开发 Java 版本级别。从 Java 7 版本开始,如果需要的话,添加 Lambda 表达式(一种编码快捷方式,我们将在后面介绍)来创建 Java 8 版本,或者打包成定制模块(Java 9 中的一个新特性)来为所有流行的 OS 平台创建 Java 9 版本。如果您使用 JavaFX 多媒体/游戏引擎,Java 8 和 Java 9 APIs 中都有最新的 JavaFX 特性。
我想向读者指出,他们可以优化他们的游戏程序逻辑,以跨越几个版本的 Java,优化 Java 7 (Android 5 或 6)到 Java 8 (Android 7,8 和现代操作系统)到 2017 年 9 月 22 日在图书发行前问世的 Java 9 版本。这也可以在不做任何重大代码更改的情况下完成,因为除了使用 Lambda 表达式之外,核心 JavaFX 游戏处理逻辑存在于所有这些 Java 版本中。
Java 开发工作站:必需的硬件
为了从我们将在本章过程中安装的所有专业开源软件中获得最佳效果,您将需要一个强大的 64 位工作站,运行付费操作系统,如 Windows 10 或 OSX,或免费操作系统,如 Ubuntu LTS 17。我在几个工作站上使用 Windows 10,在几个工作站上使用 Ubuntu LTS 17.10。你还需要一个大显示器,最好是高清的(1920 x 1080)或者 UHD 的(3840 x 2160)。如果你算一下,一台 UHD 显示器是四个高清显示器在一个边框里,UHD 显示器现在是 300 到 500 美元。我在感恩节大减价时花了 250 美元买了一个。我使用的高清尺寸从 32 英寸到 43 英寸不等,UHD 尺寸从 44 英寸到 55 英寸不等,像素密度很小。
一台计算机工作站应该配备(包含)至少 8GB 的 DDR3 系统内存(16GB 或 32GB 的系统内存会更好)。该存储器应以 1333、1600、1866 或 2133 兆赫的时钟速度循环。尖端系统通常配备以 2400 兆赫时钟速度运行的 DDR4 系统内存。DDR4 内存还配有 16GB DIMMS,因此您可以在工作站主板上安装 48GB、64GB 或 128GB 内存。我会为运行 Fusion 9、DaVinci Resolve 14、Blender 2.8、JavaFX 9 或其他 i3D 制作软件的工作站这样做。
系统内存运行得越快,计算机处理数据的速度就越快,CPU 获得所需处理数据的速度也就越快。这就把我们带到了进行处理的工作站的“大脑”或 CPU/GPU。同样的概念也成立;64 位 CPU 每秒可以处理的指令越多,您在更短的时间内完成的任务就越多,您的 i3D 应用执行给定功能的速度就越快。
如今,几乎所有 64 位工作站都将配备多核处理器,通常称为 CPU 或中央处理器。流行的 CPU 包括 AMD 锐龙(四核、六核或八核)、9590(八核或八核)或更昂贵的英特尔 i7,它有四核、六核、八核和十核版本。像 AMD 锐龙一样,英特尔 i7 的每个内核都有两个线程,因此对于一个操作系统来说,这些处理器看起来像 8、12、16 或 20 个内核的处理器,这就是它们比 AMD FX 9590 系列处理器更贵的原因。我使用 AMD 锐龙或英特尔 i7 处理器,这取决于应用。例如,Android Studio 3 针对英特尔硬件架构进行了优化,在 AMD FX CPU 上模拟 Android 虚拟设备(AVD)的速度不够快,无法顺利进行开发和测试。
为了存储数据,你还需要一个硬盘驱动器。现在的电脑通常配备 1TB 的硬盘,你甚至可以得到 2TB、3TB、4TB、6TB 或 8TB 硬盘的工作站。如果您正在处理具有 UHD 或 4K 屏幕分辨率的游戏(或 3D、电影、特效或视频素材),请选择 3GB 或 4GB 型号。如果您希望您的系统快速引导(启动),并快速将软件加载到内存中,请确保将 SSD(固态硬盘)作为您的主要(C:\ for Windows,或 C:/ for Linux)硬盘分配。这些硬盘比传统的 1tb 硬盘更贵,但你只需要 64GB 或 128GB 来存放你的操作系统和软件。我有一个 256GB 的固态硬盘,512GB 的固态硬盘和 768GB 的固态硬盘也变得更加实惠。
具有这些功能的工作站基本上已经成为商品,价格在 500 美元到 750 美元之间,可以在沃尔玛或百思买购买,或者在网上的 www.PriceWatch.com
购买,在那里你可以比较我在本章这一节提到的任何组件的市场价格。如果你是 Java 9 游戏开发的新手,如果你还没有合适的工作站,去沃尔玛或 PriceWatch.com,购买你负担得起的 3D 多核(购买 4、6 或 8 核)64 位计算机,运行 Windows 10 或 Ubuntu LTS 17,至少有 8、16 或 32g 的 DDR3 系统内存。你还需要一个相当大的硬盘驱动器,至少 750GB,甚至 1.5TB 或 2TB 的硬盘驱动器,以及 AMD(镭龙)或 nVidia (GeForce)的 3D GPU,用于 JavaFX 9 以及 Fusion、Blender 和 DaVinci Resolve 的实时 i3D 渲染。
在本章的剩余部分中,我将假设您刚刚购买了一台经济实惠的 64 位工作站,我们将完全从头开始创建一个 premiere Java 9 游戏和物联网开发工作站!如果你已经有一个现有的游戏开发工作站,我将包括一个简短的部分,向你展示如何从 Windows 中删除过时的 Java 开发软件,以便我们都可以从头开始。
为 Java 9 游戏开发准备一个工作站
假设您已经有一个专业级工作站用于新媒体内容开发和游戏开发,您可能需要移除过时的 JDK 或 IDE,以确保您拥有最新的软件。在本节中,您要做的第一件事是确保您已经删除了任何过时的 Java 版本,如 Java 6 或 Java 7,以及任何过时的 NetBeans 版本,如 NetBeans 6 或 NetBeans 7。这包括从工作站卸载(移除或完全删除)过时的 Java 开发软件版本。我不得不在我的一个四核 AMD 工作站上这样做,以便为 NetBeans 9.0 IDE 开发 Java 9 和 JavaFX 9 应用和游戏腾出空间,因此本节中的屏幕截图显示了 Windows 7 操作系统。您将通过使用操作系统软件管理实用程序来实现这一点。在 Windows 上,这是“程序和功能”实用程序。这可以在 Windows 控制面板下找到,在图 1-1 的中间一列(第七行)以蓝色突出显示。
图 1-1。
Use the Programs and Features utility icon to uninstall or change programs on your computer workstation
如果您有一个全新的工作站,您将不必删除任何以前的软件。对于 Linux 和 Mac 来说,有类似的软件安装和卸载工具,如果你碰巧正在使用这些操作系统的话。由于大多数开发人员使用的是 64 位版本的 Windows 7、8.1 或 10,因此本书只使用 64 位操作系统平台。
值得注意的是,Java 9 现在只有 64 位版本,所以你必须有一个 64 位的工作站,正如我在本书的前一节中所指出的(事实上,现在你甚至不能买一台新的 32 位计算机)。
自定义 Windows 操作系统“chrome”(窗口 UI 元素)、桌面和已安装软件包的方式是通过 Windows 控制面板及其 50 多个实用程序图标。其中之一是程序和功能图标(在 Windows 版本 7 到 10 中),在图 1-1 中可以看到它被选中为蓝色。
请注意,在早期版本的 Windows (Vista 或 XP)中,此程序实用程序图标的标签会有所不同,如:添加或删除程序。它仍然以同样的方式工作,选择软件,右键单击,并删除旧版本。我不建议使用过时的 Vista 或 XP,因为高级 Java 9 JDKs 和 ide 不再支持它们。
对于以前版本的 Windows,单击此程序和功能链接,或双击图标,并启动该实用程序。向下滚动,查看您的工作站上是否安装了任何旧版本的 Java 开发工具(Java 5、Java 6 或 Java 7)。请注意,如果您有一个全新的工作站,您应该会发现系统上没有预装的 Java 或 NetBeans 版本。如果您找到它们,请将系统退回,因为它以前可能被使用过。
如图 1-2 所示,在我的 Windows 7 开发工作站上,我安装了一个旧版本的 Java 8u131,占用了 442 兆的硬盘空间,安装于 2017 年 4 月 22 日。这用于运行 NetBeans 9 的“Alpha”版本,该版本在 Java 8 上运行。要删除一个软件,通过点击选择它(它会变成蓝色),或者点击图 1-2 顶部所示的卸载按钮,或者你也可以右击(蓝色)软件包(删除)选项,并从出现的上下文菜单中选择卸载。
我在屏幕截图中留下了工具提示:“卸载”,这样你就可以看到,如果你将鼠标“悬停”在程序和功能实用程序中的任何东西上,它会告诉你该特定功能的用途。
图 1-2。
Select versions of Java older than Java 9 and click the Uninstall option at the top, or right-click and uninstall
单击卸载按钮后,该实用程序将删除旧版本的 Java 8。首先删除较小版本的 Java 8(非 JDK ),然后删除较大版本(完整 JDK ),因为需要完整 JDK 来删除较小的 JDK 以及任何旧版本的 NetBeans。如果要删除 NetBeans IDE,您需要安装 Java 8,因为 NetBeans IDE 是用 Java 编写的,需要安装 Java JDK 才能卸载。
一旦去掉完整的 Java 8 JDK,就只剩下 Java 9 的(Alpha)版本(如果你是我,写这本书的话,就是这样),如图 1-3 所示,标注为 9.0.0.0 版本。如果您想保留您的旧 Java 项目文件,请确保备份您的 Java 项目文件文件夹,如果您还没有这样做的话。确保您定期备份工作站的硬盘驱动器,以便不会丢失任何 3D、内容制作和编码工作。
我移除了 Java 9 JDK 软件的任何 Alpha 或 Beta 版本,方法是再次点击它(它会变成蓝色),然后或者点击图 1-3 顶部的卸载按钮,或者你也可以右键单击蓝色软件包(移除)选项,并从上下文相关菜单(通过右键单击打开)中选择卸载。
图 1-3。
Select Alpha versions of Java 9, and click the Uninstall option at the top, or, right-click, and select Uninstall
现在,我已经从我的工作站上删除了 Java 的过时版本,我将从互联网上获取最新的 Java 9 开发工具包(JDK)版本,并将其安装在我的 Windows content development 工作站上。
下载并安装 Oracle Java 9 JDK
既然已经从您的工作站中删除了 Java 的过时版本,那么您需要上网并访问 Oracle 网站来获取最新的 Java 9 开发 JDK 和 IDE,因为毕竟这是一本专业的 Java 9 游戏开发书籍。在我写这本书的时候,我将向你展示如何使用直接下载的 URL 来做到这一点,以及它们现在的位置。如果这些链接已经更改,只需使用谷歌搜索“Java 9 JDK 下载”该下载目前位于甲骨文技术网,如图 1-4 中屏幕截图的顶部所示。
图 1-4。
The JDK9 Download link at oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.htm
在下载用于 64 位 Windows 的 360 兆字节 JDK9 安装程序文件之前,您需要单击“Accept License Agreement”选项旁边的单选按钮,该选项位于下载表的左上角。
一旦您接受本许可协议,五个特定于操作系统的链接将被激活使用,包括 Linux、Mac OS/X、Windows (7 到 10)和 Solaris。确保下载的 Java JDK 软件与您的操作系统相匹配。如您所见,现在只有 64 位(或 x64)版本可用于 64 位系统。
要启动下载的 JDK9 安装程序,请右键单击该文件,并使用管理员权限(或在 Linux 上作为超级用户)以管理员身份运行来安装它。接受六个对话框中的默认设置,如图 1-5 所示。
图 1-5。
Install the Java 9 JDK on the workstation, accepting the default settings in the six Java 9 installation dialogs
如果你想检查你的系统上是否安装了 Java 9,只需使用与图 1-1 到 1-3 中相同的控制面板实用程序。如图 1-6 所示,Java 9.0 的真实版本(不是 alpha 版本)现在安装在我的系统上,大小为 763 兆字节,在我的情况下,安装于 2017 年 10 月 7 日。
图 1-6。
Find the JDK-for the latest (currently 9.0.1) Java 9 version and make sure it is installed
接下来,让我们安装 Java 8,它目前用于运行 NetBeans 8.2(您可能已经在使用该 IDE 进行开发),并且目前也用于运行 NetBeans 9.0 IDE (beta),我在本书中使用了该 IDE,因为最终 Java 9 和 NetBeans 9 将一起用于开发 Java 9 游戏。在过渡期间,NetBeans 9.0 在 Java 8 上运行,所以我添加了一两节来介绍它是如何工作的,以供可能使用这种配置的早期读者使用。
下载并安装 Oracle Java 8 JDK
你可能想知道为什么我们现在正在下载 Java 8 的最新版本(目前更新 152 ),因为这是一本 Java 9 游戏本。原因是尽管 Java 9 JDK 于 9 月份发布,但 NetBeans 9 IDE 版本仍处于测试阶段(我写这本书时它仍处于 alpha 阶段),这意味着 NetBeans 9(测试版)仍运行在 Java 8 之上,这是由于 Java 9 中模块的复杂性(这意味着程序员仍在模块化 NetBeans 9,以便它将在 Java 9 中编码)。一旦 NetBeans 9 发布,它很可能会直接运行在 Java 9 JDK 之上。有一种方法可以访问 Oracle Tech Network 上的一个网页,该网页同时链接到 Java 8u144 和 Java 9.0,位于 URLwww.oracle.com/technetwork/java/javase/overview/index.html,如图 1-7 所示。两个 JDK 的下载链接都位于网页的最底部,所以只需单击 Java SE 8 update 144 JDK(已经升级到 8u152)的下载链接。
图 1-7。
The Oracle Tech Network Java SE Overview webpage, which has links to Java 9 JDK as well as to Java 8u144
正如你所看到的,在网页的中间也有一个红色的甲骨文 JDK 8 警告的公共更新。Java 8 没有太多的 bug,毕竟它已经经历了超过 144 次的更新,非常稳定!从某种意义上说,Java 9 是一种重写,因为它已经被重新模块化,所以所有 API 类和包(到模块中)的“连接”都被重做,这就是为什么 NetBeans 9(用 Java 编码)没有与 Java 9 同时完成(编码和调试)。Java 和 NetBeans 的早期版本同时(或接近同时)发布,并有 NetBeans 捆绑包下载(在图 1-8 的顶部显示了 NetBeans 8.2,通过右侧的下载图标)。
图 1-8。
The Oracle Tech Network Java SE Download webpage which has links to Java 8u144 JDK at the very bottom
图 1-8 中的 Java SE 下载页面是上一页的下载链接将带您进入的页面。在页面底部,您会找到 Java SE 8u144 部分,其中有三个下载按钮。最上面的第一个按钮写着 JDK。这是您想要单击以开始 JDK 8u144 下载的按钮。这将把你带到 Oracle . com/tech network/Java/javase/downloads/JDK 8-downloads-2133151 . html 的页面,如图 1-9 所示。
单击接受许可证单选按钮以启用所有下载链接,然后单击适用于您的操作系统版本的链接。
图 1-9。
The JDK8 Download link at oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.htm
注意,对于 Java 8,有 32 位(i586)和 64 位(x64)版本,以及 ARM CPU 版本,这给了我们十几个选择。为您的操作系统选择 64 位版本,以匹配您为 Java 9.0 安装的版本。
现在,我们可以安装 NetBeans 9.0 集成开发环境(简称 IDE),它将使用我们刚刚安装的 Java 8 运行时引擎(JRE)来运行 Java 代码,这些代码将为您创建 NetBeans 9 IDE。
因为 NetBeans 9 正在从 Oracle 过渡到 Apache,所以目前实际上有两个代码存储库。首先,我将向您展示我在写书时使用的服务器,它由 Oracle 托管,其次,我将向您展示由 Apache 托管的服务器,它使用了一个名为 Jenkins 的测试版存储库,还有一个到 GIT 的链接,如果您愿意,您可以从零开始构建 NetBeans IDE。最终还会有一个 Java 9 和 NetBeans 9 的“捆绑包”作为单一安装。这当然是最简单也是最需要的,但是目前还不存在,所以我将介绍构建和安装 NetBeans 9.0 的更高级的方法,因为它还没有完成。目前这使得安装变得复杂,但是我对此无能为力,只能提供所有这些额外的信息,以便您可以在最终的 NetBeans 9 on Java 9 捆绑包发布之前,为 Java 9 和 JavaFX 9 开发安装并运行 NetBeans 9.0。就 Pro Java 9 游戏开发而言,这给了你一个领先他人的开始。
安装 Oracle NetBeans 9.0(开发)IDE
由于 NetBeans 9 仍在开发中,我将向您展示我是如何从 Oracle 获得 NetBeans 9 发行版的,并在下一节中展示普通公众最终将如何从 Apache 获得 NetBeans 9 IDE。通过这种方式,您将了解下载和安装 NetBeans 9 的所有方法。Oracle 存储库(在软件正式转移到 Apache 之前将一直存在)位于 bits.netbeans.org/download/trunk/nightly/latest/,看起来像大家都熟悉的原始 NetBeans 下载页面,如图 1-10 所示。我建议使用该软件最简单(最小)的 Java SE 版本,因为它包括我们在本书中涉及的三个 API(NetBeans、Java 和 JavaFX)。单击第一个下载(免费,97MB)按钮,开始 NetBeans 下载过程。
图 1-10。
The Oracle NetBeans 9.0 IDE download page located at bits.netbeans.org/download/trunk/nightly/latest/
一旦这个安装文件下载完毕,右击它,选择以管理员身份运行(Linux 上的超级用户),你会看到第一个欢迎对话框,在六个对话框截图的左上角,如图 1-11 所示。
单击“下一步”按钮开始默认(完全)安装,您将看到 NetBeans IDE 9 许可协议对话框,如图 1-11 中上部所示。选择“我接受许可协议中的条款”复选框(用红色圈出),然后单击“下一步”按钮,前进到 NetBeans IDE 编译安装对话框,如图 1-11 右侧所示。第三个对话框指定了 Program Files 目录中的安装位置,还指定了用于 Java 开发的 JDK。请注意,NetBeans 9 足够聪明,可以选择 Java 9 而不是 Java 8(您已经安装了 Java 9 和 Java 8,因为您可以在任何给定的工作站上安装多个 Java 版本),并定义您将为哪个版本的 Java 开发游戏(以前必须在 NetBeans 中手动设置)。保持默认设置,点击下一步按钮进入总结对话框,如图 1-11 左下方所示。确保选中“检查更新”,以便 NetBeans 9 会自动更新自身。
图 1-11。
Accept the terms of the license agreement, click the Next button (left) and do the same for JUnit (right)
单击“安装”按钮后,NetBeans 将安装基本 IDE,如图 11-1 中底部的对话框所示,通过进度条和下面的解压缩文件文本向您显示它正在做什么。设置完成后,您将看到“设置完成”对话框,该对话框将为您提供“通过提供匿名使用数据为 NetBeans 项目做贡献”复选框选项我选择了这个选项,以帮助 NetBeans 开发人员。
我们将在第一章中安装各种游戏开发和游戏素材开发软件包,最后一步是通过启动软件来测试安装,以确保它能够运行。
这是通过在您的桌面(双击桌面图标以启动它们)或任务栏(称为快速启动图标,只需单击即可启动)中找到软件图标并启动软件来完成的。在 NetBeans 9 IDE 的情况下,结果应该类似于左侧的图 1-12 。
要确认 NetBeans 9 是如何设置的,请使用关于菜单序列的帮助➤,它位于图 1-12 的右侧,显示了您的产品版本、正在使用的 Java JDK 版本、正在使用的 Java 运行时环境(JRE )( JRE 是 JDK 安装的一部分)来运行 NetBeans 9.0、正在使用的操作系统以及用户目录位置和缓存目录位置。如果您在安装此 IDE 的后续版本时遇到问题,请尝试删除这两个文件(即删除\dev 文件夹),因为它们包含以前 NetBeans 安装的信息,这可能会误导下一次 NetBeans 安装。
图 1-12。
Launch NetBeans using the Desktop or Quick Launch (Silver Cube), and make sure the software will launch
接下来,我将向您展示如何以及在哪里安装 Apache NetBeans 产品,因为在某个时间点,NetBeans 9.0 将完成从 Oracle 到 Apache 的转移(就像 Open Office 一样)。我不确定这将在何时发生,可能在 2018 年的某个时候,但我不能等那么久来发布这本书,所以我将简单地向您展示您可以获得 NetBeans 9.0 的所有不同方法。注意,如果您想在 Java 8 的基础上使用 8.2,这也很好,因为 JavaFX 8(和 JavaFX 9)类(API)没有改变。这是因为 Java 9(和 NetBeans 9)的重点只是将模块引入工作流并使 IDE 工作,所以 JavaFX 被单独留下,重点是 Java 的其他部分(正如您将看到的,JavaFX 是 Java 多媒体/游戏引擎)。
安装 Apache NetBeans 9(开发)IDE
接下来,我们将了解用于 NetBeans 的 Apache Jenkins 和 GIT 存储库,在当前正在进行的传输完成后,软件将在这里“着陆”。Apache Jenkins 的 NetBeans 站点位于 https://builds.apache.org/job/incubator-netbeans-windows/,是一个所谓的“孵化器”站点。孵化器用于孵化鸡蛋,因此这里的推论是,在“孵化”NetBeans 9 on Java 9 捆绑包之前,这是您可以在 NetBeans 9 IDE 软件仍处于开发阶段时获得它的地方。你可以在图 1-13 中看到 Apache Jenkins 网站此时的样子(这有可能会改变)。如你所见,它有很多选项。
图 1-13。
Apache Jenkins’ NetBeans is located at https://builds.apache.org/job/incubator-netbeans-windows/
Jenkins 的左上方有 Jenkins 软件孵化器功能导航链接,因此您可以返回到仪表板(主页),获得开发状态,查看构建之间的更改,查看依赖关系图,获得构建时责任报告,查看 GIT 轮询日志,获得嵌入式构建状态,查看测试结果分析器,跳过构建并打开 Blue Ocean。Blue Ocean 是一个免费的、开源的、持续更新的工具,它让你感觉你是软件开发团队的一员。
下面是构建历史。这是一个包含构建的窗格,在构建时,带有进度条、构建时间和完成估计。如果你点击其中的一个版本(如果已经完成),它会打开另一个窗口(browser 选项卡),显示该版本的详细信息和下载链接。如图 1-14 所示。
要下载其中一个 ZIP 文件,请右键单击该文件,并使用“另存为”功能,将该 ZIP 文件保存在硬盘上要从中解压缩 NetBeans 9 的位置(目录/文件夹)。请注意,这是一种不同于安装程序的方法。exe 或。msi)将采取,因为安装程序会将文件与其他已安装的应用一起放入 Program Files 文件夹,并将创建桌面图标和任务栏快速启动图标。
有人告诉我,Linux 版本也可以在 Windows 下运行,但是在某些时候,Mac OS/X、Linux 和 Windows 可能会有不同的版本。我还在开发人员列表中添加了一个请求,请求建立一个 Ubuntu LTS Linux 17 PPA 存储库,以便 Ubuntu LTS 可以自动更新 NetBeans 9.0 IDE,几乎不需要最终用户干预。如果你还没有看过 Ubuntu LTS 17.10 或 18.04,你可能想现在就看;你会惊讶于 Ubuntu Linux (Debian,另一个主要的 Linux 发行版是 Fedora)相对于 OSX 或 Windows 已经走了多远。
一旦你解压 NetBeans 9(我把我的文件夹命名为 NetBeans-9-Build ),进入/bin(二进制)文件夹,右键单击 netbeans64.exe,并使用 Run as Administrator。在启动品牌屏幕和加载进度条之后,您将看到一个许可协议对话框,您必须接受(同意)该对话框才能启动 IDE 软件。
图 1-14。
Click a version to get a build page, shown is builds.apache.org/job/incubator-netbeans-linux/74/
接下来,让我们去下载十几个最流行的(免费的)开源新媒体内容开发软件包,这样您就拥有了 Pro Java 9 游戏开发业务最终需要的所有强大的专业工具。这代表了数以万计的(你的货币是美元,我的是美元)付费软件包,所以这第一章最终会对所有读者变得相当有价值。
之后,我将告诉你一些我在工作站上使用的其他令人印象深刻的开源软件,这样,如果你想在本章结束之前组装最终的软件开发工作站,你可以这样做,以硬件(和操作系统)的成本创建一个非常有价值的内容制作工作站。
安装新媒体内容制作软件
JavaFX 9 支持许多新媒体元素的“类型”,我称之为“素材”, Java FX 9 是 Java 9 的新媒体“引擎”,因此您将使用它作为专业 Java 9 游戏开发的基础。在本章的剩余部分,您将安装领先的开源软件的主要新媒体类型包括:SVG 数字插图、数字图像合成、数字音频编辑、数字视频编辑、VFX 或视觉效果、3D 建模和动画、虚拟世界创建、角色动画、歌曲创作、数字音频采样、办公效率(是的,您还必须销售您的游戏)等等。
下载并安装 InkScape for SVG 数字插图
由于 JavaFX 支持 Adobe Illustrator 和 Freehand 等数字插图软件包中常用的 2D 或“矢量”技术,我们将下载并安装流行的开源数字插图软件包 InkScape,该软件包最近在版本控制方面从 0.48 大幅提升到 0.92,并具有专业功能。InkScape 可用于 Linux、Windows 和 Macintosh 操作系统,就像我们将在本章安装的所有软件包一样,因此读者可以使用他们喜欢的任何平台来开发游戏。如果您想了解更多关于数字插图和 SVG 的知识,可以看看来自 Apress 的数字插图基础知识。
要在互联网上找到 InkScape 软件包,请使用谷歌搜索引擎,并输入 InkScape。访问网站,点击左上方的下载菜单或右边的下载图标,如图 1-15 所示。下载图标将代表您正在使用的操作系统,由网站代码自动检测,该代码会轮询您的系统正在使用的操作系统,并通过单击自动为您提供正确的版本。
图 1-15。
Google the word InkScape, go to the inkscape.org website, click on the download icon, or download menu
下载 InkScape 软件后,右键单击文件名,并以管理员身份运行,在您的工作站上安装该软件。如果您愿意,可以使用本章前面使用的程序和功能实用程序来卸载以前的 Inkscape 版本。
安装软件后,在任务栏上创建一个快速启动图标,这样只需单击鼠标即可启动 InkScape。接下来,您将安装一个名为 GIMP 的流行数字图像软件包,它允许您使用 JPEG、PNG、WebP 或 GIF 数字图像格式为游戏创建“光栅”或基于像素的艺术作品。
下载并安装 GIMP 进行数字图像合成
因为 JavaFX 还支持利用“光栅”图像技术的 2D 图像,该技术将图像表示为像素阵列。这是付费数字图像合成软件包中使用的内容,如 Adobe Photoshop 和 Corel Painter。我们将下载并安装流行的开源数字图像编辑和合成软件包“Gimp”GIMP 适用于 Linux、Windows、Solaris、FreeBSD 和 Macintosh 操作系统。如果你想了解更多关于数字图像合成的知识,看看来自 Apress 的数字图像合成基础知识。要在互联网上找到 GIMP 软件,使用谷歌搜索,并输入 GIMP。网站如图 1-16 所示。
图 1-16。
Google Search GIMP; go to gimp.org; click the Download link for 2.8.22, or for 2.10 (currently 2.9.6 beta)
单击 Download 链接(或右键单击,并在单独的选项卡中打开它),并单击 Download GIMP 2.8.22(或更高的版本,如新的 2.10 或 3.0 版本,目前正在测试 2.9.6,很快将是 2.9.8),它代表您正在使用的操作系统。
下载页面会自动检测你用的是什么 OS,给你正确的 OS 版本;就我而言,我在 Windows7、Windows 10 和 Ubuntu LTS Linux 17.04 上使用 GIMP,因为我在我的每一个工作站上都安装了它。不用说,开源软件比付费软件包有更多的优势。
下载完软件后,安装最新版本的 GIMP,然后为您的工作站任务栏创建一个快速启动图标,就像您为 InkScape 所做的那样。
接下来,我们将安装一个强大的数字音频编辑和特效软件包 Audacity。
下载并安装 Audacity 进行数字音频编辑
JavaFX 支持利用数字音频技术的 2D(和 3D)数字音频。数字音频通过采集数字音频“样本”来表示模拟音频数字音频内容通常使用诸如 Cakewalk Sonar 的数字音频合成和序列器软件包来创建。如果您想了解更多关于数字音频编辑的知识,请查看来自 Apress 的《数字音频编辑基础》一书。在本节中,我们将下载并安装流行的开源数字音频编辑和优化软件包“Audacity”Audacity 可用于 Linux、Windows 和 Macintosh 操作系统。要在互联网上找到 Audacity 软件包,请使用 Google 搜索引擎,并输入 Audacity,这会显示 Audacity 团队网站。进入该网站,如左上方图 1-17 所示。点击下载 Audacity 链接(或使用下载菜单)并点击 Audacity for Windows(或您正在使用的操作系统版本)。我也在 Ubuntu Linux LTS OS 17.04 上使用 Audacity 2.1.3。
图 1-17。
Google the word Audacity, go to audacityteam.org, and click a Download Audacity link matching your OS
下载并安装 Audacity 的最新版本,目前是 2.1.3,并为您的工作站任务栏创建快速启动图标,就像您为 InkScape 和 GIMP 所做的那样。当你读到这篇文章时,Audacity 2.2.0 可能已经发布了,它增加了新的用户界面设计和许多很酷的新数字音频编辑、合成和增音功能。
接下来,您将安装一个专业的非线性数字视频编辑和“颜色计时”(也称为颜色校正)软件包,用于故事片,该软件包最近从 12.5 版升级到 14 版,名为 Black Magic Design DaVinci Resolve。仅仅在一两年前,这个软件包曾经要花费数千美元!
下载并安装用于数字视频的 DaVinci Resolve 14
JavaFX 9 支持数字视频,它利用了基于“光栅”像素的动态视频技术。这将视频表示为一系列帧,每个帧包含一个基于像素阵列的数字图像。数字视频素材通常是使用数字视频编辑和色彩计时软件包(如 AfterEffects 和 EditShare LightWorks)创建的。在本节中,我们将下载并安装最新版本的开源数字视频编辑软件 DaVinci Resolve 14。该软件包适用于 Windows 10、Mac OSX 和 Ubuntu Linux 以及其他发行版。要找到达芬奇密码,使用谷歌搜索并输入达芬奇密码。点击中间如图 1-18 所示的下载按钮,或者滚动到页面底部,点击免费下载按钮。
图 1-18。
Google the word DaVinci Resolve; go to BlackMagicDesign.com webpage; click on the Download button
安装软件,并为任务栏创建一个快速启动图标,就像您对所有其他软件所做的那样。如果你想了解更多关于数字视频编辑的知识,可以看看来自 Apress 的数字视频编辑基础知识。接下来,我们将安装一个高级特效,3D 建模和动画,以及 VR 包,名为 BlackMagic Fusion。
下载并安装 Blackmagic Fusion 以获得视觉效果
JavaFX 还支持特效管道,因为所有的新媒体类型都可以使用 Java 9 代码无缝地组合在一起。SFX 同时利用基于“光栅”像素的运动视频技术、静态图像合成、数字音频、3D、i3D 和 SVG 数字插图,因此与 3D 建模和动画一样先进。BlackMagicDesign 的 Fusion 曾经是一个付费软件包,直到它被开源。有一个专业版以前要 999 美元现在要 299 美元!如果你是认真的多媒体,购买这个!
您首先必须在BlackMagicDesign.com
网站上注册,才能下载和使用该软件。该软件包适用于 Linux、Windows 10 和 Macintosh 操作系统。要在互联网上找到 Fusion,使用谷歌搜索引擎并输入 Fusion 9,你将被指引到图 1-19 所示的地方。单击代表您正在使用的操作系统的下载按钮。此下载页面将自动检测您使用的操作系统;在我的情况下,Windows。
图 1-19。
Google the word Fusion 9; go to the blackmagicdesign.com download page; click on the download button
在 BlackMagicDesign.com 网站上注册,如果你还没有这样做,一旦你被批准,你就可以下载并安装 Fusion 9 的最新版本。安装软件,并为任务栏创建一个快速启动图标,就像您对其他软件所做的那样。如果你想详细了解融合,Apress.com 最近有一本名为《VFX 基础》的书,该书更详细地介绍了融合和视觉效果合成管道。
接下来,我们将安装一个名为 Blender 的 3D 建模和动画包。
下载并安装用于 3D 建模和动画的 Blender
JavaFX 最近支持在 JavaFX 环境之外创建的 3D 新媒体素材,这意味着您将能够使用第三方软件包(如 Autodesk 3D Studio Max 或 Maya 和 NewTek Lightwave)创建 3D 模型、纹理和动画。在本节中,我们将下载并安装流行的开源 3D 建模和动画软件包“Blender”Blender 可用于 Linux、Windows 和 Macintosh 操作系统,因此读者可以使用任何他们喜欢的操作系统平台来创建和优化 3D 模型、3D 纹理映射和 3D 动画,以用于他们的 Java 9 和 JavaFX 9 游戏。
要在互联网上找到 Blender 软件,使用谷歌搜索引擎并输入 Blender,如图 1-20 所示。点击正确的下载链接下载并安装 Blender,然后创建快速启动图标。
图 1-20。
Google the words Blender 3D, go to www.blender.org
and click on the blue Download Blender 2.79 button
下载并安装用于 3D 地形或世界创建的 Terragen
另一个令人印象深刻的(免费的,对于基础版本,或者如果你在教育行业)3D 世界生成软件包是英国 Planetside Software 的 Terragen 4.1。你可以在 Planetside.co.uk 下载基本版,也可以加入他们的论坛。我也在我的几本 Android 应用开发书籍中使用过这个软件,所以我知道它在多媒体应用、交互式电视或 iTV 以及游戏等项目中使用效果很好。它也被专业电影制作人使用,因为它的质量水平非常原始。由于我们在本书中涉及 3D,您可能希望了解 Terragen,因为它价格实惠,并且被电视制片人和电影工作室使用。要在互联网上找到 Terragen 软件,请使用谷歌搜索引擎并输入 Terragen 4.1。点击链接,将弹出 Planetside 软件网站,如图 1-21 所示。
图 1-21。
Google the word Terragen; go to the planetside.co.uk website; click on a blue GET IT button to download
单击 GET IT download 链接下载并安装 Terragen,然后为该软件创建一个快速启动图标。如果你喜欢这个 3D 软件,一定要升级到软件的专业版,这是非常实惠的。
下载并安装 Daz Studio Pro 以制作角色动画
对于专业的 3D 角色建模和动画,如果有机会的话,一定要看看位于 daz3d.com 的 DAZ 3D 公司的 3D 软件包。DAZ Studio PRO 目前的版本是 4.9,没错,是免费的!你必须登录并注册,就像你为黑魔法设计软件所做的那样,但这只是一个小小的代价!这个网站上还有一个免费的 3D 建模软件包叫做 Hexagon。DAZ 3D 网站上最贵的软件是 Carrara(150 美元)或 Carrara Pro(285 美元)。DAZ 工作室的大部分收入来自销售各种类型的角色模型,所以去看看他们吧,因为他们是 3D 内容(虚拟)世界中不可忽视的力量!
要在互联网上找到 Daz Studio Pro 软件,使用谷歌搜索引擎并键入 Daz Studio Pro 5 下载。该链接会将您带到 daz3d.com/daz_studio 页面,如图 1-22 所示。点击下载链接下载并安装最新版本的 Daz Studio Pro,然后创建您的快速启动图标。
图 1-22。
Google the words Daz Studio Pro, go to www.daz3d.com
, and download the latest version of Daz Studio
其他开源新媒体软件包
我在新媒体内容制作业务中也使用了大量其他专业水平的开源软件包。我想让你知道其中的一些会很好,以防你没有听说过它们。这些将为您在本章中构建的新媒体制作工作站增加更多功能和多样性。值得注意的是,在进行所有这些大量下载和安装的过程中,您已经为自己节省了数千美元(或您的本地货币单位),这些钱本来可以花在类似的付费内容制作软件包上。我想我的座右铭可以说是:“第一次就做对,并确保一直做下去”,所以我将继续向您介绍其他一些免费的,甚至是一些更易于使用(不是免费的,但非常实惠)的新媒体内容制作软件包,我通常在我的 3D 内容制作工作站上安装这些软件包。
除了过去价格接近六位数的 DaVinci Resolve 软件包之外,开源软件的最佳价值之一是一个商业生产力软件套件,它在 Oracle 收购 Sun Microsystems 后被收购,然后成为开源软件。Oracle 将其 OpenOffice 软件套件转移到流行的 Apache 开源项目,就像他们目前对 NetBeans 9 所做的一样。
Open Office 4.3 是一个完整的办公效率软件套件,其中包含六个成熟的商务效率软件包!由于您的内容制作代理实际上是一个成熟的企业,您可能应该了解办公软件,因为这是一个非常可靠的开源软件产品。您可以在:OpenOffice.org
找到它,这个流行的商业软件包已经被像您这样精明的专业人士下载了超过一亿次,所以,这不是一个玩笑,正如他们所说的那样!
对于用户界面(UI)设计原型,Evolus.vn 提供了一个名为 Pencil 2.0.6 的免费软件包,允许您在用 Java、Android 或 HTML5 创建用户界面设计之前,轻松地制作用户界面设计原型。该软件位于pencil.evolus.vn
,可用于 Linux 发行版、Windows 7 和 8.1 以及 Macintosh OS/X。
对 Audacity 2 数字音频编辑软件的一个很好的补充是 Rosegarden MIDI sequencing 和 music composition and scoring 软件,它可以用于音乐创作,并打印出音乐出版的结果乐谱。Rosegarden 目前正在从 Linux 移植到 Windows。请注意,功能最全的版本是针对 Linux 的,如图 1-23 所示。可以使用谷歌搜索或者在RoseGardenMusic.com
找到它,目前它的版本是 17.04(与 LTS 的 Ubuntu 相同)。这就是通常所说的“千载难逢的两次”版本。
图 1-23。
Rosegarden is a MIDI, music scoring, and notation program for Linux which is being ported to Windows 10
另一个令人印象深刻的音频、MIDI 和声音设计软件包叫做 Qtractor,这是一个基于硬盘驱动器的音频采样器、编辑器和声音设计软件包,如图 1-24 所示。因此,如果你运行的是 Linux 操作系统,一定要谷歌搜索、下载并安装这个专业级数字音频合成软件包,你可以在 SourceForge 的Qtractor.SourceForge.net
URL 网址找到它。
图 1-24。
Qtractor, the hard disk based digital audio editing software for Linux
另一个令人印象深刻的免费 3D 建模和动画软件是 Caligari TrueSpace 7.61,当它由 Caligari Corporation(后来被微软收购)的罗曼·奥曼迪开发时,曾经花费近 1000 美元,你可以在多个网站上找到它,只需在谷歌上搜索 Caligari TrueSpace 3D。
另一个你应该看看的 3D 渲染软件是 POVRay。POV 代表“视觉暂留”,该软件被称为“光线跟踪器”,是一种高级渲染引擎,可与任何 3D 建模和动画软件包配合使用,使用高级光线跟踪渲染算法生成令人印象深刻的 3D 场景。最新版本可在 www.povray.org
网站上找到。它是 3.7,最新版本是 64 位,并且是多核(多线程)兼容的,可以免费下载,这就是我在这里告诉你的原因。
另一个专为 POVRay 设计的时尚 3D 建模软件包是 Bishop 3D。该软件可以用来创建自定义的 3D 对象,然后可以导入到 POVRay(然后导入到 JavaFX)中,以便在您的 pro Java 游戏中使用。最新版本是适用于 Windows 7 或 10 的 1.0.5.2。该软件可以在 www.bishop3d.com
上找到,最新版本是 8MB 下载,目前可以免费下载。
另一个你应该看看的免费 3D 细分建模软件是 Wings3D。这个软件可以用来创建 3D 对象,然后导入 JavaFX 在你的游戏中使用。最新版本是 2.1.5,于 2016 年 12 月发布,适用于 Windows 10、Macintosh OS/X 和 Ubuntu Linux。该软件可以在wings3d.com
上找到,最新版本为 64 位,下载容量为 16MB,目前可以免费下载。
接下来我将向你展示我是如何在任务栏上组织一些基本的操作系统工具和开源软件的。在接下来的几章中,我们将开始学习使用新媒体素材背后的原理,之后,我们将学习如何使用 NetBeans 9 创建 JavaFX 9 项目,然后,在我们开始学习有关强大的 JavaFX 9.0 多媒体游戏引擎的细节之前,我们将在下一章中学习 Java 编程语言。
在任务栏区域组织快速启动图标
有一些操作系统实用程序,如计算器、文本编辑器(在 Windows 中称为记事本)和文件管理器(在 Windows 中称为资源管理器),我在任务栏中保留了快速启动图标,因为它们在编程和新媒体内容开发工作流程中经常使用。我还在我的任务栏上保留了各种新媒体开发、编程和办公效率应用作为快速启动图标,如图 1-25 所示,其中显示了十几个应用,包括我们刚刚安装的所有应用(按安装顺序排列),以及其他一些应用,包括 OpenOffice 4.3、DAZ Studio Professional 4.9 和 Bryce Professional 7.1。
图 1-25。
Make Taskbar Quick Launch Icons for key system utilities, NetBeans 9 and new media production software
有几种方法可以创建这些快速启动图标;您可以将程序直接从“开始”菜单拖放到任务栏上,或者右键单击桌面上或资源管理器文件管理器中的图标,然后选择“固定到任务栏”上下文菜单选项。一旦图标出现在任务栏中,你可以简单地通过向左或向右拖动来改变它们的位置。
恭喜你!您刚刚创建了您的新媒体 Java 游戏和物联网开发工作站,该工作站经过了高度优化,将允许您创建您的客户可以想象的任何新媒体 Java 游戏或物联网项目!
摘要
在第一章中,您确保自己拥有开发创新 Java 游戏或物联网项目所需的一切,包括最新版本的 Java 9、JavaFX 9.0、NetBeans 9 和所有最新的开源新媒体软件。这包括获得最新的 Java 9 JDK 和 NetBeans 9 IDE 软件,然后我们安装 Java 9 和 NetBeans 9。在那之后,你为一群专业的、开源的、新媒体内容工具做了同样的事情。
二、内容创作导论:2D 新媒体素材基础
由于上一章的介绍,现在您已经有了一个专业的 Java 游戏和物联网开发工作站,让我们直接进入并了解大多数新媒体内容开发软件包所基于的基本 2D 内容开发概念和原则。Blender 是个例外,它基于更高级的 3D 内容开发,我们将在下一章讨论。在我们开始学习 Java、NetBeans 和 JavaFX 之前,我们需要先介绍这些基础多媒体材料,因为 JavaFX 多媒体引擎为使用可缩放矢量图形(SVG)的数字插图提供了令人难以置信的支持;使用光栅(位图)图像格式(如 PNG、JPEG 或 GIF)的数字成像;使用 MP3、MPEG4 AAC、WAV 或 AIFF (PCM)等音频格式的数字音频,以及使用 JavaFX 内部渲染引擎的 3D。我假设您不会创建基于文本的游戏,而是创建交互式新媒体应用,所以我想先介绍一些与编码无关的主题。一旦我们开始使用 NetBeans、Java 和 JavaFX APIs 编码,我们将永远不会停止编码。
在本章中,您将详细了解 JavaFX 支持的每种 2D 新媒体内容类型背后的概念,包括数字插图(矢量)、数字图像(光栅)、数字视频(运动)和数字音频(波形)。我们这样做是为了让你掌握基础知识,能够使用你在第一章下载并安装的免费开源多媒体内容制作工具进行游戏设计。
我想介绍的第一件事是数字图像的基本新媒体素材类型,因为它将被用作许多其他新媒体素材类型的基本输入素材。例如,您的数字视频只是一系列随时间快速播放的数字图像,以创建一种运动错觉。您的 2D 矢量插图素材可以使用 JavaFX ImagePattern 类填充数字图像数据,您的 3D 矢量素材可以将数字图像素材用于着色器和纹理贴图,我们将在第三章中对此进行介绍,包括高级 3D 内容创建和用于实现这些 3D 内容元素的相关 JavaFX 包和类。
接下来,我将介绍数字视频的概念、技术和“行话”,包括帧、帧速率、比特率以及其他添加第四维时间的概念,从而将静态数字图像素材制作成动画数字视频素材。这些概念也与动画有关,包括 2D 动画和 3D 动画。我们将在第三章讨论 2D 矢量和 3D 矢量概念,因为它们密切相关。
最后,我们将看看数字音频的概念;数字音频与数字视频密切相关,因为它可以包含在数字视频文件格式中。数字音频也可以独立存在,因此我们还将介绍 JavaFX 中的数字音频格式支持,以及数字音频素材数据足迹优化工作流程。因此,我们将在本章涵盖所有 2D (X,Y 数据表示)新媒体形式,2D 矢量插图除外,它与 3D 矢量渲染密切相关,我们将在第三章的第一部分涵盖。
游戏设计素材:新媒体内容概念
让你的游戏内容变得专业并在视觉上让你的客户满意的最强大的工具之一是你在第一章下载并安装的多媒体制作软件。在我深入阅读本书之前,我需要花一些时间向您提供关于 Java 通过 JavaFX 多媒体引擎支持的四种主要类型的新媒体素材的基础知识。这些包括数字图像,用于精灵,背景图像和 2D 动画;向量形状,用于 2D 插图、碰撞检测、2D 形状、路径和曲线;数字音频,用于音效、旁白和背景音乐;和数字视频,在游戏中用于动画背景循环(天空中飞翔的鸟,飘动的云,等等。)和高度优化的视频播放。正如你在图 2-1 中看到的,这四个 2D 流派,或者区域,都是使用 JavaFX 场景图安装在你的游戏中的。还有一个新的媒体领域,我想称之为交互式 3D (i3D)。i3D 为 Java 8 和 9 带来了实时 3D 渲染,我们将在下一章介绍 OpenGL ES。
图 2-1。
How 2D or audio new media assets are implemented in Scene Graph using JavaFX 9, Java 9, and NetBeans 9
因为在您能够在 Java 游戏设计(或编程)管道中创建或正确实现这些新媒体元素之前,您需要有一个技术基础,所以我将对四个新媒体领域中的每一个领域的核心概念进行回顾。仅有的两个在概念上相关的是 2D 动画和数字视频,因为它们都涉及到使用第四维度的时间和帧,所以我将把它们放在一起讨论。由于数字音频也涉及到时间的第四维度,我将用数字音频的概念来结束;最后,我们将快速了解数字内容优化,以便您的专业 Java 游戏和物联网项目紧凑且下载快速。
数字成像概念:分辨率、色深、Alpha、图层
JavaFX 支持最流行的数字图像文件(数据)格式,这给了我们游戏设计者极大的灵活性。由于 JavaFX 8 API 现在是 Java 8 和 9 的一部分,这意味着 Java 也支持这些图像格式。其中一些数字图像格式已经存在了几十年,如 CompuServe 图形信息格式(GIF)或广泛使用的联合图像专家组(JPEG)格式。一些 JavaFX 数字图像格式更加现代;例如,便携式网络图形(PNG,发音为“ping”)是我们将在游戏中使用的文件格式,因为它可以产生最高的质量水平,并支持图像合成,我们将很快了解这一点。Java 支持的所有这些主流数字图像文件格式在 HTML5 浏览器中也受支持,由于 Java 应用可以在 HTML 应用或网站中使用,这确实是一个非常合乎逻辑的协同作用!如果需要更广泛的数字图像文件格式支持,也可以使用名为 ImageJ 的第三方数字图像库。
最古老的格式是一种称为 CompuServe GIF 的无损数字图像文件格式。之所以称之为无损,是因为它不会丢弃(丢失)任何源图像数据来实现压缩结果。GIF 压缩算法不像 PNG 格式那样精细(不像 PNG 格式那样强大),GIF 只支持索引颜色,这是它获得压缩的方式(文件更小)。在这一部分的后面,我们将详细了解颜色深度(索引色与真彩色)。如果你所有的游戏图像资源都是使用 GIF 格式创建的,你就可以在你的 Java 游戏中毫无问题地使用它们,而不是低效的图像压缩和有限的图像合成能力。
Java 通过 JavaFX 支持的最流行的数字图像格式是 JPEG。JPEG 使用“真彩色”色深,而不是索引色深。稍后我们将讨论色彩理论和色彩深度。JPEG 使用所谓的有损数字图像压缩。这是因为压缩算法会“丢弃”图像数据,以便获得更小的文件大小。这个图像数据就永远丢失了,除非你聪明点,把原来的 raw 图像保存下来!
如果您在压缩后放大 JPEG 图像,您会看到变色或脏污的区域,而这些区域在原始图像中是不存在的。在数字成像行业中,图像中的退化区域被称为压缩伪像。这只会发生在有损图像压缩中,在 JPEG(和 MPEG)压缩中很常见。
我推荐您在专业 Java 游戏中使用的数字图像格式是可移植网络图形文件格式。PNG 有两个真彩色文件版本;一个名为 PNG24,不能用于图像合成,另一个名为 PNG32,带有一个用于定义透明度的 alpha 通道,我们将在稍后介绍。还有一个索引(最多 256;可以更少)彩色版的 PNG 格式,称为 PNG8。
我为你的游戏推荐 PNG 的原因是因为它有一个不错的图像压缩算法,并且是一种无损图像格式。这意味着 PNG 有很好的图像质量以及合理的数据压缩效率,这将使你的游戏发行文件更小。PNG32 格式的真正力量在于它能够使用透明度和抗锯齿(通过其 alpha 通道)与其他游戏图像进行合成。
数字图像分辨率和长宽比:定义您的图像大小和形状
您可能知道,数字图像是由二维(2D)像素阵列组成的。Pixels 是图片(pix)元素(els)的缩写。图像中的像素数量由其分辨率表示,分辨率是图像宽度(或 W,有时称为 x 轴)和高度(或 H,有时称为 y 轴)维度中的像素数量。图像的像素越多,分辨率就越高。这与数码相机的工作原理类似,因为图像捕捉设备(通常是相机的电荷耦合器件(CCD),用于捕捉图像数据)中的像素越多,可以实现的图像质量就越高。
要计算图像像素的总数,请将宽度像素乘以高度像素。例如,一个宽 VGA 800x480 图像将包含 384,000 个像素,正好是 1MB 的 3/8。这就是你如何找到你的图像的大小,包括使用的千字节(或兆字节)和显示屏上的高度和宽度。
使用图像纵横比来指定数字图像素材的形状。纵横比是数字图像的宽高比,定义了正方形(1:1 纵横比)或矩形(也称为宽屏)数字图像形状。具有 2:1(宽屏)宽高比的显示器,例如 2160x1080 分辨率,已经广泛应用。
1:1 纵横比的显示器或图像总是完美的正方形,2:2 或 3:3 纵横比的图像也是如此。例如,物联网开发人员可能会在智能手表上看到这个长宽比。值得注意的是,定义图像或显示屏形状的是这两个宽度和高度数字之间的比率,或者 X 和 Y 变量,而不是实际的数字本身。实际数字定义了屏幕的分辨率或总像素阵列能力。
纵横比应该始终表示为纵横比冒号两边可以达到(减少)的最小数字对。如果你在高中时注意学习最小公分母,那么长宽比对你来说很容易计算。我通常通过继续将冒号的每一边除以 2 来计算长宽比。例如,如果您采用 SXGA 1280x1024 分辨率,则 1280x1024 的一半是 640x512,而 640x512 的一半是 320x256。320x256 的一半是 160x128,一半是 80x64,一半是 40x32,一半是 20x16。20x16 的一半是 10x8,而其中的一半给你 SXGA 5:4 的长宽比。
数字图像色彩理论和色彩深度:定义精确的图像像素颜色
每个数字图像像素的颜色值可以由三种不同颜色(红色、绿色或蓝色(RGB ))的量来定义,这三种颜色在每个像素中以不同的量存在。消费电子显示屏利用加色,即每个 RGB 颜色通道的光波长相加在一起,以创建 1680 万个不同的颜色值。在 LCD、LED 或有机发光二极管显示器中使用加色。它与印刷中使用的减色法相反。为了向您展示不同的结果,在减色模式下,将红色与绿色(油墨)混合将产生紫色,而在加色模式下,将红色与绿色(浅色)混合将产生鲜艳的黄色。加色可以提供比减色更宽的颜色范围。
为每个像素保存的每个红色、绿色和蓝色值有 256 个亮度级别。这允许您设置 8 位数据值范围,或 0 到 255,控制每个红色、绿色和蓝色值的颜色亮度变化。该数据使用十六进制记数法表示,从最小值零(#00 或关,全黑或黑色)到最大值 255 (#FF 或全开,或贡献最大 RGB 颜色,形成白色)。
用于表示支持的数字图像像素颜色数量的位数被称为图像的颜色深度,并使用“2 的幂”,就像 3D 用于纹理映射一样,我们将在下一章讨论这一点。因此,PNG8 图像使用 256 种颜色,PNG7 使用一半的颜色(128),PNG6 使用一半的颜色(64),PNG5 使用一半的颜色(32),因此 PNG 4 使用 16,PNG3 使用 8,PNG2 使用 4,PNG1 使用 2,或黑白(开或关)。通常,您会希望使用完整的 256 色,因为 JavaFX 仅支持 PNG8、PNG4 或 PNG1,所以如果您要使用索引彩色影像,请使用 PNG8。
数字成像行业中常用的颜色深度包括 8 位、16 位、24 位和 32 位。我将在这里概述常见的,以及它们的格式。最低色深存在于 8 位索引的彩色图像中。这些具有最多 256 个颜色值,并使用 GIF 和 PNG8 图像格式来保存这种索引颜色类型的数据。
中等色深图像将采用 16 位色深,因此将包含 65,536 种颜色(按 256 乘以 256 计算),并且受 TARGA (TGA)和标记图像文件格式(TIFF)数字图像格式支持。如果您想在 Java 8 游戏中使用除 GIF、JPEG 和 PNG 之外的数字图像格式,请导入 ImageJ 库。
真彩色色深图像将采用 24 位色深,因此将包含超过 1600 万种颜色。计算方法是 256 乘以 256 乘以 256,等于 16,777,216 种颜色。支持 24 位颜色深度的文件格式包括 JPEG(或 JPG)、PNG、BMP、XCF、PSD、TGA、TIFF 和 WebP。JavaFX 支持其中的三种:JPEG、PNG24 (24 位)和 PNG32 (32 位)。使用真彩色深度 24 位或 32 位影像将为您提供最高水平的质量。这就是为什么我一直建议你在 Java 9 游戏和物联网项目中使用 PNG24 或 PNG32 格式。
接下来,让我们看看如何通过使用 PNG32 图像的 alpha 通道来表现图像的透明度。
数字图像合成:对图层使用 Alpha 通道和透明度
接下来,让我们看看如何使用 alpha 通道定义数字图像像素透明度值,以及如何使用这些值为 Java 游戏实时合成数字图像。合成是将多个数字影像层无缝融合在一起的过程。正如你所想象的,这对于游戏设计和开发来说是一个非常重要的概念。当您想要在显示器上创建一个看起来像是单个图像(或动画)但实际上是(多个)合成图像层的无缝集合的图像时,合成非常有用。您想要设置图像或动画合成的主要原因之一是,通过将每个元素放在不同的层上,允许对这些图像中的各种元素进行编程控制。
要实现这一点,您需要有一个 alpha 通道透明度值,您可以利用它来精确地控制该像素与它下面的其他层上相同 X,Y 图像位置中的像素的混合。在数字成像软件中,每个图像层的透明度值通过使用棋盘图案来表示,您可以在图 2-2 的右侧看到。
图 2-2。
Showing the checkerboard representation of transparent pixels in an image, as well as the RGBA channels
在 GIMP 的左边你可以看到 Alpha 层,我选择的是蓝色。它包含 MindTaffy 徽标的透明度值。GIMP 通道调色板是我选择向您显示这些颜色和 alpha 通道(红色、绿色、蓝色、alpha)的选项卡,它为每个层分别保存这些颜色(和 Alpha)通道,允许您对每个图像复合层中的每个像素进行难以置信的控制。
像其他 RGB 通道一样,alpha 通道有 256 个级别,但这些值不是红色、绿色或蓝色,而是透明度级别。在 Java 编程中,alpha 通道由十六进制表示中的前两个槽表示,以#AARRGGBB 数据值格式描述。我们将在下一节详细介绍这一点。Alpha 加颜色通道 ARGB 数据值利用八个槽(32 位)数据,而不是 24 位图像中使用的六个数据槽(#RRGGBB),这可以被认为是具有零(无)alpha 通道数据的 32 位图像。
因此,24 位(PNG24)图像没有 alpha 通道,不会用于合成,除非它是合成层堆栈中的背景(底部)图像板。另一方面,PNG32 影像将用作 PNG24(背景板)影像之上的合成层,或者用作 z 顺序较低的 PNG32 合成层之上的合成层,这些合成层将需要其 alpha 通道功能,以便通过这些 alpha 通道透明度值显示图像合成中需要某种透明度(或不透明度)的某些像素位置。
数字图像 alpha 通道和图像合成的概念是如何影响 Java 游戏设计的?你一定在想!主要优势是能够将游戏画面、精灵、投射物和背景图形元素分解成多个组件层。这样做的原因是为了能够将 Java 编程逻辑(或 JavaFX 或 SVG 特效)应用于单个图形图像元素,以控制游戏画面的各个部分。如果没有 2D 合成方法,你将无法单独控制游戏组件,因为对大多数设备来说,逐个像素的处理过于密集。
图像合成的另一部分,称为混合模式,也是专业图像合成功能的重要因素。JavaFX 混合模式是通过使用包含 BlendMode 常量值的 Blend 类来应用的,该常量值位于 javafx.scene.effect 子包中,我们将在本书的后面部分介绍该子包。这个 JavaFX blend effect 类为 Java 游戏开发人员提供了许多与 Photoshop 或 GIMP 为数字图像制作人员提供的图像合成模式相同的模式。这将 Java 和 JavaFX 变成了一个强大的图像合成引擎,就像 GIMP 一样,混合算法可以在非常灵活的级别上进行控制,使用自定义的 Java 代码。一些 JavaFX 混合模式常量包括 ADD、SCREEN、OVERLAY、DARKEN、LIGHT、MULTIPLY、DIFFERENCE、EXCLUSION、SRC_ATOP、SRC_OVER、SOFT_LIGHT、HARD_LIGHT、COLOR_BURN 和 COLOR_DODGE 常量。
在 Java 游戏逻辑中表示颜色和 Alpha:使用十六进制记数法
现在,您已经知道了什么是色深和 alpha 通道,并且在任何给定的数字图像中,颜色和透明度是通过使用四种不同的 alpha、红色、绿色和蓝色(ARGB)图像通道的组合来表示的,现在重要的是,作为程序员,我们应该如何在 Java 和 JavaFX 中表示这四种 ARGB 图像颜色和透明度通道值。
在 Java 编程语言中,颜色和 alpha 不仅用于 2D 数字图像,通常称为位图图像,还用于 2D 插图,通常称为矢量图像。颜色和透明度值也经常在许多不同的颜色设置选项中使用。例如,您可以为 JavaFX Stage、场景、布局容器(如 StackPane)、矢量形状填充或 UI 控件设置背景色(或透明度值),以及其他内容,如 3D 素材特征。我们将在以后的章节中讨论 3D 和 JavaFX。
在 Java 和 JavaFX API 中,不同级别的 ARGB 颜色强度值用十六进制表示。十六进制,或简称为“hex ”,是基于原始的 16 进制计算机符号。这在很久以前被用来表示 16 位数据值。与更常见的从 0 到 9 计数的 Base10 不同,Base16 记数法从 0 到 F 计数,其中 F 表示 15 的 Base10 值(0 到 15 产生 16 个数据值)。
Java 中的十六进制值总是以 0 和 x 开头,所以白色的 24 位颜色值应该是这样的:0xFFFFFF。这个十六进制颜色值代表 Java 的颜色。白色常数,不使用 alpha 通道。白色的 32 位颜色值看起来像 0xFFFFFFFF,alpha 通道数据完全不透明。带有透明 alpha 通道的白色,可能根本不是白色,而是“透明的”,使用十六进制编码如下:0x00FFFFFF。我在 Java 代码中一般用 0x00000000 来表示一个清晰(透明)的 alpha+颜色值。
24 位十六进制表示中的每个槽代表一个 16 进制值,因此要获得每种 RGB 颜色所需的 256 个值需要 2 个槽,因为 16 乘以 16 等于 256。因此,要使用十六进制表示法表示 24 位图像,我们需要在 0x 后面有六个槽来保存这六个十六进制数据值(每个数据对表示 256 个级别的值)。如果乘以 16×16×16×16×16×16,您应该得到 16,777,216 种颜色,这些颜色可以通过使用 24 位来寻址,也称为真彩色数字图像数据。
十六进制数据槽以下列格式表示 RGB 值:0xRRGGBB。对于 Java 常量颜色。白色,十六进制颜色数据值表示中的所有红色、绿色和蓝色通道都处于全(最大颜色值)亮度设置。如果你把所有这些颜色相加,你将得到白光。
黄色表示红色和绿色通道打开,蓝色通道关闭,因此颜色的十六进制表示法。因此,黄色将为 0xFFFF00,其中红色和绿色通道槽完全打开(FF,或 255 Base10 数据值),蓝色通道槽完全关闭(00,或零值)。
ARGB 值的八个十六进制数据槽将保存以下格式的数据:0xAARRGGBB。因此,对于颜色。白色,十六进制颜色数据值表示中的所有阿尔法、红色、绿色和蓝色通道将处于它们的最大亮度(或不透明度),并且阿尔法通道是完全不透明的,即不透明的,如 FF 值所表示的。因此,颜色为 32 位十六进制值。白色常数将是 0xFFFFFFFF。
100%透明的 alpha 通道可以由设置为零的 alpha 槽来表示,从而创建“清晰”的图像。因此,您可以使用 0x00000000 和 0x00FFFFFF 之间的任何数据值来表示透明图像像素值。重要的是要注意,如果 alpha 通道值等于此完全透明度级别,那么将包含在其他六个(RGB)十六进制数据值槽中的 16,777,216 个颜色值将无关紧要,因为该像素将被评估为不存在,因为它是透明的,因此不会在最终图像或动画合成图像中合成,所以它的颜色是没有意义的(根本无关紧要)。
数字图像对象遮罩:使用 Alpha 通道合成游戏精灵
alpha 通道在游戏设计中的主要应用之一是遮蔽图像或动画(图像系列)的区域,以便它可以在游戏图像合成场景中用作游戏精灵。蒙版是从数字图像中“剪切”出主题的过程,以便可以使用 alpha 通道透明度值将主题放在自己的图层上。这是使用数字成像软件包完成的,如图 2-2 所示。
数字图像合成软件包,如 Photoshop 或 GIMP 功能工具,用于遮罩和图像合成。如果不进行有效的遮罩,就无法进行有效的图像合成,因此对于希望将图形元素(如图像精灵和精灵动画)集成到游戏设计中的游戏设计师来说,这是一个需要掌握的重要领域。数字图像蒙版技术已经存在很长时间了!
可以使用专业的蓝屏(或绿屏)背景以及可以自动提取精确颜色值来创建蒙版的计算机软件,自动为您完成蒙版。这个遮罩被转换成 alpha 通道(透明度)信息(数据)。还可以通过使用数字图像软件、通过结合各种锐化和模糊算法使用算法选择工具之一来手动进行遮罩。
在本书的过程中,我们将会使用通用的开源软件包,比如 GIMP,来学习很多关于这个工作过程的知识。掩蔽可能是一个复杂的工作过程,完全掌握这个过程可能需要跨越几个章节,而不是试图将所有内容放入书中的一个章节(这一章)。本章旨在向您展示我们在书中所承担的工作流程的基础知识。
遮罩过程的一个关键考虑因素是在被遮罩的对象(主题)周围获得平滑、清晰的边缘。这是为了当你将一个被遮罩的物体(在本书中,它将是一个游戏精灵)放入(覆盖)新的背景图像中时,它将看起来像是在第一个地方被拍摄的一样(就像它在视频中一样)。
成功做到这一点的关键在于像素选择工作过程,这涉及到使用数字图像软件选择工具,如 GIMP 中的剪刀工具或 Photoshop 中的魔棒工具。这些必须以正确的方式(顺序)使用才能完全有效。使用正确的选择工作流程至关重要!
例如,如果您想要遮罩的对象周围有统一颜色的区域(可能您是对着蓝屏拍摄的),您将使用具有适当阈值设置的魔棒工具来选择除对象之外的所有内容。然后反转选择,这将为您提供包含该对象的选择集。通常,正确的工作流程包括逆向处理某些事情。其他选择工具包含复杂的算法,可以查看像素之间的颜色变化。这些对于边缘检测是有用的,我们可以将其用于其他选择方法。
平滑数字图像合成:使用抗锯齿平滑图像边缘
抗锯齿是一种流行的数字图像合成技术,其中数字图像中位于不同颜色的两个区域之间的边缘上的两种相邻颜色沿着该边缘混合在一起。这将有助于在缩小图像时使边缘看起来更平滑(更少锯齿)。这样做是为了“欺骗”观众的眼睛看到更平滑的边缘,并消除所谓的图像锯齿。抗锯齿通过使用平均颜色值提供了令人印象深刻的结果,该平均颜色值仅使用需要变得更平滑的边缘上的几个彩色像素。所谓平均颜色值,我指的是某个颜色范围,它是沿着图像锯齿状边缘聚集在一起的两种颜色之间的一部分。这只需要半打左右的中间色。我创造了一个例子来展示我所说的。参见图 2-3 。
图 2-3。
A red circle composited on a yellow background (left) and a zoomed-in view (right) showing anti-aliasing
正如你所看到的,我在一个图层上创建了一个清晰的红色圆圈,在背景图层上叠加了一个黄色填充。我放大了红色圆圈形状的边缘,拍了另一张截图,并把它放在缩小的圆圈的右边。这显示了从黄橙色到橙色到红橙色抗锯齿颜色值的范围,正好在圆与背景相遇的边缘处的红色和黄色之间。
值得注意的是,JavaFX 引擎将使用 Java2D 软件渲染器或硬件渲染的 i3D,使用 Prism 引擎(可以使用 OpenGL 或 DirectX ),针对背景颜色和背景图像消除 2D 形状和 3D 对象的锯齿。您仍将负责正确合成,也就是说,通过有效地使用 alpha 通道为您的多层影像提供抗锯齿,这一点我们在本章前面已经了解过。
数字图像数据优化:使用压缩、索引颜色和抖动
影响数字图像压缩的因素有很多,您可以使用一些基本的技术以较小的数据占用量获得较高质量的结果。这是优化数字图像的主要目标;为您的应用(在本例中是一个游戏)获得尽可能小的数据占用空间,同时获得最高质量的视觉效果。我们将从对数据足迹影响最大的几个方面入手,研究这些方面对任何给定数字图像的数据足迹优化有何贡献。有趣的是,这些类似于我们在成像这一节到目前为止所涉及的数字成像概念的顺序。
影响最终数字图像素材文件大小的最关键因素,我喜欢称之为数据足迹,将是像素数或数字图像的分辨率。这是合乎逻辑的,因为需要存储每个像素,以及包含在其三个(24 位)或四个(32 位)通道中的颜色和 alpha 值。在保持图像清晰的同时,图像分辨率越小,生成的文件也就越小。
对于 24 位 RBG 图像或 32 位 ARGB 图像,原始(或未压缩)图像大小的计算方法是宽度乘以高度乘以 3,宽度乘以高度乘以 4。例如,未压缩的真彩色 24 位 VGA 图像将具有 640 乘以 480 乘以 3,等于 921,600 字节的原始(raw)未压缩数字图像数据。要确定此原始 VGA 图像中的千字节数,您需要将 921,600 除以 1024(千字节中的字节数),这将在真彩色 VGA 图像中得到 900 KB 的数据。
通过优化数字影像分辨率来优化原始(未压缩)图像大小非常重要。这是因为一旦图像从游戏应用文件解压缩到系统内存中,这就是它将要占用的内存量,因为图像将使用 24 位(RGB)或 32 位(ARGB)表示法逐个像素地存储在内存中。这是我在游戏开发中使用 PNG24 和 PNG32 而不是索引颜色(GIF 或 PNG8)的原因之一,因为如果操作系统要将颜色转换为 24 位颜色“空间”,那么出于质量原因,我们应该利用 24 位颜色空间,并处理(接受)稍大的应用文件大小。
图像颜色深度是压缩图像的数据足迹的第二个最重要的因素,因为图像中的像素数乘以一个(8 位)、两个(16 位)、三个(24 位)或四个(32 位)颜色数据通道。这种小文件大小是 8 位索引彩色图像仍然被广泛使用的原因,尤其是使用 GIF 图像格式。
如果用于构成图像的颜色变化不太大,索引彩色图像可以模拟真彩色图像。索引彩色影像仅使用 8 位数据(256 种颜色)来定义图像像素颜色,使用多达 256 种最佳选择颜色的调色板,而不是 3 个 RGB 颜色通道或 4 个 ARGB 颜色通道,每个通道包含 256 种颜色级别。同样,重要的是要注意,在通过压缩将 24 位图像转换为 8 位图像之后,一旦在系统内存中解压缩并转换回游戏所用的 24 位 RGB 或 ARGB 数据模型(系统内存外使用的表示),您就只能使用原始 16.8M 颜色中的潜在(最多)256 种颜色了!这就是为什么我提倡使用 PNG24 或 PNG32 图像,而不是 JavaFX 也支持的 gif 或 PNG1 (1 色)、PNG2 (4 色)、PNG4 (16 色)和 PNG8 (256 色)图像。
根据任何给定的 24 位源图像中使用的颜色数量,使用 256 种颜色来表示最初包含 16,777,216 种颜色的图像可能会导致一种称为条带的效果。这是在结果(来自压缩)256(或更少)调色板中相邻颜色之间的转换不是渐进的,因此看起来不是平滑的颜色渐变。索引的彩色图像有一个视觉校正条带的选项,称为抖动。
抖动是一种算法过程,沿着图像中任何相邻颜色之间的边缘创建点图案,以欺骗眼睛认为使用了第三种颜色。抖动将为您提供 65,536 种颜色(256x256)的最大感知量,但只有当这 256 种颜色中的每一种都与其他(不同)256 种颜色中的一种相邻时,才会出现这种情况(这是必要的)。尽管如此,您仍然可以看到创建额外颜色的潜力,并且您会对索引颜色格式在某些压缩场景中(对于某些图像)可以达到的效果感到惊讶。
让我们拍摄一张真彩色图像,如图 2-4 所示,并将其保存为 PNG5 索引彩色图像格式,向您展示这种抖动效果。需要注意的是,尽管在 Android 和 HTML5 中支持 PNG5,但在 JavaFX 中不支持,所以如果您自己做这个练习,请选择 PNG1 (2)、PNG2 (4)、PNG4 (16)或完整 PNG8 (256)颜色选项!
图 2-4。
This is a true-color PNG24 image created with Autodesk 3ds Max that we are going to compress as PNG5
我们将看看奥迪 3D 图像中驾驶员侧后挡泥板上的抖动效果,因为它包含一个我们将应用此抖动效果的灰色渐变。您可以在图 2-4 中看到 24 位源数字图像。
有趣的是,8 位索引彩色图像允许使用少于 256 种的最大颜色。这通常是为了进一步减少影像的数据足迹。例如,仅使用 32 种颜色就可以获得良好效果的图像实际上是一个 5 位图像,从技术上来说,它被称为 PNG5,尽管这种格式本身通常被称为用于索引颜色使用级别的 PNG8。请记住,JavaFX 只支持 PNG4 (16 色)或 PNG8 (256 色),因此对于 Java 游戏中的这个图像,您应该使用 PNG8 或 256 色。
我将使用 Photoshop 将这张索引色 PNG5 图像(如图 2-5 所示)设置为使用 5 位颜色(32 色),以便您可以清楚地看到这种抖动效果。正如你所看到的,在图 5-4 左侧的 Photoshop 图像预览区域,抖动算法在相邻颜色之间创建点图案,以便创建额外的颜色。
图 2-5。
Setting dithering to the Diffusion algorithm and 32 colors (5-bit color) with 100 percent dithering for PNG5 output
另外,请注意,您可以设置使用抖动的百分比。我经常选择 0%或 100%的设置;但是,您可以在这两个极值之间的任何位置微调抖动效果,以微调生成的文件大小,因为这些抖动点图案会引入更多数据来压缩和增加文件大小。
您还可以在抖动算法之间进行选择,因为您可能已经猜到,这些不同的抖动效果是通过使用最终与索引文件格式压缩兼容(受其支持)的抖动算法以数学方式创建的,索引文件格式压缩使用调色板来保存用于像素的颜色值。
我使用扩散抖动,这给了不规则形状的梯度一个平滑的效果,就像在汽车挡泥板上看到的那样。您也可以使用更随机的“噪波”选项,或更不随机的“图案”选项。扩散选项通常给出最好的结果,这就是为什么我在使用索引颜色时使用它(这并不经常)。
正如你所想象的,抖动会将数据模式添加到你的图像中。这些更难压缩。这是因为图像中的平滑区域(如渐变或填充区域)通常更容易被这些压缩算法压缩,而尖锐的过渡(抗锯齿边缘)或随机像素图案通常是由抖动产生的,或者可能是由例如带有不合格 CCD 的相机的“噪声”产生的。
因此,应用抖动选项总是会增加几个百分点的数据占用空间。请务必检查应用和不应用抖动(在“导出”对话框中选择)的结果文件大小,以查看这是否值得它提供的改进的视觉效果。请注意,索引彩色 PNG 图像也有一个透明度选项复选框,但 PNG8 图像中使用的 alpha 通道只有 1 位(开/关),而不是 PNG32 中的 8 位。
到目前为止,我们了解到的最后一个可以增加图像数据量的概念是添加 alpha 通道来定义合成的透明度。这是因为添加 alpha 通道会给正在压缩的图像添加另一个 8 位颜色通道(更准确地说,是透明度或 alpha 通道)。如果您需要一个 alpha 通道来定义图像的透明度,很可能是为了支持未来的合成需求,例如将图像用作游戏精灵,在这方面没有太多的选择,所以包括 alpha 通道。
如果您的 alpha 通道包含全零(即,使用全黑填充颜色,这会将您的图像定义为完全透明),或者如果您的 alpha 通道包含全白填充颜色,会将您的图像定义为完全不透明或背景板),您实际上(在实际使用中)定义了一个不包含任何有用的 alpha 数据值的 alpha 通道。因此,需要移除未使用的 alpha 通道,并且需要将不透明图像定义为 PNG24 而不是 PNG32,以节省数据占用空间。
最后,大多数用于遮罩数字图像 RGB 层中的对象的 alpha 通道应该能够很好地压缩。这是因为它们主要是白色(不透明)和黑色(透明)的区域,沿着两种颜色之间的边缘有一些中等灰度值,以消除蒙版的锯齿(见图 2-2 )。这些灰色区域包含 alpha 通道中的消除锯齿透明度值,这将始终为您提供图像的 RGB 图层中的对象与任何背景颜色或可能在它后面使用的背景图像之间的平滑边缘过渡。实质上,alpha 通道中的反走样为您提供了 alpha 通道所服务的对象的实时合成,因为您可以将视频放在它的后面,在背景板中,alpha 反走样将实时保证视频的每一帧上具有不同边缘颜色混合的平滑边缘结果。
其原因是,由于 alpha 通道图像蒙版使用 8 位透明度渐变,范围从白色到黑色,并定义透明度级别而不是颜色,这应该被视为每像素混合,或不透明度强度值。因此,alpha 通道中包含的遮罩中每个对象的边缘上的中等灰度值将用于基本上平均对象边缘和任何目标背景的颜色,无论背景板可能包含什么颜色值、图像资源、插图资源、动画资源或视频资源。
这为可能使用的任何目标背景提供了实时抗锯齿,即使您的对象是静态对象,因为 alpha 通道提供的抗锯齿甚至可以使用动画背景。
数字视频或动画:帧、帧速率、循环、方向
有趣的是,我们刚刚谈到的数字图像的所有概念同样适用于数字视频和 2D 动画,因为这两种第四维(基于时间的)新媒体格式都使用数字图像作为其内容的基础。数字视频和 2D 动画一样,通过引入一种叫做帧的东西,将数字成像扩展到了时间的第四维度。数字视频和动画由有序的帧序列组成,这些帧随着时间的推移快速显示,以产生运动的幻觉,使图像栩栩如生。
术语帧来自电影工业,即使在今天,电影帧也是通过电影放映机以每秒 24 帧(通常缩写为 24 FPS)的帧速率放映的。这就产生了运动的错觉。由于数字视频和动画都是由包含数字图像的帧的集合组成的,所以当涉及到内存数据占用优化工作过程(对于动画素材)和数字视频文件大小数据占用优化工作过程时,以每秒帧数表示的帧速率的概念也是非常重要的。在 JavaFX 中,您将很快了解到,动画的这个属性存储在动画对象的 rate 变量中。
关于动画对象或数字视频素材中的帧的优化概念非常类似于关于图像中的像素(数字图像的分辨率)的优化概念;用的越少越好!这是因为动画或视频中使用的帧数会使所使用的系统内存和所使用的每一帧的文件大小数据量成倍增加。在数码视频中,不仅每帧(图像)的分辨率会极大地影响文件大小,在“压缩设置”对话框中指定的每秒帧数或帧速率也会影响文件大小。在本章的前面,我们了解到,如果我们将图像中的像素数量乘以其颜色通道的数量,我们将获得图像的原始数据足迹。对于动画或数字视频,我们现在将这个数字再乘以需要使用的总帧数,以便创建一个运动的幻觉。
因此,如果我们的游戏有一个动画 VGA (RGB)背景板(请记住,每帧是 900KB ),它使用五帧来创建运动的幻觉,我们使用 900KB 乘以五,或 4500KB(或 4.5MB)的系统内存来保存该动画。当然,这对于一个背景来说占用了太多的内存,这就是为什么我们将使用静态背景和精灵覆盖来在不到一兆字节的时间里达到同样的最终效果。数字视频的计算稍有不同;和数字视频一样,你有成百上千的帧。对于数字视频,您可以将原始图像数据大小乘以每秒帧数(帧速率),这是数字视频设置的回放速率(此帧速率值在压缩过程中指定),然后将结果乘以视频文件中包含的内容持续时间的总秒数。
继续前面使用的 VGA 示例,您现在知道一个 24 位 VGA 映像有 900KB。这使得下一步的计算变得非常容易。数字视频通常以 30 FPS 的速度运行,因此在屏幕上播放之前,系统内存中一秒钟的标清(SD 或 VGA)原始(未压缩)数字视频将是 30 个图像帧,每个图像帧为 900KB,总内存数据量为 27000KB,约为 27MB!
您可以看到为什么拥有 MPEG-4 H.264 AVC 格式这样的数字视频压缩文件格式是极其重要的,这种格式可以显著压缩数字视频所产生的大量原始数据。
JavaFX 多媒体包使用最令人印象深刻的视频压缩编解码器之一(codec 代表 COde-DECode ),它在 HTML5 和 Android 中也受支持:前面提到的 MPEG-4 H.264 高级视频编解码器(AVC)。这种跨越当今三大“开放平台”(Java、HTML5 和 Android)的“交叉开放平台支持”对于开发人员素材优化来说极其方便,因为一个数字视频素材可以跨 Java、JavaFX、HTML5 和 Android 应用使用。如果没有安装 H.264,JavaFX 引擎中还包含一个“本地”数字视频编解码器,称为 VP6。接下来,在深入探讨数字音频之前,我将介绍数字视频素材压缩和数据占用优化的基础知识。然后,在下一章,我们将进入 3D 的复杂性,这样你就对游戏中的新媒体元素有了一个完整的基础理解。
数字视频压缩概念:比特率、数据流、标清、高清和 UHD
让我们从商业视频中使用的主要或标准分辨率开始。这些也恰好是常见的消费电子设备屏幕分辨率,可能是因为如果显示屏像素分辨率与屏幕上“全屏”播放的视频像素分辨率匹配,将会出现零“缩放”,这可能会导致缩放伪像。在 HDTV 或高清出现之前,视频被称为标清(SD ),使用 480 像素的标准像素垂直分辨率。VGA 是标清分辨率,720 x480 可以称为宽标清分辨率。高清(HD)视频有两种分辨率,1280×720,我称之为伪高清,1920×1080,业界称之为真高清。这两种高清分辨率都具有 16:9 的宽高比,用于电视机和 iTV 电视机、智能手机、平板电脑、电子书阅读器和游戏机。现在还有一款 UHD 分辨率为 4096×2160 像素的产品。IMAX 分辨率为 4096 x 4096,因此 UHD 在水平方向(即 x 轴方向)拥有 IMAX 分辨率,这非常令人印象深刻,因为消费者现在只需 1000 美元就可以在他们的客厅拥有 IMAX!
视频流是一个比分辨率更复杂的概念,因为它涉及到在大范围内播放视频数据,例如 Java 游戏应用和远程视频数据服务器之间的视频数据,这些服务器将保存您潜在的大量数字视频素材。流媒体是复杂的,因为运行 Java 游戏应用的设备将与远程数据服务器实时通信,在视频播放时接收视频数据包!这就是为什么它被称为流;因为视频通过互联网从视频服务器流入硬件设备。MPEG-4 H.264 AVC 格式编解码器(编码器-解码器对)支持视频流。
本节中我们需要介绍的最后一个概念是比特率的概念。比特率是视频压缩过程中使用的关键设置;比特率代表您的目标带宽或数据管道大小,每秒钟能够容纳一定数量的比特流。您的比特率设置还应该考虑任何给定的支持 Java 的设备中存在的 CPU 处理能力,这使得您的数字视频的数据优化更具挑战性。幸运的是,如今大多数设备都配备了双核或四核 CPU!
这样做的原因是,一旦这些位通过数据管道传输,它们也需要被处理并显示在设备屏幕上。因此,数字视频素材的比特率不仅需要针对带宽进行优化,还需要考虑 CPU 处理能力的变化。一些单核 CPU 可能无法在不丢帧的情况下解码高分辨率、高比特率的数字视频素材。如果你的目标是旧的或更便宜的消费电子设备,比如第三世界国家使用的设备,请确保优化低比特率视频素材。
数字视频数据足迹优化:视频编解码器的重要设置
正如您在上一节中了解到的,数字视频资源是使用称为编解码器的软件实用程序进行压缩的。视频编解码器有两个“方面”:一方面编码视频数据流,另一方面解码视频数据流。视频解码器将成为使用它的操作系统、平台(JavaFX)或浏览器的一部分。解码器主要针对速度进行了优化,因为回放的平滑度是一个关键问题,编码器也进行了优化,以减少其生成的数字视频素材的数据占用量。因此,编码过程可能需要更长的时间,这取决于工作站包含多少个处理核心。大多数数字视频内容制作工作站应该支持八个处理器内核,比如我的 64 位 AMD 八核工作站。
编解码器(编码器端)类似于插件,因为它们可以安装到不同的数字视频编辑软件包中,以便能够对不同的数字视频资源文件格式进行编码。由于 Java 和 JavaFX 9 本身支持 ON2 VP6 格式和 MPEG4,如果安装了它,您需要确保您使用的数字视频软件包支持使用这些数字视频文件格式之一对数字视频进行编码。
需要注意的是,不止一家软件制造商生产 MPEG4 编码软件,因此就编码速度和文件大小而言,将会有不同的 MPEG4 H.264 AVC 编解码器产生不同(更好或更差)的结果。我更喜欢 MainConcept H.264 编解码器。一个专业的解决方案是 Sorenson Squeeze Pro,它支持 ON2 VP6,如果你想制作专业的数字视频,我强烈推荐你使用它。
我将在图 2-6 中向您展示 Sorenson Squeeze Pro 的数字视频压缩设置(预设)对话框,然后我们将在本章本节的剩余部分讨论一些重要的设置。
图 2-6。
Digital video compression Presets dialog for the Sorenson Squeeze Pro digital video compression utility
还有一个名为 EditShare LightWorks 14 的开源解决方案,计划在 2018 年前原生支持开源编解码器的输出。目前,我将不得不为这本书使用 Squeeze Pro 11,直到 2018 年的某个时候 EditShare LightWorks 14 添加对 JavaFX(以及 HTML5 和 Android)的编解码器支持。优化数字视频数据文件大小(设置压缩设置)时,有大量变量会直接影响数字视频数据的占用空间。我将按照它们影响视频文件大小的顺序来讨论这些问题,从影响最大到影响最小,以便您知道调整哪些参数来获得您想要的结果。
与数字图像压缩一样,视频每帧的分辨率或像素数是开始优化过程的最佳位置。如果您的用户使用 1024x640 或 1280x720 的智能手机、电子阅读器或平板电脑,那么您不需要使用 1920 x 1080 分辨率的真高清来获得数字视频素材的良好视觉效果。有了今天的超精细密度(小点距)显示器,你可以将一个 1280 的视频放大 33%,而且看起来相当不错。这种情况的例外可能是高清或 UHD(通常称为 4K iTV)游戏,目标是 iTV 设置;对于这些 65 到 96 英寸的大屏幕场景,您可能希望使用 1920x1080 分辨率的行业标准真高清。
假设数字视频本身的实际秒数无法缩短,下一级优化将是每秒视频使用的帧数(或 FPS)。这就是所谓的帧速率,不要将视频标准帧速率设置为 30 FPS,如图 2-6 的左上角所示,设置为 1:1,或者每个源帧压缩一帧,而是考虑使用 24 FPS 的电影标准帧速率,甚至 20 FPS 的多媒体标准帧速率。您甚至可以使用 15 FPS 的帧速率,这是视频标准 30 FPS 的一半,这相当于图 2-6 所示的帧速率字段的 1:2 设置,这取决于内容内的移动量(和速度)。请注意,15 FPS 的数据量是 30 FPS 的一半(编码的数据量减少了 100%)。对于某些视频内容,这将回放(看起来)与 30 FPS 的内容相同。测试的唯一方法是尝试不同的帧速率设置,并在视频优化(编码)过程中观察结果。
获得较小数据占用空间的下一个最佳设置是您为编解码器设置的比特率。这显示在图 2-6 的左侧,用红色圈出。比特率等同于应用的压缩量,因此设定了数字视频数据的质量水平。值得注意的是,您可以简单地使用 30 FPS、1920 分辨率的高清视频,并指定低比特率上限。如果您这样做,结果看起来就不会像您第一次尝试使用较低的帧速率和(或)较低的分辨率进行压缩,同时使用较高(质量)的比特率设置时那样专业。这方面没有固定的经验法则,因为每个数字视频素材都包含 100%不同且唯一的数据(从编解码器算法的角度来看)。
获得更小数据足迹的第二个最有效设置是关键帧的数量,编解码器使用它对您的数字视频资源进行采样。该设置在图 2-6 的右侧用红色圈出。视频编解码器通过查看每一帧,然后对接下来几帧中的任何像素变化进行编码来应用压缩,因此编解码器算法不必对视频数据流中的每一帧进行编码。这就是为什么会说话的头部视频比每个像素都在每帧上移动的视频(如摄像机平移的视频)编码更好。
关键帧是编解码器中的设置,它强制编解码器不时对视频数据资源进行新的采样。关键帧通常有一个自动设置,允许编解码器决定采样多少个关键帧,还有一个手动设置,允许您指定关键帧采样的频率,通常是每秒特定次数或整个视频时间长度(总帧数)的特定次数。
一些编解码器设置对话框具有质量或清晰度设置(滑块),用于控制压缩前应用到视频帧的模糊量。如果您不知道这个技巧,对您的图像或视频应用轻微的模糊(这通常是不可取的)可以实现更好的压缩,因为图像中的尖锐过渡(尖锐边缘)比柔和过渡更难编码(这需要更多的数据来重现)。也就是说,我会将质量(或清晰度)滑块保持在 85%和 100%的质量水平之间,然后尝试使用我们在此讨论的其他变量来减少数据占用空间,例如降低分辨率、帧速率或比特率。
最终,对于任何给定的数字视频数据素材,您都需要对许多变量进行微调,以实现最佳的数据占用优化。请务必记住,每个数字视频资源与数字视频编解码器“看起来”不同(数学上)。由于这个原因,不可能有可以被开发来实现任何给定压缩结果的标准设置。也就是说,随着时间的推移,调整各种设置的经验最终会让您对需要更改的各种设置有更好的感觉,以获得想要的最终结果。
数字音频概念:振幅、频率、样本、波形
你们这些音响发烧友已经知道声音是通过在空气中发送声波脉冲而产生的。数字音频很复杂;部分复杂性来自于需要将使用扬声器纸盆创建的“模拟”音频技术与数字音频编解码器连接起来。模拟扬声器通过脉冲产生声波。我们的耳朵以完全相反的方式接收模拟音频,捕捉和接收那些空气脉冲或不同波长的振动,然后将它们转换回我们大脑可以处理的“数据”。这就是我们“听到”声波的方式;然后,我们的大脑将不同的音频声波频率解释为不同的音符或音调。
声波根据每个声波的频率产生不同的音调。宽的或不常见的(长的)波产生低的(低音)音调,而更频繁的(短的)波长产生更高的(高音)音调。有趣的是,不同频率的光会产生不同的颜色,所以模拟声音(音频)和模拟光(颜色)之间有密切的相关性。您很快就会看到,数字图像(和视频)与数字音频之间还有许多其他相似之处,它们将贯穿到您的数字新媒体内容制作中。
声波的音量由声波的振幅或波的高度(或大小)决定。因此,如果你在 2D 观察,声波的频率等于声波在 x 轴上的间距,振幅等于声波在 y 轴上的高度。
声波可以独特地成形,允许声波“搭载”各种音效。“纯”或基线类型的声波称为正弦波,这是您在高中三角学中使用正弦、余弦和正切数学函数学到的。熟悉音频合成的人都知道,在声音设计中还可以使用其他类型的声波,例如看起来像锯子边缘的锯齿波(因此得名)或仅使用直角整形的脉冲波,这种波可以产生即时的开/关声音,并转化为合成数字音频的脉冲(或突发)。
甚至在声音设计中使用随机波形(如噪声)来获得“尖锐”的声音效果。通过使用最近获得的关于数据足迹优化的知识,您可能已经确定,声波(以及一般的新媒体数据)中出现的“混乱”或噪声越多,编解码器就越难压缩它们。因此,由于数据中的混乱,更复杂的声波将导致更大的数字音频文件。
将模拟音频转换为数字音频数据:采样、精度和高清音频
将模拟音频(声波)转换为数字音频数据的过程称为采样。如果你在音乐行业工作,你可能听说过一种叫做采样器的键盘(甚至是架装式设备)。采样是将模拟音频波分割成片段的过程,以便您可以使用数字音频格式将波形存储为数字音频数据。这将一个无限精确的模拟声波转换成离散的数字数据,也就是 0 和 1。使用的 0 和 1 越多,无限精确(原始)模拟声波的再现就越精确。被采样的音频声波的每个数字段被称为样本,因为它在那个精确的时间点对声波进行采样。样本精度决定了用于再现模拟声波的 0 和 1 的数量,因此样本的精度取决于用于定义每个波切片高度的数据量。图 2-7 显示了我使用 Audacity 采样的一个按钮音效,使用 32 位浮点采样精度和 48 kHz 采样速率,我们将在接下来讨论。
图 2-7。
Stereo sample of button sound effect in Audacity using 32-bit float sample accuracy and 48kHz sample rate
就像数字成像一样,这种采样精度被称为分辨率,或者更准确地说(没有双关语),采样分辨率。采样分辨率通常用 8 位、12 位、16 位、24 位或 32 位分辨率来定义。Java 游戏大多利用 8 位分辨率来实现效果,如清晰度不太重要的爆炸,使用 12 位分辨率来实现清晰的语音对话和更重要的音频效果素材,并可能使用 CD 质量的 16 位分辨率来实现背景音乐或需要展现原始音频质量的音频元素。
在数字成像和数字视频中,分辨率以像素数来量化,而在数字音频中,分辨率以使用多少位数据来定义每个模拟音频样本来量化。就像数字成像一样(像素越多,质量越好),样本分辨率越高,声音再现越好。因此,更高的采样分辨率,使用更多的数据来再现给定的声波样本,将产生更高的音频回放质量,代价是更大的数据足迹。这就是为什么 16 位音频(通常称为 CD 音质音频)听起来比 8 位音频更好的原因。根据所涉及的音频,12 位可能是一个很好的折衷方案。
在数字音频领域,消费电子行业有一种新型 24 位音频样本,称为高清音频。高清数字音频广播电台使用 24 位采样分辨率。每个音频样本或声波片段可能包含高达 16,777,216 位的声波采样分辨率,尽管很少使用所有这些位。
一些新的硬件设备现在支持高清音频,例如你在广告中看到的智能手机,具有“高清质量”的音频。这意味着他们有 24 位音频硬件。如今,个人电脑和笔记本电脑以及游戏机和独立电视也标配了 24 位音频播放硬件,因此支持高质量的音频。
需要注意的是,对于 Java 9 游戏来说,HD 音频可能不是必需的,除非您的游戏是面向音乐的,并且使用了高质量的音乐,在这种情况下,您可以通过 WAVE 文件格式使用 HD 音频样本。
除了数字音频采样分辨率,我们还有数字音频采样频率。这是在一秒钟的采样时间帧内,以特定采样分辨率采集的样本数量。在数字图像编辑中,采样频率类似于数字图像中包含的颜色数量。采样频率也可以称为采样率。您可能对术语 CD 音质音频很熟悉,它被定义为使用 16 位采样分辨率和 44.1 kHz 采样速率。这需要 44,100 个样本,每个样本包含 16 位的样本分辨率,即这 44,100 个样本中包含 65,536 位的音频数据。通过将采样比特率乘以采样频率,再乘以音频片段中的秒数,可以计算出音频文件中的原始数据。你可以看到这可能是一个巨大的数字!音频编解码器在优化采样声波数据方面非常出色,数据占用空间非常小,音质损失非常小。
因此,我们在数字成像和数字视频中的权衡同样存在于数字音频中。我们包含的数据越多,我们获得的结果质量就越高!然而,这总是以更大的数据占用为代价。在视觉媒体中,数据足迹的数量是使用颜色深度、像素来定义的,在数字视频和动画的情况下,还使用帧来定义。在数字音频媒体中,它由采样分辨率和采样速率共同定义。数字音频行业目前最常见的采样率包括 8kHz、11.25 kHz、22.5kHz、32kHz、44.1kHz、48kHz、96kHz、192kHz,甚至 384kHz。
我们将在游戏中使用较低的采样率,如 8kHz、22kHz 和 32kHz,经过精心优化,这些采样率可以产生高质量的音效和街机音乐。这些速率对于采样任何“基于语音的”数字音频也是最佳的,例如电影对白或电子书旁白音轨。较高的采样率允许音频再现展现剧院音质,但大多数游戏并不需要。
数字音频素材回放:强制音频回放与流式音频
就像数字视频数据一样,数字音频数据可以被捕获,保存在应用分发文件中(在 Java 中,这是一个 JAR 文件);或者,可以使用远程数据服务器流式传输数字音频。与数字视频类似,流式数字音频数据的优势在于它可以减少应用文件的数据占用空间,就像流式数字视频数据一样。缺点是可靠性。许多相同的概念同样适用于音频和视频。流式音频将节省数据空间,因为您不必在 JAR 文件中包含所有繁重的新媒体数字音频数据,所以如果您计划编写自动点唱机应用,您可能需要考虑流式传输您的数字音频数据。否则,请尝试优化您的数字音频数据,以便您可以将它(受控)包含在 JAR 文件中。这样,当应用的用户需要时,它总是可用的!
流式数字音频的缺点是,如果用户的连接(或音频数据服务器)中断,您的数字音频文件可能无法始终呈现给您的最终用户使用您的游戏应用播放和收听。数字音频数据的可靠性和可用性是在“流式音频数据与捕获式数字音频数据”权衡的另一方面要考虑的关键因素。同样的权衡也适用于数字视频素材。
就像数字视频一样,流式传输数字音频的一个主要概念是数字音频数据的比特率。正如您在上一节中了解到的,该比特率是在压缩过程中定义的。与数字视频一样,需要支持较低比特率带宽的数字音频文件将对音频数据进行更多压缩,这将导致质量下降。这些将在更多的设备上更平滑地传输(回放),因为可以更容易地快速传输和处理更少的位。
JavaFX 中的数字音频素材:数字音频编解码器和数据格式支持
JavaFX 中的数字音频编解码器比数字视频编解码器多得多,因为只有两种视频编解码器,即 MPEG-4 H.264 AVC 或 ON2 VP6。JavaFX 音频支持包括 MP3 (MPEG3)文件、Windows Wave(脉冲编码调制[PCM]音频)WAV 文件、MP4(或 M4A) MPEG-4 AAC 音频和 Apple 的 AIFF (PCM)文件格式。JavaFX 支持的最常见的音频格式是 MP3 数字音频文件格式。MP3 数字音频文件格式受欢迎的原因是因为它具有良好的压缩比和质量比,并得到广泛支持。
MP3 将是一种可接受的数字音频格式,用于 Java 游戏或物联网应用,只要您使用最佳的编码工作流程获得最高的质量水平。值得注意的是,MP3 是一种有损音频文件格式,就像 JPEG 用于数字图像一样,其中一些音频数据以及一些原始音频样本质量在压缩过程中被丢弃,以后无法恢复。
JavaFX 有两种无损音频压缩编解码器,称为 AIFF 和 WAVE。您可能熟悉这些数字音频格式,因为它们是分别用于 Apple 和 Microsoft Windows 操作系统的原始音频格式。这些文件使用 PCM 音频,这是无损的,在这种情况下,因为没有应用任何压缩!脉码调制指的是它所保持的数据格式。
PCM 音频通常用于 CD-ROM 内容以及电话应用。这是因为 PCM Wave 音频是一种未压缩的数字音频格式,它没有应用于数据流的 CPU 密集型压缩算法;因此,解码(CPU 数据处理)对于电话设备或 CD 播放器来说不是问题。
因此,当我们开始将数字音频素材压缩成这些不同的文件格式时,我们将使用 PCM 作为我们的基准文件格式。我们不仅可以看到 PCM (Wave)和 MP3 或 MP4 音频压缩结果之间的差异,从而了解我们对 JAR 文件进行了多少数据占用优化,更重要的是,我们可以看到我们的采样分辨率和采样频率优化将如何影响用于游戏音频效果的系统内存。即使我们使用 MP3 或 MP4 格式,在音频素材可以与AudioClip
类一起使用之前,它仍然必须被解压缩到内存中,并在 Java 游戏中用作声音效果。
由于 Wave 或 AIFF 文件不会有任何质量损失,因为也不需要解压缩,这种脉冲编码调制数据可以直接从 JAR 文件放入系统内存!这使得 PCM audio 非常适合持续时间短(0.1 到 1 秒)的游戏音效,并且可以高度优化,使用 8 位和 12 位采样分辨率以及 8kHz、22kHz 或 32kHz 采样频率。最终,对于任何给定的数字音频数据,要找出 JavaFX 支持的哪种音频格式具有最佳的数字音频压缩结果,唯一真正的方法是用我们知道受支持且高效的主要编解码器对您的数字音频进行编码。当我们在游戏中添加音频时,我们将在第二十一章中经历这一工作过程,并将观察使用相同源音频样本的不同格式之间的相对数据足迹结果。然后,我们将听取音频播放质量,以便我们可以作出我们的最终质量文件大小的决定。这是开发 JavaFX 数字音频素材所需的工作流程,可用于专业 Java 游戏开发工作流程。
JavaFX 还支持流行的 MPEG-4 高级音频编码(AAC)编解码器。这种数字音频数据可以包含在 MPEG4 容器(.mp4
、.m4a
、.m4v
)或文件扩展名中,并且都可以使用所有操作系统来回放。值得注意的是,JavaFX 不包含 MPEG-4 解码器,而是支持所谓的“多媒体容器”。这意味着它使用主机操作系统的 MPEG-4 解码器进行解码。
出于这个原因,并且因为在线听力研究已经得出结论,MP3 比 MP4 格式具有更好的质量(对于音乐),我们将使用 MP3 音频文件格式用于更长形式的音频(游戏背景音乐循环),我们将通过Media
和MediaPlayer
类来使用。我们将使用 PCM Wave 音频格式的短格式音频(游戏声音效果,如枪声、铃声、叫喊声、咕哝声、笑声、欢呼声等类似的一秒钟或更短的数字音频素材),我们将通过 JavaFX 慷慨提供的AudioClip
数字音频排序引擎(类)使用它。
数字音频优化:从 CD 质量的音频开始,向后工作
优化您的数字音频素材以便在市场上最广泛的硬件设备上播放,将比优化您的数字视频或数字图像(以及动画)更容易。这是因为目标屏幕分辨率和显示器宽高比的差异要比硬件设备之间的数字音频播放硬件支持的差异大得多(具有 24 位高清音频播放硬件兼容性的新硬件可能例外)。所有硬件都可以很好地播放数字音频素材,因此音频优化是一个“一个音频素材影响所有设备”的场景,而对于视觉(视频、图像、动画)部分,您可以看到从 4096x2160 像素(4K iTV 电视机)到 320x320 像素(翻盖手机和智能手表)的显示屏。
重要的是要记住,用户的耳朵不能感知数字音频的质量差异,而用户的眼睛可以感知数字图像、2D 动画或数字视频的质量差异。一般来说,在所有硬件设备中,有三个主要的数字音频支持“最佳点”,您应该将它们作为 Java 游戏音频支持的目标。
通过使用 8 kHZ、11.25 kHz 或 22.5 kHz 采样速率以及 8 位或 12 位采样分辨率,较低质量的音频(如短旁白轨道、人物惊呼或短时声音效果)可以获得非常高的质量。中等质量的音频,如长旁白轨道、长持续时间声音效果、循环背景(称为:环境)音频等,可以通过使用 22.5 kHz 或 32 kHz 采样速率以及 12 位或 16 位采样分辨率来达到非常高的质量水平。
高质量的音频素材,如音乐,应该进行优化,以接近 CD 质量的音频,并使用 32 kHz 或 44.1 kHz 的采样速率,以及 16 位数据采样分辨率。对于这种音频频谱的超高端高清质量音频,可以使用 48 kHz 采样速率和 24 位数字音频数据采样分辨率。还有一个未命名的“中间某处”高端音频规范,使用 48 kHz 采样率和 16 位数据采样分辨率,这恰好是杜比 THX 用于其高端音频体验技术的标准。这是在《星球大战》的电影院里使用的。
最终,它归结为从数字音频数据足迹优化工作过程中出现的质量到文件大小的结果,这可以产生一些惊人的结果。因此,在所有这些硬件设备上优化您的数字音频素材的初始工作流程是创建 44.1 kHz 或 48 kHz 的“基线”16 位素材,然后使用 JavaFX 支持的不同格式优化(压缩)它们。一旦工作流程完成,您就可以看到哪些数字音频素材提供了最小的数据占用空间,以及最高质量的数字音频回放。之后,您可以将 48 kHz 或 44.1 kHz 数据降至 32 kHz,先用 16 位分辨率保存,再用 12 位分辨率保存。之后,重新打开原始 48 kHz 数据,下采样至 22.5 kHz 采样频率,并使用 16 位或 12 位分辨率导出该数据,等等。我们将在本书后面的第二十一章中执行这个工作过程,这样你就可以体验音频工作过程。
您将使用开源的 Audacity 2.1.3 数字音频编辑和工程软件包来执行这个工作过程。您在第一章下载并安装了这个软件包,理想情况下,您安装了所有这些免费的 VST、奈奎斯特、LV2 和 LADSPA 插件,以及用于 AC3、AMR-NB、M4A 和 WMA 音频格式的 LAME MPEG3 编码器和 FFMPEG 编码器。如果您还没有这样做的话,您会希望这样做,这样您就拥有了适用于您的 Java 工作站的绝对最强大的数字音频编辑和工程套件。
如果你还没有这样做,也不想现在做,在我们学习如何使用AudioClip
、Media
和MediaPlayer
类将数字音频元素添加到你的 Java 9 游戏环境的那一章,我会告诉你如何做。这真的很简单。您所要做的就是下载这些插件,并将它们放在主 Audacity 软件安装文件夹下的正确插件文件夹中,这样添加效果就不难了。
摘要
在第二章中,我们仔细研究了一些更重要的 2D 新媒体概念,我们将在我们的 pro Java 游戏和物联网开发工作流程中使用这些概念,以便您对 JavaFX 8 support 已将其添加到您的 Java 9 环境中的这些 2D 多媒体素材有一个坚实的基础知识。请注意,Java 和 JavaFX 版本是不同步的,例如 Java 6 使用 JavaFX 1.x,Java 7 使用 JavaFX 2.x。它们更接近于同步版本,因为 Java 8 使用 JavaFX 8,但是 Java 9 的重点是模块化语言,而不是 JavaFX 9,所以目前 Java 9 平台可能使用 JavaFX 8 技术。
我首先介绍了最重要和最基础的 2D 新媒体概念,因为它们与 Java FX 8 有关,Java FX 8 是 Java 8 和 Java 9 的新媒体引擎,也与 Android、iOS、Windows、Linux OS 和 HTML5 开发有关。对于游戏开发人员来说,理解新媒体概念与 Java 9 和 JavaFX 编码实践一样重要,因为新媒体使您的游戏更加身临其境、引人入胜和视觉刺激。如果你想深入了解这些新媒体话题,我在 www.apress.com
有一系列新媒体“基础”书籍。每本书都特别关注一个新媒体领域(音频、视频、VFX、插图、绘画等)。).
由于 3D 要复杂得多,我把这些基础信息留到了第三章;我想把这本书的章节保持在合理的长度,以便最佳地消化所有这些技术信息。3D 和 i3D 新媒体素材明显不同于 2D 和 i2D 新媒体素材,因为这些素材为已经很复杂的 scratch 新媒体内容创作增加了全新的维度、深度数据和 z 轴。这有助于将基本的 2D(面积)数学转化为向量或矩阵代数(学士水平到硕士或博士水平);因此,3D 比 2D 复杂一个数量级。
我们还研究了一些相当先进的数字成像概念、JavaFX 支持的格式、技术和数据占用优化。这些信息将允许您从您在专业 Java 游戏或物联网应用中使用的每个像素中提取最大效用和性能。
您了解了所有关于像素、分辨率以及纵横比如何定义图像、动画或视频的形状的知识,还了解了颜色深度、图层、通道以及 alpha 通道透明度如何允许您实现图像合成管道。您学习了如何使用十六进制表示法定义颜色以及 alpha 通道透明度值。我们研究了先进的数字成像概念,如遮罩、抖动、抗锯齿、混合和转换模式。
接下来,我们研究了时间的第四维度,并了解了如何将帧、帧速率和比特率与我们在“数字成像”一节中学到的概念结合起来,为我们的新媒体素材添加运动,从而创建 2D 动画或数字视频。我们研究了不同的格式和编码它们的编解码器,并研究了一些将编辑和编码数字视频的软件包,如 DaVinci Resolve、Lightworks 和 Sorenson Squeeze。
最后,我们看了数字音频,了解了采样频率和采样分辨率,JavaFX 支持的 MP3 和 PCM 音频格式,以及如何使用这些编解码器来优化数字音频,以使用最少的系统内存。我们研究了如何为我们的 pro Java 9 游戏和物联网应用获得最小的数据占用空间,同时仍然将高质量的产品放入游戏和应用商店。
在下一章中,我们将了解 3D 和 i3D 的概念、原理、格式、优化和工作流程,我们将在您的 pro Java 9 游戏和物联网应用中使用它们。当我们查看诸如 3D 角色建模、骨骼、装配和动画、粒子系统、物理系统(物理力)、流体动力学、视觉效果(VFX)和特殊效果(SFX)、纹理映射、着色器和 UVW 映射坐标等内容时,您将感受到 3D 相对于 2D 新媒体素材有多复杂。
三、高级 3D 内容渲染:3D 素材概念和原则
现在,您已经了解了 2D 新媒体开源内容开发软件包(GIMP、Lightworks、Audacity 和 DaVinci Resolve)所基于的 2D(光栅和音频)内容开发概念和原则,我们将在本章中通过了解 Inkscape (2D 矢量或形状)、Blender (3D 矢量或多边形)和 Fusion (2D 和 3D 视觉效果)来结束对新媒体素材的学习。我们之所以在本章而不是在 2D 内容章节中介绍 Inkscape 2D,是因为我们可以使用关于 2D 矢量图形如何工作的基本概念作为 2D 矢量图形和 3D 矢量图形之间的概念桥梁。这是因为 3D 向量的工作方式就像 2D 向量在 2D X 和 Y 维度中的工作方式一样,只是在 3D X、Y 和 Z 维度中。出于这个原因,我们将从学习 Inkscape 2D 矢量插图或数字插图开始本章,这样我们就可以建立在 2D 矢量知识的基础上,然后学习更复杂的 3D 矢量图形软件包。
我首先介绍顶点(点)和样条线(连接点的直线或曲线)的基本概念,因为它们为 2D 形状或 3D 几何图形提供了基础。这很重要,因为这是你决定成为 2D 矢量插画师或 3D 矢量建模师(或者两者都是)的基础。使用顶点和样条线可以成为一个完整的职业,所以一定要掌握前几节。
接下来,我们将进入你如何把一个空的 2D 形状或者一个 3D 线框变成一个立体的东西。这是通过对 2D 形状使用颜色填充、渐变或图案填充(这些也可以用于 3D 模型)以及对 3D 几何图形使用纹理贴图来完成的。纹理贴图使用 UVW 贴图将 2D 纹理贴图定位到 3D 几何体上。
在我们涵盖了适用于 2D 和 3D 空间的所有概念之后,我们可以进入只在 3D 中遇到的事物。其中包括 3D 渲染,这是将 3D 模型转换为 3D 图像的过程,3D 模型具有 3D 几何图形以及附有 3D UVW 纹理映射坐标的 2D 纹理映射。我把 3D 图像称为静态 3D,因为 3D 技术被用来制作不移动的图像,因此是静态的或固定的。还有 3D 动画,其特点是运动,很像数字视频,以及交互式 3D (i3D),其中编程逻辑嵌入在 3D 对象或场景层次中,这是 3D 的最高级。
动画进入第四维时间,就像数字视频一样,3D 动画为 3D 新媒体素材开发工作流程增加了另一层复杂性。3D 动画就像数字视频一样利用关键帧,因此所有这些相同的概念都适用,例如帧速率;它还有一些其他概念,如 JavaFX 支持的运动曲线,它可以改变加速和减速的速率,为 3D 动画和 JavaFX 中的 2D 动画提供逼真的运动,因为它们是独立的功能。
交互式 3D 包括将代码插入到称为场景图的对象层次结构中,场景图以分层格式保存资源、代码和其他元素。场景图是在 Amiga 时代由 3D 软件包发明的。开创这种设计和开发方法的 3D 软件包是 Realsoft OY 的 Real 3D,今天称为 Realsoft 3D。幸运的是,JavaFX 9 还具有广泛的场景图形 API,这使它非常适合创建交互式 3D 和交互式 2D 游戏以及物联网应用。
互动 2D 素材:2D 矢量内容概念
还有一种类型的 2D 素材,我们没有在第二章中详细介绍,因为它的概念与 3D 直接相关,所以我决定从逻辑上把这一信息放在本章的开头,以便信息更好地流动。2D 和 3D 在顶点和样条线的使用上非常相似,我们将在接下来学习。2D 使用 X,Y 维度,这是一个平面(或平面区域,如果你愿意),三维使用 X,Y,Z 维度(这是一个立方体区域,如果你愿意)。
因此,在本节中,我们将了解如何在 2D 空间中放置点或顶点,然后使用直线或曲线样条将它们连接在一起,并用纯色、颜色渐变或平铺图像图案填充闭合形状,从而创建 2D 矢量插图。JavaFX 9 提供了大量支持这些 2D 元素的 2D 类,以及一个用于导入所有这些 2D 数据元素的SVGPath
类,如果您选择使用 Inkscape 的话。
您将使用 JavaFX APIs 在 Java 中使用的 2D 素材或对象通常被称为形状,尽管它们在技术上也是几何图形,因为形状本身就是几何图形!通常在行业中,3D 被称为 3D 几何,2D 被称为 2D 形状。2D 和 3D 素材的基础都是从空间中称为顶点的点开始的。这些是用(直)线或(非直)曲线连接的。接下来我们来看看这些。
平面上的点:2D 顶点、模型参考原点、枢轴点、虚拟点
现在不要激动——这不是《飞机上的蛇》的续集;这只是一个关于 2D 形状的基础的讨论,它像 3D 模型一样,是基于空间中的点。因为 2D 空间由一个 X,Y 平面组成,我们在一个 2D 平面上放置点。空间中的每个点在专业术语中称为顶点,因为这毕竟是 Pro Java 9 游戏开发。您可以在平面 X,Y 空间中使用这些顶点来创建 2D 形状,也可以在立方体 X,Y,Z 空间中创建 3D 几何体,我们将在本章稍后介绍。
我给这一节加副标题“平面上的点”的原因是因为顶点是用 2D 平面上的 X,Y 网格放置在 2D 空间中的,并且是在 3D 空间的 X,Y,Z 立方体区域中。这个 2D 网格的原点位于 0,0。通常这是屏幕的左上角,对于 3D 立方体区域,该参考原点将位于 0,0,0。
对于 2D 形状和 3D 对象,此原点可以重新定位,因此不同的包将从平面或立方体的不同角引用此网格。稍后您将在 JavaFX 中看到这一点,Java FX 有不同的引用坐标的方式。该平面或立方体区域内的另一个参考点用于 2D 或 3D 对象的旋转,称为轴心点。
例如,如果您希望锤子 2D 形状(或 3D 模型)在手柄末端附近旋转,就像它在现实生活中旋转一样,您可以通过将枢轴从 2D(或 3D)建模空间中的默认(中心)位置向下移动到锤子手柄的末端来实现这一点。对于这个应用,枢轴点将成为该对象轴的中心。枢轴点是素材需要如何旋转的参考原点,而网格(空间)原点将提供如何相对于彼此定位这些点的参考。因此,旋转算法将使用建模网格原点以及枢轴点位置。这些通常是不同的点坐标;然而,在某些情况下,它们可能是同一点。
原点和枢轴都使用轴来表示空间中的点。这个轴可以移动,在 3D 软件中看起来像一个星形,在 2D 软件中看起来像一个加号。事实上,轴实际上是 2D 形状或 3D 几何图形中的一个独立对象,它甚至可以像其他 2D 或 3D 对象元素一样,使用 JavaFX 和 Java 代码来创建与形状或几何图形如何随时间旋转相关的特殊效果。还有一个“虚拟点”,用于特殊效果和高级应用,非常类似于枢轴,但用于其他目的,也用轴来表示。在本书的后面,你会看到这个轴元素对于 Java 游戏是多么重要。
连接 2D 点:矢量线和样条曲线连接你的 2D 顶点
由于顶点在数学上是无限小的,这使得这些小点基本上是不可见的,你需要将它们连接起来,以制作一些你可以可视化的东西。最简单的体现是直线,称为矢量(在 3D 渲染中有时也称为光线)。向量从一个顶点开始向外投影,直到碰到第二个顶点,第二个顶点定义了向量的方向。矢量本身是直的,所以它被认为是直线,而不是曲线。正如你将要看到的,曲线在数学上比直线复杂得多。
由于我们经常想要一条无限平滑的曲线作为我们的 2D 形状或 3D 几何的一部分,我们将需要使用一种不同类型的数学构造,称为样条。样条曲线无限光滑的原因是因为它是一种使用数学方程定义的曲率,对于所有计算机程序员(鉴于本 Pro Java 9 游戏开发书的专业性质,我希望是所有人),可以通过使用较小的数字(如使用浮点数而不是整数)来提高其分辨率。
大多数类型的样条的数学基础被称为贝塞尔曲线,它是以数学家皮埃尔·艾蒂安·贝塞尔的名字命名的,皮埃尔·艾蒂安·贝塞尔是 1910 年至 1999 年间的法国工程师。Pierre 是 3D 实体、几何建模和物理建模领域的创始人之一,也是表示曲线的专业领域的领导者,尤其是在 CAD CAM 和 3D 系统方面。贝塞尔曲线有几种数学格式,包括三次或二次贝塞尔曲线,它们是使用不同类型的数学方程定义的,这些方程定义了如何构造每条曲线。
这些曲线中最简单的是线性贝塞尔曲线,它可用于创建直线(矢量)射线,并且仅使用两个控制点来定义曲线。如果您可以仅使用线性贝塞尔曲线定义形状,您的游戏或物联网应用将使用更少的处理和内存。这是因为需要处理的控制点更少。正如你在图 3-1 的顶部所看到的,Inkscape 使用蓝色绘制控制点及其手柄。如果你想在 Inkscape 中试试这个,点击图 3-1 左侧所示的样条/直线工具,点击创建一个点,点击其他地方的第二个点添加一条直线,然后点击第三个点并拖动创建一条曲线!一旦你掌握了窍门,这就相当容易了;也就是说,事实是,你在本书中学到的一切都需要大量的练习才能达到专业水平。
图 3-1。
Creating an open shape in Inkscape using vectors and splines, closing that shape, and then filling the shape
要调整线性贝塞尔曲线的曲率,可以移动刚添加的顶点的两个手柄。如果你想要一条直线,只要点击添加顶点,直线就会把它们连接起来。另一方面,如果您想要制作一条曲线,请向下单击以添加顶点,并在单击鼠标的同时(按住鼠标),拖出贝塞尔曲线控制点手柄。
第二种最复杂的贝塞尔曲线是二次贝塞尔曲线,以用于描述它的二次数学算法的类型命名。二次贝塞尔曲线有三个控制点,而不是两个,因此它需要更多的处理,但通过使用控制柄对“调整”曲线的曲率提供了更多的控制。
最复杂的是三次贝塞尔曲线,以用于描述它的三次数学算法的类型命名,它有四个控制点,而不是三个,因此它的处理更加密集,但同样,它对调整曲线的曲率提供了更多的控制。
在 Adobe Illustrator 中,控制点被细分为使用手柄和锚点。用于影响曲率的控制柄位置是控制柄。锚点是描述贝塞尔曲线开始和结束位置的顶点。Inkscape 对锚点使用了不同的术语,称之为节点。
还有一种称为 NURBS 或非均匀有理 B 样条的 3D 建模方法,它与贝塞尔样条表示法相关,但针对 3D X,Y,Z 空间的使用进行了优化。NURBS 更复杂,并且允许创建平滑、有机的 3D 几何图形表示。迈克尔·吉布森的 3D 灵感时刻是真正负担得起的 NURBS 建模工具之一,仅售 295 美元;它基于原始的 SGI Alias 波前 NURBS 建模 API。
填充形状内部:颜色填充、渐变和图案
如果使用这些顶点和样条线(或矢量/线)创建的 2D 形状是闭合的,那么它们可以填充各种东西,如纯色、颜色渐变或平铺图像图案。这显示在图 3-1 的底部。要闭合曲线,请绘制最终的矢量(直线)或样条(曲线),直到鼠标光标位于起始顶点上,当该顶点发生变化时(在 Inkscape 中,它会从黑色变为红色),向下单击以创建闭合的形状。要填充您刚刚在 Inkscape 中关闭的形状,请单击填充工具,它与样条线/线条工具及其工具提示一起显示在左侧,然后单击底部色板上的颜色,用该颜色填充所选形状。
Inkscape 和 JavaFX 使用的 2D 矢量图形文件格式是可缩放矢量图形(SVG ),所以如果您保存您的 Inkscape 项目,它将使用.svg
扩展名,例如ProjectName.svg
。如果你想了解更多关于 SVG 的知识,可以看看 Apress.com 的数字插图基础。接下来让我们看看 i3D 媒体素材,Java 8 和 9 使用 JavaFX 9 新媒体引擎完全支持这些素材。
交互式 3D 资源:3D 矢量内容概念
最高级类型的多媒体素材是交互式 3D 矢量对象,可以使用 Java 和 JavaFX API(类和方法)或使用这种方法与 3D 建模包(如第一章中讨论的那些)或 3D 动画包(如 Autodesk 3ds Max,这是我从其第一个版本开始使用的;3D Studio DOS 或者 Blender,已经接近类似水平的专业功能)。i3D 素材由 3D 矢量几何图形组成,使用 2D 光栅图像进行表面处理(我们在第二章中了解到),并在其模型和场景层次结构中包含编程逻辑,这将使它们变得栩栩如生。
在本章的这一节,我们将学习 3D 物体如何从网格到表面模型。我们还将在本章中查看动画、运动曲线、对象层次、轴放置、虚拟对象、粒子系统、流体动力学、头发和毛发动力学、刚体动力学、软体动力学、布料动力学、绳索动力学以及相关的 3D 主题。如你所见,3D 是迄今为止最复杂、最有趣的新媒体类型。
通过使用场景图形对象层次结构内部的编程逻辑,可以进一步使这些 3D 对象具有交互性,场景图形对象层次结构定义了 3D 对象的每个部分将做什么,并且是 JavaFX 9 的一个组成部分。让我们从头开始。我将向您展示 3D 素材从 3D 几何体到 3D 模型、3D 层次结构再到 3D 对象的各种属性。这是最复杂的多媒体,也是 HTML5(使用 WebGL2)、Android 8(使用 Vulkan)以及 Java 8 和 JavaFX 中最不常见的新媒体素材类型。
3D 的基础:网格的几何学
与 2D 形状新媒体元素一样,3D 新媒体元素的最低层是顶点以及这些顶点之间的连接。在 3D 中,你仍然有顶点,但是它们之间的连接变得有点复杂。在 2D 中,顶点、向量(射线或直线)和它们之间的样条线(曲线)是空的(未填充的)、封闭的形状或开放的形状,它们不能被填充,因为它们是开放的,并且会溢出。3D 几何图形之间的连接(在纹理映射之前,在 3D 行业中有时被称为网格或线框,因为这是 3D 几何图形在纹理映射或蒙皮之前的样子),被称为顶点之间的“边”和边之间的“面”。
空间中的点:三维顶点的原点
就像 2D 顶点(在 Illustrator 中称为锚点,在 Inkscape 中称为节点)一样,顶点是 3D 几何和有机(NURBS、Catmull-Rom 样条线和散列面片)建模的基础。顶点定义模型的基础结构(无论是边还是样条)在 3D 空间中的位置,在 3D 中,顶点数据可以保存表面颜色数据、法线数据、UVW 纹理映射数据和顶点 XYZ 位置数据。熟悉 3D 扫描仪的人可能会熟悉点云这个术语,所以顶点仍然是我们在 3D 行业所做的一切的基础。
对于 Java 8 和 9 编码,JavaFX 9 有一个VertexFormat
类,可以保存顶点数据,包括你的顶点位置,法线信息(我们很快会涉及法线),以及 UVW 纹理映射坐标。因此,您可以通过使用 Java 代码为 Java 9 游戏或物联网应用放置顶点,也可以使用 3D 建模器,如 Daz Hexagon、MoI 3D 或 Nevercenter SILO,或者 3D 建模和动画包,如 Blender 或 Autodesk 3ds Max。
连接 3D 顶点:边桥接 3D 顶点
大多数 3D 几何图形使用称为边的东西来连接两个顶点。一条边是一个矢量或直线,所以它看起来像 3D 空间中剃刀的边缘。形成一个多边形需要三条或更多的边,这是我们接下来要讲的。建模三维几何图形时,可以选择顶点、边、多边形或整个对象。
如果您已经使用更高级的基于样条的建模范式创建了 3D 几何体,例如使用 MoI 3D 的 NURBS、使用 SILO 2 的四边形(仅 160 美元)或使用 Animation:Master 的散列面片(仅 80 美元),您将需要将这些格式抽取为多边形或三角形,这将在接下来进行介绍。抽取过程将这些范例中使用的无限平滑的曲线变成了直边的集合。这是使用抽取(平滑度)数值因子(滑块或设置)完成的,该因子通常在文件导出函数中提供,该函数将样条线建模格式从基于曲线的建模器输出到多边形几何模型格式。
创建曲面:三条边形成多边形,四条边形成四边形
将三条边以三角形的形式放在一起后,您就有了一个多边形,它可以作为一个表面来承载皮肤或纹理,使 3D 数据看起来更真实。多边形有时被称为三角形、三角形或面形,一些建模者使用被称为“四边形”的正方形多边形如果您的渲染引擎像 JavaFX 和它的TriangleMesh
类一样需要三角形,您可以将四边形抽取成三角形。在这种情况下,抽取算法相当简单,因为它只是在四边形曲面的两个对角之间插入一条边,从而创建两个角度特征相等(镜像)的三角形。最佳三角形来自正方形多边形,并且具有 45-45-90 度的拐角角度配置。经验法则是,三角形越均匀(正方形),渲染得越好,而“狭长”或长而细的三角形可能会导致渲染瑕疵,但通常不会。
一旦你有了一个表面(通常是一个三角形,如图 3-2 所示),基本立方体上的面是四边形,并且你已经定义了它的法线(我们接下来会了解),那么你就可以应用纹理贴图了。我们将在本章的下一个主要部分讨论纹理映射。还有另一个与相邻多边形或面相关的原则,叫做平滑组,我们将在讨论完曲面法线后再来看看。因此,至少,一个表面(多边形,三角形,四边形,面)将拥有一个法线,一个或多个纹理贴图,和一个平滑组。
图 3-2。
Use the “Display face normals as lines” button to show direction normals for each quad face as light blue lines
指定表面朝向的方向:表面法线的概念
如果你知道如何在你的 3D 软件中打开“显示法线”功能,你可以看到面表面的法线,它将显示为一条从面的中心出来的线,正如你在图 3-2 中看到的浅蓝色。
Blender 2.8 中也有用于显示顶点法线的切换(按钮),顶点法线从顶点指向外,因此对于该模型,顶点法线从立方体的角(45 度)对角指向外,这与面法线的结果完全相反,面法线从面(曲面,四边形)的中心出来,指向直上(90 度,直上,像摩天大楼一样)。正如你在图 3-2 中看到的,显示的两条法线实际上与 x 轴(红色)和 y 轴(绿色)对齐,它们与立方体成 90 度相交。
轴向导位于 3D 编辑模式视图的左下角,它也显示在 Blender UI 左下角的 XYZ 轴向导下。这个表面法线的功能相当简单;它告诉渲染引擎表面面向哪个方向。在这种情况下,这个立方体将渲染为一个立方体,无论你给它什么纹理(皮肤)来着色。同样的逻辑也适用于顶点法线;它将显示渲染引擎处理 3D 几何图形的哪一侧进行表面渲染。
如果这个立方体几何体中的法线指向内部而不是外部,那么立方体在渲染时将完全不可见。在 3D 软件中有一个翻转法线操作(算法),用于通用地反转模型的法线方向(所有法线翻转 180 度)。这将在渲染场景时使用,渲染场景时导入的对象不可见。
当 3D 导入工具将导入的 3D 几何体的法线指向(翻转)错误的方向时,或者当其他 3D 工具的导出器将法线导出到错误的方向(相对于导入法线的软件)时,会出现翻转的法线。这在你的 3D 工作流程中是很常见的,所以如果你要经常在 3D 或者 i3D 中工作的话,至少要使用几次翻转法线功能。
如果你需要一些东西(比如一座房子),其中的 3D 几何体必须从外部和内部渲染,这在 i3D(比如虚拟世界)中很常见,你必须创建双面的几何体,尤其是面。然后你需要应用一个双面纹理贴图和 UVW 贴图,我们将在本章的下一节讨论 3D 纹理贴图的概念和技术时涉及到。
值得注意的是,对于 i3D,具有双面纹理的双面几何图形需要更多的渲染引擎处理,并且渲染是基于用户对交互式 3D 环境、世界或模拟的探索而实时实现的,因此 JavaFX 将同时导航、处理和渲染 i3D 场景,这需要大量的处理器周期才能顺利完成,因此数据优化非常重要。
虽然可以在 JavaFX 中为顶点指定法线,但是法线通常是基于每个面指定的。这就是为什么VertexFormat
类有两种格式的原因。一个支持定义一次法线的多边形的位置和纹理,因为使用三个顶点定义法线不如只使用一个面有效,另一个是当你想使用顶点而不是多边形定义法线时的VertexFormat
数据格式。
平滑曲面:使用平滑组使多边形看起来像样条曲线
你可能见过被渲染为实体(而不是线框)的 3D 模型,但看起来仍然像是被凿过的;也就是说,您可以看到渲染的多边形(面)就像它们是平面一样。在这种情况下,渲染引擎关闭了平滑。如果在启用平滑的情况下进行渲染,这种效果会消失,几何体看起来就像预期的那样,无限平滑,就像是使用样条线创建的,而实际上是使用多边形。让渲染引擎进行平滑会更有效,因此有一个称为平滑组的东西,它应用于每个面,告诉渲染器何时在两个面之间进行平滑,何时不进行平滑,这就留下了通常称为接缝的部分。平滑组使用简单的整数。如果数字在面的每一侧都匹配(对于该边相对侧的每个相邻面),则渲染为平滑过渡(颜色渐变)。如果数字不同,它将呈现为一条接缝;也就是说,该边清晰可见,因为该边每一侧的颜色渐变是不同的(颜色渐变在两个面上不是无缝的,也称为多边形)。
在某些 3D 软件包中,例如 Autodesk 3D Studio Max,您可以在用户界面中看到该平滑组编号模式,并且可以实际选择每个边旁边使用的(整数)编号。您也可以选择边两侧的数字,这是一种更复杂的方法,但为 3D 建模师提供了更精确的平滑控制。
在 Blender 等其他工具中,编号是隐藏的,平滑组功能是通过使用“标记接缝”、“清除接缝”、“标记锐化”和“清除锐化”等命令来“显示”的。这些命令可以在 Blender Edges 菜单中找到,如图 3-3 的左侧所示,其中标记锐化选项显示为淡蓝色。
图 3-3。
Set edge smoothing in Blender using the Edges menu (Ctrl-E when in Edit Mode) command called Mark Seam or Mark Sharp
在 Blender 中,一些 3D 建模者(人,而不是软件)会犯错误,试图通过实际分割 3D 几何图形本身的边缘来暴露其 3D 几何图形中的接缝或锐边,这将实现这种视觉效果,但也可能在 3D 几何图形拓扑优化工作过程中引起问题。如果您熟悉映射中使用的术语拓扑,拓扑非常相似,指的是如何构建 3D 几何图形,以及如何渲染,因为渲染引擎是“基于数学的”,就像 3D 几何图形一样。
3D 模型的拓扑是 3D 几何图形的构造,即顶点、边和面相对于彼此放置的位置,或者是基于样条的有机 3D 模型的构造,其中放置了控制点、控制柄和类似的基于样条的拓扑(以及它们的放置顺序)。换句话说,3D 建模是复杂的!
通过使用 Blender 中的“标记接缝”或“标记锐边”修改器,可以避免分割几何体边来实现接缝。这些特定的混合器修改器实际上是基于平滑组的,因此实现了这种平滑(或边缝)效果,而不会实际影响 3D 几何体拓扑。
Blender 修改器在渲染之前应用,因此不会影响基础 3D 几何体的实际数学拓扑。Blender 修改器始终是一种更灵活的 3D 内容创建方法,因为它在渲染引擎级别应用平滑(或任何其他所需的效果),而不是在 3D 几何体拓扑级别,使 3D 网格保持完整。就像在 Pro Java 9 游戏开发(和物联网设计)中的任何事情一样,如果你能实现预期的效果和最终结果,越简单越好,因为越简单就意味着处理器开销越少。
蒙皮你的三维模型:2D 纹理映射概念
完成 3D 几何图形(3D 模型的基础)后,您可以对其应用纹理贴图,为 3D 模型创建实体外观,并为其添加细节和特殊效果,使其外观越来越逼真。如果您想知道 3D 几何图形和 3D 模型之间的区别,3D 几何图形只是网格或线框,而 3D 模型可以(应该)应用纹理贴图。如果您购买第三方 3D 模型,您希望它们看起来像您渲染它们时的样子,而不仅仅是纯灰色,这是在没有应用任何纹理映射(和没有顶点颜色)信息的情况下渲染的模型的样子。事实上,你会在网上找到的一些 3D 模型(免费或付费)甚至没有应用平滑组,所以你会有一些面,一些平滑,一些纹理到不同层次的细节。有些甚至可能翻转了法线,甚至不会出现在 3D 场景中,直到您对它们应用翻转法线操作或修改器。通常,您必须对任何预先存在的模型进行额外的建模、平滑和纹理映射工作,而不是从头开始创建。我通常尝试从头开始创建一切,因此我可以控制并熟悉底层的 3D 几何拓扑,以及如何将我的平滑组、UVW 贴图坐标、着色器和纹理贴图应用于模型。我们将在本节中讨论所有这些内容。
纹理贴图基础:概念,通道,阴影,效果和 UVW 坐标
纹理映射与正确创建几何拓扑一样复杂;事实上,3D 的每一个领域都同样复杂,这就是为什么 3D 是迄今为止最复杂的新媒体类型,也是为什么 3D 故事片雇佣艺术家来专门关注(工作)和处理我们在本章中看到的每一个领域。纹理映射是 3D 建模中能够使用 2D 矢量或 2D 光栅图像资源的主要领域之一。
值得注意的是,3D 纹理映射还有一个更复杂的领域,也称为纹理,它使用 3D 纹理算法,通常称为体积纹理,来创建贯穿 3D 对象的纹理效果,就好像它是一个实心的而不是空心的(这里是双面的)3D 对象。
纹理映射背后的基本概念是采用 2D 素材,例如我们在上一章中了解到的素材,并将这些 2D 素材应用到 3D 几何体的表面。这是通过使用 UVW 或 3D 映射坐标来实现的,以显示您希望 2D 图像(平面)如何定向或投影到您的 3D 几何表面拓扑上。现在,我想让你迅速从书上抬起头来,对听得见的人喊道:“我真的需要将这个样条拓扑抽取为多边形拓扑,以便我可以使用 UVW 纹理映射坐标将着色器应用到生成的几何体上,并将这个 3D 模型导出到我的 JavaFX 场景图形层次中。”然后继续阅读,就像什么都没有发生过一样,即使你刚刚向每个人展示了你的互动多媒体制作天才。
您可以使用纹理通道将多个纹理贴图添加到 3D 几何体的表面,纹理通道类似于您在 2D 图像合成软件中使用的图层。JavaFX 目前支持四个最重要的纹理通道:漫反射纹理贴图(基本的 ARGB 颜色值),镜面纹理贴图(表面有光泽或暗淡),照明纹理贴图(也称为发光贴图),以及凹凸纹理贴图。
3D 软件包支持其他纹理贴图通道类型,以获得额外的纹理贴图效果。为了能够将这些引入 JavaFX,您必须使用一个称为烘焙的过程。烘焙纹理贴图包括将所有尚不支持的纹理通道渲染到一个单一的漫反射纹理贴图中,因为这是 JavaFX 8 和 9 支持的。这与您在更高级的 3D 动画包中获得的视觉效果非常相似。
正如你在图 3-4 中看到的,Blender 2.8 也使用场景图,就像大多数现代 3D 软件包一样,JavaFX 也提供这种场景图功能;我们将在第八章中讲述它。球体几何体和纹理贴图在场景图形层次中组合在一起,这是我为您展开的。
图 3-4。
Using a Scene Graph (right) to apply a gold texture map and shader (bottom) to a sphere object in Blender
随着时间的推移,理想情况下,JavaFX 9 将添加更多的纹理通道支持,并为开发人员提供更多关于 3D 新媒体资源使用的视觉灵活性,因为透明区域(不透明贴图)和表面细节(法线贴图)是关于高级纹理映射支持的两个最重要的区域。这些最终需要使用 JavaFX API 添加到 Java 中,以便开发人员能够为 Java 游戏创建逼真的 i3D 模型。
纹理通道的集合和控制这些通道相互关系的任何代码,以及它们相对于彼此如何合成、应用和渲染,称为着色器定义。着色器在 3D 行业中也通常被称为材质。在本章的下一节,我们将讨论着色器和着色器语言,因为这是 3D 和 i3D 游戏开发的另一个专门而复杂的领域。我还在我的书《VFX 基础》(Apress,2016)中使用开源 Fusion 8.2.1 详细介绍了着色器构造。
最后,一旦在着色器中定义了纹理,您将需要将这些 2D 素材定向到您的 3D 几何图形,这是通过使用纹理映射坐标来完成的,通常通过称为 UVW 映射的东西来完成,在我们继续进行第四维和动画之前,我们也将在它自己的特定部分中进行介绍。
纹理贴图设计:着色器通道和着色器语言
着色器设计本身就是一种艺术形式;成千上万的着色器艺术家在 3D 电影、游戏和电视节目中工作,确保用于“着色”或“蒙皮”3D 几何图形的着色器使最终的 3D 模型看起来尽可能真实,这通常是 3D 的目标,以取代更昂贵的摄像机拍摄(和重拍)。
基本着色器由一系列 2D 矢量形状、2D 光栅图像或体积纹理组成,保存在不同类型的通道中,这些通道应用不同类型的效果,如漫反射(颜色)、镜面反射(光泽)、发光(照明)、凹凸(地形)、法线(高度)、不透明度(透明度)和环境(环境)贴图。体积着色器本质上也是 3D 的,因此不使用 2D 图像作为输入,而是使用复杂的算法定义来产生穿过 3D 对象的 3D 着色器,这就是为什么它被称为体积着色器。这些 3D 体积着色器也可以设置动画,并可以根据它们在 3D 空间中的位置来更改颜色和透明度。
在此基础上,高级着色器语言(如 Open GL 着色器语言(GLSL))使用代码来指定这些通道如何相互关联,如何应用或处理这些通道中包含的数据,以及如何基于复杂因素(如时间、方向或 3D 空间中的位置)在这些通道中提供其他更复杂的数据应用。着色器的复杂特性也意味着着色器的渲染时处理更加耗时,并且处理周期越长,着色器就变得越复杂。所需的处理器周期通常很昂贵,因为复杂的着色器能够产生照片级的真实效果。
这可能是 JavaFX 9.0 目前支持四种基本(也是最容易处理的)着色器的主要原因。随着硬件变得更加强大(你会在更多的消费电子产品中看到六核、八核和十核 CPU),JavaFX 可能会添加最后两个重要的着色器通道:不透明度(或透明度映射)和法线映射。
纹理贴图方向:纹理贴图投影类型和 UVW 坐标
将 2D 纹理贴图通道中的细节特征(尤其是基本的漫反射颜色通道)与 3D 几何体正确对齐是非常重要的,否则在渲染时会出现一些非常奇怪或至少视觉上不正确的结果。这需要在 3D X,Y,Z 空间中完成,特别是对于体积纹理和 2D 纹理,以定义它们如何投影到或包围 3D 几何图形。
最简单的方法之一是应用纹理贴图的投影类型和相关设置,这将自动为你设置 UVW 贴图的数值。这些 UVW 地图坐标值将定义 2D 影像平面如何映射到 3D 空间中的 3D 几何上,这是 2D 空间和 3D 空间之间的一种桥梁,并且可以手动设置或调整 UVW 浮点值以微调您的视觉效果。
其中最简单的是平面投影,您可以将它想象为好像您的纹理贴图在 3D 对象的前面,并且您用灯光穿过它,因此它看起来像漫反射纹理贴图中的颜色在 3D 对象上。平面投影对于计算机来说是最简单的处理方式,因此如果您能够获得专业 Java 游戏或物联网应用所需的结果,请使用它。但是,它通常用于静态渲染的 3D 影像,因为一旦您将(相机)移动到 3D 模型的侧面,这种类型的投影贴图就不会提供照片级的真实效果。
相机投影类似于平面投影。相机投影将您的纹理从相机镜头(100%平行于镜头)投影到 3D 对象表面,就像幻灯机一样。这可以用于在场景中投影视频背景,以便您可以在它们面前建模或最终制作 3D 素材的动画。如果相机移动,相机投影与镜头前部保持平行。这有时被称为广告牌模式(或投影)。
下一个最简单的是圆柱投影,它比纹理贴图从一个方向到 3D 对象的(固有的)2D 平面投影提供了更多的纹理贴图的 3D 应用。一个圆柱体将围绕你的对象,在上下(z 轴)维度上,将图像投射到你的对象周围!所以,如果你绕着它走,在另一个维度上会有独特的纹理细节,这是平面投影所不能提供的。
一种更复杂的投影叫做球面投影。这提供了比从 X 和 Y 方向沿着 Z 维度将纹理贴图圆柱投影到 3D 对象上更完整的 3D 纹理贴图应用。球形投影试图处理所有三个(X,Y,Z)轴投影方向。
与球形投影类似的是立方体投影,它类似于立方体格式的六个平面投影;这给出了类似于球形投影的结果。将立方投影应用于 3D 对象时,对象的面会根据多边形法线的方向或与面的接近程度指定给立方纹理贴图的特定面。然后使用平面投影方法从立方体纹理贴图的每个面投影纹理,或者对于一些 3D 软件包可能使用球形投影贴图。
如果使用体积纹理,空间投影是三维 UVW 纹理投影,它通过 3D 对象的体积进行投影。它通常与需要内部结构的材质(如木材、大理石、海绵、玛瑙等)的程序纹理或体积纹理一起使用。如果使 3D 对象变形或变换相对于 3D 对象的纹理映射坐标,将会显示体积或程序纹理的不同部分。
还有一个更简单的纹理贴图叫做 UV 贴图(没有 W 维度)。这将纹理应用于二维,而不是三维,并且更容易处理,因为它的数据更少。我们可能会使用 3D 软件在 JavaFX 外部映射我们的 3D 模型,然后使用模型导入器将已经纹理映射的 3D 对象导入到 Java 中,因为从 JavaFX 8 开始,一些更高级的 3D 映射支持的类尚未添加到 JavaFX API 中。
为 3D 模型设置动画:关键帧、运动曲线和 IK
创建 3D 几何体并使用着色器和贴图坐标对其进行纹理贴图后,您可能希望使其以某种方式移动,例如飞行飞机模型。您在第二章中学到的关于数字视频素材和 2D 动画素材的概念同样适用于 3D 动画。
线性动画:轨迹、关键帧、循环和范围
最简单的 3D 动画类型,和 2D 动画一样,是线性动画,它适合许多类型的动画。图 3-5 显示了如何在 Blender 2.8 中使用插入关键帧菜单向立方体对象添加关键帧。
图 3-5。
Using the Insert Keyframe Menu in Blender 2.8 with a Cube object selected to add a Delta Scale keyframe
键盘上的 I 热键用于访问此插入关键帧菜单,并选择立方体对象。大多数 3D 软件包都有通常所说的轨迹编辑器,允许您向轨迹添加关键帧和运动曲线。每个轨迹将与一个 3D 模型相关,如果您的 3D 模型使用子组件分组,那么将有组和子组以及组或子组内的单个组件的轨迹。
线性动画使用的处理能力最少,因此效率最高。如果可以使用线性动画来实现动画目标,请尽可能使用最少数量的轨迹和最少数量的关键帧,因为这将使用最少的系统内存。
如果动画运动是重复的,使用无缝循环而不是长距离。一个无缝运动循环比包含同一运动的多个副本的长范围占用的内存少。对于线性动画来说,使用循环是一个很好的优化原则。接下来,让我们看看一些更复杂的动画类型,包括非线性动画(在一条直线上,具有均匀间隔的关键帧)以及角色动画和程序动画,这些动画用于刚体或软体物理(物理)模拟、布料动力学、头发和毛发动力学、粒子系统和流体动力学等。
非线性动画:运动路径和运动曲线
更复杂类型的非线性动画不太规则,通常看起来更真实,尤其是在涉及人体运动和简单物理模拟的情况下,它将为动画 3D 对象或元素(层次结构中的子对象)实现移动路径。JavaFX 有一个Path
类,可以用作你自己的复杂动画或游戏精灵运动的运动路径。为了给沿该路径的运动增加更多的复杂性,可以使用运动曲线,以便运动本身可以加速或减速,模拟重力和摩擦等情况。使用这些运动曲线可视化表示的数学算法被称为插值器,JavaFX 有一个Interpolator
类,包含各种最标准的(如果有效使用,仍然非常强大)运动曲线算法。
非线性不规则运动关键帧的一个很好的例子是一个橡胶球在弯曲的道路上反弹。道路的弯曲路径将使用您的运动路径来确保球停留在道路曲率上,并且球地板符合道路的坡度(角度)。球的反弹将使用运动曲线,有时也称为运动插值器,以使每次反弹看起来更真实,因为它在空间中的运动随着时间的推移而加速和减速。在这种情况下,这将控制你的球对地面的反应。
图 3-6 显示了屏幕底部的 Blender 时间线编辑器;您可以看到两个旋转关键帧显示为垂直黄线,当前帧设置显示为垂直绿线。
图 3-6。
The Blender 2.8 Timeline Editor , with two keyframes at frame 0 and frame 10, and the current frame 6 setting
包含许多交互元素的复杂物理模拟无法使用关键帧来完成,尽管如果您有大量时间,理论上是可行的;然而,这是无利可图的(不值得你浪费时间)。就像将运动曲线应用于关键帧回放利用插值算法一样,程序动画算法更进一步,不仅影响关键帧的计时,还影响关键帧数据本身(X、Y、Z 数据、旋转数据、缩放数据等)。).
因为程序动画是以算法的形式出现的,所以它非常有效,因为一旦算法被创建,它就可以被一次又一次地使用,而不需要额外的工作。这些程序动画算法在 3D 中创建了许多特殊效果类型,包括刚体动力学和软体动力学(物理模拟)、绳索和链条动力学、布料动力学、头发和毛皮动力学、粒子系统、流体动力学、肌肉和皮肤弯曲动力学、对口型动力学和面部表情动力学。我们将在稍后介绍程序动画,因为我们在这一章的每一节都是从较低级的概念发展到较高级的概念。
接下来让我们概括一下角色动画;这是 JavaFX 可能支持的下一种动画类型,因为 JavaFX 导入程序支持导入更复杂类型的 3D 数据,包括高级类型的动画,如角色动画。
角色动画:骨骼、肌肉、皮肤、正向和反向运动学
更复杂的动画类型是角色动画,角色动画制作人是 3D 电影、游戏或电视内容制作团队中最受欢迎的职位之一。角色动画涉及许多复杂的层,包括为角色的骨骼设置“骨骼”层次,使用反向运动学来控制骨骼(角色)的运动,将肌肉附加到骨骼并定义它们如何弯曲,将肌肉附加到皮肤,甚至添加衣服和布料动态以穿着角色。在 3D 角色动画中,事情以与现实生活中非常相似的方式完成,以便逼真地模拟现实生活,这通常是 3D、i3D 和 VR 试图做的事情。
因此,使用角色动画来模拟生物就像不使用直接编码的动画一样复杂,正如你现在所知道的,这被称为程序动画。
在角色动画的最底层,你有骨骼;骨骼使用反向运动学算法,告诉骨骼它的运动范围(旋转),这样你就不会像《驱魔人》中那样肘部弯曲或头部旋转!你猜对了,骨骼以层次结构连接成骨架。该骨架是您稍后为角色设置动画(关键帧)的对象。您还可以通过将肌肉和皮肤附加到骨骼来模拟肌肉和皮肤,并定义骨骼运动将如何为角色弯曲肌肉和拉伸皮肤。正如您可能想象的那样,设置所有这些是一个复杂的过程;这是角色动画的一个领域,叫做索具。如果你需要添加服装,有一个新的 3D 领域叫做 cloth dynamics,它定义了服装在风中如何移动、起皱和吹动,还有类似的程序动画算法,旨在增加真实感。接下来让我们看看这个,以及其他一些类似的高级程序动画和模拟 FX 算法。
程序动画:物理,流体或布料动力学,粒子系统,头发
最复杂的动画类型是程序动画,因为它需要使用代码来完成,编写计算 3D 向量和矩阵以及物理和流体动力学方程的代码与游戏编程代码一样复杂,如果不是更复杂的话(取决于游戏的复杂性)。在 3D 包中,这种编码通常使用 C++、Python 或 Java 来完成,而 Pro Java 9 游戏开发中的程序化 3D 动画将通过使用 Java 9 APIs 和 JavaFX 8 APIs 的组合来完成。程序化是最复杂但也是最强大的 3D 动画类型,这也是为什么程序化动画程序员是目前 3D 电影、游戏、物联网和互动电视(iTV)行业中另一个更受欢迎的 3D 职位空缺的原因。
3D 建模和动画包中有许多“功能”,如 Blender 或 3D Studio Max,它们实际上是程序动画算法插件,向用户展示用户界面以指定参数,这些参数将在程序动画应用于 3D 模型或复杂的 3D 模型层次(通过使用 3D 软件或 JavaFX 场景图创建,如图 3-4 右侧所示的场景图)时控制程序动画的结果。我们刚刚讨论了一个复杂的骨骼-装配-肌肉-皮肤角色模型层次,可以将 cloth dynamics 应用于该层次,以使服装在 3D 角色奔跑、战斗、驾驶、跳舞等时逼真地移动。
程序动画算法控制的功能(其中许多包括真实世界物理模拟支持)的示例通常添加到高级 3D 动画软件包中,包括 3D 粒子系统、流体动力学、布料动力学、绳索动力学、毛发动力学、软体动力学和刚体动力学。
JavaFX 3D 支持:几何图形、动画和场景包
JavaFX 中有三个顶级包,包含对 2D 和 3D 新媒体资源类型的所有支持。javafx.geometry 包使用 Point2D 和 Point3D 类支持低级 3D 几何构造,例如顶点,使用 Bounds 和 BoundingBox 类支持区域。javafx.animation 包使用时间轴、关键帧、关键值和插值器类支持时间轴、关键帧和运动曲线等低级动画构造。javafx.scene 包包含了许多嵌套包,我喜欢称之为子包,包括用于 2D 或 3D 形状构造的 javafx.scene.shape,如 Mesh、TriangleMesh 和 MeshView 类;javafx.scene.transform 包支持 2D 和 3D 变换,包括旋转、缩放、剪切和变换类;javafx.scene.paint 包包含着色类,如 Material 和 PhongMaterial 类;以及 javafx.scene.media 包(MediaPlayer 和 MediaView 类)。
JavaFX API 3D 建模支持:点、多边形、网格、变换、着色
我将把 JavaFX 3D 素材支持分成两个图,一个用于静态 3D(渲染图像),一个用于动态 3D (3D 动画)。Interactive 3D 将使用 JavaFX 3D 的所有功能以及一些 Java API 功能。第一个图,图 3-7 ,展示了 JavaFX 包支持的四个主要领域。对于创建可用于静态 3D 影像的 3D 模型,以及用于动画 3D 的其他 JavaFX APIs 和用于交互式 3D 游戏、物联网应用和 3D 模拟的 Java APIs,这些都非常重要。
图 3-7。
High-level diagram of JavaFX 3D modeling asset support for geometry, shape, transform, and texture map
javafx.geometry
包包含 Java 和 JavaFX 中所有 3D 或 2D 几何图形的基础,即顶点(点)和空间(边界)。Point2D
类支持顶点(2D 空间中的一个点)和向量(2D 空间中从一个点发出的一条线)表示。一个Point3D
类也支持顶点(3D 空间中的一个点)和向量(3D 空间中从一个点发出的一条线)表示。Bounds
超类表示 JavaFX 场景图节点及其包含的对象的边界。Bounds
超类的BoundingBox
子类包含场景图形节点对象在 2D 或 3D 空间中的边界的更专业的表示。
javafx.scene.shape
包包含用于创建 3D 几何图形的Mesh
、MeshView
和TriangleMesh
对象(类),而javafx.scene.transform
包包含用于将 3D 空间变换应用到 3D 空间中的 3D 几何图形的旋转、缩放、剪切和变换对象(类)。javafx.scene.paint 包包含 Material 和 PhongMaterial 对象(类),允许您使用不同的着色器算法在 javafx 中对 3D 对象进行纹理处理。接下来,让我们仔细看看 JavaFX API 为我们提供了什么来支持第四维时间,以便我们可以为您的 Pro Java 9 游戏(或物联网应用或 3D 模拟)添加 3D 动画功能。
JavaFX API 3D 动画支持:时间轴、关键帧、键值、插值器
正如您可能想象的那样,在 JavaFX 中实现 2D 和 3D 矢量动画的大多数关键类(没有双关语)都存储在 javafx.animation 包中,如图 3-8 所示。例外情况是 Camera 超类及其两个子类 PerspectiveCamera(透视投影)和 ParallelCamera(正交投影)。时间轴对象(类)保存动画定义,动画定义由关键帧对象(类)组成,关键帧对象又由包含实际变换指令数据的 KeyValue 对象组成。一个关键帧对象可以保存一个 KeyValue 对象数组,因此一个关键帧可以保存几个不同的 KeyValue 转换数据对象。还有一个插值器类,它包含许多高级算法,用于将运动曲线应用到时间轴对象内部的关键帧对象。目前支持的插值算法包括离散或离散时间插值、EASE_IN 和 EASE_OUT 以及 EASE_BOTH(缓入缓出)和线性直线(均匀间隔)插值,这显然是处理强度最低的。
图 3-8。
High-level diagram of JavaFX 3D animation support , showing javafx.animation and javafx.scene packages
现在你已经对 2D 矢量插图和三维矢量渲染和动画概念有了一个坚实的(三维)概述,在我们进入第四章的游戏理论、概念、优化等之前,我们将稍微休息一下。
摘要
在第三章中,我们仔细研究了一些与 2D 矢量插图和 3D 矢量渲染、纹理和动画相关的更重要的新媒体概念,您将在您的专业 Java 游戏开发工作流程中使用这些概念,以便您提前掌握这些内容的基础知识。
我从介绍 2D 矢量图形概念开始,这些概念也适用于 3D 矢量图形,包括顶点(顶点或点)、矢量(射线或直线)和样条(带控制手柄的曲线)。我们还研究了如何用纯色、颜色渐变或平铺图像图案填充这些 2D 形状。
然后,我们在这些概念的基础上,带您进入 3D 矢量图形,在那里我们学习了多边形、三角形、四边形、面和边,所有这些都与顶点结合在一起,创建 3D 几何图形。我们研究了如何使用纹理贴图、UVW 贴图和投影贴图使 3D 几何线框(也称为网格对象)看起来立体,以及如何将所有这些以材质或着色器的形式结合在一起。
接下来,我们看了 3D 动画,它比 2D 动画或数字视频复杂得多,因为在高端,它包括角色动画、程序动画和算法特效,其中包括物理模拟数学和控制大量粒子等内容,这些内容产生强大的基于代码的动画系统,如群集模拟、头发和毛皮动力学、群体模拟、流体动力学、布料动力学、软体和刚体动力学。在下一章,我们将看看游戏设计流派。
最后,我们看了 JavaFX 9 APIs,我们将在本书中使用它来实现我们在本章中学到的所有 3D 概念和原理。当我们实现 Java 9 游戏的组件时,我们将详细研究这些。
在下一章,我们将全面了解游戏理论和与创建游戏相关的概念,使用 Java 9 和新媒体素材来实现我们的游戏设计和游戏目标。
四、游戏设计导论:游戏设计概念、类型、引擎和技术
让我们在前两章中学到的新媒体素材知识的基础上,看看如何利用这些强大的像素、帧、样本和向量来创建 pro Java 9 游戏和物联网应用,以及为什么(或为什么不)在某些类型的 pro Java 9 游戏开发类型和场景中使用它们。我们将了解高级游戏概念、基本游戏设计风格和游戏设计优化概念,以及适用于 Java 平台的开源游戏引擎,包括 JBox2D、JBullet、Jinngine 和 Dyn4J 等物理引擎,以及 LWJGL 和 JMonkey 等 3D 游戏引擎。
我想介绍的第一件事是静态(固定)与动态(实时)的基本概念,因为它适用于游戏类型、游戏设计以及游戏优化。我已经在第 2 (图像对音频-视频)和第 3 (渲染 3D 对 3D 动画对交互式 3D)章节中介绍了静态(图像,渲染静态 3D 图像)对动态(数字视频,2D 和 3D 动画,交互式 3D,数字音频)的概念。这个简单的概念是对游戏类型进行分类的好方法,也是游戏优化的基本原则,正如你将看到的。在这一章中,我们将对游戏设计,新媒体的引入,以及不同的游戏设计方法和策略在内存占用和 CPU 处理周期上的花费有一个高层次的概述。
为什么这很重要,为什么我们在本书的第一部分中“预先思考”所有这些游戏设计因素,是因为您应该希望您的游戏能够在用于玩游戏的所有不同平台和消费电子设备上流畅地运行,即使这些设备采用单核处理器。如今单核处理器实际上非常罕见。入门级消费电子设备现在具有双核(双处理器)、四核(四处理器)、六核(六处理器)或八核(八处理器)CPU。流畅游戏的反面会被归类为生硬或不流畅的游戏,这不是一个好的用户体验(UX)。用户体验源于用户界面设计、游戏概念、新媒体素材和代码优化的结合,以及每个用户对你的游戏设计感兴趣和好奇的程度。
接下来我将介绍游戏设计和开发的不同方面或组成部分。这些包括游戏设计和开发的概念、技术和“行话”,我想确保你能跟上速度。这些主题包括 2D 精灵、3D 模型、人工智能、层、级别、碰撞检测、物理模拟、背景板动画、游戏逻辑、游戏设计、用户界面以及类似的游戏设计和开发方面,这些都可以被视为游戏“组件”,因为每一个都为专业的 Java 9 游戏添加了不同的属性和功能。最后,我将介绍您可以设计和开发的不同类型或流派的游戏,只是为了让您的左右脑同时工作,然后我将探讨一些技术问题以及素材和代码优化考虑因素,了解这些流派之间的差异。
高级概念:静态与动态游戏
我想从一个高层次的概念开始,它涉及到我将在本章中讨论的所有内容,从您可以创建的游戏类型到游戏的优化,再到您的 JavaFX 场景图的构建。我们在第二章和第三章中看到了这个概念,我们将在下一章中再看一遍,届时我们将看到固定的或静态的不变的 Java 常量与动态的、实时变化的 Java 变量的概念。类似地,JavaFX 场景图中的用户界面设计可以是静态的(固定的或不可移动的)或动态的(动画的、可拖动的或可换肤的),这意味着您可以更改 UI 外观以适应您的个人喜好。
这些概念在游戏设计和开发中如此重要的原因是因为你的游戏引擎,你将设计来“运行”或“渲染”你的游戏,将需要不断地检查(处理)你的游戏的动态部分,看看它们是否已经改变,因此需要一个响应。响应需要处理,并且需要执行(处理)Java 代码来更新分数、移动游戏板位置、播放动画帧、改变游戏角色的状态、计算碰撞检测、计算物理、应用游戏逻辑等等。每个游戏周期(JavaFX 中称为脉冲)上的这些动态检查要求(和随后的处理)会更新,以确保变量、位置、状态、动画、碰撞、物理等符合您的 Java 游戏引擎逻辑,并且可以真正累加。这就是为什么在你的游戏设计中静态和动态的平衡是重要的;在某些时候,处理所有这些工作的处理器可能会过载,从而降低游戏速度。
这种增强游戏动态的所有实时、每脉冲检查过载的结果是,游戏运行的帧速率可能会降低。没错,就像数字视频和动画一样,游戏也有帧率,但游戏帧率是基于你的编程逻辑的效率。游戏的帧率越低,游戏性就变得越不流畅,至少对于街机游戏这样的动态、实时游戏是如此。游戏的流畅程度与用户体验的“无缝”程度有关。
因此,静态与动态的概念对于游戏设计的每个方面都非常重要,并且使得某些类型的游戏比其他类型的游戏更容易获得更好的用户体验。我们将在本章的下一节讨论不同类型的游戏,但是正如你所想象的,棋盘游戏本质上更“静态”,而街机游戏本质上更“动态”。也就是说,有一些游戏优化方法,我们将在本书中讨论,可以使游戏保持动态(看起来好像有很多正在进行),当从 CPU 的处理角度来看,真正正在进行的事情,从处理角度来看,变得可管理。这是游戏设计的众多技巧之一,说到底,都是关于以这样或那样的方式优化。
我在 Android (Java)编程书籍中涉及的最重要的静态与动态设计问题之一是使用 XML 的 UI 设计(静态设计)与使用 Java 的 UI 设计(动态设计)。Android 平台将允许使用 XML 而不是 Java 来完成 UI 设计,以便非程序员(设计者)可以为应用进行前端设计。JavaFX 允许通过使用 JavaFX 标记语言(FXML)来完成完全相同的事情。
为此,您必须创建 FXML JavaFX 应用,正如您在第六章中在 NetBeans 9 中创建游戏应用时所看到的那样。该选项将javafx.fxml
包和类添加到您的应用中,允许您使用 FXML 设计 UI,然后让您的 Java 编程逻辑“膨胀”它们,以便设计成为 JavaFX UI 对象。需要注意的是,使用 FXML 会在应用开发和编译过程中增加另一层处理器开销,包括 FXML 标记及其翻译(和处理)。出于这个原因,并且因为,归根结底,这是一本专业的 Java 9 游戏开发书,而不是 FXML 标记标题,所以在本书中,我将重点介绍如何使用 Java 9 和 JavaFX APIs 做所有事情,而不是使用 FXML 做事情。
在任何情况下,我关于使用 XML(或 FXML)创建 UI 设计的观点是,这种 XML 方法可以被视为“静态的”,因为设计是预先使用 XML 创建的,并在编译时使用 Java 进行“膨胀”。Java 膨胀方法使用设计者提供的 FXML 设计来创建场景图,该场景图基于使用 FXML 定义的 UI 设计结构填充有 JavaFX UI 对象。静态 UI 被设计为固定的,为游戏玩家处理用户界面,当你的游戏被加载时,它被一次性放入内存。
游戏优化:平衡静态元素与动态
游戏优化归结为平衡不需要实时处理的静态元素和需要持续处理的动态元素。过多的动态处理,尤其是在不需要的时候,会让你的游戏变得不稳定或生硬。这就是为什么游戏编程是一种艺术形式;它需要“平衡”,以及伟大的人物,故事情节,创造力,幻想,预期,准确性,最后,优化。
例如,表 4-1 描述了在动态游戏中优化不同游戏组件的一些注意事项。正如你所看到的,游戏中有很多地方可以优化,使处理器的工作负载大大减少。如果这些主要的动态游戏处理区域中有任何一个因为处理器宝贵的“每帧周期数”而“失控”,都会极大地影响游戏的用户体验。我们将进入游戏术语(精灵,碰撞检测,物理模拟等。)在本章的下一节。
表 4-1。
Some Aspects of Gameplay That Can Be Optimized
玩游戏方面 | 基本优化原则 |
---|---|
精灵位置(移动) | 移动精灵尽可能多的像素,同时仍然实现平滑的运动外观 |
精灵动画 | 最大限度地减少需要循环的帧数,以创建流畅的动画效果 |
冲突检出 | 仅在必要时检查屏幕上对象之间的碰撞(非常接近) |
物理模拟 | 尽量减少场景中需要进行物理计算的对象数量 |
群体/人群模拟 | 最大限度地减少需要循环的成员数量,以创造一种人群或羊群的假象 |
粒子系统 | 最大限度地减少粒子的复杂性和制造错觉所需的粒子数量 |
相机动画(3D) | 尽量减少相机动画,除非它是游戏不可或缺的一部分(绝对需要) |
背景动画 | 最小化动画背景区域,使整个背景看起来是动画,但实际上不是 |
数字视频解码 | 尽量减少数字视频的使用,除非它是游戏不可或缺的一部分(绝对需要) |
游戏性(或人工智能)逻辑 | 尽可能高效地设计/编写游戏逻辑、模拟或人工智能 |
记分板更新 | 通过绑定更新记分板,并将显示更新减少到最大每秒一次 |
用户界面设计 | 使用静态用户界面设计,这样脉冲事件就不会用于 UI 元素定位 |
考虑所有这些游戏内存和 CPU 处理优化问题将使你的专业 Java 9 游戏设计和 Java 编码,确实是一个相当棘手的努力。不用说,很多投入到专业 Java 9 游戏中。
值得注意的是,其中一些共同作用,给玩家创造了一种错觉;例如,精灵动画将创建角色奔跑、跳跃或飞行的幻觉,但如果不将该代码与精灵定位(运动)代码相结合,就无法实现幻觉的真实性。要微调幻像,可能需要调整动画的速度(帧速率)和移动的距离(每帧移动的像素)。我喜欢称这些调整为微调。调整意味着手动插入数据值,以获得最真实的最终结果。游戏开发是一个迭代的过程;尽管你可能试图坐下来预先设计你的游戏,然后创建新的媒体素材并编写 Java 代码,但修改是不可避免的。
在本书中,我们将会深入到专业 Java 游戏设计和开发的许多领域,但是为了在这里更详细地阐明这些,当我们在考虑这些因素时,如果你可以移动游戏元素(主要玩家精灵,投射精灵,敌人精灵,背景图像,3D 模型)更多的像素和更少的次数,你将会节省处理周期。需要处理时间的是移动的部分,而不是距离(移动了多少像素)。类似地,对于动画,实现令人信服的动画所需的帧越少,保存帧所需的内存就越少。同样的原则也适用于数字视频数据的处理,无论您的数字视频素材是捕获的(包含在 JAR 文件中)还是来自远程服务器的流。无论您是否在传输数据,解码数字视频帧都是处理器密集型的,并且会占用游戏其他组件的宝贵 CPU 周期,这可能也需要大量处理。
同样重要的是要记住,我们在优化内存使用和处理器周期,这两者是相辅相成的。使用的存储单元越少,处理器检索数据的努力就越少,因为一次一个存储地址地读取和处理存储单元;要处理的地址越少,意味着正在进行的处理越少。因此,内存优化也应被视为一种处理周期优化。
有了群集、群体动力学和粒子系统效果,当使用像这样的处理密集型特效时,需要处理的元素更少,每个元素的复杂性更低,这将很快增加。这些类型的基于粒子的特效为任何游戏、电影或电视剧添加了大量“哇”的因素,但也需要实时处理大量数据,这可能是处理密集型的。我们将在第五章中讨论数组。
检测碰撞是许多不同游戏类型的游戏编程逻辑的另一个主要部分,例如街机游戏、棋盘游戏和虚拟现实游戏,例如第一人称射击游戏。不盲目检查(处理)游戏元素之间的碰撞是非常重要的。请确保排除不在“游戏中”(在屏幕上)或不活动、彼此不靠近或永远不会相互碰撞的游戏资源(静态元素)。正如您可能想象的那样,冲突检测考虑、优化和编程本身就是艺术形式,关于这个主题的书籍已经有很多了,所以请客观地看待这个有趣的主题,并自己研究它,尤其是如果您对创建可能会发生大量冲突的 Pro Java 9 游戏感兴趣的话。
为物理模拟计算自然力是处理器最密集的,像碰撞一样,许多书籍都写了物理编程的每个单独领域,包括刚体或软体动力学,布料动力学,绳索动力学,头发和毛皮(实际上是连接的圆柱体粒子系统),以及流体动力学或驾驶动力学(用于驾驶游戏)。如果这些类型的物理模拟中的任何一种都没有经过精心的编码和优化,那么根据游戏玩家在他们玩游戏的消费电子硬件中拥有多少处理器内核,整个游戏用户体验可能会陷入停滞。
2D 与 3D 渲染:幕后的静态与动态
静态 3D 游戏,例如国际象棋,其中游戏棋盘是静态的,除非你将一个游戏棋子移动到棋盘上的新位置,否则游戏棋盘可能看起来是静态的;然而,由于它们利用 3D 实时渲染来创建虚拟现实环境,因此“在引擎盖下”系统可能会忙于实时渲染几何图形、照明、相机和材质,这取决于您如何在场景图形中设计所有这些元素以及如何设置 Java 游戏处理逻辑。正如我们在第三章中看到的,3D 新媒体素材的性质比 2D 新媒体素材复杂一个数量级,同样的事情也适用于 3D 游戏和 2D 游戏。2D 游戏确实有一个“渲染”元素,称为双缓冲,下一帧在显示在屏幕上之前在内存中合成。然而,3D 渲染实际上是创建像素颜色和 alpha 值,而不是简单地在 X,Y 位置组织它们。3D 渲染基于 3D 几何图形、变换、材质、着色器、贴图坐标、灯光位置和相机位置,从头开始创建像素颜色值和 X、Y 位置。
接下来,让我们更详细地了解一下专业 Java 游戏设计方面和一些核心游戏概念的考虑事项,并了解一下适用于所有游戏类型的一些核心优化原则。
游戏组件:2D、3D、碰撞、物理和人工智能
让我们来看看为了能够构建一个游戏,你需要理解的各种游戏设计概念、方面和组件,以及我们可以使用哪些 Java(或 JavaFX)包和类来实现游戏性的这些方面,我喜欢称之为游戏性设计和开发的组件。这些可能包括游戏元素本身,在游戏行业中通常称为 2D 游戏的精灵或 3D 游戏的模型,以及处理引擎,我们将自己编码或导入预先存在的 Java 代码库,如人工智能、物理模拟、粒子系统、反向运动学或碰撞检测。我将花一些时间来介绍这些组件,为什么它们适用于一个专业的 Java 游戏,以及如果你决定使用这些游戏组件时应该记住的一些优化注意事项。
2D 精灵:街机风格游戏的基础
让我们从最古老的电子游戏形式之一的基础开始,街机游戏。被称为精灵的 2D 素材定义了我们的主角,用来伤害主角的投射物,主角收集的宝物,以及发射这些投射物的敌人。精灵是 2D 图形元素,可以是静态的(固定的,单个图像)或动态的(动画,几个图像的无缝循环)。精灵可以是矢量(形状)或光栅(基于图像)资源。如果它们是基于图像的,它们通常是 PNG32 并带有 alpha 通道,以便它们可以在游戏设计的其余部分上实时合成,并使结果看起来像是数字视频,也就是说,就像是用相机拍摄并在屏幕上播放,而不是根据游戏玩家的输入(通常是游戏控制器、键盘按键或 iTV 遥控器)实时合成。
一个精灵将根据指示游戏如何运行的编程逻辑在屏幕上移动。精灵需要与场景图形中的背景图像和其他游戏元素以及其他玩家的精灵合成,因此用于创建精灵的 PNG32 图形需要支持透明背景。这也是为什么我讨论了遮蔽 2D 物体的主题——在你的游戏中用作精灵。
这也是为什么我在第二章中向你介绍了阿尔法通道透明度的概念,因为我们需要用精灵来达到同样的最终效果,这样我们才能在游戏中获得无缝的视觉体验。我们将在本书的后面介绍如何使用 GIMP 创建使用 alpha 通道的图形,这样你就可以创建专业级的精灵,与你的游戏图形无缝合成。
由于 3D 模型都在同一个渲染空间中,3D 渲染引擎会为您处理这个透明度因素,如果您使用 3D 模型而不是 2D 精灵,您不必担心游戏的每个 3D 组件都有 alpha 通道。接下来让我们看看 3D 模型,作为对这一点的跟进。
3D 模型:游戏角色扮演风格的基础
一种较新的电子游戏形式涉及实时渲染的虚拟世界,这要归功于 OpenGL ES 3 和 Vulkan 等实时渲染平台的出现,这些平台用于 Android、HTML5、JavaFX 和 DirectX (DirectX 用于 Xbox 和 Windows 等微软产品)。3D 模型提供了更加灵活、先进和逼真的游戏,因为它们将所有其他新媒体类型(除了数字音频)结合成一个;因为纹理、材质和着色器可以使用数字图像和数字视频;因为 3D 几何网格对象可以使用它们的实际几何来计算与实际对象的碰撞,并且是在三维空间而不是二维空间。此外,3D 素材可以对多种灯光和摄像机做出实时反应,您可以通过 i3D 获得一个更加强大的游戏设计环境,尽管从数学和 Java 编码的角度来看更加复杂。
由于 3D 和 i3D 非常复杂,因此有大量的优化考虑因素,例如优化网格(几何体),这是一个称为低多边形建模的过程,它涉及到少量使用点、边和面,然后使用平滑组来提供平滑的曲率,这也可以通过添加更多几何体来实现。在第二章中,我还提到了一些优化原则,比如使用更少的像素,更低的色深,以及更少的通道用于纹理贴图,也就是图像。同样,对于 3D 动画,在第二章中,我介绍了一些用于动画数据的优化原则,这类似于数字视频,例如使用更少的像素、更少的帧、更低的色深和更少的通道,以及更简单的插值算法。
3D 和 i3D 游戏的另一个优化与用于照亮虚拟世界的灯光数量有关。灯光计算往往很昂贵,以至于大多数游戏引擎,包括 JavaFX,都将允许的灯光数量限制在 8 个或更少。可以使用的灯光对象越少,渲染引擎需要进行的计算就越少,游戏运行的帧速率就越高(越快)。
同样的考虑也适用于 3D 动画软件中的相机。例如,当渲染到胶片时,可以根据需要渲染尽可能多的场景摄影机视图,而不会(实时)影响处理器。当消费电子设备处理 3D 时(相对于工作站的大规模渲染场),最大限度地减少摄像机的数量变得非常重要,因为每个摄像机都输出相当于未压缩的原始数字视频数据流。在这种情况下,您有一个 3D 渲染引擎实时生成另一个未压缩的 2D 动画(视频)资源,这再次占用了您的大量处理能力,因此仅当您的游戏绝对需要实时平视显示器(HUD)时才使用它,例如,用于实时第二游戏视角。
碰撞检测:游戏素材交互的基础
对于某些类型的游戏来说,游戏性的另一个重要组成部分或方面是碰撞检测,因为如果你的游戏元素只是在屏幕上飞过彼此,并且当它们相互接触或“相交”时从未做过任何酷的事情,那么你真的不会有一个很好的游戏!想象一下没有任何碰撞检测的弹球游戏或台球!一旦你添加了一个由相交逻辑处理例程组成的碰撞检测引擎,你的游戏将能够通过处理它们的几何组成部分(通常是边、线、曲线或它们的边界(BoundingBox
)的相交来确定任何 2D 矢量精灵或 3D 模型何时相互接触或重叠。
碰撞检测将调用(即触发)相关的游戏逻辑处理例程,这些例程将确定当任何给定的 2D 精灵或 3D 模型(如投射物和主要角色)相交时会发生什么。例如,当投射物与主角相交时,伤害点可能会增加,生命力指数可能会降低,或者可能会启动垂死挣扎动画。另一方面,如果一个宝物与一个主要角色相交(也就是说,被一个主要角色拾起),力量或能力点可能会增加,生命力指数可能会增加,或者“我找到了”庆祝动画可能会开始。
正如你所看到的,根据你正在创建的游戏类型,游戏的碰撞检测引擎很可能是你游戏背后的基础设计元素之一,除了你的 i2D 精灵或 i3D 模型,它们代表了你的角色,投射物,宝藏,敌人,障碍和道具本身,这就是为什么我以这个顺序介绍这些。一旦检测到碰撞,通常你的物理模拟代码会被触发,向游戏玩家显示物体在碰撞后需要如何相互反应。接下来让我们来看看。
物理模拟:游戏真实感的基础
另一个重要的组成部分,或者说属性,是真实世界的物理模拟。像 JavaFX Interpolator
类提供的重力、摩擦、反弹、阻力、风、加速、减速和运动曲线以及类似的力的添加,都将在已经非常逼真的精灵、同步动画序列、场景背景和高度精确的碰撞检测的基础上增加额外的真实感。
重力、摩擦力、阻力和风是在 Java 代码中模拟或调整 2D 精灵或 3D 模型运动的最简单的因素。我在我的书《Java 8 游戏开发入门》(Apress,2014)的结尾提到了这一点。反弹类似于运动曲线中加速和减速的数学计算,可用于模拟单次反弹,但不能模拟物理模拟的反弹衰减。
你可以编写自己的 Java 方法来将物理学应用到你的专业 Java 游戏中,或者你可以使用第三方 Java 库,这些库可以在 SourceForge、GitHub 或code.google.com
等网站上获得。我将介绍一些用于 2D 和 3D 游戏引擎、2D 和 3D 物理模拟引擎、2D 和 3D 碰撞检测引擎、正向和反向运动学引擎等的库。我将在本章的下一个主要部分介绍这一点,以防万一你想在你的专业 Java 游戏或物联网应用中使用这些,而不是编写你自己的。
有趣的是,我将在本章的“Java 引擎:游戏、物理和逆运动学”部分介绍的大多数开源第三方物理引擎不仅实现了物理模拟,还实现了碰撞检测。这是因为这两件事在现实生活中紧密联系在一起。为了很好地处理碰撞,物理模拟需要无缝集成到代码中。在现实生活中,碰撞之前和之后都涉及物理学,这些引擎寻求重新创建现实生活中(完全可信)的实现结果。请注意,从 Java 9 开始,为了在新的 Java 9 模块系统中正确使用,这些产品需要进行“模块化”。
人工智能:游戏逻辑的基础
最后,您可以添加到游戏中的最专有的属性或逻辑结构(Java 代码)是自定义游戏逻辑,它使您的游戏在市场上真正独一无二。这种人工智能(AI)编程逻辑应该保持在自己的 Java 类和方法中,与物理模拟或碰撞检测代码分开。毕竟,Java 使模块化变得容易,这种游戏智能就像是你的职业 Java 游戏的裁判。它监督玩家,对手,障碍,宝藏,得分,处罚,等等,确保游戏体验每次对每个人都是一样的!这与体育赛事或比赛中裁判的职能相同。
有针对 Java 的第三方 AI 引擎;然而,我建议在这个领域,你可以从头开始编写你的游戏逻辑代码,以便它与你的用户界面(UI)代码、你的评分引擎代码、你的动画引擎代码、你的演员(精灵或模型)运动代码以及你的碰撞处理代码集成,这是任何第三方 AI 规则引擎都无法做到的。
当你开始把所有这些游戏组件加在一起,它开始让游戏更可信,也更专业。一个伟大游戏的关键目标之一是“暂停信仰”,这简单地意味着你的玩家百分之百地“买入”前提、角色、目标和游戏性。这是任何内容制作人,无论是电影制作人、电视剧制作人、作者、歌曲作者、游戏程序员还是应用开发人员,都追求的目标。如今,游戏具有与任何其他内容分发类型相同的创收能力,如果不是更多的话,并且您可以直接向公众分发它们,而无需电影工作室、唱片制作人或电视网络等中间人。这是最重要的部分,因为您将获得“70%您,30%商店”的分成,而不是“70%经销商,30%您”的分成!
Java 引擎:游戏、物理和逆运动学
有许多开源的第三方游戏引擎,物理和碰撞引擎,人工智能引擎,甚至反向(或正向)运动学(IK)引擎都可以很容易地在互联网上找到。大多数都在 SourceForge 或 GitHub 或code.google.com
上,使用基本的谷歌搜索就能找到。大多数都是 JAR 格式的。
游戏引擎:JMonkey 和轻量级 Java 游戏库
LWJGL 是一个开源的跨平台 Java 库,对于 3D 图形(OpenGL)、3D 音频(OpenAL)和并行计算(OpenCL)应用的开发非常有用(图 4-1 )。API 访问是直接的、高性能的,而且还被包装在适合 Java 生态系统的类型安全层中。其他高级 3D 游戏引擎也可能使用 LWJGL。
图 4-1。
The Lightweight Java Game Library 3 is an open source Java game library compatible with Java and JavaFX
JMonkey 也是一个免费的开源游戏引擎,面向想要创作 i3D 游戏的 Java 游戏开发者(图 4-2 )。该软件完全用 Java 编程,旨在提供广泛的可访问性和快速部署。
图 4-2。
jMonkeyEngine 3.0 is an open source cross-platform game engine compatible with Java and JavaFX
值得注意的是,我将向您展示如何仅使用 Java (8 或 9)和 JavaFX 8 或 9 创建游戏,因为这正是本书的内容:使用并学习原生 Java APIs(其中一个是 JavaFX)来制作游戏或物联网应用。在我们开始之前,我想让您了解两个领先的 Java 游戏平台。
物理和碰撞引擎:Jbox2D,JBullet,Dyn4j,Jinngine
有许多第三方物理引擎也支持碰撞检测,因此您可以使用这些碰撞物理代码库中的任何一个将碰撞和物理同时添加到您的游戏中,只需将 JAR 文件导入到您的项目中并调用适当的 API 即可。
Jbox2D 是 Box2D C++物理引擎在 Java 中使用的“端口”或重新编码。Box2D 是一个开源的、基于 C++的物理和碰撞引擎,用于模拟 i2D (X,Y)空间中的刚体动力学。Box2D 由 Erin Catto 开发,在 zlib 许可下发布,不需要正式的使用确认;但是,如果您使用 C++ Box2D 中的 Jbox2D API 端口,建议您在您的 pro Java 游戏中使用 Box2D。
JBullet 是用于 Java 的 Bullet 2.7 3D C++物理引擎的部分端口。Bullet 2.87 是一个开源的 C++物理和碰撞引擎,用于模拟 3D (X,Y,Z)空间中的刚体动力学。子弹碰撞检测和物理库是由 Advanced Micro Devices 开发的,也称为 AMD。如果您想了解更多信息,可以在 http://bulletphysics.org
找到。JBullet 是在 zlib 许可下发布的,不需要正式的使用确认。但是,如果您使用来自 Bullet 2.7 C++物理引擎的 JBullet API 部分端口,建议您在您的 pro Java 游戏中使用 JBullet。
图 4-3 所示的 dyn4j 引擎是一个兼容 Java 6 和 7 的 2D 碰撞检测和物理引擎,这意味着它可以与本书中涉及的 Java 8 和 9 版本一起工作。Dyn4j 被设计为稳定、可扩展、优化(快速)且相对易于使用。Dyn4j 可免费用于商业用途,也可用于非商业用途。它是由它的作者 William Bittle 根据 BSD 许可模型授权的。
图 4-3。
Dyn4j is an open source, 2D collision detection and physics engine that is available under the BSD license
Jinngine 是一个用 Java 编写的开源轻量级 3D 物理引擎,它为您提供实时碰撞,以及实时物理计算功能。用户可以通过调用 API 函数来指定几何图形、关节和参数,从而设置和模拟物理。摩擦是通过实现一个近似的共隆摩擦定律来模拟的。这个物理引擎只专注于碰撞和物理,没有渲染功能,并且使用基于速度的算法方法构建,该方法使用高效的 NCP 解算器进行求解。您可以使用 jinngine 作为您的物理引擎,也可以使用该引擎的其他组件,就像它是一个 Java 代码库一样,例如,如果您只想实现碰撞检测,或者如果您只想利用接触点生成功能。接下来,让我们看看反向运动学,或 IK 引擎,它在角色动画中用于定义骨骼结构及其关节运动限制。
反向运动学和机器人引擎:JRoboOp 和 JavaFX-IK
JRoboOp 是一个开源的 Java 库(包),设计用于 IK 机器人模拟和 3D 机器人模型的可视化。该引擎模拟机器人反向运动学以及机器人动力学,并基于名为 ROBOOP 的 C++库。这个库是由蒙特利尔理工大学的 Richard Gourdeau 开发的,它与 Java 5 和更高版本以及 JavaFX 1.3 和更高版本兼容,这意味着它可以很好地与我们将在本书中讨论的 Java 7、Java 8、JavaFX 9 和 Java 9 版本一起工作。这个包是在 GNU 公共许可证(GPL)下发布的。
JavaFX-IK 库是大约两年前专门为 JavaFX 创建的,可以在 GitHub 的 https://github.com/netopyr/javafx-ik
下载。它是在 Apache 许可证 2.0 版下授权的。IK 软件由德国弗莱堡的高级软件工程师 Michael Heinrichs 创建,它允许您使用骨骼对象在 JavaFX 场景图形中创建骨骼对象结构。
接下来,让我们看看可以创建的不同类型的游戏,以及这些游戏在应用精灵、碰撞检测、物理模拟和游戏 AI 逻辑等核心游戏组件方面有何不同。
游戏类型:益智游戏、棋盘游戏、街机游戏、射击游戏或虚拟现实游戏
就像我们在这一章中讨论的其他东西一样,游戏本身也可以用“静态对动态”的方法来分类。静态游戏不受“处理器限制”,因为它们往往是“基于回合”的,而不是“基于手眼协调”的,所以从某种意义上说,它们更容易顺利工作,因为只有游戏“规则”的编程逻辑和吸引人的图形需要到位和调试。一个重要的机会也存在于开发新类型的游戏类型,这些游戏类型以前所未见的创造性方式使用静态和动态游戏的混合组合。我自己也在研究其中的一些!
静态游戏:策略、知识、记忆和棋盘游戏
由于这是一本专业的 Java 游戏编程书籍,我将从这个重要的(对游戏开发而言)静态与动态的角度出发,将游戏分为三个独立的类别(静态、动态和混合)是一个非常巧妙的方法。先来涵盖静态(固定图形),回合制游戏。这些游戏包括“基于移动”或“回合制”的游戏,如棋盘游戏、益智游戏、知识游戏、记忆游戏和策略游戏,所有这些游戏的受欢迎程度和可销售性都不容小觑,尤其是在家庭游戏中。并非所有的游戏客户都是青少年男性,这类游戏也最有可能被用于寓教于乐,这是一个时下流行的词,教育和娱乐融合在一起,以进一步推动教育部分的成功。缺乏有趣、有效的教育内容游戏,因此这是一个重要的游戏商机。
关于静态游戏,需要记住的重要一点是,它们可以像动态游戏一样有趣。静态游戏本质上具有明显更少的处理开销,因为它们不必为了实现流畅、专业的游戏而达到 60 FPS 的实时处理目标。这是因为游戏的本质不是基于连续的运动,而是基于做出正确的战略行动。当轮到你的时候,你就会移动,这就是为什么这些类型的静态游戏通常被称为基于移动的游戏。
静态游戏中可能会涉及到一些基本的“碰撞检测”形式,即哪些游戏棋子被移动到了游戏棋盘或游戏面上的哪个位置。然而,对于静态游戏,不存在冲突检测使处理器过载的危险,因为游戏板的其余部分是静态的,只有一个棋子在特定玩家的回合中被有策略地移动。一旦确定碰撞的过程完成,就不需要(实时)碰撞检测,直到下一轮由单人玩家(在单人游戏中)或对手(在多人游戏中)进行。
策略游戏的处理逻辑更多的是基于策略逻辑的编程,在给定正确的移动顺序的情况下,允许玩家实现给定的最终“胜利”,而动态游戏编程逻辑更多地关注游戏精灵之间发生的冲突。动态游戏侧重于分数,分数是通过躲避射弹、寻找宝藏、降落目标、杀死敌人以及完成这些类型的关卡目标来获得的,以便进入下一个关卡,玩家可以获得更高的分数。
具有大量相关规则集的复杂策略游戏,例如国际象棋,甚至可能具有比动态游戏功能复杂得多的编程逻辑例程。然而,由于代码的执行没有那么时间敏感,因此无论平台和 CPU 多么强大,最终的游戏都将是流畅的,因为玩家愿意等待游戏来验证移动的有效性并在适当的情况下得分。当然,游戏规则集逻辑必须完美无缺,这种类型的游戏才能被视为真正的专业游戏。因此,最终,不管是静态的还是动态的游戏,至少是伟大的游戏,都很难编写代码,尽管原因大相径庭。接下来让我们来看看动态游戏,这种游戏往往有很高的知名度,吸引年轻玩家,而且往往是个人玩的,而不是团体、学生或家庭玩的。
动态游戏:街机、射击、平台和动作游戏
动态游戏可以被称为动作游戏或街机游戏,并且包括显示屏上的大量运动。这些高度动态的游戏几乎总是涉及射击,例如第一人称射击游戏(例如《毁灭战士》和《半条命》)以及第三人称射击游戏(生化危机和侠盗猎车手),或者偷窃东西或躲避可怕的东西。动作运动游戏,例如足球、英式足球、棒球、篮球、高尔夫和曲棍球,在动态游戏类型中也非常流行,并且它们几乎总是使用真实感、3D 虚拟世界或虚拟现实游戏模拟环境来创建。驾驶游戏是这种类型的另一个化身,也倾向于使用实时 i3D 游戏渲染技术为驾驶者提供超现实的驾驶模拟。
还有障碍课程导航范例,如在平台游戏如大金刚、吃豆人或超级马里奥兄弟中常见的。平台游戏往往是街机游戏,这是典型的 2D 或 2.5D,这是所谓的等距。街机游戏 ZAXXON 是 2D 等轴游戏的一个很好的例子,看起来像 3D 或 Tempest,其中几何形状爬上几何井,玩家从边上向下射击,以防止攀爬的形状到达顶部。
值得注意的是,任何类型的游戏都可以使用 2D 或 3D 图形资源制作,甚至可以使用 2D 和 3D 资源的组合制作,这是 JavaFX 9.0 所允许的,我称之为混合。
混合游戏:创造性地利用 JavaFX 的机会
从 JavaFX 场景图形素材的角度来看,混合游戏将是一个同时使用 2D 和 3D 素材的游戏,其中大部分我们在第 2 和 3 章中介绍过。还有另一种类型的混合,它可以跨越不同的游戏类型,这一点我们在前一节中已经介绍过了。有这么多受欢迎的游戏类型,总是有一个极好的机会通过使用混合游戏方式来创建一个全新的游戏类型。例如,想象一下从静态(战略)游戏类型(如棋盘游戏)中提取一些特征,并添加动态(动作)游戏类型的元素。这方面的一个很好的例子是国际象棋,当棋子进入对方的棋盘方格时,它们会战斗到死。
在我开始 Java 8 游戏开发时(Apress,2014),我使用 JavaFX 8.0 创建了一个混合游戏引擎,它支持平台游戏、射击游戏和寻宝游戏的属性。正如你在图 4-4 中看到的,在那本书中创建的 BagelToons InvinciBagel 游戏引擎,我在那里介绍了 i2D 游戏的开发,具有通常在不同类型的 2D 游戏中发现的元素,包括超级英雄、敌人、射击、宝藏、障碍、藏身处、建筑、汽车、景观、魔毯猫、安全破解、食物等等。
图 4-4。
My i2D game development book called Beginning Java 8 Games Development covers using sprites to develop games
简而言之,Java 的定位是允许游戏开发者交付先锋的混合游戏,包含 2D 和 3D 素材,以及高质量的 16 位 48 kHz 和 24 位 48 kHz 数字音频。凭借一点点创造力和你在本书过程中积累的知识,你应该能够完成以前从未做过的事情。对于混合游戏将受益的领域来说尤其如此,例如教育领域(教育娱乐)和工作场所(业务流程游戏化)。这是因为 Java 广泛用于操作系统和浏览器,以及 64 位平台,如 Android 5 - 8,这些平台在消费电子产品中拥有大多数市场份额和制造商追随者。也就是说,需要指出的是,JavaFX(还)不适合具有高清或 UHD 高帧速率的 i3D VR 实时 3D 渲染游戏,比如那些使用 C++为 PlayStation 或 Xbox 等定制游戏主机创建的游戏。
摘要
在第四章中,我们仔细研究了一些更重要的游戏设计概念,我们将在我们的专业 Java 游戏开发工作流程中使用这些概念,这样你就可以在本书的第一部分提前了解这些东西的基础知识。
我首先介绍了静态与动态的关键概念,以及它们对游戏设计和游戏优化的重要性,因为如果游戏优化不是游戏设计、开发和优化过程中的一个持续考虑因素,过多的动态会使旧的单核甚至双核 CPU 过载。
接下来,您了解了游戏设计和开发的一些关键组件,如精灵位置、精灵动画、碰撞检测、物理模拟、群集或群体动力学、粒子系统、背景动画、相机动画、数字视频流、用户界面设计、评分引擎和游戏 AI 逻辑。
我们看了这些如何应用于静态游戏,即没有连续运动的游戏,如基于移动的策略游戏、棋盘游戏、谜题、知识游戏和记忆游戏,然后看了这些如何应用于动态游戏,即使用连续运动的游戏,如平台游戏、街机游戏、第一人称射击游戏、第三人称射击游戏、驾驶游戏、体育游戏、科幻游戏和类似的游戏,其中大量利用了 3D 实时渲染以及各种类型的物理系统和粒子系统模拟。
我们还看了一些最流行的第三方游戏引擎,物理(和碰撞)引擎,和反向运动学引擎。我们看了一些不同类型的游戏,以及它们的特点,这样你就可以发挥你的创造力,思考你想创建什么类型的专业 Java 游戏。
在下一章中,我们将看一看 Java 编程语言,并复习或入门,以确保每个人都了解 Java 编程语言 API 组件,如包、类、接口、方法、常量、变量、修饰符等。
五、Java 入门:Java 概念和原理介绍
让我们通过回顾 Java 编程语言背后的核心编程语言概念和原则,来确保我们所有的读者都在第五章的同一页上。重要的是,我们用这一章来给我们的读者一个 Java“入门”或全面的概述,并且在一章中简明地回顾编程语言。您在本书第一章中安装的 Java 9 JDK(和 JRE)将成为您的专业 Java 游戏和物联网应用以及 NetBeans 9 IDE 的基础。(我们将在下一章介绍 NetBeans,这样您就可以看到您将用来编写 Java 9 游戏或物联网应用的 IDE 如何作为代码编辑器和应用测试工具。)
我们将在本章中讨论的大多数核心 Java 结构和原则都可以追溯到 Java 编程语言的很久以前,大部分可以追溯到 Java 5(1.5)或 Java 6 (1.6)。我们还将介绍 Java 7 (1.7)和 Java 8 (1.8)(最新版本)中添加的功能,以及计划在 2017 年第三季度发布的 Java 9 (1.9)中的新功能。这些版本的 Java 在数十亿台设备上使用。Java 6 用于 32 位 Android 2.x、3.x 和 4.x 操作系统和应用;64 位 Android 5.x 和 6 操作系统及应用中使用 Java 7;Java 8 用于 Android 7 到 8,以及流行的操作系统(包括微软 Windows、苹果 Macintosh、Open Solaris 和大量流行的 Linux 发行版,如 SUSE、Ubuntu、Mint、Fedora 和 Debian);Java 9 现在已经面向大众发布了。
随着本书的深入,您当然会学到 Java 8 的新的高级概念,如 Lambda 表达式,以及 Java 8 和 Java 9 组件,如 JavaFX 多媒体引擎。本章将涵盖最基本的 Java 编程语言概念、技术和原则,涵盖目前在计算机、iTV 和手持设备上广泛使用的五个主要 Java 版本。
我们将从最简单的概念开始,逐步深入到更难的概念,因此我们将从 Java 的最高级别开始,即 API 及其模块,然后逐步深入到这些模块中 Java 编程结构的“实际操作”部分,包括包、类、接口、方法、常量和变量。
在进入 Java 的结构部分(如包、类和方法)之前,您将了解一下 Java 语法,包括什么是 Java 关键字,如何界定 Java 编程结构,以及如何在 Java 代码中添加函数注释。然后,我们将介绍应用编程接口(API)的顶级概念,什么是包,以及如何导入和使用作为 API 一部分的 Java 包提供的现有代码,以及如何创建包含您自己的游戏和物联网应用代码的自定义 Java 包。
您将看到保存在 Java 包中的构造,这些构造被称为 Java 类。您将了解类包含的方法、变量和常数;关于什么是超类和子类;以及什么是嵌套类和内部类以及如何使用它们。最后,您将了解 Java 对象以及它们如何构成面向对象编程(OOP)的基础。您将了解什么是构造函数方法,以及它如何使用一种特殊的方法创建 Java 对象,这种方法与包含它的类同名。
编写 Java 语法:注释和代码分隔符
关于编写 Java 语法,您需要马上理解几件事情。语法控制着 Java 如何“解析”关于编程语言的东西。解析代码语法可以让 Java 理解你想对你的编程逻辑做什么。理解主要的语法规则很重要,因为它们允许 Java 编译器理解你是如何构造 Java 代码的。Java 编译是 Java 编程过程的一部分,JDK 编译器(程序)将 Java 代码转换成字节码。这由安装在最终用户计算机系统上的 JRE Java 运行时引擎执行(运行)。这个 Java 编译器需要知道你的代码中哪些部分是 Java 编程逻辑,哪些部分是对自己的注释(或者对你项目编程团队其他成员的注释);Java 代码块开始和结束的位置;在这些 Java 代码块中,您的 Java 编程语句或指令开始和结束的地方。一旦编译器明白了这一点,它就可以解析语句,并把它们从代码转换成字节码。
让我们从评论开始,因为这个话题是最容易把握的。向 Java 代码添加注释有两种方法:单行或行内注释,可以放在每一行 Java 代码逻辑之后;多行或块注释,可以放在一行 Java 代码或一个 Java 代码块(Java 代码结构)之前(或之后)。
单行注释用于添加关于一行 Java 代码或一条 Java 编程语句正在做什么的注释。这个注释解释了那行 Java 代码在您的整个代码结构中要完成什么。Java 中的单行注释将以双正斜杠字符序列开始。例如,如果你想在你将在第六章创建的 BoardGame bootstrap 代码中注释你的一个 import 语句,你可以在代码行后添加两个正斜杠。这是您的 Java 代码行经过单行注释后的样子;在 NetBeans 右下角的图 5-1 中也有显示:
图 5-1。
Multiline comments (first five lines of code at the top) and single-line comments (last three lines of code at the bottom)
import javafx.stage.Stage; // This line of code imports Stage class from JavaFX.stage package
让我们也看看多行注释,显示在图 5-1 的顶部,在package invincibagel
语句的上方,我们将在本章的下一节学习。如您所见,块注释的处理方式不同,使用星号旁边的单个正斜杠开始注释,与此相反,使用星号然后使用单个正斜杠结束多行注释(这种注释也称为块注释)。这是在你的职业 Java 游戏中添加短(单行)或长(多行)注释的两种方式。
需要注意的是,不能“嵌套”多行注释。简单地使用一个更大的多行注释!
如果你想知道,这个 InvinciBagel 项目是我在《Java 8 游戏开发入门》一书中教读者如何创建的 i2D 街机游戏,这本书是我为一个介绍使用 Java 8 和 JavaFX 8 开发 i2D 游戏的出版社写的。那本书中的所有原则都适用于 Pro Java 9 游戏开发,所以我在这里使用这些代码。
我通常将单行注释排成一行,看起来相当有序。块注释的 Java 惯例是将星号排成一行,在开始注释分隔符中有一个星号,在结束注释分隔符中有一个星号。这显示在 NetBeans 中 InvinciBagel.java 代码编辑器选项卡顶部的图 5-1 中。
还有第三种类型的注释,称为 Javadoc 注释,在本书的专业 Java 游戏开发中不会用到,因为代码是用来创建游戏的,不会向公众发布。如果您打算编写一个 Java 游戏引擎,供其他人用来创建游戏,这就是您使用 Javadoc 注释向您的 pro Java 游戏引擎添加文档的时候了。JDK 有一个 Javadoc 工具,用于处理 Javadoc 注释并将它们添加到 NetBeans 9 IDE 中。Javadoc 注释类似于多行注释,但它使用两个星号字符来创建 Javadoc 注释的开始分隔符,正如我在这里所做的:
/** This is an example of the Java Documentation (Javadoc) type of Java code commenting
This is a type of comment that will automatically generate your Java documentation!
*/
如果您想在 Java 语句或编程结构的正中间插入一个注释,作为一名专业的 Java 游戏开发人员,您绝对不应该这样做,您应该使用多行注释格式,如下所示:
import /* This line of code imports the Stage class */ javafx.stage.Stage;
这不会产生任何错误,但是会让你的代码读者感到困惑,所以不要用这种方式注释代码。但是,下面使用单行注释格式对此进行注释的方式会产生错误:
import // This line of code will not successfully import the Stage class javafx.stage.Stage;
这是因为编译器将只看到单词 import,因为该单行注释解析到行尾,而多行注释使用块注释分隔符序列(星号和正斜杠)专门结束。出于这个原因,Java 编译器会对第二个未正确注释的代码抛出一个错误,实质上是问“导入什么?”因为不能导入任何东西,所以必须从 Java 包中导入一个 Java 类。
正如 Java 编程语言使用双正斜杠和斜杠-星号对来分隔 Java 代码中的注释一样,还有一些其他关键字符用于分隔 Java 编程语句,以及分隔整个 Java 程序逻辑块。我经常称 Java 代码块为代码结构。
分号字符在 Java(所有版本)中用于分隔 Java 编程语句,如图 5-1 所示的 package 和 import 语句。Java 编译器所做的是寻找一个 Java 关键字,该关键字开始一个 Java 语句,然后将该关键字之后的所有内容作为该 Java 代码语句的一部分,直到它到达分号字符,这是您告诉 Java 编译器“我完成了该 Java 语句的编码”的方式例如,在你的 Java 应用的顶部声明你的 Java 包,你可以使用 Java 包关键字,你的包的名字,然后一个分号字符,如下所示(如图 5-1 ):
package invincibagel;
我们将在下一节讨论 API 和包,以及如何使用 import 语句访问它们。导入语句也用分号字符分隔(也如图 5-1 所示)。import 语句以 import 关键字、要导入的包和类开始,最后是分号分隔符,如下面的 Java 编程语句所示:
import javafx.application.Application;
接下来我们应该看看花括号{…}。像多行注释分隔符一样,花括号具有一个左{花括号,它界定(或向编译器显示)一组 Java 语句的开始,以及一个右}花括号,它界定(或向编译器显示)一组 Java 编程语句的结束。花括号允许您将 Java 编程语句嵌套在其他 Java 结构中。在本书中,我们将会经常讨论嵌套 Java 结构。
正如你在图 5-2 中看到的,用这些花括号分隔的 Java 代码块可以相互嵌套(包含),允许更复杂的 Java 代码结构。图 5-2 显示了你的类中第一个(最外面的)使用花括号的代码块。里面是你的start()
方法,里面是你的。setOnAction()方法调用,其中有一个 handle()方法定义。随着本章的进行,我们将看看所有这些 Java 代码都做了什么。我现在想让你想象的是,这些花括号是如何允许你的方法(和类)定义它们自己的代码块(结构)的,每个代码块都是一个更大的 Java 结构的一部分,最大的 Java 结构是 InvinciBagel 类。每个左花括号都有一个匹配的右花括号,还要注意代码的缩进,这样最里面的 Java 代码结构向右缩进得最远。每个 Java 代码块都缩进了额外的四个字符或空格。如您所见,该类没有缩进(零),start()方法在。setOnAction()方法在中是八个空格,handle()方法在中是十二个空格。请注意,NetBeans 9 会为您缩进每个 Java 代码结构。
图 5-2。
Nested Java code blocks for InvinciBagel class, start method, setOnAction method, and the handle method
请注意,每个红色方块中的嵌套 Java 代码以花括号开始,以花括号结束。现在,您已经熟悉了各种 Java 代码注释方法,以及 Java 编程语句需要如何定界(包括单独的和作为 Java 代码块),接下来您将了解各种 Java 代码结构。您将看到如何使用它们,它们能为您的应用和游戏做些什么,以及为了实现您的 Java 编程结构使用了哪些重要的 Java 关键字。
Java 包:按函数组织 Java API
在编程平台的最高级别,如 Google 的 32 位 Android 4(使用 Java SE 6)、64 位 Android 5(使用 Java SE 7)或当前的 Oracle Java SE 平台(最近发布为 Java SE 9),有一个包含类、接口、方法和常数的包集合,它们共同形成了应用编程接口(API)。应用(在本例中是游戏)开发人员可以使用这个 Java 代码集合(在本例中是 Java 9 API)来创建跨许多操作系统、平台和消费电子设备(如计算机、笔记本电脑、上网本、平板电脑、高清和 UHD iTV 电视机、电子书阅读器和智能手机)的专业级软件。
要安装给定版本的 API 级别,您需要安装它的软件开发工具包(SDK)。Java SDK 有一个特殊的名字,Java 开发工具包(JDK)。熟悉 Android 开发(Android 实际上是 Linux 操作系统之上的 Java)的人都知道,每次添加一些新特性时,都会发布不同的 API 级别。这是因为运行 Android 的硬件设备增加了需要支持的新硬件功能,而不是因为谷歌想每隔几个月发布一个新的 SDK。Android 在短短几年内发布了超过 26 个不同的 API 级别,而 Java SE 在十多年里只发布了 9 个。目前,在数十亿台消费电子设备中,只有四个 Java API 级别(Java 6、7、8 和 9)在积极使用。
Java 6 与 Eclipse 的 ADT IDE 一起用于开发 32 位 Android(版本 1.5 到 4.4),Java 7 与 Android Studio 一起用于开发 64 位 Android(版本 5.x、6、7.x),Java 8 与 IntelliJ IDE 一起用于开发 Android Studio 3.0,Java 9 跨 Windows、Macintosh、Linux 和 OpenSolaris 操作系统使用。我有三个不同的工作站,分别针对 Java API 平台和 IDE 软件包进行了优化,这样我就可以同时为 32 位 Android 设备(Java 6)、Android 5 到 6 (Java 7)、HTML5 和 Android 7 到 8 (Java 8)以及 JavaFX 9 (Java 9)开发应用。幸运的是,你可以在 www.PriceWatch.com
花几百块钱得到一个强大的 Windows 10 或者 Ubuntu LTS 18 六核(或者八核)64 位 pro Java 9 游戏开发工作站。
除了 API 级别(您安装并正在使用的 SDK),Java 编程语言中最高级别的结构是一个包。Java 包使用 package 关键字在 Java 代码的顶部声明您自己的应用包。正如你将在第六章中看到的,这需要是声明的第一行代码而不是注释(并且在本章前面的图 5-1 中显示)。只能有一个包声明并且只能声明一个包,而且必须是第一个 Java 语句!您将在第六章中使用的 NetBeans 9 中的新项目系列对话框将为您创建您的包,并根据您希望在应用中执行的操作导入您需要使用的其他包。在我们的例子中,这些将是 JavaFX 9 包,因此我们可以利用 JavaFX 新媒体引擎。Java 9 进一步将包分组为模块,这些模块被添加到主 Java 程序逻辑之外的应用中。
正如您可能已经从名称中确定的那样,Java 包捆绑了您将在本章中学习或复习的所有 Java 编程结构。这些包括与您的应用相关的类、接口和方法,因此 gameboard 包将包含您的所有代码,以及您为使用代码而导入的所有代码,这是创建、编译和运行您的棋盘游戏所需要的。接下来我们将研究导入的概念和 Java import 关键字,因为它与包的概念密切相关。
Java 包对于组织和包含您自己的所有应用代码很有用,但是对于组织和包含 SDK(API)的 Java 代码更有用,您将利用这些代码和您自己的 Java 编程逻辑来创建您的专业 Java 游戏或物联网应用。从 Java 9 开始,Java 包现在将由功能模块来组织,我们将在本章末尾介绍这些功能模块,因为模块不会影响你的 Java 游戏编程逻辑;它们只是在一个高层次上组织事情,允许您优化您的发行版,以便您可以为您的 Java 游戏发行版获得最小的下载量,以供您的目标游戏终端用户使用。
通过使用 Java import 关键字,您可以使用作为您正在开发的 API 的一部分的任何类,该关键字与您想要使用的包和类一起被称为 import 语句。这个 import 语句以 import 关键字开始,接下来是包和类引用路径(完整的专有名称),然后需要使用分号终止该语句。如图 5-1 所示,用于从 javafx.event 包中导入 JavaFX EventHandler 类的导入语句应该如下所示:
import javafx.event.EventHandler;
import 语句通知 Java 编译器,它需要将指定的外部包放入您的包中(将它导入到您的包中),因为您将使用 import 关键字引用的类中的方法(和常量),以及它存储在哪个包中。如果您在自己的 Java 9 类中使用一个类、方法或接口,比如您将在第六章中创建的 BoardGame 类,并且您没有通过使用 import 语句声明该类,Java 9 编译器将抛出一个错误。这是因为它无法定位或引用要在您的包中使用的类,因此它无法导入该功能。
Java 类:模块化游戏的 Java 结构
包级别下的第二大 Java 编程结构是 Java 类级别,正如您在 import 语句中看到的那样,它引用包含类的包和类本身。就像一个包组织所有相关的类一样,一个类组织所有相关的方法、变量和常量,有时还包括其他嵌套类,我们将在本章的下一节讨论这些。
Java 类可用于在功能组织的下一个逻辑级别组织您的 Java 代码,因此,您的类将包含为游戏应用添加特定功能的 Java 代码构造。这些包括方法、变量、常量、嵌套类或内部类,所有这些都将在本章中介绍。
Java 类也可以用来创建 Java 对象,我们将在了解类、嵌套类、方法和数据字段之后讨论这些。Java 对象是使用 Java 类构造的。它们与 Java 类以及该类的构造函数方法同名,这一点我们将在本章稍后介绍。
正如你在图 5-2 中看到的,你使用 Java class 关键字和你的类名来声明你的类。你也可以用 Java 修饰关键字作为声明的开头,我们将在本章的后面介绍。Java 修饰符关键字总是放在 Java class 关键字之前,使用以下格式:
<Java modifier keywords here> class <your custom class name goes here>
Java 类的一个强大特性是,它们可以用来模块化您的 Java 游戏代码,这样您的核心游戏应用特性就可以成为一个高级类的一部分,可以对该类进行子类化,以创建该类的更专业的版本。用 Java 类层次术语来说,一旦一个类被用来创建一个子类,它就变成了超类。一个类通常会使用 Java extends 关键字子类化另一个超类。
使用 Java extends 关键字告诉编译器,您希望将超类的能力和功能添加(扩展)到您的类中,一旦使用了这个“extends”关键字,该类就变成了子类。子类“扩展”了它所扩展的超类所提供的核心功能。要扩展类定义以包含超类,可以使用以下格式添加(或扩展,没有双关的意思)现有的 Java 类声明:
<Java modifier keywords here> class <your class name here> extends <superclass name here>
当您使用您的类扩展一个超类时,该类成为该超类的子类,您可以在您的子类中使用该超类的所有特性(嵌套类、内部类、方法、构造函数、变量和常量)。您可以做到这一点,而不必在类的主体中显式地重写(重新编码)这些 Java 构造,这将是多余的(并且是无组织的),因为您的类扩展了超类,使其成为您的类的一部分。我们将在本章的下一节讨论嵌套类和内部类,以防你想知道它们是什么。
您的类的主体在花括号内编码(图 5-2 中外部的红色框),它跟在您的类和 javafx.application.Application 超类(在这种特殊情况下)声明之后。这就是为什么你要先学习或复习 Java 语法;您在此基础上构建了类声明,然后是包含类定义(变量、常量、方法、构造函数、嵌套类、内部类)结构的 Java 语法。
注意图 5-2 中 InvinciBagel 类从 JavaFX 应用包中扩展了 Application 超类。这样做为 InvinciBagel 类提供了托管或运行 JavaFX 8 应用所需的一切。这个 JavaFX 8 应用类所做的是“构造”您的应用对象,以便它可以使用系统内存,调用。init()方法(初始化任何可能需要初始化的东西),并调用。start()方法,如图 5-2 (第二个红框中)。这个。start()方法是放置 Java 代码语句的地方,这些语句最终将被用来“启动”(即启动或运行)InvinciBagel i2D arcade game Java 8 应用。这款 Java 8 的游戏也将在 Java 9 下运行,无需修改。
当最终用户使用完 i2D InvinciBagel Java 应用时,应用类使用 Application()构造函数方法创建的应用对象将调用它的。stop()方法并从系统内存中移除您的应用。这将释放内存空间,供最终用户用于其他用途。我们将很快进入方法、构造器和对象,因为我们正在从高级包和类构造进展到较低级的方法和对象构造,这样我们就可以在前进的过程中从高级概述到较低级来处理学习过程。您可能想知道 Java 类是否可以相互嵌套。也就是说,Java 类可以包含其他 Java 类吗?答案是肯定的,他们可以。接下来让我们仔细看看 Java 嵌套类的概念。
嵌套类:存在于其他类中的 Java 类
Java 中的嵌套类是定义在另一个 Java 类内部的类。嵌套类是嵌套在其中的类的一部分,这种嵌套意味着这两个类打算以某种方式一起使用。嵌套类有两种类型:静态嵌套类,通常简称为嵌套类;非静态嵌套类,通常称为内部类。
静态嵌套类,我称之为嵌套类,用于创建包含它们的类所使用的实用程序,有时只是用来包含包含它们的类所使用的常量。开发 Android 应用的人都熟悉嵌套类,因为它们在 Android API 中非常常用,要么用于保存实用方法,要么用于包含 Android 常量,这些常量用于定义屏幕密度设置、动画运动插值曲线类型、对齐常量和用户界面元素缩放设置等。在第四章中,我们讨论了与游戏相关的静态概念,对于代码来说,这具有相同的含义。Java 常量可以被认为是固定的,或者不能被改变。
嵌套类使用 Java 中通常所说的点标记法,以便“脱离”其主(或父)包含类来引用嵌套类。比如大师级。NestedClass 将是引用格式,它将用于使用或通过其主类(包含类)名称引用嵌套类,这里使用泛型类类型名称。如果您创建了 SplashScreen 嵌套类来绘制 Java 棋盘游戏的闪屏,那么它将在 Java 代码中被引用为 board game。SplashScreen 通过使用 Java 点符号语法。
作为一个例子,让我们看看 JavaFX 应用类,它包含参数嵌套类。这个嵌套类封装或包含您可以为 JavaFX 应用设置的参数。因此,这个应用。参数嵌套类将是与应用类相同的 javafx.application 包的一部分,并且如果使用 import 语句,将被引用为 Java FX . Application . Application . parameters。
类似地,构造函数方法(我们将很快学习构造函数方法)将被写成应用。Parameters(),因为构造函数方法必须与包含它们的类同名。除非您正在编写供其他开发人员使用的代码,这是最常使用嵌套类的地方,如 JavaFX 应用类或 Android 8 操作系统中的许多嵌套类(实用程序或常量提供者),否则您更有可能使用非静态嵌套类。这些非静态嵌套类通常被称为 Java 游戏的内部类。
一个嵌套类,技术上称为静态嵌套类,是使用 static 关键字(修饰符)来声明的,这一点你将在本章的后面部分学到。所以,如果你要创造棋盘游戏。SplashScreen 嵌套类、BoardGame 类和 SplashScreen 嵌套类声明类似于以下代码:
public class BoardGame extends Application {
static class SplashScreen {
// The Java code that creates and displays your splashscreen is in here
}
}
需要注意的是,如果您使用 import Java FX . Application . Application . Parameters(作为示例)来导入嵌套类,您可以在此时引用您的类中的嵌套类,只需使用参数类名,而不必使用完整的类名“path ”,该类名显示了您的类的代码如何通过其父类到达使用应用的嵌套类。参数(类名。NestedClassName)引用。
正如你将在本书中多次看到的,方法也可以用点符号来访问。因此,不使用类名。如果使用 import 语句导入这个嵌套类,只需使用 NestedClassName.MethodName 即可。这是因为 import 语句已经用于建立这个嵌套类的完整“引用路径”,通过它的包含类,所以您不必这样做。
接下来,让我们看看非静态嵌套类,它们通常被称为内部类。
内部类:不同类型的非静态嵌套类
Java 内部类也是嵌套类,但是它们不是在 class 关键字和类名之前使用 static 关键字修饰符声明的,这就是它们被称为“非静态”嵌套类的原因。因此,任何在另一个不使用 static (keyword)修饰符的类内部的类声明在 Java 中都被称为内部类。Java 中有三种类型的内部类:成员类、本地类和匿名类。在本节中,我们将详细讨论这些内部类类型之间的区别,以及它们是如何实现的。
像嵌套类一样,成员类是在包含类(父类)的主体中定义的。您可以在包含类的体中的任何地方声明成员类。当您想要访问属于包含类的数据字段(变量或常量)和方法,而不必提供数据字段或方法(类名)的路径(通过点标记)时,您可能想要声明一个成员类。数据字段或类名。方法)。成员类可以被认为是不使用 Java static modifier 关键字的嵌套类。
嵌套类是通过其包含类或“顶级”类引用的,使用静态嵌套类的点标记路径,成员类由于不是静态的,所以是“特定于实例的”,这意味着使用该类创建的对象(实例)可以彼此不同(一个对象是一个类的唯一“实例”),而静态(固定)嵌套类将只有一个不变的版本。例如,私有内部类只能由包含它的父类使用。编码为私有类的 SplashScreen 内部类如下所示:
public class BoardGame extends Application {
private class SplashScreen {
// The Java code that creates and displays your splashscreen is in here
}
}
因为它被声明为 private,所以它是供我们自己的应用使用的(特别是包含类的使用)。因此,这不是供其他类、应用或开发人员使用的实用程序或常量类。您也可以在不使用 private access 修饰符关键字的情况下声明您的内部类,类似于下面的 Java 编程结构:
public class BoardGame extends Application {
class SplashScreen {
// The Java code that creates and displays your splashscreen is in here
}
}
这种级别的访问控制称为包或包私有,是应用于任何类、接口、方法或数据字段的“默认”访问控制级别,声明时没有使用其他 Java 访问控制修饰符关键字(public、protected 或 private)。这种类型的内部类不仅可以被顶级类或包含类访问,还可以被包含该类的包中的任何其他类成员访问。这是因为包含类被声明为“public”,而内部类被声明为“package private”如果希望内部类在包外可用,可以使用下面的 Java 代码结构将其声明为 public:
public class BoardGame extends Application {
public class SplashScreen {
// The Java code that creates and displays your splashscreen is in here
}
}
您还可以声明一个内部类来保护,这意味着它可以被父类或包含类的任何子类访问。在我们讲述了 Java 方法和 Java 变量之后,我们将进入 Java 修饰符。
如果您在不是类的低级 Java 编程结构(如方法或迭代控制(通常称为循环)结构)中声明了一个类,从技术上来说,它被称为局部类。这个局部类只在代码块内部可见,因此它不允许使用(或者说不允许使用)类修饰符,比如 static、public、protected 或 private。
局部类的用法类似于局部变量,只是它是一个更复杂的 Java 编码结构,而不是一个简单的局部使用的数据字段值。这在游戏中并不常用,因为你通常希望你的游戏被“功能性地”划分成功能类,这些功能类中的方法和变量显然有不同的用途和原因,以保持使用 Java 的组织或封装来清晰地定义游戏设计和处理的复杂性。从第六章开始,我们将在整本书的每一章中设计不同的游戏功能组件。通过这种方式,我们最大限度地利用 Java 的特性来创建游戏设计。
最后,还有一种叫做匿名类的内部类。匿名类是没有任何类名的局部类。您可能会比本地类更频繁地遇到匿名类。这是因为程序员通常不命名他们的本地类(使它们成为匿名类)。本地类包含的逻辑只在本地声明中使用,因此,这些类实际上不需要名字,因为它们只在 Java 代码块内部被引用。
Java 方法:核心逻辑函数 Java 构造
在类内部,通常有方法和这些方法使用的数据字段(变量或常量)。因为我们要从外部结构到内部结构,或者从顶层结构到底层结构,所以接下来我们将讨论方法。在其他编程语言中,方法有时被称为函数,您可以看到。图 5-2 中的 start()方法,展示了该方法如何保存创建基本 Java 游戏应用的编程逻辑。该方法中的编程逻辑使用 Java 编程语句创建舞台和场景,在 StackPane 中的屏幕上放置一个按钮,并定义事件处理逻辑,以便在单击按钮时,引导 Java 代码将一些“Hello World”文本写入 NetBeans 9 IDE 输出区域。
声明方法:修饰符、返回类型和方法名
方法声明以访问控制修饰符关键字开始,可以是 public、protected、private 或 package private(这是通过根本不使用任何访问控制修饰符关键字来指定的)。如图 5-2 所示,你的。start()方法已使用公共访问控制修饰符声明。我们将在本章后面更详细地讨论访问修饰符关键字。
在这个访问控制修饰符之后,您需要声明该方法的返回类型。这是该方法在被调用后将返回的数据类型。自从。start()方法执行设置操作,但不返回任何特定类型的值,它使用 void 返回类型,这表示该方法执行任务,但不向调用实体返回任何结果数据。在这种情况下,调用实体是 JavaFX 应用类,因为。start()方法是关键方法之一(其他方法是。停止()和。init()方法),它控制着 i3D BoardGame JavaFX 应用的生命周期阶段。
在返回类型之后,您将提供您的方法名称,按照惯例(编程规则),该名称应该以小写字母(或单词,最好是动词)开头,任何后续的(内部)单词(名词或形容词)都应该以大写字母开头。例如,显示闪屏的方法将被命名为。showSplashScreen()或。displaySplashScreen()和,因为它执行某些操作但不返回值,所以将使用以下代码进行声明:
public void displaySplashScreen() { method Java code to display splashscreen goes in here }
如果您需要传递参数,这些参数是命名的数据值,需要在方法体(花括号内的部分)中进行操作,这些参数放在附加到方法名的括号内。在图 5-2 中。bootstrap HelloWorld JavaFX 应用的 start()方法使用以下 Java 方法声明语法接收一个名为 primaryStage 的 Stage 对象:
public void start(Stage primaryStage) { bootstrap Java code to start Application goes in here }
您可以使用数据类型和参数名称对来提供任意多的参数,每对之间用逗号分隔。方法也可以没有参数,在这种情况下,参数括号是空的,左括号和右括号紧挨着;这就是我在本书中写方法名的方式,这样你就知道它们是方法。我在方法名前面使用了点号(符号),后面使用了括号字符,比如。开始()或。stop()等等,这样你就知道我引用的是一个 Java 方法。
定义您的方法的编程逻辑将包含在方法的“主体”中,正如您已经了解的,主体在定义方法开始和结束的花括号中。方法中的 Java 编程逻辑可以包括变量声明、程序逻辑语句、迭代控制结构和迭代循环等,所有这些我们都将在本书中用来创建我们的 Java 游戏。
重载方法:提供唯一的参数列表
Java 中还有另一个适用于方法的概念,在我继续之前,我将在本节中介绍它,这就是重载 Java 方法。重载 Java 方法特指使用相同的方法名,但使用不同的参数列表配置。重载意味着如果你定义了不止一个同名的方法,Java 编译器将能够判断出使用哪个重载的方法。
Java 编译器通过查看参数数据类型以及它们被传递给被调用方法的顺序来区分重载方法。然后,Java 编译器使用参数列表的唯一性作为各种类型的指纹,来辨别要使用哪个同名的方法(具有相同的名称)。因此,为了使 Java 方法重载特性能够正确工作,您的参数列表配置必须彼此完全不同。
在本书的过程中,我们将学习如何使用和如何编写 Java 方法,从介绍 NetBeans 9 的第六章开始,一直到本书的结尾,所以我在这里不打算花太多时间在它们上面,只是定义它们是什么以及它们在 Java 类中如何声明和使用的基本规则。
构造器方法:将 Java 类转换成 Java 对象
在本章的这一节,我将详细介绍一种特殊类型的 Java 方法,称为构造函数方法。这是一种特殊类型的方法,可用于创建(构造)Java 对象,我们将在本章稍后介绍,在我们介绍了可用于创建、定义和连接这些 Java 对象的所有不同类型的 Java 语法和编程结构之后。Java 对象恰好是面向对象编程(OOP)的基础,所以我们将在这里看一下构造器方法;在本章后面讨论 Java 对象本身之前,理解这一点是很重要的。因为我们在本节中讨论方法,所以这是研究构造函数最合理的地方,因为构造函数方法有时被资深 Java 游戏开发人员(简称为)调用,而您正在成为这样的开发人员。
创建 Java 对象:调用类构造函数方法
一个 Java 类可以包含一个与该类同名的构造函数方法,并且可以使用该类创建 Java 对象。构造函数方法使用它的 Java 类作为蓝图,在系统内存中创建该类的实例,从而创建 Java 对象。构造函数方法总是返回一个 Java 对象,因此不使用其他方法通常使用的任何其他 Java 返回类型(void、String、float、int、byte 等。).我们将在本章后面讨论这些 Java 返回类型。因为您正在创建一个新的 Java 对象,所以应该使用 Java new 关键字调用构造函数方法。
您可以在图 5-2 的第 20、28 和 30 行中的引导 JavaFX 代码中看到这样的例子。这些行是通过使用以下对象声明、命名和创建 Java 代码结构分别创建 Button、StackPane 和 Scene 对象的地方,如下所示:
<Java class name> <object instance name> =
new <Java constructor method name><parameter list><semicolon>
以这种方式声明 Java 对象的原因是因为每个 Java 对象都是一个 Java 类的实例,在一个 Java 语句中使用类名、正在构造的对象名、Java new 关键字和该类的构造函数方法名(以及参数,如果有的话),该语句以分号字符结束。
以当前 Java 代码第 20 行中的按钮对象创建为例,您使用 equals“运算符”左侧的 Java 语句部分告诉 Java 语言编译器,您想要使用 JavaFX 按钮类作为对象蓝图来创建名为 btn 的按钮类型对象。这“声明”了按钮类(对象类型)并给它一个惟一的名称。(在本章的稍后部分,我们将很快介绍操作符。)
因此,创建对象的第一部分称为对象声明。创建 Java 对象的第二部分称为对象实例化,这部分对象创建过程可以在 equals 操作符的右边看到,它涉及一个构造函数方法和 Java new 关键字。
实例化 Java 对象的方法是调用或利用 Java new 关键字和对象构造器方法调用。因为这发生在 equals 操作符的右侧,所以对象实例化的结果被放入声明的对象中,该对象位于 Java 语句的左侧。当我们在本章后面讨论操作符时,你会看到,这就是等于操作符的作用,它是一个有用的操作符。
这就完成了声明(类名)、命名(对象名)、创建(使用 new 关键字)、配置(使用构造函数方法)和加载(使用 equals 操作符)您自己的定制 Java 对象的过程。
值得注意的是,这个过程的声明和实例化部分可以使用单独的 Java 代码行进行编码。例如,按钮对象实例化(图 5-2 ,第 20 行)可以编码如下:
Button btn; // Declare a Button object named btn
btn = new Button(); // Instantiate btn object using Java new keyword and Button() constructor
这一点非常重要,因为以这种方式编写对象创建代码允许您在类的顶部声明一个对象,其中类内部使用或访问这些对象的每个方法都可以“看到”该对象。在 Java 中,除非使用修饰符声明,否则对象或数据字段只在声明它的 Java 编程结构(类或方法)中可见,我们将在下面讨论。
如果在类内部声明一个对象,因此在类中包含的所有方法之外,类中的所有方法都可以访问(查看和使用)该对象。类似地,在一个方法中声明的任何东西对该方法来说都是“局部的”,只对该方法的其他“成员”是“可见的”,这意味着该方法范围内的所有 Java 语句都在{…}分隔符内。如果您想在类中实现这个单独的对象声明,在。start()方法,在当前的 BoardGame 类中,您的类的前几行 Java 代码将变成如下 Java 编程逻辑:
public class BoardGame extends Application {
Button btn;
@Override
public void start(Stage primaryStage) {
btn = new Button();
btn.setText("Say 'Hello World'");
// other programming statements continue here
}
}
当对象声明和实例化被分开时,可以根据可见性的需要将它们放在方法的内部(或外部)。在前面的代码中,BoardGame 类的其他方法可以调用前面显示的 btn.setText()方法调用,而 Java 编译器不会“抛出”错误。图 5-2 中按钮对象的声明方式,只有。start()方法可以“看到”对象,所以只有。start()方法可以实现 btn.setText()方法调用。
创建构造器方法:设计和编码 Java 对象结构
构造函数方法是一种特殊类型的方法,用于在系统内存中创建对象。这与其他方法有很大的不同(如果您使用不同的编程语言,您习惯于将它们称为函数)。Java 中的非构造方法用于执行某种复杂的计算或某种形式的封装(模块化)处理。constructor 方法用于在内存中创建 Java 对象,而不是执行其他一些编程功能,Java new 关键字与 constructor 方法的结合使用就证明了这一点,constructor 方法在内存中创建该唯一类类型的新 Java 对象。因此,构造函数方法将定义一个独特类型的 Java 对象的内部结构。如果希望在实例化 Java 对象的同时配置它,可以定义构造函数方法参数列表,以允许调用实体用特定(自定义)数据值填充对象结构。这样,您可以通过在构造函数的参数列表中传递不同的属性来创建不同类型的对象。
在这一节中,我们将创建几个示例构造函数方法,向您展示关于如何创建构造函数方法的基础知识以及它通常包含的内容。假设您正在为游戏创建一个对象。您可以使用以下 Java 代码结构声明一个公共 BoardGame()构造函数方法,例如:
public BoardGame() {
int healthIndex = 1000; // Defines units of Health
int scoreIndex = 0; // Defines units of Scoring
int boardIndex = 0; // Current Game Board Location
boolean turnActive = false; // Flag showing if current turn
}
使用BoardGame playerName = new BoardGame();
构造函数方法调用调用的构造函数方法创建了一个名为 playerName 的 BoardGame 游戏玩家对象。该对象具有 1000 单位的生命值,没有当前分数,因为该对象在游戏板的第一个方块上,并且当前没有移动,因为当前没有轮到他们。
接下来,让我们探索一下重载这个构造函数方法的概念,我们在前面已经学过了,并创建另一个构造函数方法,它具有允许我们在创建 BoardGame 对象的同时定义 healthIndex 和 turnActive 变量的参数。构造函数方法如下所示:
public BoardGame(int startingHealthIndex, boolean isTurnActive) {
int healthIndex = startingHealthIndex;
int scoreIndex;
int boardIndex;
boolean turnActive = isTurnActive;
}
在这个版本中,我仍然将 scoreIndex 和 boardIndex 变量初始化为零,这是一个整数值的默认值,所以我不必在这段代码中使用 lifeIndex = 0 或 hitsIndex = 0,只是为了向您展示编写这两个语句的可选方法。由于 Java 编程语言支持方法重载,如果您使用BoardGame playerOne = new BoardGame(1250, true);
方法调用来实例化一个 BoardGame 对象,将会使用正确的构造函数方法来创建该对象。这个名为 playerOne 的 BoardGame 对象的健康指数为 1250 个健康单位,得分为零,位于第一个游戏棋盘位置,目前轮到他们了。
Java 关键字this
可用于访问使用构造函数方法创建的数据字段。例如,在对象的代码中,this . startinghealthindex = value;将该对象自己的内部数据字段设置为您指定的值。您还可以使用 this()在同一个类构造中调用另一个构造函数方法。
您可以拥有任意多的(重载的)构造函数方法,只要它们都是 100%唯一的。这意味着重载的构造函数必须有不同的参数列表配置,包括参数列表长度(参数的数量)、顺序和/或不同的参数列表类型(不同的数据类型)。正如您所看到的,正是您的参数列表(参数数量、参数数据类型和参数顺序)允许 Java 编译器区分您的重载方法。
Java 变量和常量:数据字段中的值
下一层,从 API 到包到类到方法,是在这些 Java 类和方法中操作的实际数据值。在 Java 中,这被称为数据字段。数据保存在称为字段的东西中,就像在数据库设计中一样。Java 数据字段可以是动态的,也可以是可变的,这就是为什么它们通常被称为“变量”,并且可以在 Java 游戏或物联网应用的操作过程中发生变化。或者,它们可以是静态的(固定的),这使得数据是永久的,在这种情况下,它将被称为常数。常量是一种特殊类型的变量,我们将在下一节中讨论,因为在 Java 编程语言中正确地声明一个常量比声明一个 Java 变量要复杂(高级)一些。
就 Java 行话(约定)而言,在类的顶部声明的变量被称为成员变量、字段或数据字段,尽管所有的变量和常量都可以被认为是基本级别的数据字段。
在方法或其他低级 Java 编程结构(嵌套在类或方法中)内部声明的变量称为局部变量,因为它只能在用花{…}括号分隔的编程结构内部局部“看到”或使用。最后,在方法声明、构造函数方法定义或方法调用的参数列表区域内传递的变量被称为参数,这并不奇怪。
变量是保存 Java 对象或软件属性的数据字段,在软件执行过程中,这些属性会发生变化。正如你所想象的,这对于游戏编程来说尤其重要。最简单的变量声明形式可以通过使用一个 Java 数据类型关键字以及您希望在 Java 程序逻辑中用于该特定变量的名称来实现。在上一节的构造函数方法中,我们声明了一个名为 scoreIndex 的整数变量来保存你的对象在游戏过程中累积的分数。我们定义了变量数据类型,并使用以下 Java 变量声明编程语句对其进行命名:
int scoreIndex; // This could be coded as: int scoreIndex = 0; (default integer value is zero)
正如您在上一节构造函数方法中看到的,您可以使用 equals 运算符将变量初始化为一个初始值,以及一个与声明的数据类型相匹配的数据值。这里有一个例子:
boolean turnActive = false; // Could be: boolean turnActive; (default boolean value is false)
此 Java 语句声明了一个布尔数据类型变量,并将其命名为 turnActive,位于 equals 运算符的左侧,然后将声明的变量设置为 false 值,这表示该玩家的回合未激活。这类似于对象的声明和实例化,只是 Java new 关键字和构造函数方法被数据值本身所取代,因为现在声明的是变量(数据字段),而不是创建的对象。我们将在本章的下一节讨论不同的数据类型(我们已经讨论过整数、布尔和对象)。
你也可以在变量声明中使用 Java 修饰符关键字,我将在本章的下一节向你展示如何声明一个不可变的变量,也称为常量,它在内存中是固定的或锁定的,不能以任何方式改变或更改,所以它保持不变,你猜对了。
我们将在下一节常量之后的小节中讨论 Java 访问修饰符关键字,因为它们与所有 Java 构造都相关。所以,现在我几乎已经完成了从最大的 Java 构造(或包)到最小的(或数据字段)的讨论,我们将开始讨论那些适用于 Java 所有级别(类、方法、数据字段)的主题。这些 Java 概念的复杂性会随着我们阅读 Java 初级章节的结束而增加,因为我想从更简单的高级概念开始,然后深入到更复杂的低级概念。在本章的最后,我们还将介绍如何使用新的 Java 9 模块特性来打包您的 Java 项目以供分发,这将允许您优化您的 Pro Java 9 游戏的数据占用,并使其更加安全。Java 9 应该在这本书向公众发布的同时发布,所以我把这本书变成了一本 Java 9 的书。Java 8 游戏开发入门书中的所有内容仍然适用于 Java 9 开发。
在内存中固定数据值:在 Java 中定义数据常量
如果您已经熟悉计算机编程,您会知道通常需要数据字段总是包含相同的数据值,并且在应用的运行周期中不会改变。这些被称为常量,使用 Java 访问修饰符关键字的特殊组合来定义或声明,这些关键字用于固定内存中的内容,使它们不能被更改。还有一些 Java 修饰符关键字将限制(或取消限制)对象实例,或者对 Java 类或包内部或外部的某些类的访问。我们将在本章的下一节详细讨论这些,包括 Java 修饰符关键字。
要将 Java 变量声明为“fixed”,必须使用 Java 的 final 修饰符关键字。Final 和你父母说某事是最终的意思是一样的;它固定在一个地方,是生活的事实,永远不会改变。因此,创建常数的第一步是在声明中的数据类型关键字前面添加这个 final 关键字。
声明 Java 常量(以及其他编程语言中的常量)时的惯例是使用大写字符,每个单词之间带有下划线,这表示代码中的常量。
如果我们想为你的游戏创建屏幕宽度和屏幕高度常量,你可以这样做:
final int SCREEN_HEIGHT_PIXELS = 480;
final int SCREEN_WIDTH_PIXELS = 640;
还有一个“空白”final,它是一个非静态的 final 变量,其初始化将被推迟到您的构造函数方法体。同样重要的是要注意,每个对象都有自己的非静态最终变量的副本。
如果您希望由您的类的构造函数方法创建的所有对象能够“看到”并使用该常量,您还必须在最终的修饰符关键字之前添加 Java static modifier 关键字,如下所示:
static final int SCREEN_HEIGHT_PIXELS = 480;
static final int SCREEN_WIDTH_PIXELS = 640;
如果您希望只有您的类和由该类创建的对象能够看到这些常量,您可以在 static modifier 关键字前面使用 Java private modifier 关键字来声明这些常量,使用以下代码:
private static final int SCREEN_HEIGHT_PIXELS = 480;
private static final int SCREEN_WIDTH_PIXELS = 640;
如果您希望任何 Java 类,甚至那些在您的包之外的类(即任何其他人的 Java 类),能够看到这些常量,您可以使用下面的 Java 代码在 static modifier 关键字之前使用 Java public modifier 关键字来声明这些常量:
public static final int SCREEN_HEIGHT_PIXELS = 480;
public static final int SCREEN_WIDTH_PIXELS = 640;
正如您所看到的,声明常量比声明一个简单的变量在您的类中使用要详细得多。接下来,我们应该更深入地了解一下 Java 的访问修饰符关键字,因为它们允许您控制一些事情(比如对类、方法、常量和变量的访问,允许您锁定 Java 代码结构以防被修改),以及类似的相当复杂的高级 Java 代码控制概念。
现在您已经理解了主要的 Java 编程逻辑构造或结构,您已经准备好学习(或回顾)更复杂的语言特性,比如修饰符、运算符、数据类型和语句。
Java 修饰符关键字:访问控制等等
Java 修饰符关键字是保留的 Java 关键字,用于修改到目前为止您已经了解(复习过)的 Java 编程结构的主要类型内部的代码或数据结构的访问控制、可见性或寿命(在应用执行期间,某些内容在内存中存在多长时间)。修饰符关键字是在 Java 代码结构的“外部”和“头部”(开头)上“声明”或使用的第一个 Java 保留字,因为结构的 Java 逻辑(至少对于类和方法来说)包含在大括号{…}分隔符内,这些分隔符位于类关键字和类名之后,或者方法名和参数列表之后。修饰符关键字出现在所有这些关键字之前,可以与 Java 类、方法、数据字段(变量和常量)以及 Java 接口一起使用,我们将在稍后讨论这些。
正如您在图 5-2 底部看到的。main()方法,它是由 NetBeans 9 为 BoardGame 类定义创建的(它使用了我们将在下面介绍的 public 修饰符),您可以使用多个 Java 修饰符关键字。那个。main()方法首先使用一个公共修饰符关键字,这是访问控制修饰符关键字,然后它使用一个静态修饰符关键字,这是非访问控制修饰符关键字。接下来让我们讨论 Java 访问控制修饰符,之后,我们将讨论更复杂的非访问控制修饰符。随着 Java 9 中由 Java 模块特性提供的额外安全保护,这些访问控制修饰符变得更加重要,Java 模块特性控制您的包和 API 是如何捆绑和分发的。
访问控制修饰符:公共、受保护、包或私有
让我们先讨论访问控制修饰符,因为它们首先在任何非访问控制修饰符关键字和任何返回类型关键字之前声明;它们在概念上也更容易理解。有四种访问控制修饰符级别可以应用于任何 Java 编程结构。如果您没有声明任何访问控制修饰符关键字,那么一个“默认”的包私有访问控制级别将被应用于该 Java 代码结构,这允许它对您的 Java 包内的任何 Java 编程结构“可见”,从而可供其使用。在这种情况下,这将是棋盘游戏包。
其他三个 Java 访问控制修改级别都有自己的访问控制修饰符关键字,包括 public、private 和 protected 关键字。这些都是根据它们所做的事情而恰当命名的,所以您可能已经对如何应用它们来公开共享您的代码或者保护它不被公开使用有了一个相当好的想法,但是为了确保万无一失,让我们在这里详细地讨论其中的每一个。如您所知,访问控制和安全性一样,是目前 Java 软件的重要问题,无论是在代码内部还是外部,这就是 Java 9 增加模块的原因。我们将从最少的访问控制(安全性)开始,首先是公共访问控制修饰符。
Java 公共修饰符:独立于实例存在的变量或方法
Java 公共访问修饰符关键字可以被类、方法、构造函数、数据字段(变量和常量)和接口使用。如果您将某个东西声明为 public,它就可以被公众访问。这意味着它可以在任何其他包中被任何其他类导入和使用,只要它是在模块中导出的。本质上,这意味着您的代码可以在任何使用 Java 9 语言创建的软件中使用。正如您将在 Java 和 JavaFX 编程平台(API)中使用的类中看到的,public 关键字最常用于开源编程 Java 平台或用于创建定制应用(包括游戏)的包。
值得注意的是,如果您试图访问和利用的公共类存在于您自己的包之外的另一个包中(在我们的例子中,您自己的包将被命名为 boardgame),那么您将需要使用 Java import 关键字来创建一个 import 语句,以便能够利用该公共类。这就是为什么,在本书结束时,你会在你的 JavaFXGame.java 课程的顶端有几十个重要的陈述。您将利用代码库中预先存在的 Java 和 JavaFX 类,这些类已经通过使用公共访问控制修饰符关键字进行了编码、测试、优化和公开,以便您可以创建利用 Java APIs 的 pro Java 9 游戏和物联网应用。
由于 Java 中的类继承,公共类中的所有公共方法和公共变量都将被该类的子类继承(一旦被子类化,就成为超类)。你可以在 Invincibagel 类关键字前面看到一个公共访问控制修饰符关键字的例子,如图 5-2 所示。
Java Protected 修饰符:变量和方法允许子类访问
Java protected access modifier 关键字可由数据字段(变量和常量)和方法(包括构造函数方法)使用,但不能由类或接口使用。我们将在本章后面讨论 Java 接口。protected 关键字允许超类中的变量、方法和构造函数只能被其他包(如 boardgame 包)中该超类的子类访问,或者被包含这些受保护成员的类(Java 构造)的同一个包中的任何类访问。使用这个访问控制修饰符就像给原始 Java 代码上了一把锁;要使用原始代码(更不用说添加代码并修改其预期用途),您必须扩展或继承受保护的类,然后您可以覆盖它的方法。
因此,这个访问修饰符关键字本质上保护了一个类中的方法或变量,该类旨在(希望)通过被其他开发人员子类化(扩展)而被用作超类。除非您拥有这个包,其中定义了这些受保护的 Java 构造(您没有),否则您必须扩展这个超类并创建自己的子类实现,以便能够利用这些受保护的方法和变量。
您可能想知道什么时候会有人想要这样做并像这样保护 Java 代码结构?当你在设计一个更大的项目时,比如 Android 操作系统 API,你通常会希望不直接使用最高层的方法和变量,或者直接在类外使用,或者直接在类内使用。
在这种情况下,当其他人正在使用您的代码结构时,您宁愿您的原始 Java 代码在一个单独定义的、开发人员编码的子类结构中使用。这“隔离”了超类代码,因此它将直接保持不变,在某种意义上,保证了原始的方法、字段和意图被维护,因为它们是 Java 代码作者(包所有者)最初想要的,防止其他人的修改。这确保了你的 API 和它的超类永远作为一个“蓝图”供其他 Java 9 开发者用来创建他们自己的(Android,JavaFX 等)。)游戏、商业实用工具和物联网应用。
您可以通过保护方法和变量构造不被直接使用来实现这种直接使用的预防,这样它们就只成为其他类中更详细实现的蓝图,而不能被直接使用。本质上,保护一个方法或变量会将它变成一个蓝图,或者“实现路线图”
Java 私有修饰符:允许本地访问的字段、方法或构造函数
Java private access control 修饰符关键字可以由数据字段(变量或常量)和方法使用,包括构造函数方法和接口,但不能由类使用。我们将在本章后面讨论 Java 接口。private access control 关键字允许类中的变量、方法和构造函数只能在该类内部被访问,从 Java 9 开始,现在允许使用私有接口。这个私有访问控制关键字允许 Java 实现一个称为封装的概念,其中一个类(以及使用该类创建的对象)可以封装自己,可以说对外部 Java 世界隐藏了它的“内部”。这种封装在 Java 9 中通过使用模块得到了进一步的增强,我们将在本章的末尾讨论这一点。封装的 OOP 概念可用于允许团队创建(和调试)他们自己的类和对象。这样,其他人的 Java 代码都无法破解存在于类内部的代码,因为它的方法、变量、常量、接口和构造函数都是私有的。封装还可以用来保护代码和资源(素材)不被公众访问。
这个访问修饰符关键字本质上是将类中的方法或变量“私有化”,这样它们就只能在该类中本地使用,或者由该类的构造函数方法创建的对象使用。除非您拥有包含这些私有 Java 构造的类,否则您无法访问或利用这些方法或数据字段。这是 Java 中最严格的访问控制级别。如果从类内部访问私有变量的公共方法称为公共方法,则可以在类外部访问声明为私有的变量。get()方法调用被声明为 public,因此提供了通过该公共方法访问私有变量或常量中的数据的路径(或门道)。
Java 包私有修饰符:包中的变量、方法或类
如果没有声明 Java 访问控制修饰符关键字,那么默认访问控制级别(也称为包私有访问控制级别)将应用于该 Java 构造(类、方法、数据字段、构造器或接口)。这意味着这些打包的私有 Java 构造对于包含它们的 Java 包中的任何其他 Java 类都是可见的或可用的。这种包私有级别的访问控制最容易应用于您的类、接口、方法、构造函数、常量和变量,因为它是作为默认操作应用的,只需在 Java 构造之前不显式声明任何 Java 访问控制修饰符关键字。
您将在自己的专业 Java 游戏和物联网应用编程中大量使用这种默认的包私有访问控制级别,因为通常您会在自己的包中创建自己的应用,供用户在 Java 9 新的增强安全性 Java 模块系统(Project Jigsaw)的完整、编译和可执行状态下使用。
从 Java 9 开始,您还将把您的包安装到一个核心 JavaFX 模块中,可能是 javafx.media 或 javafx.graphics。正如您将在本章的最后一节看到的,正确使用 public 和 private 关键字将允许您充分利用 Java 9 的新模块功能。我们将在本章结束时详细介绍模块,在我们介绍了 Java 的许多早期版本中存在的所有其他核心 Java 编程语言特性之后,这些特性今天仍在 Java 6 (32 位 Android)、Java 7 (64 位 Android 5 到 6)和 Java 8 (64 位 Android 7 到 8 和当前版本的 Java,直到 Java 9 在 2017 年最后一个季度发布)中使用。
但是,如果您正在开发游戏引擎供其他游戏开发者使用,那么您很可能最终会更多地使用我们在本节中讨论的其他三个访问控制修饰符关键字,以便您能够精确地控制其他人如何实现您的游戏引擎的 Java 代码结构。接下来,让我们看看非访问控制修饰符关键字,这在智力上更具挑战性!
非访问控制修饰符:Final、Static 和 Abstract
不专门为 Java 构造提供访问控制特性的 Java 修饰符关键字被称为非访问控制修饰符关键字。这些包括经常使用的 static、final 和 abstract 修饰符关键字,以及不经常使用的 synchronized 和 volatile 修饰符关键字,它们用于更高级的线程控制,我将在这个专业级编程标题的后面介绍。我将在这一节中介绍这些关键字,这样,如果您在此之前在 Java 编程中遇到它们,您就会知道它们的含义。
我将按照复杂性的顺序介绍这些概念,从开发人员最容易理解的到面向对象编程开发人员最难理解的。面向对象编程就像冲浪,看起来很难,直到你练习了很多次,然后突然有一天你就明白了。
Java Final 修饰符:不能修改变量引用、方法或类
我们已经看到了最后一个修饰符关键字,因为它用于声明一个常量和一个静态关键字。最终数据字段变量可以初始化(设置)一次。final 引用变量是一种特殊类型的 Java 变量,它包含对内存中某个对象的引用,不能被更改(重新分配)来引用不同的对象。然而,保存在(最终)被引用对象内部的数据是可以更改的,因为只有对对象本身的引用才是最终的引用变量,它实际上是使用 Java final 关键字“锁定”的。
使用 final 修饰符关键字也可以“锁定”Java 方法。当一个 Java 方法被设为“final”时,这意味着如果包含该方法的 Java 类被子类化,那么这个最终的方法就不能在子类的主体中被覆盖或修改。这实质上“锁定”了方法代码结构内部的内容。例如,如果您想要。JavaFXGame 类的 start()方法(如果它曾经被子类化过的话)总是做与 JavaFXGame 超类相同的事情(准备 JavaFX 登台环境),您应该这样做:
public class JavaFXGame extends Application {
Button btn;
@Override
public final void start(Stage primaryStage) {
btn = new Button(); // other Java statements can be added
}
}
这将防止任何子类(公共类 JavaFXGame3D 扩展 JavaFXGame)对 JavaFXGame 游戏引擎(JavaFX)的初始设置进行任何更改。start()方法适用于你的游戏应用,你将在第 7 和 8 章看到,涵盖了 JavaFX 9 多媒体引擎。使用 final modifier 关键字声明的类不能被扩展(也称为子类),从而锁定该类以防将来被使用。
Java 静态修饰符:独立于实例存在的变量或方法
正如您已经看到的,static 关键字可以与 final 关键字结合使用来创建一个常量。static 关键字用于创建 Java 构造(方法或变量),这些构造独立存在,或者位于使用定义静态变量或静态方法的类创建的任何对象实例之外。类中的静态变量将强制该类的所有实例共享该变量中的数据。在其他编程语言中,这通常被称为全局变量,由代码创建的任何东西都可以访问和共享。
类似地,静态方法也将存在于该类的实例化对象之外,并将由所有这些对象共享。静态方法不会引用自身“外部”的变量,例如实例化对象的变量。
通常,静态方法将从其声明类中引用其局部或静态变量和常数,还将使用该方法的参数列表接受变量。然后,它将提供基于这些参数的处理或计算,并使用方法自身的静态或局部常量或变量以及编程逻辑。
由于 static 是一个应用于类实例的概念,因此本质上比任何类本身都要低,因此 Java 类不会使用 static nonaccess control 修饰符关键字来声明。
Java 抽象修饰符:要扩展或实现的类或方法
Java abstract modifier 关键字与保护实际代码的关系比与运行时放入内存的代码(对象实例和变量等)的关系更大。abstract 关键字允许您指定如何将代码用作超类,也就是说,一旦它被扩展,如何在子类中实现它。因此,abstract modifier 关键字只适用于类和方法,而不适用于数据字段(变量和常量),因为这些数据结构保存值,而不是代码(编程逻辑)构造。
使用 abstract modifier 关键字声明的类不能被实例化,它只能用作超类(蓝图)来创建(扩展)其他类。因为 final 类不能被扩展,所以在类级别上,不能同时使用 final 和 abstract 修饰符关键字。如果一个类包含任何使用 abstract 修饰符关键字声明的方法,那么该类本身必须声明为抽象类。然而,抽象类不必包含任何抽象方法。
使用 abstract modifier 关键字声明的方法是声明用于子类但没有当前实现的方法。这意味着它的“方法体”中没有 Java 代码,如你所知,在 Java 中是用花括号描述的。任何扩展抽象类的子类都必须实现所有这些抽象方法,除非该子类随后也被声明为抽象的,在这种情况下,抽象方法被传递到下一个子类级别以最终实现。
Java Volatile 修饰符:对数据字段的高级多线程控制
Java volatile modifier 关键字在开发多线程应用时使用,但在 Java 9 游戏开发中不打算这样做,因为您希望对游戏进行足够好的优化,以便它只使用 JavaFX 线程。volatile 修饰符的作用是告诉运行应用的 Java 虚拟机(JVM)将声明为 volatile 的数据字段(变量或常量)的私有(线程的)副本与系统内存中该变量的主副本合并。
易失性与运行的应用的可见性属性相关联。当一个变量被声明为 volatile 时,写操作将影响变量的主存副本,因此在任何 CPU 或内核上运行的任何线程都将观察到这种变化。当一个变量没有被声明为 volatile 时,写操作将被写入到一个缓存的副本中,因此只有做出改变的线程能够观察到这个改变。只有在 Java 9 游戏绝对需要时才使用 volatile。
这类似于 static modifier 关键字,区别在于静态变量(数据字段)由多个对象实例共享,而可变数据字段(变量或常量)由多个线程共享。
Java Synchronized 修饰符:对方法的高级多线程控制
Java synchronized 修饰符关键字也用于开发多线程应用,在本书中,我们不会为 Java 9 游戏开发引擎这样做。synchronized 修饰符的作用是告诉运行应用的 Java 虚拟机(JVM ),声明为 synchronized 的方法一次只能被一个线程访问。这个概念类似于数据库访问中的 synchronized 概念,所以不会有数据记录访问冲突。因此,synchronized modifier 关键字还可以防止访问您的方法(在系统内存中)的线程之间的冲突,方法是一次“序列化”一个方法的访问,这样就不会发生多个线程同时访问内存中的一个方法(冲突)的情况。synchronized 关键字与正在运行的应用的可见性和互斥性属性相关联。许多多线程场景不需要互斥,只需要可见性,因此在这些情况下使用 synchronized 关键字而不是 volatile 关键字会被认为是矫枉过正(与优化相反)。
现在我们已经介绍了主要的 Java 构造(类、方法和字段)和基本修饰符(公共、私有、受保护、静态、最终、抽象等)。)关键词,让我们来看看花括号:{ }现在,学习用于创建 Java 编程逻辑的工具,这些逻辑将最终定义您的 pro Java 9 游戏。
Java 数据类型:在应用中定义数据类型
因为我们已经讨论了变量和常量,所以您已经遇到了一些 Java 数据类型。让我们进入下一个主题,因为对于我们目前从容易理解到更难的主题的进展来说,这还不算太高级!Java 中有两种主要的数据类型分类:原始数据类型,如果您使用过不同的编程语言,这可能是您最熟悉的;引用(对象)数据类型,如果您使用过另一种面向对象的编程语言,例如 LISP、Python、Objective-C、Ruby、Groovy、Modula、Object COBOL、ColdFusion、C++和 C# (C Sharp 和。NET)。
原始数据类型:字符、数字和布尔值
Java 编程语言中有八种原始数据类型,如表 5-1 所示。我们将在本书中使用这些来创建我们的 JavaFXGame i3D Java 9 游戏,所以我现在不打算深入讨论其中的每一个,只是说布尔数据通常在游戏中用于保存“标志”或“开关”(开/关),char 数据通常用于包含 Unicode 字符或用于创建更复杂的 String 对象(本质上是一个 char 数组),其余的数据用于保存不同大小和分辨率的数值。整数值保存整数,而浮点值保存分数(小数点值)。
为变量的“作用域”或使用范围使用正确的数值数据类型是很重要的,因为正如你在表 5-1 中看到的,大数值数据类型可以比小数值数据类型多使用八倍的内存。请注意,布尔数据值可以比长整型或双精度型数值小 64 倍,因此设计 Java 9 游戏来利用大量的布尔值可能是一种令人难以置信的内存优化技术。不要使用任何超过你完成游戏处理目标绝对需要的数值分辨率,因为内存是一种宝贵的资源。
表 5-1。
Primitive Data Types in Java 9 Along with Their Default Values, Size in Memory, Definition, and Numeric Range
数据类型 | 默认 | 二进制大小 | 定义 | 范围 |
---|---|---|---|---|
布尔 | 错误的 | 1 位(或 1 字节 8 位) | true 或 false 值 | 0 到 1(假或真) |
茶 | \u0000 | 16 位 | Unicode 字符 | \u0000 到\ uffff |
字节 | Zero | 8 位 | 有符号整数值 | -128 到 127(总共 256 个值) |
短的 | Zero | 16 位 | 有符号整数值 | -32768 到 32767(总共 65,536 个值) |
(同 Internationalorganizations)国际组织 | Zero | 32 位 | 有符号整数值 | -2147483648 转 2147483647 |
长的 | Zero | 64 位 | 有符号整数值 | -9223372036854775808 转 922337203685 |
漂浮物 | Zero | 32 位 | IEEE 754 浮点值 | 1.4E-45 至 3.4028235E+38 |
两倍 | Zero | 64 位 | IEEE 754 浮点值 | 4.9E-324 至 1.7976931348623157E+308 |
接下来,让我们看一下引用数据类型,之所以这样命名,是因为它们引用内存中更复杂的数据结构,如对象和数组,这两者都包含更复杂的数据结构,这些数据结构要么保存复杂的数据和方法子结构(对象),要么保存更广泛的数据列表(数组)。在数据类型后面的部分,我将从逻辑上介绍 Java 操作符,它们“操作”这些 Java 数据结构。
引用数据类型:对象和数组
面向对象编程(OOP)语言也有引用数据类型,它在内存中提供对另一个包含更复杂数据结构的结构的引用,如对象或数组。这些更复杂的数据结构是使用代码创建的。在 Java 中,这是一个类。有一种或另一种类型的 Java 数组类创建数据数组(像简单的数据库),以及任何 Java 类中的构造函数方法,甚至是您创建的自定义类,它们可以在内存中创建对象结构,其中可以包含 Java 代码(方法)以及数据(字段)。
因为引用数据类型是对内存位置的引用,所以默认值总是 null,这意味着对象还没有被创建,因为没有引用。由于存在不同的数组和数据集类,数组也是引用对象,但由于它们是由类构造函数方法创建的,所以它们实际上是对象。底线是引用数据类型是使用类创建的,并且总是一种或另一种类型的对象,在内存中被引用。通常这种引用是静态的和/或最终的,因此存储位置是固定的,从而优化了存储器的使用。接下来,让我们看一下 Java 操作符,它们被用来对我们刚刚讨论过的不同 Java 数据类型进行操作(也就是说,对它们执行操作)。
Java 操作符:操纵应用中的数据
在这一节中,我们将介绍 Java 编程语言中一些最常用的操作符,尤其是那些对游戏编程最有用的操作符。这些包括算术运算符,用于数学表达式;关系运算符,用于确定关系(等于、不等于、大于、小于等)。)在数据值之间;逻辑运算符,用于布尔逻辑;赋值运算符,它执行算术运算,并在一次紧凑运算中将值赋给另一个变量(运算符);以及条件运算符,也称为三元运算符,它根据真或假(布尔)计算的结果为变量赋值。
还有概念上更高级的按位运算符,用于在二进制数据(零和一)级别执行运算,其应用超出了本书的范围。二进制数据的使用在 JavaFX 游戏编程中不像其他更主流类型的运算符那样常见,在本书中,您将使用每种运算符来完成您的专业 Java 游戏和物联网应用逻辑中的各种编程目标。
Java 算术运算符:基础数学
Java 算术运算符是专业 Java 游戏编程中最常用的运算符,尤其是在动态动作类游戏中,游戏中的事物在屏幕上以精确、高度受控的像素数量移动。不要低估简单的算术运算符,就像在 OOP 语言的框架中一样。使用 Java 结构可以创建复杂得多的数学方程,例如方法,这些方法利用 Java 提供的其他强大工具利用这些基本算术运算符,我们将在本章中回顾(学习)这些工具。
表 5-2 中所示的算术运算符中,您可能不太熟悉的是模数运算符,它将在除法运算完成后返回余数(剩余的部分);和递增或递减运算符,分别从一个值中加 1 或减 1。这些运算符有时用于实现您的计数器逻辑。计数器(使用递增和递减操作符)最初用于循环,我们将在下一节中介绍它;然而,递增和递减运算符在游戏设计中也非常有用,用于计分、寿命损失、游戏棋子移动和类似的线性数字级数。
表 5-2。
Java Arithmetic Operators, Their Operation Type, and a Description of That Arithmetic Operation
操作员 | 操作 | 描述 |
---|---|---|
加号+ | 添加 | 运算符将运算符两边的操作数相加 |
减- | 减法 | 运算从左操作数中减去右操作数 |
相乘* | 增加 | 运算符将运算符两边的操作数相乘 |
划分/ | 分开 | 运算将左操作数除以右操作数 |
模数% | 剩余物 | 运算将左操作数除以右操作数,返回余数 |
增量++ | 添加一个 | 增量运算会将操作数的值增加 1 |
减量- | 减去一 | 减量操作会将操作数的值减一 |
要实现算术运算符,请将希望接收算术运算结果的数据字段(变量)放在等号赋值运算符的左侧(我们也将在本章的这一节讨论赋值运算符),将希望执行算术运算的变量放在等号的右侧。下面是一个添加 X 和 Y 变量并将结果赋给 z 变量的示例:
Z = X + Y; // Using the Addition Operator
如果你想从 X 中减去 Y,你应该用减号而不是加号,如果你想把 X 和 Y 值相乘,你应该用星号而不是加号。如果你想用 X 除以 Y,你应该使用一个正斜杠字符,而不是加号。如果你想得到 X 除以 Y 的余数,你可以使用一个百分号。下面是这些基本算术运算在代码中的样子:
Z = X - Y; // Subtraction Operator
Z = X * Y; // Multiplication Operator
Z = X / Y; // Division Operator
Z = X % Y; // Modulus Operator
如果您的 Java 代码涉及被零(0)除,您应该小心。将整数除以 0 将导致算术异常。将浮点值除以 0 将得到+无穷大、-无穷大或 NaN。在游戏开发环境中,您可能会遇到这种情况,您必须重新设计您的编程逻辑,以确保这些情况不会干扰您的游戏。
在本书中,你会经常用到这些算术运算符,所以在你完成游戏之前,你会得到一些很好的练习。接下来让我们更仔细地看看关系运算符,因为有时您会想要比较值,而不是精确地计算值。
Java 关系运算符:进行比较
在某些情况下,Java 关系运算符可用于在两个变量之间或变量和常量之间进行逻辑比较。这些你从初中开始应该也很熟悉了,它们包括等于、不等于、大于、小于、大于等于、小于等于。大于使用箭头的开放端(人字形),因为开放跨度大于封闭跨度,小于使用箭头的封闭端(人字形),因为封闭跨度小于开放跨度。这是一个很好的视觉观察方式。当您这样做时,您可以立即看到在关系运算符 X > Y 中,X 大于 Y。在 Java 中,等于关系运算符在被比较的数据字段之间并排使用两个等号,并在等号之前使用一个感叹号来表示不等于,如表 5-3 所示,该表显示了关系运算符以及每个运算符的示例和描述。
表 5-3。
Java Relational Operators, an Example Where A=10 and B=20, and a Description of the Relational Operation
操作员 | 例子 | 描述 |
---|---|---|
== | 不正确 | 两个操作数的比较:如果它们相等,那么条件等于真 |
!= | (答!= B)为真 | 两个操作数的比较:如果它们不相等,则条件等于真 |
> | 不正确 | 两个操作数的比较:如果左操作数大于右操作数,则等于真 |
< | (A < B)为真 | 两个操作数的比较:如果左操作数小于右操作数,则等于真 |
>= | (A >= B)不正确 | 比较两个操作数:如果左操作数大于或等于右操作数等于真 |
<= | (A <= B)为真 | 比较两个操作数:如果左操作数小于或等于右操作数,则等于真 |
大于符号是向右的箭头,小于符号是向左的箭头。它们用在等号前,分别创建大于或等于和小于或等于关系运算符,如表 5-3 底部所示。
这些关系运算符返回布尔值 true 或 false。因此,它们也在 Java 的控制(循环)结构中被大量使用,也在游戏编程逻辑中被用来控制游戏将采取的路径(结果)。例如,假设您想要确定游戏板的左边缘在哪里,以便当游戏块 3D 对象被移动到左边时不会从板上掉下来。使用这种关系比较:
boolean gameBoardEdge = false; // boolean variable gameBoardEdge initialized to be false
gameBoardEdge = (GamePieceX <= 0); // boolean gameBoardEdge set to TRUE if left side reached
请注意,我使用了< =小于或等于(是的,Java 也支持负数),因此,如果游戏棋子已经越过了屏幕的左侧(x=0),gameBoardEdge 布尔标志将被设置为 true 值,游戏移动编程逻辑可以通过改变移动方向(因此游戏棋子不会从游戏棋盘上掉落)或完全停止移动(因此游戏棋子停在边缘)来处理这种情况。
在本书中,你将会接触到大量的关系操作符,因为它们在创建游戏逻辑时非常有用,所以我们很快就会从中获得很多乐趣。接下来让我们来看看逻辑运算符,这样我们就可以处理布尔集合并分组比较,这对游戏也很重要。
Java 逻辑操作符:处理组和对立面
Java 逻辑运算符有点类似于布尔运算(并集、交集等)。)因为它们将布尔值相互比较,然后基于这些比较做出决定。Java 逻辑运算符将允许您确定两个布尔变量是否持有相同的值,这被称为 AND 运算,或者一个布尔变量是否与另一个不同,这被称为 or 运算。还有第三个逻辑运算符,称为 NOT 运算符,它将反转任何比较的布尔操作数的值,甚至反转没有比较的布尔操作数的值,如果你只是想在游戏编程逻辑中翻转开关或反转布尔标志。正如您可能已经猜到的,AND 运算符使用两个 AND 符号,就像这样:&&。OR 运算符使用两个竖线,就像这样:||。NOT 运算符使用感叹号,就像这样:!。所以,如果我说我不是在开玩笑,我会写!开玩笑(嘿,那会是一件很棒的程序员 t 恤)。表 5-4 显示了 Java 逻辑操作符,每一个都有一个例子,还有一个简短的描述。
表 5-4。
Java Logical Operators, an Example Where A=true and B=false, and a Description of the Logical Operation
操作员 | 例子 | 描述: |
---|---|---|
&& | (A && B)是假的 | 当两个操作数都为真值时,逻辑 AND 运算符等同于真。 |
|| | (A || B)是真的 | 当任一操作数为真值时,逻辑 OR 运算符等同于真。 |
! | !(A && B)是真的 | 逻辑 NOT 运算符会反转应用它的运算符(或集合)的逻辑状态。 |
让我们使用逻辑运算符来增强我在上一节中使用的游戏逻辑示例,以确定当玩家移动游戏棋子时(也就是说,当轮到他们时),他们是否从游戏棋盘上掉了下来(移动到边缘之外)。
修改后的代码将包含一个逻辑 AND 运算符,如果 gameBoardEdge = true 且 turnActive = true,该运算符会将 fellOffBoard 布尔变量设置为 true 值。确定这一点的 Java 代码将类似于以下 Java 语句:
boolean gameBoardEdge = false; // boolean variable gameBoardEdge is initialized to be false
gameBoardEdge = (GamePieceX < 0); // boolean gameBoardEdge set TRUE if past (before) left side
fellOffBoard = (gameBoardEdge && turnActive) // It's your turn, but you fell off the left edge!
现在,您已经开始练习声明和初始化变量,并使用关系和逻辑运算符来确定游戏棋子的回合、边界和位置。接下来,我们来看看 Java 赋值操作符。
Java 赋值运算符:将结果赋给变量
Java 赋值运算符将赋值运算符右侧的逻辑结构中的值赋给赋值运算符左侧的变量。最常见的赋值运算符也是 Java 编程语言中最常用的运算符,即等号运算符。等号运算符可以以任何算术运算符为前缀,以创建也执行算术运算的赋值运算符,如表 5-5 所示。当变量本身将成为等式的一部分时,这允许创建“更密集”的编程语句。因此,不必写出 C = C+A;,可以简单的用 C+= A;并获得相同的最终结果。在我们的游戏逻辑设计中,我们会经常使用这个赋值操作符快捷键。
表 5-5。
Java Assignment Operators, What That Assignment Is Equal to in Code, and a Description of the Operator
操作员 | 例子 | 描述 |
---|---|---|
= | C=A+B | 基本赋值运算符:将右侧操作数的值赋给左侧操作数 |
+= | C+=A 等于 C=C+A | 加法赋值运算符:将右操作数加到左操作数上;将结果放入左操作数 |
-= | 等于 C=C-A | 子赋值运算符:从左操作数中减去右操作数;将结果放入左操作数 |
*= | C*=A 等于 C=C*A | MULT 赋值:右操作数和左操作数相乘;将结果放入左操作数 |
/= | C/=A 等于 C=C/A | DIV 赋值运算符:左操作数除以右操作数;导致左操作数 |
%= | C%=A 等于 C=C%A | 模赋值:左操作数除以右操作数;将余数放入左操作数 |
最后,我们要看看条件运算符,它也允许我们编写强大的游戏逻辑。
Java 条件运算符:如果为真,则设置一个值,如果为假,则设置另一个值
Java 语言还有一个条件运算符,它可以计算一个条件,并根据该条件的解析为您进行变量赋值,只需使用一个紧凑的编程结构。条件运算符的通用 Java 编程语句格式始终采用以下基本格式:
Variable = (evaluated expression) ? Set this value if TRUE : Set this value if FALSE ;
因此,在等号的左边,您有一个变量,它将根据等号右边的内容而变化(将被设置),这与您在本节中学到的内容一致。
在等号的右边,有一个计算表达式。例如,“x 等于 3”,然后你有一个问号字符。然后是两个数值,用冒号分隔开,最后,条件操作符语句用分号结束。如果希望在 x 等于 3 时将变量 y 的值设置为 25,而在 x 不等于 3 时将其值设置为 10,则可以使用以下 Java 编程逻辑编写条件运算符编程语句:
y = (x == 3) ? 25 : 10 ;
需要注意的是,在。和之后:必须与等于运算符另一侧的数据变量类型一致。例如,您不能指定以下内容:
int x = (y > z) ? "abc" : 20;
接下来,我们将看看 Java 逻辑控制结构,它利用了您刚刚学习的操作符。
Java 条件控制:循环还是决策
正如您刚才看到的,许多 Java 操作符,尤其是条件操作符,可以具有相当复杂的程序逻辑结构,并使用很少的 Java 编程代码字符提供大量的处理能力。Java 还有几个更复杂的条件控制结构,一旦你为 Java 设置了做出这些决定的条件,它可以自动为你做出决定或者自动为你执行重复的任务。您还可以通过编写通常称为 Java 逻辑控制结构的代码来执行这些重复的任务。
在本章的这一节,我们将首先看一看决策控制结构,如 Java Switch-Case 结构和 If-Then-Else 结构,然后我们将看一看 Java 的循环控制结构,包括 For、While 和 Do-While 迭代(循环)控制结构。
决策控制结构:开关盒和 If - Else
一些最强大的 Java 逻辑控制结构,特别是当涉及到专业 Java 游戏开发时,是那些允许你定义游戏决策的结构,当你的游戏应用运行时,你希望你的游戏程序逻辑为你作出决策。其中一个称为开关,提供逐案“平面”决策矩阵,另一个称为 if-else,提供级联决策树,评估“如果这样,就这样,如果不这样,就这样,如果不这样,就这样,如果不这样,就这样,如果都不这样,就这样。”这两者都可以用来创建一种评估结构,在这种结构中,事物按照您希望的顺序和方式进行评估。
让我们从 Java switch 语句开始,它使用 Java switch 关键字和这个决策树顶部的一个表达式。在决策树内部,switch 构造使用 Java case 关键字为 switch 语句表达式求值的每个结果提供 Java 语句块。如果表达式求值没有使用 switch 语句结构内(即花括号{}内)的这些情况,则可以提供一个 Java default 关键字和一个 Java 语句代码块,以便在这些情况都没有被调用时执行您想要的操作。
切换情况决策树编程构造的一般格式如下所示:
switch(expression) {
case value1 :
programming statement one;
programming statement two;
break;
case value2 :
programming statement one;
programming statement two;
break;
default :
programming statement one;
programming statement two;
}
case 语句中使用的变量可以是五种 Java 数据类型之一:char(字符)、byte、short、string 或 int(整数)。您通常希望在每个 case 语句代码块的末尾提供 Java break 关键字,至少在需要“排他地”切换值的用例中,并且对于每次调用 switch 语句,只有一个值是可行的(或允许的)。
默认语句不需要使用任何 break 关键字。
如果您没有在每个 case 逻辑块中提供 Java break 关键字,则可以通过 switch 语句在同一个过程中对多个 case 语句进行求值。这将在表达式求值树从顶部(第一个 case 代码块)到底部(最后一个 case 代码块或默认关键字代码块)的过程中完成。
这样做的意义在于,您可以根据 case 语句的求值顺序以及是否将 break 关键字放在任何给定 case 语句代码块的末尾,来创建一些相当复杂的决策树。
假设你想在你的游戏中决定当游戏角色移动时(行走、跳跃、跳舞等)游戏角色移动动画的名称。).游戏人物动画例程(方法)将基于游戏人物在移动时所做的事情来调用,例如行走(W)、跳跃(J)、跳舞(D)或空闲(I)。假设这些“状态”保存在 char 类型的名为 gpState 的数据字段中,该字段保存一个字符。你的 switch-case 代码构造使用这些游戏棋子状态指示器来调用一个正确的方法,一旦已经进行了一个回合,并且需要进行移动。这应该类似于下面的 Java 伪代码(原型代码):
switch(gpState) { // Evaluate gpState char, execute case code blocks accordingly
case 'W' :
gamePieceWalking(); // Java method controlling Walk sequence if GamePiece is walking
break;
case 'J' :
gamePieceJumping(); // Java method controlling Jump sequence if GamePiece is jumping
break;
case 'D' :
gamePieceDancing(); // Java method controlling Dance sequence if GamePiece is dancing
break;
default :
gamePieceIdle(); // Java method controlling processing if a GamePiece is idle
这个 switch -case 逻辑结构在 switch()语句的求值部分内对 gpState char 变量求值(注意,它使用了 Java 方法结构),然后为游戏中行走、跳跃和跳舞的每种状态提供一个 case 逻辑块。它还实现了空闲状态的默认逻辑块。这是最符合逻辑的设置方式,因为游戏角色通常是空闲的,除非轮到该用户。
因为一个游戏棋子不能同时空闲、行走、奔跑和跳舞,所以我需要使用 break 关键字来使这个决策树的每个分支对其他分支(状态)是唯一的(互斥的)。
switch-case 决策结构通常被认为比 if-else 决策结构更高效、更快速,if-else 决策结构仅使用 if 关键字进行简单评估,如下所示:
if(expression == true) {
programming statement one;
programming statement two;
}
您还可以添加一个 else 关键字,使这个决策结构评估在布尔变量(true 或 false 条件)评估为 false 而不是 true 时需要执行的语句,这使这个结构更强大(也更有用)。这种通用编程结构将如下所示:
if(expression == true) {
programming statement one;
programming statement two;
} else { // Execute this code block if (expression == false)
programming statement one;
programming statement two;
}
还可以嵌套 if-else 结构,从而创建 if-{else if}-{else if}-else{}结构。如果这些结构嵌套得太深,那么您可能想要切换到使用 switch-case 结构,这并不是双关语。这个结构会变得越来越高效,相对于嵌套的 if-case 结构,你的 if-else 嵌套越深。下面是一个例子,说明我之前为 BoardGame 游戏编写的 switch-case 语句如何转化为 Java 编程结构中的嵌套 if-else 决策结构:
if(gpState = 'W') {
gamePieceWalking();
} else if(gpState = 'J') {
gamePieceJumping();
} else if(gpState = 'D') {
gamePieceDancing();
} else {
gamePieceIdle();
}
正如您所看到的,这个 if-else 决策树结构与我们之前创建的 switch-case 非常相似,只是决策代码结构相互嵌套,而不是包含在一个“平面”结构中。作为一般的经验法则,对于一值和二值评估,我会使用 if 和 if-else,对于三值或更多值评估场景,我会使用 switch-case。我在涉及 Android 的书籍中广泛使用了开关盒结构,例如面向绝对初学者的 Android 应用(Apress,2017 年)和 Pro Android 可穿戴设备(Apress,2015 年)。
接下来,让我们看看 Java 中广泛使用的其他类型的条件控制结构,即“循环”或迭代编程结构。这些迭代条件结构将允许您通过使用 for 循环执行任何编程语句块预定的次数,或者通过使用 while 或 do-while 循环直到实现 Java 编程目标。
正如你所想象的,这些迭代控制结构对你的游戏控制逻辑非常有用。
循环控制结构:While、Do - While 和 For 循环
尽管决策树类型的控制结构被遍历了固定的次数(除非遇到 break [switch-case]或 resolved expression [if-else],否则遍历一次),但循环控制结构会随着时间的推移不断执行,这对于 while 和 do-while 结构来说有点危险,因为如果不小心使用编程逻辑,可能会生成无限循环!for 循环结构执行循环定义中指定的有限数量的循环,我们将在本章的这一节中看到。
让我们从有限循环开始,先讨论 for 循环。Java for 循环使用以下通用格式:
for(initialization; boolean expression; update equation) {
programming statement one;
programming statement two;
}
括号内 for 循环求值区域的三个部分用分号分隔,每个部分都包含一个编程构造。第一个是变量声明和初始化,第二个是布尔表达式求值,第三个是显示如何在每次循环中递增循环的更新方程。
如果你想在棋盘上对角移动游戏棋子 40 个像素,你的 for 循环如下:
for (int x; x < 40; x = x + 1) { // Note: the x = x + 1 statement could also be coded as x++
gamePieceX++; // Note: gamePieceX++ could be coded gamePieceX = gamePieceX + 1;
gamePieceY++; // Note: gamePieceY++ could be coded gamePieceY = gamePieceY + 1;
}
另一方面,while(或 do-while)类型的循环不在有限数量的处理周期内执行,而是使用以下结构执行循环内部的语句,直到满足条件:
while (boolean expression) {
programming statement one;
programming statement two;
expression incrementation;
}
使用 while 循环结构编写将游戏块移动 40 个像素的 for 循环,如下所示:
int x = 0;
while(x < 40) {
invinciBagelX++;
invinciBagelY++;
x++;
}
do-while 循环和 while 循环之间的唯一区别是,在 do-while 循环中,循环逻辑编程语句在求值之前执行,而不是像在 while 循环中那样在求值之后执行。因此,前面的示例将使用 do- while 循环编程结构编写,该结构在花括号内有一个 Java 编程逻辑结构,在 Java do 关键字之后,在右花括号之后有 while 语句,编码如下:
int x = 0;
do {
invinciBagelX++;
invinciBagelY++;
x++;
}
while(x < 40);
您还应该注意,对于 do {…} while(…);构造中,while 求值语句(以及整个 do-while 编程构造)需要以分号结束,而 while(…){…}结构则不需要。
如果您希望确保 while 循环结构中的编程逻辑至少执行一次,请使用 do-while,因为求值是在循环逻辑执行之后执行的。如果您想确保循环内部的逻辑只在求值成功之后或任何时候执行(这是编写代码的更安全的方式),请使用 while 循环结构。
Java 对象:在 Java 中使用 OOP 虚拟现实
我把最好的留到最后,Java 对象,是因为它们可以用一种或另一种方式构造,使用我在这一章中已经介绍过的所有概念,也因为它们是面向对象编程(OOP)语言的基础,在这里是 Java 7、8 和 9。java 编程语言中的一切都基于 Java 语言的 Object 超类(我喜欢称之为 master 类),它在 java.lang 包中,因此它的 import 语句将引用 java.lang.Object,这是 Java 对象类的完整路径名。所有其他 Java 类都是使用这个类创建的,或者说是子类化的,因为 Java 中的所有东西都是对象。
注意,您的 Java 编译器会自动为您导入这个 java.lang 包!Java 对象是用来“虚拟化”现实的,它允许你在日常生活中看到的对象(或者,在你的游戏中,你根据自己的想象创建的对象)被逼真地模拟。这是通过使用数据字段(变量和常量)和你在本章中学到的方法来完成的。这些 Java 编程结构将组成对象特征或属性(常量)、状态(变量)和行为(方法)。
Java 类构造将组织每个对象定义(常量、变量和方法),并将产生该对象的一个实例。它通过使用设计和定义对象的类的构造函数方法,以及通过使用在本章中学到的各种 Java 关键字和编程构造来实现这一点。在这一节中,我将向您介绍如何做到这一点,我想如果您是 Java 9 的新手,您会发现这非常有趣。
思考 Java 对象的一种方式是把它们当成名词,即存在于自身之中的事物(对象)!使用方法创建的对象行为类似于动词,即名词可以做的事情。举个例子,让我们考虑一下我们生活中最受欢迎的东西:汽车。我们完全可以把这辆车作为游戏的一部分或者作为棋盘游戏的另一个组成部分加入到我们的棋盘游戏中。
接下来让我们定义汽车对象属性。一些特性或属性不会改变,并保存在常量中,可定义如下:
- 颜色(糖果苹果红)
- 发动机类型(燃气、柴油、氢气、丙烷或电动)
- 传动系统类型(2WD 或 4WD)
一些实时改变、定义汽车并保存在变量中的状态可以定义如下:
- 方向(北、南、东或西)
- 速度(每小时 15 英里)
- 档位设置(1、2、3、4 或 5)
以下是汽车应该能够做的一些事情,即汽车的行为,定义为方法:
- 加速
- 变速
- 刹车
- 转动轮子
- 打开音响
- 用前灯
- 使用转向灯
你明白了。现在停止幻想你的新游戏,让我们回到学习物体上来!
图 5-3 展示了 Java 对象结构,以这辆车为例。它显示了汽车的特征或属性,这些特征或属性对于定义汽车对象和可用于汽车对象的行为至关重要。
图 5-3。
The anatomy of a car GamePiece object, with methods encapsulating variables or constants inside a class
这些属性和行为将为外界定义一辆汽车,就像您的 pro Java 9 游戏应用对象将为您的 Java 9 和 JavaFX 9 游戏应用所做的那样。
对象可以像您希望的那样复杂,Java 对象也可以在其对象结构或对象层次结构中嵌套或包含其他 Java 对象。一个对象层次结构就像一个树形结构,当你沿着树形结构向上(或向下)移动时,有一个主干、分支,然后是子分支,非常类似于 JavaFX 或 3D 软件场景图,你可以在第三章中看到(在图 3-4 的右边)。
您每天使用的层次结构的一个很好的例子是多级目录或文件夹结构,它位于您计算机的硬盘驱动器上。
硬盘上的目录或文件夹将包含其他目录或文件夹,这些目录或文件夹又可以包含其他目录和文件夹,从而允许创建复杂的组织层次结构。
你会注意到,在现实生活中,物体可以由其他物体组成。例如,一个汽车引擎对象由数百个离散的对象组成,这些对象共同作用,使引擎对象作为一个整体工作。
这种从简单对象中构造更复杂对象的方法也可以在 OOP 语言中实现,其中复杂的 Java 对象层次结构可以包含其他 Java 对象。这些 Java 对象中的许多可能是使用预先存在的或以前开发的 Java 代码创建的,这是模块化编程的目标之一。
作为练习,您应该练习识别您周围房间中的不同复杂对象,然后将它们的定义或描述分解为状态(可变状态或恒定特征)以及行为(对象可以或将要做的事情)和对象及子对象层次。
这是一个很好的练习,因为这是您最终需要开始思考的方式,以便在更大的 Java 编程语言框架内使用 JavaFX 引擎在专业的面向对象游戏编程工作中获得更大的成功。
编码对象:将对象设计转化为 Java 代码
为了进一步说明这一点,让我们为汽车对象示例构造一个基本类。要创建一个 Car 类,可以使用 Java 关键字 class,后跟您正在编写的新类的自定义名称,然后是包含您的 Java 代码类定义的花括号。通常放入类中的第一件事(在大括号{}内)是数据字段(变量)。这些变量将保存汽车对象的状态或特征。在这种情况下,您将有六个数据字段,它们将定义汽车的当前档位、当前速度、当前方向、燃料类型、颜色和传动系统(两轮或四轮驱动),如前面为该汽车对象指定的那样。因此,有了图 5-3 中的六个变量,汽车类的定义最初看起来会像这样:
class Car {
int speed = 15;
int gear = 1;
int drivetrain = 4;
String direction = "N";
String color = "Red";
String fuel = "Gas";
}
注意这个例子是如何在它们自己的行上隔开花括号:{ },以及缩进某些行。这是 Java 编程惯例,这样您就可以更容易、更清楚地将包含在花括号内的 Java 类结构中的代码结构可视化,类似于 Java 9 代码结构的“鸟瞰图”。
由于我们使用等号为所有这些变量指定了一个起始值,请记住这些变量都将包含这个缺省值或起始数据值。这些初始数据值将在构造时被设置(在系统内存中)为汽车对象的默认值,因为它们被设置为你的类的“起始”变量数据值。
Java 类定义文件的下一部分将包含您的方法。Java 方法将定义您的 Car 对象将如何运行,也就是说,它将如何“操作”您在保存 Car 对象当前“操作状态”的类的顶部定义的变量方法“调用”将调用变量状态更改,方法也可以将数据值“返回”给“调用”或“调用”该方法的实体,如已成功更改的数据值,甚至是某个等式的结果。
例如,应该有一种方法允许您通过将对象的档位变量或属性设置为不同的值来换档。此方法将被声明为 void,因为它执行一个函数,但不返回任何函数。在这个汽车类和汽车对象定义的例子中,我们将有四个方法,如图 5-3 所示。
那个。shiftGears()方法会将汽车对象的 gear 属性设置为传递到。shiftGears()方法。您应该允许将一个整数传递给该方法,以允许“用户错误”,就像您在现实世界中驾驶汽车时,用户可能会意外地从第一档换到第四档一样。
void shiftGears (int newGear) {
gear = newGear;
}
那个。accelerateSpeed()方法获取你的对象速度状态变量,然后将你的加速因子加到该速度变量上,这将导致你的对象加速。这是通过获取对象的当前速度设置或状态,并向其添加加速因子,然后将该加法操作的结果设置回原始速度变量,以便对象的速度状态现在包含新的(加速的)速度值。
void accelerateSpeed (int acceleration) {
speed = speed + acceleration;
}
那个。applyBrake()方法获取对象的速度状态变量,并从当前速度中减去一个制动因子,从而使对象减速或制动。这是通过获取对象的当前速度设置并从中减去制动系数,然后将相减的结果设置回原始速度变量来完成的,这样对象的速度状态现在包含更新的(减速的)制动值。
void applyBrake (int brakingFactor) {
speed = speed - brakingFactor;
}
那个。turnWheel()方法很简单,很像。shiftGears()方法,只不过它使用一个字符串值 N、S、E 或 W 来控制汽车转弯的方向。什么时候。使用转轮(“W”)时,汽车对象将向左转。什么时候。使用 turn wheel(“E”)时,汽车将向右转,当然,假设汽车对象当前正在向北行驶,根据其默认的方向设置,它是向北行驶的。
void turnWheel (String newDirection) {
direction = newDirection;
}
使 Car 对象函数进入类内部的方法在变量声明之后,如下所示:
class Car {
int speed = 15;
int gear = 1;
int drivetrain = 4;
String direction = "N";
String color = "Red";
String fuel = "Gas";
void shiftGears (int newGear) {
gear = newGear;
}
void accelerateSpeed (int acceleration) {
speed = speed + acceleration;
}
void applyBrake (int brakingFactor) {
speed = speed - brakingFactor;
}
void turnWheel (String newDirection) {
direction = newDirection;
}
}
这个 Car 类将允许您定义一个 Car 对象,即使您没有明确包含 Car()构造函数方法,我们将在接下来讨论这个方法。这就是为什么你的变量设置将成为你的汽车对象的默认值。但是,最好编写自己的构造函数方法,这样您就可以完全控制对象的创建,并且不必将变量预先初始化为某个值。因此,您要做的第一件事是不定义变量声明,删除等号和初始数据值,如下所示:
class Car {
String name;
int speed;
int gear;
int drivetrain;
String direction;
String color;
String fuel;
public Car (String carName) {
name = carName;
speed = 15;
gear = 1;
drivetrain = 4;
direction = "N";
color = "Red";
fuel = "Gas";
}
}
相反,Car()构造函数方法本身将设置数据值,作为 Car 对象的构造和配置的一部分。如您所见,我添加了一个字符串名称变量来保存汽车对象的名称(carName 参数)。
Java 构造函数方法与常规的 Java 方法有许多不同之处。首先,它不会使用任何数据返回类型,比如 void 和 int,因为它是用来创建一个 Java 对象而不是执行一个函数的。它不返回 nothing (void 关键字)或 number (int 或 float 关键字),而是返回 java.lang.Object 类型的对象。注意,每个需要创建 java 对象的类都有一个与类同名的构造函数,因此构造函数是一种方法类型,它的名称可以(并且应该总是)以大写字母开头。正如我提到的,如果您没有编写构造函数,Java 编译器会为您创建一个!
构造函数方法和任何其他方法之间的另一个区别是,构造函数需要利用公共访问控制修饰符,而不能使用任何非访问控制修饰符。如果您想知道如何修改前面的 Car()构造函数方法,比方说,如果您不仅想使用构造函数方法命名 Car 对象,还想使用重载的 Car()构造函数方法调用来定义它的速度、方向和颜色,您可以通过使用以下代码为构造函数创建一个更长的参数列表来实现这个更高级的目标:
class Car {
String name;
int speed;
int gear;
int drivetrain;
String direction;
String color;
String fuel;
public Car (String carName, int carSpeed, String carDirection, String carColor) {
name = carName;
speed = carSpeed;
gear = 1;
drivetrain = 4;
direction = carDirection;
color = carColor;
fuel = "Gas";
}
}
这里需要注意的是,只要包含非公共构造函数的类不需要从它们的包之外实例化,构造函数方法就可以不使用 public 关键字来声明。如果您想使用 this()编写一个调用另一个构造函数的构造函数,您也可以这样做。比如 Car()可能会执行构造函数方法调用 this("myCar “,10,1,4,” N “,” red ");这将是合法的 Java 代码。
要使用重载的 Car()类构造函数和 Java new 关键字创建一个新的 Car 对象,可以使用如下 Java 代码:
Car carOne = new Car(); // Creates a Car object using default values
Car carTwo = new Car("Herbie", 25, "W", "Blue"); // Creates a customized Car object
构造对象的语法类似于声明变量,但也使用 Java new 关键字:
- 定义对象类型汽车。
- 给汽车对象起一个名字(carOne,carTwo 等。)可以在 Java 代码类中引用。
- 使用默认的 Car()构造函数方法创建通用或默认的 Car 对象,或者…
- 使用具有不同值参数的重载汽车(名称、速度、方向、颜色)构造函数。
使用这些 Car 对象调用 Car 对象方法需要使用一种叫做点符号的东西,这种符号用于将 Java 结构相互链接或引用。一旦 Java 对象被声明、命名和实例化,您就可以“脱离它”调用方法例如,这可以使用以下 Java 代码来完成:
objectName.methodName(parameter list variable);
因此,要切换到第三档,对于名为 carOne 的汽车对象,您可以使用以下 Java 编程语句:
carOne.shiftGears(3);
这“调用”或“调用”了。shiftGears()方法“关闭”carOne Car 对象,并“忽略”gear 参数,该参数包含一个整数值 3,然后将其放入 newGear 变量中,该变量由。shiftGears()方法内部代码,用于更改汽车对象实例的齿轮属性,设置一个新值 3。
Java 点符号将 Java 方法调用“连接”到 Java 对象实例,然后 Java 对象实例调用或“调用”该 Java 对象实例的方法。仔细想想,Java 的工作方式是符合逻辑的,也是很酷的。
扩展 Java 对象结构:Java 继承
Java 还支持开发不同类型的增强类(以及相应的对象)。这是通过使用一种叫做继承的面向对象技术来实现的。继承是指可以使用原始超类对更专门化的类(更独特定义的对象)进行子类化;在这种情况下,应该是汽车。继承过程如图 5-4 所示。一旦一个类通过“子类化”被用于继承,它就成为超类。最终,在链的最顶端只能有一个超类,但是可以有无限数量的子类。所有子类都从超类继承方法和字段。Java 中这方面的终极例子是 java.lang.Object 超类(我有时称之为 master 类),它用于创建 Java 9 中的所有其他类。
图 5-4。
The inheritance of a Car object superclass will allow you to create an SUV Car object and a Sport Car object
作为使用 Car 类继承的一个例子,您可以从 Car 类中“继承”Suv 类,使用 Car 类作为超类。这是使用 Java extends 关键字来创建 Suv 类定义的,该关键字扩展了 Car 类定义。除了扩展适用于所有类型汽车对象的所有属性(常数)、状态(变量)和行为(方法)之外,这个 SUV 类将只定义那些适用于 Suv 类型汽车对象的附加属性(常数)、状态(变量)和行为(方法)。这是 Java extends 关键字为这个子类化(继承)操作提供的功能,这是 Java 9 OOP 语言中代码模块化的一个更重要和有用的特性。您可以在图 5-4 中直观地看到这种模块化,每个子类的附加汽车功能以橙色添加。这是组织代码的好方法!
Suv 汽车对象子类可能有额外的。onStarCall()和。定义的 turnTowLightOn()方法,除了继承通常的汽车对象操作方法之外,还允许汽车对象换挡、加速、踩刹车、转动方向盘。
类似地,您也可以生成第二个子类,称为 Sport 类,它创建跑车对象。这些可能包括。activateOverdrive()方法来提供更快的齿轮传动。openTop()方法放下活顶。要使用超类创建子类,可以通过在类声明中使用 Java extends 关键字从超类扩展子类。因此,Java 类结构看起来就像这样:
class Suv extends Car {
void applyBrake (int brakingFactor) {
super.applyBrake(brakingFactor);
speed = speed - brakingFactor;
}
}
这扩展了 Suv 对象,使其能够访问(本质上是包含)Car 对象所具有的所有数据字段和方法。这使得开发人员只需关注新的或不同的数据字段和方法,这些数据字段和方法与 Suv 对象和常规或“主”汽车对象定义的区别相关。
要从您正在编码的子类中引用超类的方法之一,您可以使用 Java super 关键字。例如,在新的 Suv 类中,您可能想要使用 Car 超类。applyBrake()方法,然后将一些特定于 Suv 的附加功能应用于制动器。你称汽车为物体的。通过在 Java 代码中使用 super.applyBrake()来调用 applyBrake()方法。前面显示的 Java 代码将为 Car 对象的。applyBrake()方法,在 Suv 对象内部。applyBrake()方法,方法是使用这个 super 关键字来访问汽车对象的。applyBrake()方法,然后添加附加逻辑,使 brakingFactor 应用两次。这为 SUV 对象提供了两倍于标准汽车的制动力,这是 Suv 所需要的。
这个 Java 代码使 Suv 的制动力加倍的原因是因为 SUV 对象的。applyBrake()方法首先调用汽车对象的。使用 Suv 子类中的一行 Java 代码从 Car 超类中调用 applyBrake()方法。applyBrake()方法。接下来的 Java 代码行通过再次应用 brakingFactor 来增加速度变量,使您的 SUV 对象的刹车功能加倍。
Java 接口:定义类使用模式
在许多 Java 应用中,Java 类必须符合特定的使用模式。有一种专门的 Java 构造,称为接口,可以实现该接口,以便应用开发人员确切地知道如何实现这些 Java 类,包括提醒开发人员正确实现类需要方法。定义一个接口将允许您的类通知使用该类的其他开发人员,为了正确利用您的 Java 类的基础结构,您的类的行为(哪些 Java 方法)必须被实现。
接口本质上规定了类和开发社区的其余部分之间的编程契约。通过实现 Java 接口,Java 编译器可以在构建时强制执行契约。如果一个类“声明”要实现一个公共接口,那么在该类成功编译之前,由该 Java 接口定义“定义”的所有方法都必须出现在实现该接口的类的源代码中。
当在复杂的、基于 Java 的编程框架(如 Android uses)中工作时,接口尤其有用,开发人员利用这些框架在 Java 类上构建应用,Google Android OS 开发团队成员专门为此编写了 Java 类。Java 接口应该像路线图一样使用,向开发人员展示如何最好地实现和利用由另一个 Java 编程结构中的 Java 类提供的 Java 代码结构。
基本上,Java 接口保证给定类中的所有方法将作为一个互通的、相互依赖的集体编程结构一起实现,保证实现该功能集体所需的任何单个函数不会被无意中遗漏。一个类“呈现”给使用 Java 语言的其他开发人员的这个公共接口使得使用该类更加可预测,并允许开发人员在编程结构和目标中安全地使用该类,其中特定最终使用模式的类适合于他们的实现。从 Java 9 开始,你也可以定义私有接口在你的应用内部使用。
下面是一个 ICar 接口,它强制所有的汽车实现这个接口中定义的所有方法。这些方法必须实现并存在,即使它们没有被使用,也就是说,花括号内没有代码。这也保证了 Java 应用的其余部分知道每个 Car 对象可以执行所有这些行为,因为实现 ICar 接口为所有 Car 对象定义了一个公共接口。对于当前 Car 类中的那些方法,实现 ICar 公共接口的方式如下:
public interface ICar {
void shiftGears (int newGear);
void accelerateSpeed (int acceleration);
void applyBrake (int brakingFactor);
void turnWheel (String newDirection);
}
要实现一个接口,您需要使用 Java implements 关键字,如下所示,然后像以前一样定义所有的方法,只是现在除了 void 返回数据类型之外,还必须使用公共访问控制修饰符来声明这些方法。因此,您将在 void 关键字之前添加 public 关键字,这将允许其他 Java 类能够调用这些方法,即使这些类在不同的包中。毕竟这是一个公共接口,任何开发者(或者更准确地说,任何类)都应该能够访问它。下面是您的 Car 类应该如何通过使用 Java implements 关键字来实现这个 ICar 接口:
class Car implements ICar {
String name = "Generic";
int speed = 15;
int gear = 1;
int drivetrain = 4;
String direction = "N";
String color = "Red";
String fuel = "Gas ";
public void shiftGears (int newGear) {
gear = newGear;
}
public void accelerateSpeed (int acceleration) {
speed = speed + acceleration;
}
public void applyBrake (int brakingFactor) {
speed = speed - brakingFactor;
}
public void turnWheel (String newDirection) {
direction = newDirection;
}
}
Java 接口不能使用任何其他 Java 访问控制修饰符关键字,因此它不能声明为 private(在 Java 9 之前)或 protected。需要注意的是,只有那些在接口定义中声明的方法才需要实现。我在类定义顶部的数据字段是可选的。在这个例子中,这些是为了显示它与 Car 类是并行的,我之前没有使用接口就声明了 Car 类。除了使用 implements 关键字之外,没有太大的区别,只是实现一个接口告诉 Java 编译器检查并确保开发人员包含了使 Car 类正常工作的所有必要方法。
Java 9 的新特性:模块化和拼图项目
您可能想知道为什么我最后才讨论 Java 9 和它的新模块,这有几个原因,在我们讨论 Java 9 的新特性之前,我将首先解释一下。Java 9 中的任何新特性都不会影响您的游戏代码,这很好,因为您可以在 Java 6、Java 7 和 Java 8 中编写相同的基本 Java 游戏代码;这意味着你的游戏可以在还没有使用 Java 9 的地方运行,而且可能在一段时间内都不会。由于 32 位 Android 使用 Java 6,64 位 Android 使用 Java 7 (Android 5 和 6)和 Java 8 (Android 7、8 和更高版本),这意味着您可以用 Java 编写跨越十年平台的游戏逻辑。由于 Java 9 比原计划于 2015 年第四季度发布的版本晚了几年,我不得不使用 Java 8 为这本书开发代码,这本书与 Java 9 同时发布。幸运的是,Jigsaw 项目(Java 9 的主要特性)影响了编程语言的模块化,而不是模块内部的代码,这与 Java 8 和 JavaFX 8 是一样的。因此,出于本书的目的,即编写专业 Java 游戏逻辑,Java 8 和 Java 9 之间没有重大变化。一个游戏是否使用 Java 9 特性模块化并不影响性能(游戏性),只影响它的分布,所以我在本章的最后讨论这个模块化特性,因为它是关于游戏性能最不重要的 Java 方面。
我确实想在 Java 中包含这些模块,因为从 Java 9 开始,模块现在是一个核心特性,尽管它们只影响专业 Java 游戏的打包,而不影响游戏实际上是如何编码和优化内存和处理器使用的。
Java 9 模块的定义:包的集合
Java 9 的定义特性是 JEP 200(模块化 JDK),它代表 JDK 增强提案 200。这是 JEP 201(模块化源代码)、JEP 220(模块化运行时)、JEP 260(封装 API)和 JEP 261(模块系统)之上的“伞式”JEP,它们封装了实现模块化 JDK (JEP 200)需要完成的内容。
目前,Java 8 和 JavaFX 8.0 就像是两种不同的编程语言合二为一。因此,Java 9 JDK 首先要模块化的是 JavaFX 8(现在改名为 JavaFX 9),因为这是你要用来创建游戏的,我们将在这一节详细讨论。如果您是企业(业务应用)Java 9 开发人员,这个 Java 9 模块系统将允许您排除所有“繁重的”JavaFX API 库、包、类等等。然而,这把刀也有反作用,所以如果你只打算开发一个 i2D 或 i3D Java 游戏,你只需要声明并包含 javafx.graphics 模块,你的游戏发行包(模块)不需要包含游戏不需要的过多的其他 Java APIs,因为它只关注图形和事件处理(屏幕上的多媒体视觉效果以及它们如何与玩家交互)。
Java 模块包含 Java 包的集合,因此 Java 模块在当前存在的 Java 包-类-方法-变量层次结构之上添加了另一个层次结构级别。一个 Java 包只能属于一个模块,并且不能在模块之间拆分 Java 包。这使得包的组织对于您自己的游戏和 JavaFX 来说更加重要,Java FX 在 Java 9 中已经被组织成包和模块。我们将在本章的后半部分学习这一点。
Java 包允许您按功能组织,而模块允许您按特性组织。这允许数据(和代码)足迹优化。例如,我们不会在我们的 Pro Java 9 游戏中使用 JavaFX Swing、标准 UI 控件、FXML 或 WebKit 因此,我们不需要在发行版中包含这些代码模块。
Java 模块的属性:显式、自动或未命名
创建 Java 模块有三种方式:显式、隐式和匿名。开发人员通过指定一个module-info.java
文件来有意创建一个显式模块,该文件定义了显式模块中的其他模块和包。显式模块定义了需求:输入(所需的 API 包)和导出:输出(已发布的包)。导出的包对 Java 环境是可见的,因此可以被执行。仅要求(输入或读取包)模块定义文件中指定的包可以被该模块访问(利用)。在本章的这一节,我将在稍后向你展示这个定义的格式。
如果开发人员没有提供一个module-info.java
文件,并且如果它在一个不包含module-info.java
定义文件的模块路径上找到一个 JAR 文件,那么 Java 9 环境也可以创建一个隐式模块,也称为自动模块,因此 Java 9 环境会自动为 JAR 文件的内容创建一个隐式模块。在这种情况下,它将自动导出所有需要的包,要求(输入或读取)所有需要的模块,并包括任何未命名的模块,我们将在接下来讨论。
最后,Java 9 环境还可以通过在类路径上添加不在 JAR 文件中并且没有开发人员提供的module-info.java
文件的类来创建一个未命名的模块。这使得 Java 9 环境可以容纳较旧的 Java 6 到 8 项目,方法是将它们制作成未命名的模块,这样它们仍然可以在 Java 9 环境中运行,即使它们是在还不存在module-info.java
的 Java 早期版本中创建的。显式模块不能要求未命名的模块,这意味着旧版本 Java 软件的开发者必须创建一个module-info.java
定义文件来将他们的软件带入 Java 9 模块化领域。
如果应用的主类在未命名的模块中,那么所有的默认模块都将被加载以确保 Java 9 应用能够运行,而模块化的好处(减少发行版的数据占用)将会丧失。如果你用运行你的 Pro Java 9 游戏所需的最少的包定义了一个module-info.java
文件,那么 javapackager 实用程序将能够为你生成一个只包含所需模块的捆绑应用。
Java 9 模块层次结构的一个例子:JavaFX 模块
由于 JavaFX 是 Oracle 模块化的第一个 API,也是我们将需要用来创建 Pro Java 9 游戏的原生 Java 多媒体 API,因此使用它作为模块如何工作的示例是有意义的,这同时将向我们展示 JavaFX 是如何模块化的,以及我们可能需要为我们自己的 Java 9 模块定义文件“要求”(输入)哪些 JavaFX 模块。使用 Java 9 模块,JavaFX 可以直接链接到 JDK 的“映像”中,不需要引用外部 JFXRT。JAR 文件。JFXSWT 等第三方 jar。JAR 将成为自动模块。这个 JFXSWT。JAR 将被重命名为 JAVAFX-SWT。JAR for Java 9,这样当自动模块使用 JAR 文件派生其名称时,它将变成 JAVAFX.SWT。
Java 9 运行时环境(JRE)包含七个 JavaFX 模块,您可以根据需要在自己的游戏模块中“要求”这些模块。您需要的这些资源越少,游戏的数据和内存占用就越优化,就有越多的系统资源可以满足您的游戏处理需求。
表 5-6 向您展示了新的 JavaFX 模块层次结构,哪些模块(基本和图形)是任何 JavaFX 使用所必需的,以及哪些非基本和非图形 JavaFX 模块需要其他 JavaFX 模块。
表 5-6。
Seven Core JavaFX Modules Contained in the Java 9 Runtime Environment for Use in New Media Applications
模块名 | 必需的? | 用于 | 必需的 JavaFX 8 模块 |
---|---|---|---|
javafx.base | 是 | 事件、实用程序、Beans、集合 | 无(这是一个基础 JavaFX 库) |
javafx.graphics | 是 | 舞台、图像、几何图形、动画 | javafx.base |
javafx.controls | 不 | 用户界面控制模块 | javafx.graphics (require 也将导入 base) |
javafx.media | 是 | 音频/视频媒体播放器模块 | javafx.graphics |
javafx.fxml | 不 | JavaFX 标记语言模块 | javafx.graphics |
javafx.swing | 不 | Java Swing 兼容性模块 | javafx.graphics |
javafx.web | 不 | WebKit 支持模块 | javafx.controls,javafx.media,javafx.graphics |
例如,Java FX . web(WebKit API web engine)将需要 javafx.controls(用于音频和视频传输条用户界面元素的 UI 元素)、javafx.media(音频或视频回放编解码器支持)和 javafx.graphics(应用、舞台、场景、几何图形、图像、形状、画布、效果、文本和动画支持)。
由于模块化应用需要在那个module-info.java
文件中列出依赖关系,让我们看看如何在 JavaFX 中查找使用 WebKit 支持 API 的应用。下面是module-info.java
的语法:
module myWebKitApp.app { requires javafx.web; }
您可能想知道为什么在module-info.java
文件的module myWebKitApp.app { … } module use
声明中没有明确要求 javafx.web 模块要求的那些其他模块,人们可能会怀疑这些模块看起来应该更像这样:
module myWebKitApp.app {
requires javafx.base;
requires javafx.graphics;
requires javafx.controls;
requires javafx.media;
requires javafx.web;
}
这是因为链下游模块所需的模块是作为语法的一部分自动导入的。因此,如果我们创建一个不使用“固定”UI 元素(在 JavaFX 中称为控件)或 WebKit、FXML 或 Java Swing 的 JavaFX 游戏,我们可以只使用基本、图形和媒体模块。
因为 javafx.graphics 模块需要 javafx.base,而 javafx.media 模块需要 javafx.graphics,所以您只需在一行代码中编写整个模块声明 Java 文件,如下所示:
module ProJava9GamesDevelopment.app { requires javafx.media; }
在这三个 JavaFX 模块(base、graphics 和 media)之间,您拥有 Java 9 中游戏所需的一切,只要您制作自己的用户界面元素控制图形,这是游戏开发中的标准做法。在本书的课程中,我们将会看到这些核心模块中包含了哪些包、类和方法。当我介绍一个给定的包和类时,我会让你知道它是哪个模块的一部分。
Java 9 模块的目的:安全、强大的封装
多年来最大的抱怨之一是 Java 不像其他平台那样安全,这阻止了它作为数字内容分发渠道的广泛使用。喜欢所有的分发格式(Kindle,Android,HTML5 等。),安全的 DRM 也是需要的,并且在明年即将推出,但是 API 本身的内部安全性也是一个需要特别在 Java 中解决的问题,正如您将在本节中看到的,Java 9 Modules 强封装规则将通过阻止对 Java 内部 API 以及私有封装的访问来实现这一点。
Java 9 之前的应用必须模块化,以使用前面讨论的module-info.java
定义文件“锁定它们”。只有显式导出的包才可见;用于创建应用的内部 API 将不再可见(可访问)。试图访问未显式导出的包中的任何类型都将引发错误。高级程序员将无法使用反射调用. setAccessible()方法来强制访问。对于测试,目前有一个命令行开关,允许访问非导出包。这将被称为- add-exports,应该仅在绝对必要时使用。
导出的包中的安全性也增加了,因为现在只有使用公共访问控制修饰符声明的类型才是可访问的。试图访问未使用公共访问控制声明的显式导出包中的任何类型都会引发错误。程序员将无法使用反射调用. setAccessible()方法来强制访问。为了测试,目前有一个命令行开关允许访问非公共类型。正如您可能已经猜到的,这将被称为- add-exports-private,应该只在绝对必要时使用。
强封装允许您有选择地只支持或公开 API 的部分(模块)。在这种情况下,我们使用 JavaFX API,我将在本书中向您展示如何仅使用两个核心 JavaFX 包(基础和图形)和 MediaPlayer(媒体)包,以及高度优化的新媒体资源来创建一个健壮的 i3D 游戏。我们将不需要包括“沉重的”WebKit (web)、Swing (swing)、FXML (fxml)或 UI (controls)包,这些包将减少分发文件数据占用空间,并增加这个 Pro Java 9 游戏的安全性。您访问(要求)的每个模块将列出其公开导出的包,并且您的公共类和方法将是 API 的一部分。
创建 Pro Java 9 游戏模块:使用 Exports 关键字
让我们继续看看如何设置我们将使用 JavaFX API 创建的 Java 9 游戏,方法是看我们如何使用 exports 关键字将您的 BoardGame 包添加到我们将用来创建游戏的 Java(Java FX)API 的其余部分。JavaFX launcher 将构造您的应用子类的一个实例,它将被称为 BoardGame 或类似的东西。你可以在图 5-4 中看到,这是在 Java 8 游戏开发初期创建的 InvinciBagel i2D 游戏。您可以使用 exports 关键字和包名称以及 to 关键字和模块名称,将包含此应用子类的包导出到 javafx.graphics 模块(包含将构成此游戏的大多数包和类)。下面是 Java 代码:
module BoardGame.app {
requires javafx.media;
exports boardgame.pkg to javafx.graphics;
}
需要注意的是,如果您使用 FXML,它允许非程序员设计 UI 布局,这将需要 javafx.fxml 模块,该模块需要能够访问您的私有变量和方法。这将需要在您的module-info.java
声明文件中包含 exports private 关键字字符串,如下所示:
module BeginnerBoardGame.app {
requires javafx.media;
requires javafx.fxml;
exports private beginnerboardgame.pkg to javafx.fxml;
}
我们在这本专业的 Java 9 游戏开发书中没有使用 FXML,因为我们将使用 Java 和外部新媒体内容开发应用做所有事情,就像专业的 Java 9 游戏开发人员一样。这将使您的应用更加安全和紧凑,因为使用 FXML 的库在大小和范围上都是巨大的;使用 CSS、HTML5 和 JavaScript 的库(javafx.web)以及使用通用用户界面控件或小部件集合的库(javafx.swing 和 javafx.controls)也是如此。
资源封装:进一步的模块安全措施
Java 9 模块性不仅包括 Java 代码,还包括应用资源,在 Java 9 游戏中,包括音频、视频、图像、矢量(SVG)和动画资源。您可能知道,通过这些文件格式可以像通过文本文件格式一样容易地破坏安全性。阻止 Java 在 web、电子邮件、应用、电子书或 iTV 设备中起飞的唯一问题是安全性问题,看起来 Oracle 决心很快解决这个问题。Java 的另一个问题是一个巨大的 Java 运行时的部署和这个运行时的许多不同版本。新的模块特性将允许开发者优化他们的发行版,只包含需要的 Java 组件。
使用java.lang.Class
类封装资源,并且只能使用Class.getResource()
方法检索资源。从 Java 9 开始,不像以前的 Java 版本那样有ClassLoader.getResource()
方法调用访问。
您也不能再使用 URL 来访问类中的资源,因此/path/name/voiceover.mp3 将不再有效。这再次使 Java 9 发行版更加安全。其他一些模块仍然允许 URL 访问,比如 javafx.web 和 Java FX . media;然而,我们将使用捕获的(即 JAR 内部的)媒体素材,并且我们不打算通过不需要 javafx.web (WebKit API)模块来将我们的游戏向互联网开放。包含资源的包必须是可访问的(导出的),以便资源“可见”并且不隐藏在公众视野之外。这是通过您的exports boardgame.pkg to javafx.graphics;
行代码完成的。由于 javafx.media 也依赖于 javafx.graphics,exports boardgame.pkg to javafxmedia;
也应该可以工作,因为你的模块需求链是从 Java FX . media➤Java FX . graphics➤Java FX . base 开始的,如表 5-6 所示。
如果您想将您的新媒体和设计素材外部化(对于我的 Android 和 Java 9 开发,我从来不这样做),有几个 JavaFX APIs 仍然接受 URL 对象或使用字符串 URL 值指定的 URL。其中包括 CSS(Java FX . web 中的级联样式表);FXML(JavaFX . FXML 中的 Java FX 标记语言);图像、音频和视频素材(javafx.media 和 Java FX . graphics);以及 HTML 和 JavaScript(WebKit WebEngine Java FX . web)。
摘要
在第五章中,我们回顾了 Java 编程语言中一些更重要的概念和结构。当然,我不可能在一章中涵盖 Java 的所有内容,所以我坚持使用本书中你将用来创建游戏的关键概念、构造和关键字。大多数 Java 书籍都有 1000 页或更多,所以如果你想真正深入纯 Java,我推荐 Apress 的 Pro Java 编程书籍。当然,随着本书的深入,我们将学习更多关于 Java 的知识,以及 JavaFX 9.0 引擎的类。
我们首先通过查看 Java 的语法(包括 Java 注释和分隔符)来了解 Java 的高级视图,然后我们了解了什么是应用编程接口(API)。我们还学习了 Java API 包含的 Java 包。
接下来,我们讨论了 Java 类,包括嵌套类和内部类,因为这些 Java 包包含 Java 类。我们了解到 Java 类有一个构造函数方法,可以用来实例化类中的对象。
Java 的下一层是方法,就像你在其他编程语言中熟悉的函数一样,我们看到了一种必需的 Java 方法,叫做构造函数方法。
接下来,我们看了 Java 如何使用字段或数据字段来表示数据,我们还看了不同类型的数据字段,如常量(固定数据字段)和变量(或可以改变值的数据字段)。
之后,我们仔细查看了 Java 访问控制修饰符关键字,包括 public、private 和 protected 访问控制关键字,然后我们查看了 nonaccess 修饰符关键字,包括 final、static、abstract、volatile 和 synchronized nonaccess control 修饰符关键字。
在我们介绍完基本的代码结构以及如何修改它们来完成我们想要它们做的事情之后,我们看了主要的 Java 数据类型,比如 boolean、char、byte、int、float、short、long 和 double,然后我们看了用于处理这些数据类型或将这些数据类型“桥接”到我们的编程逻辑的 Java 操作符。我们研究了用于数值的算术运算符、用于布尔值的逻辑运算符、用于数据值之间关系的关系运算符、允许我们建立任何条件变量赋值的条件运算符,以及允许我们为变量赋值(或在变量之间赋值)的赋值运算符。
接下来,我们看了 Java 逻辑控制结构,包括决策(我喜欢称之为决策树)控制结构和循环,或者迭代逻辑控制结构。我们学习了 Java switch-case 结构、if-else 结构、for 循环结构和 do-while 循环结构。
接下来,我们看了 Java 对象,学习了如何使用 Java 类、方法和构造器方法定义对象属性、状态和行为,我们看了 Java OOP 语言的继承和公共 Java 接口概念,学习了如何使用 Java 代码实现这些概念。
最后,我们看了 Java 9 中添加的 Java 模块,并通过 JavaFX API(第一个模块化的主要 Java API)的例子学习了如何定义模块层次结构和模块类型。我们了解了 Java 9 模块的优点,以及这是重新设计 Java 语言以获得公众对该语言的期望和我们一直期待的安全级别的一个重要步骤。
在下一章中,我们将在本书的剩余部分探讨 NetBeans 9 集成开发环境(IDE)以及如何为您的游戏开发创建基础(项目和核心 API)。