原本我计划将这部分内容放在稍后讲解,但是由于这个部分迟迟没有更新,所以我调整了本系列的教程计划,现在就先来介绍这个"block state"。它可以存储简单的参数,用于方块的功能扩展时会派上用场。然而,我们必须清楚,"block state"仅能存储少量简单的参数。之前我们提到过,在游戏中,所有方块都是同一个实例,因此通过"block state"为方块添加参数实际上是为该方块生成了多个不同的实例。
每个实例可能具有其中的某种参数。若使用"block state"存储大量复杂的参数,将导致游戏生成大量实例,严重影响游戏性能。接下来举个例子让大家更直观地感受一下。比如我们的石头按钮方块,它具有三种属性:面朝东南西北的四个方向、依附墙面、天花板和地板、以及是否被按下的激活状态和未激活状态,看起来参数很简单,只有几种可能。
即使参数组合很少,像这样的按钮总共有24种不同的可能性组合。因此,在游戏中会生成24个石头按钮方块实例,每个实例对应一种参数组合。如果参数再复杂一些,情况将变得更加棘手。要记住,"block state"只能存储简单少量的参数。接下来举例说明。
我制作了一个模型,是一个开关门的小门,分别有打开和关闭两种状态,且是双开门。这是同一个方块内的双开门,默认面向东。我需要将这个注册到游戏中。对于复杂方块,我们需要专门使用一个方块类,不能再使用传统方式。
因此,我们新建一个Java文件,思考一下,这个方块需要哪些参数。首先是面朝的方向参数,然后是开启和关闭状态参数。当然,如果想要更复杂一些,还可以考虑是否含水等参数,作为含水方块的一个特性。但在演示教学中,我们就保持简单,只设置面向和开启状态这两个参数。
那么,我们有一个带有面向和是否打开参数的实体。我们需要在原始版本中稍作查找。我们将继续浏览这个外部库,并且将Neo Forge的冒号复制出来。找到其中的net.minecraft下的"Word level block"(译为:世界级方块)、"栅栏门"(Frigate fence gate)。这里还有一些其他内容,但我们要重点关注我们需要的内容。
它包含了一个存储栅栏门是否打开的参数,但似乎我们没有看到它的朝向。实际上是因为它继承了垂直面向方块的类。我们点开进去,可以看到对于朝向这个参数是通过继承垂直面向类的方式实现的。分别是东南西北四个方向。为了实现这两种参数,我们需要继承这个类,并且自定义一个属性,总共两种。
我们直接将这部分复制过来。然后再往下看,这里有很多"voile sheep"的参数,看起来像是处理方块碰撞体积的。我们还可以看到一些使用了"block box"的参数,总共有六个,用来确定两个对角点,就像我们在模型中需要通过确定两个对角点来判断长方体的碰撞体积。此外,还有"shift point",给出了两个参数,看起来像是要将两个方块拼合在一起。
让我们也尝试写一下关于碰撞体积的部分,与栅栏门可能略有不同,所以我们也尝试一下。首先是参数部分,我们将其复制过来。另外,有一个名为"ZSHOX7pro"的漏掉的部分。我们可以直接看到,它稍微比正常的矮一点。在游戏中,我们可以观察到,如果栅栏直接放在地上,它看起来并不高,但如果放东西在上面,它的高度会增加成一个完整的方块大小。我们模型的高度是16,所以我们不考虑这个漏掉的部分,直接使用前面的两个。再往下,发现除了"shift",还有一个"collision onship",即碰撞体积。
"Shift"通常指鼠标指向方块时的体积,而"collision onship"表示碰撞体积。考虑到这是一个门,不应该随意跃过,所以总共共有四个参数:Z、X和碰撞体积的参数。当门打开时,Z、X和碰撞体积的参数会有所变化,总共有八种模型。让我们首先写出我们的代码。我们先写X轴的部分,因为默认方向是向东的,而东西方向都属于X轴。我们先看看默认向东的模型,可以看到碰撞箱,用枢纽点可以看到坐标,零点描述长方体所需获取的点,以及右上角点的坐标是700,厚度为2,因此右上角点是9666。让我们一起编写"block box"。
将7.0D0.0D0.0D转换为700后,描述了东西方向XSHA的设置。然后需要设置南北方向的Z Ship,通过调整中间的枢纽点形成ZSHA。在Z形状下,关注一个点和右上角的另一个点,分别是007和十六十六九,复制并修改这两个点得到Z Ship。
此外,创建碰撞体积需要使用Collection进行直接复制粘贴,完成关闭状态模型的制作。随后制作开启状态的模型,合并两个盒子。举例说明以800和十六十六二为坐标点,描述盒子的合并方式;再描述另一形状的坐标,需要拼合在一起。
指出门的朝向不能简单使用XZ坐标来衡量,开着的门会导致不同朝向的不同碰撞箱形状,因此需要详细描述每个朝向以确保准确。最后,调整碰撞箱高度至24,并探讨将模型传送至读取位置的方法。
描述获取形状和碰撞型的方法,重写构造函数并调整参数;提及获取形状时面向问题,以及获取轴相关信息。重点强调调整碰撞箱高度至24,完成所有模型制作后,学习如何将模型传送至读取地点。通过get sha和death leonship获取形状和碰撞体积信息,重写构造函数以确保可用性。
在面向问题中,我们只有XY两种可能性。而需要额外判断的是门是否打开,即block state中获取开启状态的值和朝向的值。开始进行判断,如果门是打开的,则根据朝向返回对应的开启模型,总共有四种可能性:north ship open、west ship open、south ship open以及east ship open。此外,设定了一个default情况,如果不满足前述条件,则根据朝向确定是X Ship还是Z Ship,并进行返回。
在对模型进行检查时,默认情况下面向东,即默认轴为X轴,对Y轴和Z轴进行相应判断,颠倒一下条件逻辑。针对默认朝向向东,使用default便于理解且消除错误。接下来是collision on ship,用于获取碰撞体积信息。同样方式复制并调整参数,返回COLLISION版本的结果,将相应内容加入返回值中。
再提到is path fendable方法,用于判断生物是否可以通过该路径。简单地判断门是否处于打开状态,返回相应的block state的open值。使用use属性时,可在方块上挂载该属性,确保任何物品都能实现对门的开关。挂载属性的位置取决于开发经验,参考原版栅栏门方法,若门处于打开状态,则通过set value设置open状态为false,对block state进行相应修改。
我们之前已经提到了,所有关于这个方块状态的可能性都在游戏启动时就确定了。因此,我们只是将这个方块状态转换成另一个方块状态,并没有真正设置数值,而只是改变了另一种可能性。因此,这个方块在世界中并没有发生改变。我们仍然需要通过level点set block的方式,将坐标block传递进去。在这里我们不需要理解特定代码段的含义,只需要按照要求写入,因为我们要实现的功能与其类似,所以直接模仿即可。
这个方法在开发中很常见,在遇到不清楚的部分时,直接找相似功能的模块,然后进行参考和模仿,而将主要精力放在实现自己想要的逻辑上,而非在琢磨那些难以理解的参数上,这样做会浪费大量时间。最好的方式是直接模仿,把注意力集中在主要逻辑上。另外,原文还提到了根据玩家面向来判断是否能够开门,这是合理的设计。
总之,复制粘贴整段代码应该是个不错的选择,当然需要根据具体情况进行一定的修改和优化。有些部分可以省略,例如声音相关的内容,或者可以直接复制其他模块的实现。如果想要添加声音,也可以在资源文件中加入自定义声音文件,并在对应位置引用即可。此外,要小心区分哪些部分需要修改,哪些需要保留,这是一个需要经验积累的过程。在构造函数中注册默认状态和设置数值的操作需要被移植到新的代码中。
我们还可以设置面向参数(facing),应该是好东方(east)。然后将这些参数都填写完整,这样就没有问题了吧。接着,我们来注册我们的方块。有时候,当你更新 Forge 后,这些设置可能会出现问题。你可以在编辑配置中进行调整,然后应用一下更改,问题就会解决。同样的,测试服务器选择测试服务器,服务器选项选择相应的服务器,这样就不会出现问题了。
最后,我们再创建 block state 中的内容。之前我们在我的方块部分没有详细说明这些意思,现在可以解释了。引号里面写的是所有方块状态参数的组合,在模组中指定需要使用的模型。因为我们有两个模型,一个是关闭状态,一个是开启状态,所以我们保存了两个。参数 Y 表示绕 Y 轴旋转的意思,因为四个方向只需要旋转即可,无需额外处理模型。最后在语言文件中写上对应信息,类似于 "block.block.testmod" 这样的格式。
进入游戏后,我们可以拿出小门检查是否有问题。四个方向都检查一遍,应该没什么问题。模型和碰撞体积也都正常。这样我们的方块就算是成功了,我们还可以堆叠形成大门。当然,由于我们直接复制了栅栏门的代码,栅栏门在打开时会改变方块朝向,这部分逻辑可以自行完成。虽然这些修改并不困难,但我觉得比较麻烦,所以选择直接复制。