diff --git a/SUMMARY.md b/SUMMARY.md index 8e8f2ede..509f086e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -83,12 +83,15 @@ * [13.2 注意事项](chapter-13/note.md) * [14. 用户交互界面(GUI)](chapter-14/index.md) * [14.1 Widget](chapter-14/widget/index.md) - * [14.1.1 文本输入](chapter-14/widget/text-field.md) - * [14.1.2 按钮](chapter-14/widget/button.md) + * [14.1.1 按钮](chapter-14/widget/button.md) + * [14.1.2 标签](chapter-14/widget/label.md) * [14.1.3 列表](chapter-14/widget/list.md) - * [14.2 游戏主界面与 HUD](chapter-14/main-screem/index.md) - * [14.3 Toast](chapter-14/toast/index.md) + * [14.1.4 滑块](chapter-14/widget/slider.md) + * [14.1.5 文本输入](chapter-14/widget/text-field.md) + * [14.2 游戏主界面与 HUD](chapter-14/in-game/index.md) + * [14.3 Toast](chapter-14/toast.md) * [14.4 `Container` 与数据同步](chapter-14/container.md) + * [14.5 `FontRenderer` 与文本渲染](chapter-14/font-render.md) * [15. 音效控制](chapter-15/index.md) * [16. 粒子效果](chapter-16/index.md) * [17. 资源包](chapter-17/index.md) diff --git a/chapter-14/container.md b/chapter-14/container.md new file mode 100644 index 00000000..5ff03b42 --- /dev/null +++ b/chapter-14/container.md @@ -0,0 +1,108 @@ +# `Container` 与数据同步 + +默认,GUI 是纯客户端的概念。这意味着,当一名玩家打开了一个 GUI 时,服务器对此是一无所知的。 +但 Minecraft 中有不少 GUI 是会显示玩家背包中的物品的。玩家对背包里的物品进行操作时,操作的结果是需要反馈回服务器的。 +网络通信不可避免。Minecraft 给出的解决方案是 `Container`——一套专门用来简化这种需要服务器与客户端之间保持通信的 GUI 的编写机制。 +如果你在写某个方块的 GUI,你几乎不可能避免使用 `Container`——因为你十有八九需要显示玩家背包内物品。 + +## `IGuiHandler` + +在正式介绍 `Container` 之前有必要先介绍一下 `IGuiHandler`。 +这是 FML 提供的一套历史悠久的简易机制,通过它我们就可以通过一个 `openGui` 的调用来简单让客户端正确打开 GUI 并建立与服务器之间对应的 `Container` 的连接,而不用头疼幕后的数据包等乱七八糟的东西。 + +```java +import javax.annotation.Nullable; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.network.IGuiHandler; + +public enum MyGUIHandler implements IGuiHandler { + + INSTANCE; + + private MyGUIHandler() { + // 注册。推荐的调用时机是 FMLPreInitializationEvent 或者 FMLInitializationEvent。 + // 这里我们写在构造器里好了。 + NetworkRegistry.INSTANCE.registerGuiHandler(ExampleMod.instance, this); + } + + @Nullable //感谢 mezz 的 MinecraftForge/MinecraftForge#3550 + @Override + public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { + // 返回的对象必须是一个 Container。 + // 见 NetworkRegistry.getRemoteGuiContainer 中的强制转型。 + return null; + } + + @Nullable //感谢 mezz 的 MinecraftForge/MinecraftForge#3550 + @Override + public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { + // 返回的对象必须是一个 GuiScreen。 + // 见 FMLClientHandler.showGuiScreen 中的强制转型。 + return null; + } +} +``` + +### 简单的使用 + +```java +EntityPlayer player = ...; +World world = ...; +if (!world.isRemote) { + // 第二个参数即是 IGuiHandler 两个方法的第一个 int 参数。 + // 最后的 posX、posY 和 posZ 则是 IGuiHandler 两个方法中的最后三个参数。 + player.openGui(ExampleMod.instance, 0, world, posX, posY, posZ); +} +``` + +### 四个 int? + +第一个 int 参数理论上是指你的 Mod 内部的 GUI 编号,剩下三个 int 参数理论上是指 `TileEntity` 在指定维度(`World`)里的坐标。这样设计的理由虽然不得而知,但很明显它简化了带 GUI 的 `TileEntity` 的编写难度…… + +但理想总是与现实有差距。根据[某个 Forge 的 Issue Ticket][ref-1],最后的三个 `x`、`y`、`z` 不一定非得是坐标——它们可以拿来代表任意数据。换言之,你可以根据总长度 128 bit 的信息来确定应该打开哪个 GUI…… + +[ref-1]: https://github.com/MinecraftForge/MinecraftForge/issues/3228 + +## `GuiContainer` 与 `Container` + +就 Container 和 GuiContainer 之间的关系,ustc-zzzz 有一张图(FMLTutor § 3.4.1)很好地对其进行了解释: + +![Container 和 GuiContainer 的关系](https://fmltutor.ustc-zzzz.net/resources/gui_analysis.png) + +虽然你完全可以直接看 zzzz 的教程,但为方便起见,我们在这里重新复述一遍: + + * 序号 1 是指客户端的操作发包至服务器。 + * 序号 2 是指客户端对 `Slot` 的操作由客户端侧的 `Container` 实例代理。 + * 序号 3 是指 `Container` 操作它控制的 `Slot`…… + * 序号 4 是指 `Slot` 的操作结果返回给 `Container`。 + * 序号 5 是指服务器端的业务发信(乱七八糟诸如物品和“进度条”这种)给客户端。 + * 序号 6 是指客户端的操作都会通知服务器端。 + * 虚线是 Mojang 用它的黑魔法帮你搞定了。 + * 实线是 Mojang 表示这个你要自己来。 + + + +### 一个空架子 + +我们拿第九章里的 `LavaFurnace` 来做例子: + +```java +public class LavaFurnaceContainer extends Container { + @Override + public boolean canPlayerInteractWith(EntityPlayer player) { + // 决定了指定玩家是否能与这个 Container 交互。 + // 返回 false 会阻止玩家正常使用 GUI。 + return true; + } +} + +public class LavaFurnaceGui extends GuiContainer { +} +``` + +### 显示玩家背包 + +### 显示机器内物品 + +### 进度条 diff --git a/chapter-14/font-render.md b/chapter-14/font-render.md new file mode 100644 index 00000000..c17c55eb --- /dev/null +++ b/chapter-14/font-render.md @@ -0,0 +1,3 @@ +# `FontRenderer` + +WIP diff --git a/chapter-14/in-game/index.md b/chapter-14/in-game/index.md new file mode 100644 index 00000000..533a3a9e --- /dev/null +++ b/chapter-14/in-game/index.md @@ -0,0 +1,3 @@ +# `GuiIngame`:游戏主界面 + +WIP diff --git a/chapter-14/index.md b/chapter-14/index.md index a77b7270..425bb623 100644 --- a/chapter-14/index.md +++ b/chapter-14/index.md @@ -1,100 +1,23 @@ -# 图形化用户交互界面 +# GUI 绪论 -````java -public class MyContainer extends Container { -} +图形化用户交互界面(Graphic User Interface,下文统一简称 GUI),顾名思义,是指以图像形式呈现的用户交互界面。 -public class MyGui extends GuiContainer { -} -```` +说穿了就是把一堆贴图以一种友好的方式贴在一起,然后根据用户的输入作出反馈…… -这就是 Minecraft 自己使用的 GUI 系统的基础:一个客户端显示的 `GuiContainer`(准确地说,应该是 `GuiScreen`),以及可选的 `Container`,用于逻辑服务器。 +## `Gui` 与 `GuiScreen` -## 第一步:为什么要有 Container? -当且仅当你需要和服务器打交道时。 +自然的,Minecraft 也有一套 GUI 的轮子。 +这套轮子是基于 LWJGL 从零写出来的,专门用于 Minecraft 游戏内的所有你看得到的界面。从启动完成后出现的主菜单到进入存档后的游戏主界面,这些界面都基于 Minecraft 自己造的 GUI 框架。 -比方说一个工作台。你可以用这个 GUI 合成东西!所以必须有服务器端的业务逻辑,不然你合成的物品全是客户端特效你骗谁呢你。 +几乎所有和 GUI 相关的类都继承自一个叫 `Gui` 的不明所以的类。之所以说“不明所以”,是因为这个类本身只有这样几个方法: -就 Container 和 GuiContainer 之间的关系,ustc-zzzz 有一张图很好的对其进行了解释: + - 画线段 + - 画矩形(需首先绑定纹理) + - 画字符串 + - …… -![Container 和 GuiContainer 的关系](https://fmltutor.ustc-zzzz.net/resources/gui_analysis.png) +然后才是我们需要打交道的一堆类,这些类大致可分为三种: -(屏幕前的读者你要是直接看 Markdown 源文件的话你就会发现这图直接来自他教程… 准确地说是3.4.1的第一张插图。) -虽然你完全可以直接看 zzzz 的教程,但我还是再用自己的理解复述一遍吧: - -* 序号 1 是指客户端的操作发包至服务器。 -* 序号 2 是指客户端对 Slot 的操作由客户端侧的 Container 实例代理。 -* 序号 3 是指 Container 操作它控制的 Slot…… -* 序号 4 是指 Slot 的操作结果返回给 Container。 -* 序号 5 是指服务器端的业务发信(乱七八糟诸如物品和“进度条”这种)给客户端。 -* 序号 6 是指客户端的操作都会通知服务器端。 -* 虚线是 Mojang 用它的黑魔法帮你搞定了。 -* 实线是 Mojang 表示这个你要自己来。 - -````java -public class MyContainer extends Container { - // 此方法必须覆写,因为父类里这是个抽象方法。 - @Override - public boolean canInteractWith(EntityPlayer player) { - return true; - // 返回 false 的时候会给你关掉 GUI。 - } -} -```` - -## 且慢!我不需要和服务器打交道啊! -那就直奔 `GuiContainer` 好了。想想看,一本游戏内置的手册多数时候不需要服务器端有什么操作吧…… - -## 组成GUI的元件 (Components) -说是这么说,其实能称得上 Components 的东西真的不多。 -### 背景 -### 文本框 -`GuiTextField` -### 按钮 -`GuiButton` -### 滚动菜单 -### 滑块 -### 勾选框是啥来着? -你可以使用按钮来模拟勾选框。 - -## 那如果我的 GUI 里还要和服务器打交道呢? - -你需要一个 `Container`。不需要的话也许也可以,但是很多事情就需要重新从零开始写。 - -## 等等!我怎么打开GUI? -````java -import javax.annotation.Nullable; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.world.World; -import net.minecraftforge.fml.common.network.IGuiHandler; - -public enum MyGUIHandler implements IGuiHandler { - INSTANCE; - - private MyGUIHandler() { - NetworkRegistry.INSTANCE.registerGuiHandler(ExampleMod.instance, this); - } - - @Nullable //感谢 mezz 的 MinecraftForge/MinecraftForge#3550 - @Override - public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { - return null; - } - @Nullable //感谢 mezz 的 MinecraftForge/MinecraftForge#3550 - @Override - public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { - return null; - } -} -```` - -以及发挥点想象力。并非只有ID可以存储数据,最后的三个 `x`、`y`、`z` 并非总是坐标——它们也是可以拿来存储数据的。换言之,你在返回对应的 GUI 对象时最多可以用到 4 个整数的信息(总长度128 bit)来确定应该打开哪个 GUI…… [参考MinecraftForge/MinecraftForge#3228](https://github.com/MinecraftForge/MinecraftForge/issues/3228)。 -最后只需要在有 EntityPlayer 的地方这么做: - -````java -//playerIn是个EntityPlayer -//5不是arbitary number,是上面IGuiHandler中的id,具体含义由实现决定 -//worldIn是个World -//最后的三个xyz没有强制要求是坐标,可用于传入别的数据 -playerIn.openGui(ExampleMod.instance, 5, worldIn, pos.getX(), pos.getY(), pos.getZ()); -```` + 1. Widget,例如 `GuiButton`、`GuiTextField`。Widget 是下面两者的基础“零件”。 + 2. `GuiScreen`,几乎所有的界面都基于它,除了下面这一个。 + 3. `GuiIngame`(以及 Forge 替代它使用的 `GuiIngameForge`),这个是进入存档后游戏的主界面。 diff --git a/chapter-14/toast.md b/chapter-14/toast.md new file mode 100644 index 00000000..b5052d54 --- /dev/null +++ b/chapter-14/toast.md @@ -0,0 +1,3 @@ +# Toast + +WIP diff --git a/chapter-14/widget/button.md b/chapter-14/widget/button.md new file mode 100644 index 00000000..16adacc9 --- /dev/null +++ b/chapter-14/widget/button.md @@ -0,0 +1,3 @@ +# GUI Widget:按钮 + +WIP diff --git a/chapter-14/widget/index.md b/chapter-14/widget/index.md new file mode 100644 index 00000000..3995fcfa --- /dev/null +++ b/chapter-14/widget/index.md @@ -0,0 +1,9 @@ +# Widget + +Minecraft 的 GUI 系统的确存在 Widget 的概念,只是并不非常明显。这些 Widget 包括: + + - [按钮](button.md) + - [标签](label.md) + - [列表](list.md) + - [滑块](slider.md) + - [文本框](text-field.md) diff --git a/chapter-14/widget/label.md b/chapter-14/widget/label.md new file mode 100644 index 00000000..a384c48e --- /dev/null +++ b/chapter-14/widget/label.md @@ -0,0 +1,3 @@ +# GUI Widget:标签 + +WIP diff --git a/chapter-14/widget/list.md b/chapter-14/widget/list.md new file mode 100644 index 00000000..eb8a63af --- /dev/null +++ b/chapter-14/widget/list.md @@ -0,0 +1,3 @@ +# GUI Widget:列表 + +WIP diff --git a/chapter-14/widget/slider.md b/chapter-14/widget/slider.md new file mode 100644 index 00000000..34bd904e --- /dev/null +++ b/chapter-14/widget/slider.md @@ -0,0 +1,3 @@ +# GUI Widget:滑块 + +WIP diff --git a/chapter-14/widget/text-field.md b/chapter-14/widget/text-field.md new file mode 100644 index 00000000..6f74a407 --- /dev/null +++ b/chapter-14/widget/text-field.md @@ -0,0 +1,3 @@ +# GUI Widget:文本框 + +WIP