-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ch.14): rewrite Ch. 14 #59
Draft
3TUSK
wants to merge
2
commits into
bleeding
Choose a base branch
from
feat/gui
base: bleeding
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 表示这个你要自己来。 | ||
|
||
<!-- 感谢潜渣指出的这里潜伏已久的一个问题,见 Harbinger PR GH-55) --> | ||
|
||
### 一个空架子 | ||
|
||
我们拿第九章里的 `LavaFurnace` 来做例子: | ||
|
||
```java | ||
public class LavaFurnaceContainer extends Container { | ||
@Override | ||
public boolean canPlayerInteractWith(EntityPlayer player) { | ||
// 决定了指定玩家是否能与这个 Container 交互。 | ||
// 返回 false 会阻止玩家正常使用 GUI。 | ||
return true; | ||
} | ||
} | ||
|
||
public class LavaFurnaceGui extends GuiContainer { | ||
} | ||
``` | ||
|
||
### 显示玩家背包 | ||
|
||
### 显示机器内物品 | ||
|
||
### 进度条 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# `FontRenderer` | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# `GuiIngame`:游戏主界面 | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 有一张图很好的对其进行了解释: | ||
- 画线段 | ||
- 画矩形(需首先绑定纹理) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
drawGradientRect 要哭了 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. …… |
||
- 画字符串 | ||
- …… | ||
|
||
![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`),这个是进入存档后游戏的主界面。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Toast | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# GUI Widget:按钮 | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Widget | ||
|
||
Minecraft 的 GUI 系统的确存在 Widget 的概念,只是并不非常明显。这些 Widget 包括: | ||
|
||
- [按钮](button.md) | ||
- [标签](label.md) | ||
- [列表](list.md) | ||
- [滑块](slider.md) | ||
- [文本框](text-field.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# GUI Widget:标签 | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# GUI Widget:列表 | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# GUI Widget:滑块 | ||
|
||
WIP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# GUI Widget:文本框 | ||
|
||
WIP |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这么写会有复制物品的bug。既然它是一个tile,应当判断这个tile还在不在玩家附近。具体参见
TileEntityLockableLoot#isUsableByPlayer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这是一个空架子……