<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://mcbbs.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=MashKJo</id>
	<title>MCBBS Wiki - 用户贡献 [zh-cn]</title>
	<link rel="self" type="application/atom+xml" href="https://mcbbs.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=MashKJo"/>
	<link rel="alternate" type="text/html" href="https://mcbbs.wiki/wiki/%E7%89%B9%E6%AE%8A:%E7%94%A8%E6%88%B7%E8%B4%A1%E7%8C%AE/MashKJo"/>
	<updated>2026-06-26T04:13:32Z</updated>
	<subtitle>用户贡献</subtitle>
	<generator>MediaWiki 1.40.3</generator>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/17.%E7%9F%BF%E7%89%A9%E8%AF%8D%E5%85%B8%E5%92%8C%E5%90%84%E7%B1%BB%E9%85%8D%E6%96%B9&amp;diff=55041</id>
		<title>用户:MashKJo/1.12.2模组开发教程/17.矿物词典和各类配方</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/17.%E7%9F%BF%E7%89%A9%E8%AF%8D%E5%85%B8%E5%92%8C%E5%90%84%E7%B1%BB%E9%85%8D%E6%96%B9&amp;diff=55041"/>
		<updated>2025-01-20T15:46:06Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“给原版的工作台、熔炉等增加新的配方以服务于我们的模组，可以说是最常见的需求之一了。本节教程将详细阐述这些。  == 矿物词典 == 矿物词典（Ore Dictionary），是Forge的一个历史非常悠久的系统——它旨在让设定上为同一样东西，而实际上因是不同模组添加而无法混用的物品在许多场合表现得可以互相通用。如：几乎所有科技模组都会添加自己的…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;给原版的工作台、熔炉等增加新的配方以服务于我们的模组，可以说是最常见的需求之一了。本节教程将详细阐述这些。&lt;br /&gt;
&lt;br /&gt;
== 矿物词典 ==&lt;br /&gt;
矿物词典（Ore Dictionary），是Forge的一个历史非常悠久的系统——它旨在让设定上为同一样东西，而实际上因是不同模组添加而无法混用的物品在许多场合表现得可以互相通用。如：几乎所有科技模组都会添加自己的铜锭，如果这许多种铜锭不能互相通用，对多模组整合包玩家将会是一场灾难。&lt;br /&gt;
&lt;br /&gt;
矿物词典提供的解决方案是：给这些设定上相同实际上不同的物品都统一给予一个词典名，如对于铜锭，其词典名为“ingotCopper”，在决定两个物品是否可以通用时，只需比较它们的词典名即可。词典名的命名没有强制规范，不过它有约定俗成的规范：&lt;br /&gt;
# 小写驼峰式命名&lt;br /&gt;
# 形态（种类）在前，质地（材料）在后&lt;br /&gt;
&lt;br /&gt;
且，虽然名称叫矿物词典，但实际上它可不仅仅只能用于和矿物有关的物品，相反，它能应用于一切物品。且有些物品还不止一个词典名——如青金石既是gemLapis，又是dyeBlue。&lt;br /&gt;
&lt;br /&gt;
Forge为许多原版物品已经内建了矿物词典名，这些都可以在OreDictionary#initVanillaEntries这个私有方法中找到。&lt;br /&gt;
&lt;br /&gt;
注册矿物词典非常简单：&lt;br /&gt;
 OreDictionary.registerOre(name, ore);&lt;br /&gt;
这样就可以了，name是词典名，而ore，可以是Item、Block或ItemStack。&lt;br /&gt;
&lt;br /&gt;
此外，OreDictionary类下还有如下辅助用的静态方法：&lt;br /&gt;
* &amp;lt;code&amp;gt;static boolean matches(ItemStack target, ItemStack input, boolean strict)&amp;lt;/code&amp;gt;：判断2个ItemStack是否在矿物词典中相匹配。那个boolean参数影响的是：2个ItemStack的meta值影不影响判断结果。&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;static boolean containsMatch(boolean strict, NonNullList&amp;lt;ItemStack&amp;gt; inputs, ItemStack... targets)&amp;lt;/code&amp;gt;：判断两组ItemStack中是否有那么一对在矿物词典中相匹配。&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;static boolean doesOreNameExist(String name)&amp;lt;/code&amp;gt;：检查矿物词典中某词典名有没有对应的物品。&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;static NonNullList&amp;lt;ItemStack&amp;gt; getOres(String name, boolean alwaysCreateEntry)&amp;lt;/code&amp;gt;：获取某一词典名下所有符合条件的物品。&lt;br /&gt;
&lt;br /&gt;
随着1.13更为强大易用的标签（Tag）系统的加入，矿物词典也正式退出了Forge Mod开发的历史舞台。&lt;br /&gt;
&lt;br /&gt;
=== WILDCARD_VALUE ===&lt;br /&gt;
&amp;lt;code&amp;gt;OreDictionary.WILDCARD_VALUE&amp;lt;/code&amp;gt;是Forge定义的一个常量，用于取代Mojang对于幻数的使用。这个幻数为Short.MAX_VALUE。你可能注意到了，initVanillaEntries()方法中，有些ItemStack的meta值这个位置填入的就是这个WILDCARD_VALUE。&lt;br /&gt;
&lt;br /&gt;
meta值为WILDCARD_VALUE的ItemStack，实际上是一个抽象的概念了，其代表一类meta值不定的ItemStack——即“不在乎”ItemStack的meta值到底是多少，只要物品类型和堆叠数量一致了，就可以在词典中成功匹配。这个常量除了能应用在代码中，也能应用在简单工作台配方的.json文件中。&lt;br /&gt;
== 工作台配方 ==&lt;br /&gt;
=== 简单工作台配方：写JSON文件 ===&lt;br /&gt;
1.12开始，简单的原版工作台配方并非要在代码中指定，而是已经数据驱动化了——Minecraft会自动读取所有位于&amp;lt;code&amp;gt;assets/[modid]/recipes&amp;lt;/code&amp;gt;路径下的所有.json文件，并自动将这些.json文件代表的合成表载入游戏。&lt;br /&gt;
&lt;br /&gt;
这属于原版的数据包知识（尽管1.12时期数据包还并未被正式加入），参见：[https://zh.minecraft.wiki/w/%E9%85%8D%E6%96%B9#%E5%90%88%E6%88%90%E9%85%8D%E6%96%B9 配方#合成配方 - 中文 Minecraft Wiki]。注意：Wiki上关于这部分的阐释，很多内容都是高版本才有的，如：标签（Tag）、物品组件（DataComponent），请自行甄别。&lt;br /&gt;
&lt;br /&gt;
这里笔者对原版的JSON格式只做简要阐述了。例如，对于我们之前添加的红宝石和红宝石块，我们可以添加从块到宝石的转换配方——显然，是无序配方：&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;minecraft:crafting_shapeless&amp;quot;,&lt;br /&gt;
    &amp;quot;group&amp;quot;: &amp;quot;tutorial_mod:example_group&amp;quot;,&lt;br /&gt;
    &amp;quot;ingredients&amp;quot;: [ { &amp;quot;item&amp;quot;: &amp;quot;tutorial_mod:ruby_block&amp;quot; } ],&lt;br /&gt;
    &amp;quot;result&amp;quot;: {&lt;br /&gt;
        &amp;quot;item&amp;quot;: &amp;quot;tutorial_mod:ruby&amp;quot;,&lt;br /&gt;
        &amp;quot;count&amp;quot;: 9&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
type为&amp;lt;code&amp;gt;minecraft:crafting_shapeless&amp;lt;/code&amp;gt;代表这是一个无序合成配方，group读者可以随便填写。ingredients和result分别代表输入和输出，前者是一个数组。&lt;br /&gt;
物品格式不仅仅有item和count这两种属性，还可以有data这个属性——用于指定metadata。将data设定为32767，即可无视meta进行匹配——也即WILDCARD_VALUE所起到的作用。&lt;br /&gt;
&lt;br /&gt;
那么有序合成呢？&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;minecraft:crafting_shaped&amp;quot;,&lt;br /&gt;
    &amp;quot;group&amp;quot;: &amp;quot;tutorial_mod:example_group&amp;quot;,&lt;br /&gt;
    &amp;quot;pattern&amp;quot;: [&amp;quot;XXX&amp;quot;, &amp;quot;XXX&amp;quot;, &amp;quot;XXX&amp;quot;],&lt;br /&gt;
    &amp;quot;key&amp;quot;: { &amp;quot;X&amp;quot;: { &amp;quot;item&amp;quot;: &amp;quot;tutorial_mod:ruby&amp;quot; } },&lt;br /&gt;
    &amp;quot;result&amp;quot;: { &amp;quot;item&amp;quot;: &amp;quot;tutorial_mod:ruby_block&amp;quot;, &amp;quot;count&amp;quot;: 1 }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
其中pattern是一个三元字符串数组，代表该有序合成配方的“图案”，若有地方不需要材料填充则留空。而key这一属性则阐明图案中用于占位的字符到底都代表什么具体的物品（严格来说是ItemStack）。&lt;br /&gt;
&lt;br /&gt;
以上所述的是原版的JSON格式，鉴于矿物词典的存在，Forge在原版的基础上提供了对矿物词典的支持——type这一属性新增了2种：&amp;lt;code&amp;gt;forge:ore_shapeless&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;forge:ore_shaped&amp;lt;/code&amp;gt;，用于指代有矿物词典机制参与时的无序合成和有序合成。而词典物品的格式为：&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;ore_dict&amp;quot;,&lt;br /&gt;
    &amp;quot;ore&amp;quot;: &amp;lt;an existing ore dictionary name&amp;gt;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
这样就行了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;注意：在工作台配方中，矿物词典不能被滥用。一来，如果一个配方的输入和输出物品全都是你自己的模组的物品，那么绝大多数情况下你都不该用矿物词典；二来，矿物词典是仅针对输入的物品的，你不能将其用于配方的输出物品。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== 高级工作台配方：重新实现IRecipe ===&lt;br /&gt;
所有工作台配方在代码层面都实现了IRecipe接口，这个接口规定了一系列工作台配方所必需的方法。用JSON添加有序和无序合成配方的实质，是Minecraft会先读取这些JSON文件中的信息，再构造ShapedRecipes和ShapelessRecipes这两个IRecipe实现类。&lt;br /&gt;
然而，对于复杂的、动态的配方，JSON文件便不能胜任。如，我们可能想要操纵ItemStack的损害值和附加NBT，输出的ItemStack的损害值和附加NBT和输入的ItemStack有着一定的对应关系，这时JSON文件便爱莫能助了，我们必须重新实现IRecipe。典型的例子就是原版的工具修复配方（RecipeRepairItem）和皮革套染色配方（RecipesArmorDyes）。&lt;br /&gt;
&lt;br /&gt;
 //和其他那些注册项不同，IRecipe本身是一个接口，没法继承IForgeRegistryEntry.Impl&amp;lt;IRecipe&amp;gt;这个类，因此我们只能在这里继承了……&lt;br /&gt;
 public class MyCustomRecipe extends IForgeRegistryEntry.Impl&amp;lt;IRecipe&amp;gt; implements IRecipe{&lt;br /&gt;
 &lt;br /&gt;
    //这个方法用于检验合成界面中放入的ItemStack是否符合该配方的条件——如果不符合则不能合成出对应物品。&lt;br /&gt;
    @Override&lt;br /&gt;
    public boolean matches(InventoryCrafting inv, World worldIn) {&lt;br /&gt;
        //通常是用这样一个循环来遍历所有格子中的ItemStack。&lt;br /&gt;
        for(int i = 0; i &amp;lt; inv.getSizeInventory(); i++){&lt;br /&gt;
            ItemStack stack = inv.getStackInSlot(i);&lt;br /&gt;
            ...&lt;br /&gt;
            //对于比较静态、不变的配方，如果其中有哪怕一个物品不符合要求，则直接return false即可。&lt;br /&gt;
            //对于非常动态的配方，你也可以选择弄一个List&amp;lt;ItemStack&amp;gt;，用循环先写入所有ItemStack，再对List中的元素进行你自己想要的判断……&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //这个方法用于决定：matches方法返回true之后，合成出来的ItemStack，即“合成结果（Crafting Result）”。&lt;br /&gt;
    @Override&lt;br /&gt;
    public ItemStack getCraftingResult(InventoryCrafting inv) {&lt;br /&gt;
        //坏消息是：对于非常动态的配方，合成产物和输入的物品强相关，但你没法直接从上一个方法中拿到输入的物品。&lt;br /&gt;
        //且由于IRecipe实例是享元对象，因此将输入的物品信息存进实例变量也不可行，你基本上只能再重复一次上一个方法中的循环。&lt;br /&gt;
 &lt;br /&gt;
        return new ItemStack(...);&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //用于描述“当前合成界面容纳得了当前这个配方与否”，想想看背包中的合成界面和工作台中的合成界面各自的尺寸，就不难理解了。&lt;br /&gt;
    @Override&lt;br /&gt;
    public boolean canFit(int width, int height) {&lt;br /&gt;
        ...&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //这是一个很奇怪的方法，“RecipeOutput”意为“合成配方的输出”，但这个“输出”仅仅是抽象意义上的、合成目标物品成功后的一个“Callback”（？）&lt;br /&gt;
    //原版大部分高级配方都在这里返回ItemStack.EMPTY。&lt;br /&gt;
    //输出和getCraftingResult方法一样的结果是不可行的，因为这个方法没有InventoryCrafting类型的形参。&lt;br /&gt;
    //除非你重新实现一个IRecipeFactory，以从JSON文件中读取你的自定义配方，这时候你便可以通过构造器来接受读取后的配方的各方面信息，包括输出结果，你才能输出非ItemStack.EMPTY的结果。&lt;br /&gt;
    //实际上原版的ShapedRecipes和ShapelessRecipes这两个IRecipe实现类就是这么干的，这个方法也是专门为这两种配方服务的。&lt;br /&gt;
    @Override&lt;br /&gt;
    public ItemStack getRecipeOutput() {&lt;br /&gt;
        return ItemStack.EMPTY;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //IRecipe接口中的默认方法，用于获取：合成结束后留在合成界面中的ItemStack。&lt;br /&gt;
    @Override&lt;br /&gt;
    public NonNullList&amp;lt;ItemStack&amp;gt; getRemainingItems(InventoryCrafting inv)&lt;br /&gt;
    {&lt;br /&gt;
        //默认实现：留存所有输入的ItemStack的containerItem。&lt;br /&gt;
        return ForgeHooks.defaultRecipeGetRemainingItems(inv);&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //IRecipe接口中的默认方法，用于获取：该合成表的原料（Ingredient）列表，Ingredient就是对不定数量的ItemStack的抽象。&lt;br /&gt;
    @Override&lt;br /&gt;
    public NonNullList&amp;lt;Ingredient&amp;gt; getIngredients()&lt;br /&gt;
    {&lt;br /&gt;
        //默认实现：返回一个空集合，原因同getRecipeOutput，实际上这是专门为ShapedRecipes和ShapelessRecipes服务的。&lt;br /&gt;
        return NonNullList.&amp;lt;Ingredient&amp;gt;create();&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //IRecipe接口中的默认方法，用于标识：该合成配方是否为动态的。&lt;br /&gt;
    @Override&lt;br /&gt;
    public boolean isDynamic()&lt;br /&gt;
    {&lt;br /&gt;
        //默认实现：并非动态的。&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    //IRecipe接口中的默认方法，用于给合成配方划分组别。实际上这个的意义并不是很大。&lt;br /&gt;
    @Override&lt;br /&gt;
    public String getGroup()&lt;br /&gt;
    {&lt;br /&gt;
        //默认实现：返回长度为0的字符串。&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
IRecipe是IForgeRegistryEntry&amp;lt;IRecipe&amp;gt;，因此，它的注册也是走Forge的注册表的：&lt;br /&gt;
&lt;br /&gt;
    @SubscribeEvent&lt;br /&gt;
    public static void registerCraftingRecipes(RegistryEvent.Register&amp;lt;IRecipe&amp;gt; event){&lt;br /&gt;
        event.getRegistry().register(new MyCustomRecipe().setRegistryName(TutorialMod.MODID, &amp;quot;my_custom_recipe&amp;quot;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
== 熔炉配方和燃料热值 ==&lt;br /&gt;
熔炉配方在1.12并未数据驱动化，而是仍然采用硬编码来指定。所有熔炉配方都被放在了FurnaceRecipes这个类当中，暴露出的添加熔炉配方的公开方法为addSmelting和addSmeltingRecipe，我们还可以拿到该类的单例，理论上这就足够我们添加新的熔炉配方了。&lt;br /&gt;
不过这实际上是不推荐的做法，因为Forge提供了一个方法：&amp;lt;code&amp;gt;GameRegistry#addSmelting&amp;lt;/code&amp;gt;，通过它我们就能便捷地添加熔炉配方。&lt;br /&gt;
 GameRegistry.addSmelting(input, output, xp);&lt;br /&gt;
其中xp代表烧炼后获取的经验，为float类型；output是一个ItemStack；input可以是Item、Block以及ItemStack。&lt;br /&gt;
&lt;br /&gt;
而指定某个物品是燃料，以及指定该物品的燃烧时间，Forge提供了2种方案。&lt;br /&gt;
第1种方案是实现一个IFuelHandler，该接口为函数式接口，根据当前的ItemStack信息返回一个int整数——即燃烧的tick数，再通过&amp;lt;code&amp;gt;GameRegistry#registerFuelHandler&amp;lt;/code&amp;gt;注册该IFuelHandler：&lt;br /&gt;
 GameRegistry.registerFuelHandler(stack -&amp;gt; &amp;lt;a certain int value&amp;gt;);&lt;br /&gt;
&lt;br /&gt;
第2种方案是覆写&amp;lt;code&amp;gt;Item#getItemBurnTime&amp;lt;/code&amp;gt; + 监听FurnaceFuelBurnTimeEvent事件。前者用于决定你自己的物品的燃料情况，后者用于决定原版和其他模组的情况。burnTime为一个正数代表其可燃，且单位为游戏刻tick；0则代表它不是燃料；-1则代表：移交给原版Minecraft处理相关游戏逻辑。&lt;br /&gt;
&lt;br /&gt;
这两种方案，实际上更推荐大家用第二种，第一种实际上已经被Forge不推荐使用（@Deprecated）了。虽然第一种方案实现起来比较简单，但它实质上就是往GameRegistry的一个静态ArrayList&amp;lt;IFuelHandler&amp;gt;中添加新元素的过程，这样如果多个模组同时针对同一种物品实现IFuelHandler，那么最终结果到底是怎样的，恐怕要看FML加载模组的顺序了——这显然不是我们所期望的；而FurnaceFuelBurnTimeEvent是一个可取消的事件，配合着事件优先级来操作，这种问题就可以得到有效的缓解。&lt;br /&gt;
&lt;br /&gt;
== 酿造台配方 ==&lt;br /&gt;
原版的酿造台配方是由Forge接管的。如果你只是想添加一些简单的酿造配方：&lt;br /&gt;
 BrewingRecipeRegistry.addRecipe(input, ingredient, output);&lt;br /&gt;
其中input指的是位于水瓶槽位的输入的ItemStack，output则是最终的产物，同样也是一个ItemStack。而ingredient则比较有趣，它指的是炼药消耗的材料，例如地狱疣、金萝卜，但addRecipe有两个重载，区别就在于这个ingredient的类型——其中之一为ItemStack，还有一个为String。这也就说明，Forge为添加炼药配方提供了矿物词典支持。&lt;br /&gt;
&lt;br /&gt;
如果我想实现更高级的酿造配方，该怎么做？类似于原版工作台配方的IRecipe，Forge定义了IBrewingRecipe接口，以代表抽象的酿造台配方。&lt;br /&gt;
原版的酿造台配方被抽象为了VanillaBrewingRecipe这个类，它直接实现IBrewingRecipe接口，VanillaBrewingRecipe的一个单例即包含原版中所有的酿造配方，且在BrewingRecipeRegistry的静态初始化块中即被addRecipe。&lt;br /&gt;
&lt;br /&gt;
IBrewingRecipe接口规定了isInput、isIngredient和isOutput这三个方法，自己按需实现即可。&lt;br /&gt;
&lt;br /&gt;
还有一个半成品：AbstractBrewingRecipe&amp;lt;?&amp;gt;，它允许ingredient可以是任意类型，比如一个List&amp;lt;ItemStack&amp;gt;，泛型参数即为ingredient的类型。多数时候我们不必直接实现IBrewingRecipe，直接继承它就好。&amp;lt;code&amp;gt;BrewingRecipeRegistry#addRecipe&amp;lt;/code&amp;gt;的两个重载，原理其实分别是new了BrewingRecipe和BrewingOreRecipe而已，这两个类就是分别继承于AbstractBrewingRecipe&amp;lt;ItemStack&amp;gt;和AbstractBrewingRecipe&amp;lt;List&amp;lt;ItemStack&amp;gt;&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
=== 相关事件 ===&lt;br /&gt;
==== PotionBrewEvent ====&lt;br /&gt;
有2个子事件：Pre和Post。Pre可取消，发生在药水炼制出来之前，取消即可彻底接管原版的逻辑；Post不可取消，但你也可以从这里拿到相关的信息。&lt;br /&gt;
&lt;br /&gt;
==== PlayerBrewedPotionEvent ====&lt;br /&gt;
这个事件的名称可能会有迷惑性，它实际发布的时机实际上是玩家从酿造台的输出位取出药水的时候。从这个事件中，我们可以拿到EntityPlayer对象，以及取出的ItemStack。&lt;br /&gt;
&lt;br /&gt;
== 铁砧配方 ==&lt;br /&gt;
原版并没有标准化的铁砧配方这一概念，因为原版当中铁砧的作用也就是敲附魔、修理装备以及重命名物品而已，这些是完全写死在ContainerRepair中的。但Forge依然给我们提供了一个事件：AnvilUpdateEvent，当玩家向其中放入物品时，该事件即会触发。我们可以在这里判断放入的ItemStack，从而设定铁砧的输出位（event.setOutput）。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/16.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E5%9B%9B%EF%BC%89_-_%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E6%A8%A1%E5%9E%8B&amp;diff=55040</id>
		<title>用户:MashKJo/1.12.2模组开发教程/16.高级物品（四） - 高级物品模型</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/16.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E5%9B%9B%EF%BC%89_-_%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E6%A8%A1%E5%9E%8B&amp;diff=55040"/>
		<updated>2025-01-20T15:37:37Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“== 物品模型在不同场合下的渲染变换 == 这里的“各种场合”包括：GUI内、被玩家以不同视角拿在手上时、在物品展示框中时等情况。这属于原版的内容，参见：[https://zh.minecraft.wiki/w/%E6%A8%A1%E5%9E%8B#%E6%B8%B2%E6%9F%93%E5%8F%98%E6%8D%A2 模型#渲染变换 - 中文 Minecraft Wiki]。  小提示：直接手写这些渲染变换的参数很不直观，推荐读者使用Blockbench进行这些参数的可…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 物品模型在不同场合下的渲染变换 ==&lt;br /&gt;
这里的“各种场合”包括：GUI内、被玩家以不同视角拿在手上时、在物品展示框中时等情况。这属于原版的内容，参见：[https://zh.minecraft.wiki/w/%E6%A8%A1%E5%9E%8B#%E6%B8%B2%E6%9F%93%E5%8F%98%E6%8D%A2 模型#渲染变换 - 中文 Minecraft Wiki]。&lt;br /&gt;
&lt;br /&gt;
小提示：直接手写这些渲染变换的参数很不直观，推荐读者使用Blockbench进行这些参数的可视化调控。&lt;br /&gt;
&lt;br /&gt;
== 物品属性覆盖（Item Property Override） ==&lt;br /&gt;
这是一个允许我们根据当前的ItemStack所持有的信息来指定不同的物品模型的机制。&lt;br /&gt;
&lt;br /&gt;
首先我们需要一个IItemPropertyGetter：&lt;br /&gt;
 public static final IItemPropertyGetter MY_ITEM_PROPERTY_GETTER = new IItemPropertyGetter(){&lt;br /&gt;
    @SideOnly(Side.CLIENT)&lt;br /&gt;
    @Override&lt;br /&gt;
    public float apply(ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entityLiving){&lt;br /&gt;
        ...&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
这里用的是匿名内部类而非lambda——因为内部那个方法需要用@SideOnly(Side.CLIENT)修饰。apply方法的形参列表很丰富，你大可以好好利用这些形参，最后输出一个float值。&lt;br /&gt;
&lt;br /&gt;
紧接着你就需要告知：你的某一物品有这一IItemPropertyGetter，一般是在该物品类的构造器中这样写：&lt;br /&gt;
 this.addPropertyOverride(new ResourceLocation(...), MY_ITEM_PROPERTY_GETTER);&lt;br /&gt;
&lt;br /&gt;
然后我们就可以把这个IItemPropertyGetter体现在该物品的模型json文件中了：&lt;br /&gt;
 {&lt;br /&gt;
    ...&lt;br /&gt;
    &amp;quot;overrides&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;predicate&amp;quot;: {&lt;br /&gt;
                &amp;lt;your property getter&#039;s resource location name&amp;gt;: &amp;lt;a float value&amp;gt;&lt;br /&gt;
            },&lt;br /&gt;
            &amp;quot;model&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
        },&lt;br /&gt;
        ...&lt;br /&gt;
        ]&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
这样，当该Property等于特定的值时，物品模型会被替换为overrides中的相应的model的。注意：这个数值是精确匹配的。&lt;br /&gt;
&lt;br /&gt;
原版内建了4种IItemPropertyGetter：DAMAGED_GETTER（ItemStack是否受到损害）、DAMAGE_GETTER（ItemStack当前的损害程度）、LEFTHANDED_GETTER（持有该ItemStack的生物实体的主手是否为左手）、COOLDOWN_GETTER（该ItemStack对应的Item的冷却进度），且Item类的构造方法中，addPropertyOverride了后两种IItemPropertyGetter，也就是说理论上所有物品都有这两种IItemPropertyGetter。&lt;br /&gt;
&lt;br /&gt;
== ItemMeshDefinition ==&lt;br /&gt;
Property Override并不是所有时候都好用的——有些时候它甚至有些画蛇添足，硬是要把条件分支转化为不同的float值，太过麻烦。如果在你的需求中，物品模型的变化并不依赖于当前的World和持有该ItemStack的生物实体，我们就可以考虑使用ItemMeshDefinition。它是一个原版的函数式接口，唯一的方法为：&amp;lt;code&amp;gt;ModelResourceLocation getModelLocation(ItemStack stack)&amp;lt;/code&amp;gt;。ItemMeshDefinition相比Property Override，更加直观好用——直接根据当前的ItemStack的信息指定ModelResourceLocation，而省去了Property Override的转化数据、写overrides json的过程。&lt;br /&gt;
&lt;br /&gt;
Forge提供了一个方法来设置我们自己的ItemMeshDefinition：&lt;br /&gt;
 ModelLoader.setCustomMeshDefinition(item, stack -&amp;gt; { ... } );&lt;br /&gt;
&lt;br /&gt;
但注意，&#039;&#039;&#039;ItemMeshDefinition并不能用于动态地生成模型&#039;&#039;&#039;。通过它所指定的模型仍然需要通过常规的声明模型的方式声明，一定能被穷尽，且Minecraft必须提前知道所有的可能的模型：&lt;br /&gt;
 ModelBakery.registerItemVariants(item, resourceLocation1, resourceLocation2...);&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/15.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%B8%89%EF%BC%89_-_%E7%9B%94%E7%94%B2&amp;diff=55039</id>
		<title>用户:MashKJo/1.12.2模组开发教程/15.高级物品（三） - 盔甲</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/15.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%B8%89%EF%BC%89_-_%E7%9B%94%E7%94%B2&amp;diff=55039"/>
		<updated>2025-01-20T15:36:55Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* ISpecialArmor */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== ArmorMaterial ==&lt;br /&gt;
和武器/工具的类类似，盔甲对应的类ItemArmor的构造方法也需要一个类似于“质地”的东西——ItemArmor.ArmorMaterial。该类同样为枚举类，因此，类似于ToolMaterial，我们这样来构造新的ArmorMaterial：&lt;br /&gt;
 public static final ItemArmor.ArmorMaterial MY_CUSTOM_ARMOR_MATERIAL = EnumHelper.addArmorMaterial(name, textureName, durability, reductionAmounts, enchantability, soundOnEquip, toughness);&lt;br /&gt;
&lt;br /&gt;
其中，name应该由小写字母和下划线组成；textureName看似是指代该盔甲穿在玩家身上时的贴图，但实则起到一个充当该ArmorMaterial的唯一标识符的作用，你应该填入&amp;lt;code&amp;gt;&amp;quot;modid:armor_material_name&amp;quot;&amp;lt;/code&amp;gt;；durability即盔甲各部件的耐久值；reductionAmounts是一个长度为4的int数组，用于指定盔甲4个部件各自对伤害的免疫程度，其实就是护甲值；enchantability指盔甲的附魔能力；soundOnEquip是一个SoundEvent参数，用于指定该盔甲被穿上时的音效，关于SoundEvent的知识后续会有专门讲解，你可以先复用原版的SoundEvent，它们均位于SoundEvents类中；toughness指的是盔甲韧性值。&lt;br /&gt;
&lt;br /&gt;
== 创建一套新盔甲 ==&lt;br /&gt;
我们首先看看ItemArmor类的构造方法：&amp;lt;code&amp;gt;(ItemArmor.ArmorMaterial materialIn, int renderIndexIn, EntityEquipmentSlot equipmentSlotIn)&amp;lt;/code&amp;gt;。其中renderIndex在原版中没有任何实际用途，乱填一个就好，而最后一个EntityEquipmentSlot参数代表了它是头盔/胸甲/护腿/靴子中的哪一种。因此，要创建一整套新盔甲，你需要new ItemArmor类或其子类4次。&lt;br /&gt;
&lt;br /&gt;
原版的ItemArmor类的自定义空间几乎为0，不过Forge在Item类中patch了一些专门针对盔甲的方法：&lt;br /&gt;
* &amp;lt;code&amp;gt;void onArmorTick(World world, EntityPlayer player, ItemStack stack)&amp;lt;/code&amp;gt;：这个方法会在盔甲穿上后每一tick都会被调用一次，通常你可以用这个方法来给盔甲加上穿戴药水效果之类的东西。&lt;br /&gt;
* &amp;lt;code&amp;gt;String getArmorTexture(ItemStack stack, Entity entity, EntityEquipmentSlot slot, String type)&amp;lt;/code&amp;gt;：这个方法用于确定盔甲穿在身上对应的纹理，返回一个String，实际上这个String的格式类似于ResourceLocation用于指代具体文件时的格式（且路径被写全了）。例：原版的铁套护腿的材质的路径为&amp;lt;code&amp;gt;assets/minecraft/textures/models/armor/iron_layer_2.png&amp;lt;/code&amp;gt;，那么体现在这里，返回的String应为&amp;lt;code&amp;gt;&amp;quot;minecraft:textures/models/armor/iron_layer_2.png&amp;quot;&amp;lt;/code&amp;gt;。看起来你可以很自由地指定材质文件，实则不然——除非你给盔甲自定义一个新的模型，否则盔甲会自动使用原版默认模型，那么材质文件必须有2份——其中一张包含头盔、胸甲、靴子的贴图，另外一张包含护腿的贴图，且实际的贴图在材质文件中对应的UV坐标有严格限制。因此如果你不指定自定义模型，那么在这里是要判断传入的EntityEquipmentSlot，并根据情况返回2个路径；至于材质文件，你基本上只能在原版的基础上修改。&lt;br /&gt;
* &amp;lt;code&amp;gt;ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack stack, EntityEquipmentSlot armorSlot, ModelBiped _default)&amp;lt;/code&amp;gt;：这个方法用于给盔甲指定自定义模型，指定后我们就可以自由地根据自己的需求调整材质文件的方方面面了。注意到返回一个ModelBiped，它实际上是ModelBase的子类，关于ModelBase的相关知识，详见后续教程内容。&lt;br /&gt;
&lt;br /&gt;
=== ISpecialArmor ===&lt;br /&gt;
Forge提供了一个接口：ISpecialArmor，通过让我们的ItemArmor子类实现该接口，就能实现对盔甲各方面属性的更精细的调控。该接口有一个静态内部类：ArmorProperties，主要用于阐明盔甲的伤害吸收能力。&lt;br /&gt;
 public interface ISpecialArmor{&lt;br /&gt;
    ArmorProperties getProperties(EntityLivingBase player, @Nonnull ItemStack armor, DamageSource source, double damage, int slot);&lt;br /&gt;
 &lt;br /&gt;
    int getArmorDisplay(EntityPlayer player, @Nonnull ItemStack armor, int slot);&lt;br /&gt;
 &lt;br /&gt;
    void damageArmor(EntityLivingBase entity, @Nonnull ItemStack stack, DamageSource source, int damage, int slot);&lt;br /&gt;
 &lt;br /&gt;
    ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
通过实现该接口，我们可以设定：该盔甲的ArmorProperties，穿戴后在HUD中显示的护甲值的多少，等等。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/15.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%B8%89%EF%BC%89_-_%E7%9B%94%E7%94%B2&amp;diff=55038</id>
		<title>用户:MashKJo/1.12.2模组开发教程/15.高级物品（三） - 盔甲</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/15.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%B8%89%EF%BC%89_-_%E7%9B%94%E7%94%B2&amp;diff=55038"/>
		<updated>2025-01-20T15:36:04Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“== ArmorMaterial == 和武器/工具的类类似，盔甲对应的类ItemArmor的构造方法也需要一个类似于“质地”的东西——ItemArmor.ArmorMaterial。该类同样为枚举类，因此，类似于ToolMaterial，我们这样来构造新的ArmorMaterial：  public static final ItemArmor.ArmorMaterial MY_CUSTOM_ARMOR_MATERIAL = EnumHelper.addArmorMaterial(name, textureName, durability, reductionAmounts, enchantability, soundOnEquip, toughness);…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== ArmorMaterial ==&lt;br /&gt;
和武器/工具的类类似，盔甲对应的类ItemArmor的构造方法也需要一个类似于“质地”的东西——ItemArmor.ArmorMaterial。该类同样为枚举类，因此，类似于ToolMaterial，我们这样来构造新的ArmorMaterial：&lt;br /&gt;
 public static final ItemArmor.ArmorMaterial MY_CUSTOM_ARMOR_MATERIAL = EnumHelper.addArmorMaterial(name, textureName, durability, reductionAmounts, enchantability, soundOnEquip, toughness);&lt;br /&gt;
&lt;br /&gt;
其中，name应该由小写字母和下划线组成；textureName看似是指代该盔甲穿在玩家身上时的贴图，但实则起到一个充当该ArmorMaterial的唯一标识符的作用，你应该填入&amp;lt;code&amp;gt;&amp;quot;modid:armor_material_name&amp;quot;&amp;lt;/code&amp;gt;；durability即盔甲各部件的耐久值；reductionAmounts是一个长度为4的int数组，用于指定盔甲4个部件各自对伤害的免疫程度，其实就是护甲值；enchantability指盔甲的附魔能力；soundOnEquip是一个SoundEvent参数，用于指定该盔甲被穿上时的音效，关于SoundEvent的知识后续会有专门讲解，你可以先复用原版的SoundEvent，它们均位于SoundEvents类中；toughness指的是盔甲韧性值。&lt;br /&gt;
&lt;br /&gt;
== 创建一套新盔甲 ==&lt;br /&gt;
我们首先看看ItemArmor类的构造方法：&amp;lt;code&amp;gt;(ItemArmor.ArmorMaterial materialIn, int renderIndexIn, EntityEquipmentSlot equipmentSlotIn)&amp;lt;/code&amp;gt;。其中renderIndex在原版中没有任何实际用途，乱填一个就好，而最后一个EntityEquipmentSlot参数代表了它是头盔/胸甲/护腿/靴子中的哪一种。因此，要创建一整套新盔甲，你需要new ItemArmor类或其子类4次。&lt;br /&gt;
&lt;br /&gt;
原版的ItemArmor类的自定义空间几乎为0，不过Forge在Item类中patch了一些专门针对盔甲的方法：&lt;br /&gt;
* &amp;lt;code&amp;gt;void onArmorTick(World world, EntityPlayer player, ItemStack stack)&amp;lt;/code&amp;gt;：这个方法会在盔甲穿上后每一tick都会被调用一次，通常你可以用这个方法来给盔甲加上穿戴药水效果之类的东西。&lt;br /&gt;
* &amp;lt;code&amp;gt;String getArmorTexture(ItemStack stack, Entity entity, EntityEquipmentSlot slot, String type)&amp;lt;/code&amp;gt;：这个方法用于确定盔甲穿在身上对应的纹理，返回一个String，实际上这个String的格式类似于ResourceLocation用于指代具体文件时的格式（且路径被写全了）。例：原版的铁套护腿的材质的路径为&amp;lt;code&amp;gt;assets/minecraft/textures/models/armor/iron_layer_2.png&amp;lt;/code&amp;gt;，那么体现在这里，返回的String应为&amp;lt;code&amp;gt;&amp;quot;minecraft:textures/models/armor/iron_layer_2.png&amp;quot;&amp;lt;/code&amp;gt;。看起来你可以很自由地指定材质文件，实则不然——除非你给盔甲自定义一个新的模型，否则盔甲会自动使用原版默认模型，那么材质文件必须有2份——其中一张包含头盔、胸甲、靴子的贴图，另外一张包含护腿的贴图，且实际的贴图在材质文件中对应的UV坐标有严格限制。因此如果你不指定自定义模型，那么在这里是要判断传入的EntityEquipmentSlot，并根据情况返回2个路径；至于材质文件，你基本上只能在原版的基础上修改。&lt;br /&gt;
* &amp;lt;code&amp;gt;ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack stack, EntityEquipmentSlot armorSlot, ModelBiped _default)&amp;lt;/code&amp;gt;：这个方法用于给盔甲指定自定义模型，指定后我们就可以自由地根据自己的需求调整材质文件的方方面面了。注意到返回一个ModelBiped，它实际上是ModelBase的子类，关于ModelBase的相关知识，详见后续教程内容。&lt;br /&gt;
&lt;br /&gt;
=== ISpecialArmor ===&lt;br /&gt;
Forge提供了一个接口：ISpecialArmor，通过让我们的ItemArmor子类实现该接口，就能实现对盔甲各方面属性的更精细的调控。该接口有一个静态内部类：ArmorProperties，主要用于阐明盔甲的伤害吸收能力。&lt;br /&gt;
 public interface ISpecialArmor{&lt;br /&gt;
    ArmorProperties getProperties(EntityLivingBase player, @Nonnull ItemStack armor, DamageSource source, double damage, int slot);&lt;br /&gt;
 &lt;br /&gt;
    int getArmorDisplay(EntityPlayer player, @Nonnull ItemStack armor, int slot);&lt;br /&gt;
 &lt;br /&gt;
    void damageArmor(EntityLivingBase entity, @Nonnull ItemStack stack, DamageSource source, int damage, int slot);&lt;br /&gt;
&lt;br /&gt;
    ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
通过实现该接口，我们可以设定：该盔甲的ArmorProperties，穿戴后在HUD中显示的护甲值的多少，等等。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/14.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%BA%8C%EF%BC%89_-_%E6%AD%A6%E5%99%A8%E5%92%8C%E5%B7%A5%E5%85%B7&amp;diff=55037</id>
		<title>用户:MashKJo/1.12.2模组开发教程/14.高级物品（二） - 武器和工具</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/14.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%BA%8C%EF%BC%89_-_%E6%AD%A6%E5%99%A8%E5%92%8C%E5%B7%A5%E5%85%B7&amp;diff=55037"/>
		<updated>2025-01-20T15:34:32Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“武器和工具体系，是Minecraft的一大特色。原版中武器包括剑和弓箭，工具则包括镐、斧、铲、锄、剪刀。若我们想给模组添加新的武器和工具，通常只需复用原版写好的类即可。  小提示：武器/工具的物品模型文件，其父模型应该设定为&amp;lt;code&amp;gt;minecraft:item/handheld&amp;lt;/code&amp;gt;。  == 工具质地（ToolMaterial） == 剑和绝大部分工具的类的构造参数都需要一个Item.ToolMateri…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;武器和工具体系，是Minecraft的一大特色。原版中武器包括剑和弓箭，工具则包括镐、斧、铲、锄、剪刀。若我们想给模组添加新的武器和工具，通常只需复用原版写好的类即可。&lt;br /&gt;
&lt;br /&gt;
小提示：武器/工具的物品模型文件，其父模型应该设定为&amp;lt;code&amp;gt;minecraft:item/handheld&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== 工具质地（ToolMaterial） ==&lt;br /&gt;
剑和绝大部分工具的类的构造参数都需要一个Item.ToolMaterial参数，该类是一个枚举类，用于指定该武器/工具的质地——木质、石质、铁质等等，和Material一样，该类用于封装武器/工具的一些固有属性，如：伤害加成、挖掘速度、附魔能力等等。原版提供了5种ToolMaterial，作为Modder，我们肯定有自定义工具质地的需求，但枚举类是不能在除它之外的地方产生新实例的，那么怎么办？&lt;br /&gt;
Forge提供了一个名为EnumHelper的类来解决“给枚举类在其之外的地方产生新实例”这种需求，原理自然是反射等一系列黑魔法。&lt;br /&gt;
&lt;br /&gt;
我们只需这么写：&lt;br /&gt;
 public static final Item.ToolMaterial MY_CUSTOM_TOOL_MATERIAL = EnumHelper.addToolMaterial(name, harvestLevel, maxUses, efficiency, damage, enchantability);&lt;br /&gt;
&lt;br /&gt;
name即该ToolMaterial的名称，为小写字母+下划线的形式；harvestLevel为该质地对应工具的挖掘等级；maxUses即最大耐久值；efficiency为该质地对应工具的挖掘速度；damage代表该质地对于攻击伤害的加成——注意，是该质地的加成，不是具体的武器/工具的加成，实际上后者还会有一个额外加成，因此在没有其他因素干扰时，玩家手持武器/工具造成的伤害为：空手伤害（1）+质地加成的伤害+武器/工具对应的种类加成的伤害；enchantability代表该质地对应的武器/工具获得优秀附魔的能力，是一个int整数，值越大该能力越强。&lt;br /&gt;
上述有些数值，如果读者觉得有些拿不准，先看看原版的5个ToolMaterial在这里填写的数值都是什么，方便读者自行取舍填入的数值。&lt;br /&gt;
&lt;br /&gt;
然而，新的ToolMaterial并非必须——它只是对武器/工具的一些固有属性的封装，我们可以通过覆写类下一些方法来达到同样的效果，因此如果你只是制作单一的武器/工具而非一整套，你也大可胡乱填入一个ToolMaterial，并覆写父类下一系列方法来实现更精细的调控。&lt;br /&gt;
&lt;br /&gt;
== 新的武器 ==&lt;br /&gt;
=== 剑 ===&lt;br /&gt;
所有的剑都是ItemSword类的实例，简单复用该类即可。你可以通过覆写&amp;lt;code&amp;gt;MultiMap&amp;lt;String, AttributeModifier&amp;gt; getItemAttributeModifiers(EntityEquipmentSlot equipmentSlot)&amp;lt;/code&amp;gt;方法来实现自定义剑的攻击伤害，这块涉及到生物属性和属性修饰符的知识，详见后续教程。&lt;br /&gt;
&lt;br /&gt;
=== 盾牌 ===&lt;br /&gt;
盾牌对应的类是ItemShield，我们可以先继承再实例化；不过Forge给了我们一个更为灵活的方案：覆写&amp;lt;code&amp;gt;Item#isShield&amp;lt;/code&amp;gt;方法即可。该方法是Forge patch进Item类的，这样我们就可以让一个物品对应的类不必继承于ItemShield类而拥有盾牌的特性。&lt;br /&gt;
&lt;br /&gt;
=== 弓箭 ===&lt;br /&gt;
弓对应的类是ItemBow类，你可以通过继承该类来创建和原版不相同的弓，通过覆写方法，你可以定义：该弓所使用的箭矢满足的条件，拉弓至满弦的时长，弓箭被发出时的初速度等。弓所能决定的东西也差不多只有这些，至于发出的箭矢打在实体上造成的效果，那是由EntityArrow决定的。&lt;br /&gt;
原版的箭矢物品对应的类是ItemArrow，但它只有2个方法，且都是配合着ItemBow用的辅助方法，因此新的箭矢物品你完全可以直接new Item类即可，重点在于箭矢实体。我们没必要创建EntityArrow的子类，除非真的有特殊需求。我们完全可以直接复用EntityArrow类，然后我们监听ProjectileImpactEvent.Arrow事件，从该事件中我们能获取箭矢实体本身，以及一个RayTraceResult，由此我们就能实现我们想要实现的自定义逻辑。那么如果我们需要获取发射出该EntityArrow的弓的信息怎么办？很简单，射出该箭矢时给对应的EntityArrow上个NBT就好了。关于实体的知识，详见后续教程。&lt;br /&gt;
&lt;br /&gt;
== 新的工具 ==&lt;br /&gt;
=== ItemTool ===&lt;br /&gt;
这块可能有点反直觉——虽说这个类叫ItemTool，但是只有ItemPickaxe、ItemSpade和ItemAxe三个类继承了该类——即镐、铲和斧。简单复用这三个类即可创建新的镐、铲或斧。&lt;br /&gt;
如果你想创建新的工具类型，可以选择继承ItemTool类。该工具能挖掘什么样的方块而产生掉落物，你可以通过覆写&amp;lt;code&amp;gt;boolean canHarvestBlock（IBlockState state)&amp;lt;/code&amp;gt;来实现。有读者可能要问：走Forge提供的那套setHarvestLevel机制不行吗？确实可行，覆写Item类下的getHarvestLevel和getToolClasses这2个方法即可，注意后者返回的是一个Set&amp;lt;String&amp;gt;，也就是说你的新工具类型实际上可以代表不止一种“采掘类型”。&lt;br /&gt;
&lt;br /&gt;
=== 锄、剪刀以及其他 ===&lt;br /&gt;
锄和剪刀对应的类分别是ItemHoe和ItemShears。ItemHoe没有作为工具的通用行为，它没有对应的toolClass，也不会加速任何方块的破坏或造成任何方块的掉落，唯一的作用就是右键把泥土变成耕地。因此，与其说它是一种工具类型，不如说只是一种特殊的物品罢了。&lt;br /&gt;
而ItemShears则比较有趣，经由Forge的一番patch之后，剪刀在破坏方块或右键与实体交互时，会检查它们是否实现了IShearable接口，如果是，则执行接口所规定的方法。因此，如果你想创建新的带有特殊逻辑的剪刀，且特殊逻辑仅涉及你自己新增的方块/实体，那么你甚至都不需要搞个子类，直接new ItemShears类，再让你的方块/实体实现IShearable接口，实现的时候判断一下ItemStack的物品种类即可。&lt;br /&gt;
剪刀在挖掘特定方块（蜘蛛网、羊毛等）时速度较快或可以产生掉落物等行为，是因为它覆写了canHarvestBlock和getDestroySpeed方法。&lt;br /&gt;
所以你在创建新的工具物品的时候，如果它和剪刀一样，挖掘方块的情况很多都是特例，那么就不用继承ItemTool类，模仿ItemShears的思路直接继承Item类，再覆写一些方法即可。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55036</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55036"/>
		<updated>2025-01-20T15:33:38Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念|11.高级物品和方块 - 前置概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/12.高级物品和方块 - 常用的待覆写方法|12.高级物品和方块 - 常用的待覆写方法]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/13.高级物品（一） - 食物和饮料|13.高级物品（一） - 食物和饮料]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/14.高级物品（二） - 武器和工具|14.高级物品（二） - 武器和工具]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/15.高级物品（三） - 盔甲|15.高级物品（三） - 盔甲]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/16.高级物品（四） - 高级物品模型|16.高级物品（四） - 高级物品模型]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/17.矿物词典和各类配方|17.矿物词典和各类配方]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/13.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%B8%80%EF%BC%89_-_%E9%A3%9F%E7%89%A9%E5%92%8C%E9%A5%AE%E6%96%99&amp;diff=55032</id>
		<title>用户:MashKJo/1.12.2模组开发教程/13.高级物品（一） - 食物和饮料</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/13.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%EF%BC%88%E4%B8%80%EF%BC%89_-_%E9%A3%9F%E7%89%A9%E5%92%8C%E9%A5%AE%E6%96%99&amp;diff=55032"/>
		<updated>2025-01-04T08:34:21Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“在1.13+的Minecraft源代码中，食物和流体类似，都是被单独分离成了一个特殊的概念——流体对应Fluid类，而食物对应Food类，而声明某物品是一种食物，则需要调用&amp;lt;code&amp;gt;Item.Properties#food&amp;lt;/code&amp;gt;方法来完成。但在1.12中，食物仅仅是一种特殊的物品——ItemFood。因此，我们可以通过简单复用该类创建出许多种食物。  ItemFood类的构造器为&amp;lt;code&amp;gt;(int amount, float saturati…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在1.13+的Minecraft源代码中，食物和流体类似，都是被单独分离成了一个特殊的概念——流体对应Fluid类，而食物对应Food类，而声明某物品是一种食物，则需要调用&amp;lt;code&amp;gt;Item.Properties#food&amp;lt;/code&amp;gt;方法来完成。但在1.12中，食物仅仅是一种特殊的物品——ItemFood。因此，我们可以通过简单复用该类创建出许多种食物。&lt;br /&gt;
&lt;br /&gt;
ItemFood类的构造器为&amp;lt;code&amp;gt;(int amount, float saturation, boolean isWolfFood)&amp;lt;/code&amp;gt;。amount为所恢复的饥饿值，saturation为所恢复的饱食度的比例系数（而真正的饱和度值计算公式为&amp;lt;code&amp;gt;2 * amount * saturation&amp;lt;/code&amp;gt;），而isWolfFood则用于决定该食物是否能被用于驯服狼狗。前两个参数均有对应的getter可以用，从这些getter中你可以拿到对应的ItemStack，也就是说你可以配合着meta-hack来覆写这2个getter。&lt;br /&gt;
&lt;br /&gt;
此外，该类还有2个setter可以用：&lt;br /&gt;
* setAlwaysEdible：用于设定：无论玩家的饥饿值是否是满的，均可以食用该食物。&lt;br /&gt;
* setPotionEffect：用于设定：吃下该食物后，获得的药水效果，第一个参数要求一个PotionEffect，这个后面会讲到的，它的用法和ItemStack其实是类似的，而Potion之于PotionEffect就相当于Item之于ItemStack。第二个参数是&amp;lt;code&amp;gt;float probability&amp;lt;/code&amp;gt;，取值范围为[0.0F, 1.0F]，这个其实是表示产生预期药水效果的概率（想想看生鸡肉吧）——1.0F即代表100%，即一定会发生。&lt;br /&gt;
&lt;br /&gt;
不过显然，光是一个setPotionEffect，根本没法满足大部分模组甚至于原版的需求——例如原版的金苹果就追加了不止一种PotionEffect，而该方法却只能传入一个PotionEffect。所以我们实际上可以覆写&amp;lt;code&amp;gt;onFoodEaten&amp;lt;/code&amp;gt;这个方法。形参列表为&amp;lt;code&amp;gt;(ItemStack stackIn, World worldIn, EntityPlayer playerIn)&amp;lt;/code&amp;gt;，显然这是个很丰富的形参列表，因此，我们不妨放开点思路——我们真的只能在这里追加不止一种PotionEffect吗？实际上我们可以在这里写任何逻辑。&lt;br /&gt;
&lt;br /&gt;
创建一种新食物很简单，那么新的饮料呢，比如原版的牛奶桶和药水？很遗憾，原版并没有提供专门的饮料物品类，不过也没关系，我们自己从头写就好。让我们想想看一种饮料物品应当具有怎样的行为？答案是：有饮用的时长、动画和音效。没错，就这么简单。还记得上一节的内容吗？对于设定饮用的时长，我们可以覆写&amp;lt;code&amp;gt;getMaxItemUseDuration&amp;lt;/code&amp;gt;，单位为tick（游戏刻，原版的食物和饮料默认为32ticks），想追加饮用后的特定逻辑的话，再覆写&amp;lt;code&amp;gt;onItemUseFinish&amp;lt;/code&amp;gt;即可。至于饮用的动画和音效，这些是由枚举对象EnumAction.DRINK来决定的。另外，由于设定上饮料一般都是由容器盛装的，因此我们通常会设定该饮料物品的containerItem，以及喝完它后返还给我们1个该containerItem&amp;lt;ref&amp;gt;原版的牛奶桶并未设定其containerItem（设定上应该是桶），所以原版中有“拿牛奶桶做蛋糕，蛋糕把三个桶生吞了”这种奇葩现象。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
 import net.minecraft.entity.EntityLivingBase;&lt;br /&gt;
 import net.minecraft.entity.player.EntityPlayer;&lt;br /&gt;
 import net.minecraft.item.EnumAction;&lt;br /&gt;
 import net.minecraft.item.Item;&lt;br /&gt;
 import net.minecraft.item.ItemStack;&lt;br /&gt;
 import net.minecraft.world.World;&lt;br /&gt;
 &lt;br /&gt;
 public class MyCustomDrink extends Item{&lt;br /&gt;
    public MyCustomDrink(){&lt;br /&gt;
        this.setRegistryName(...);&lt;br /&gt;
        this.setUnlocalizedName(...);&lt;br /&gt;
        this.setCreativeTab(...);&lt;br /&gt;
 &lt;br /&gt;
        this.setMaxStackSize(1);&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    @Override&lt;br /&gt;
    public ItemStack getContainerItem(ItemStack stack){&lt;br /&gt;
        ...&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    @Override&lt;br /&gt;
    public int getMaxItemUseDuration(ItemStack stack){&lt;br /&gt;
        return 32;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    @Override&lt;br /&gt;
    public EnumAction getItemUseAction(ItemStack stack){&lt;br /&gt;
        return EnumAction.DRINK;&lt;br /&gt;
    }&lt;br /&gt;
 &lt;br /&gt;
    @Override&lt;br /&gt;
    public ItemStack onItemUseFinish(ItemStack stack, World worldIn, EntityLivingBase entityLiving){&lt;br /&gt;
        ... //Do your custom game logics...&lt;br /&gt;
 &lt;br /&gt;
        ItemStack container = getContainerItem(stack);&lt;br /&gt;
 &lt;br /&gt;
        if(entityLiving instanceof EntityPlayer &amp;amp;&amp;amp; !((EntityPlayer)entityLiving).capabilities.isCreativeMode){&lt;br /&gt;
            stack.shrink(1);&lt;br /&gt;
        }&lt;br /&gt;
        return stack.isEmpty() ? container : stack;&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
就是这样，实际上该类的实现和ItemFood是大同小异的。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55031</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55031"/>
		<updated>2025-01-04T07:34:42Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念|11.高级物品和方块 - 前置概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/12.高级物品和方块 - 常用的待覆写方法|12.高级物品和方块 - 常用的待覆写方法]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/13.高级物品（一） - 食物和饮料|13.高级物品（一） - 食物和饮料]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55030</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55030"/>
		<updated>2025-01-04T06:51:11Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念|11.高级物品和方块 - 前置概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/12.高级物品和方块 - 常用的待覆写方法|12.高级物品和方块 - 常用的待覆写方法]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/13.高级物品（一） - 食物|13.高级物品（一） - 食物]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/12.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BE%85%E8%A6%86%E5%86%99%E6%96%B9%E6%B3%95&amp;diff=55029</id>
		<title>用户:MashKJo/1.12.2模组开发教程/12.高级物品和方块 - 常用的待覆写方法</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/12.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BE%85%E8%A6%86%E5%86%99%E6%96%B9%E6%B3%95&amp;diff=55029"/>
		<updated>2025-01-03T12:36:26Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“对于高级物品，Item类有如下常用的方法值得覆写： * onItemUseFirst、onItemUse：在物品被玩家手持，且右键非空气方块时触发，均返回一个EnumActionResult，前者先于后者被触发。对于前者而言，返回EnumActionResult.PASS，则代表会继续执行原版的相关逻辑。 * onItemUseFinish：该方法和上述2个方法有所区别——该方法限定的实体并不仅仅是玩家（EntityPlayer）了，而…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;对于高级物品，Item类有如下常用的方法值得覆写：&lt;br /&gt;
* onItemUseFirst、onItemUse：在物品被玩家手持，且右键非空气方块时触发，均返回一个EnumActionResult，前者先于后者被触发。对于前者而言，返回EnumActionResult.PASS，则代表会继续执行原版的相关逻辑。&lt;br /&gt;
* onItemUseFinish：该方法和上述2个方法有所区别——该方法限定的实体并不仅仅是玩家（EntityPlayer）了，而是所有生物（EntityLivingBase）。它返回一个ItemStack，代表某个ItemStack被使用后的结果。&lt;br /&gt;
* onItemRightClick：该方法会在玩家手持物品右键时触发——没错，它不要求玩家必须选中某个非空气方块，只要右键就行，实际上它也先于onItemUseFirst和onItemUse触发。Minecraft本身只提供了修改右键物品的逻辑的该方法，那么如果我们想干涉左键逻辑怎么办？考虑监听这两个事件：PlayerInteractEvent.LeftClickBlock和PlayerInteractEvent.LeftClickEmpty，具体在监听时可以判断玩家的手持物品，再写入相关逻辑，即可。&lt;br /&gt;
* canHarvestBlock：返回一个布尔值，注意该方法有2个重载，其中一个是原版的，还有一个是Forge塞进来的，当然推荐使用后者。用于判断某个BlockState是否可以被该Item对应的某个ItemStack挖掘并产生掉落物，原版中该方法的实现很丑陋，全是面向结果编程的味道，且鉴于Forge patch的&amp;lt;code&amp;gt;Block#setHarvestLevel&amp;lt;/code&amp;gt;这一方法的存在，一般你不该动这个方法，不过如果你想做出像剪刀那样比较特殊的挖掘、掉落效果，就可以覆写这个方法。&lt;br /&gt;
* onUpdate：这是一个比较重要的方法，它在游戏主循环中，每一tick都会被调用一次，也就是说理想状态下每秒钟内两个逻辑端各会调用该方法20次。因此如果我们覆写了该方法，便可以赋予相应的ItemStack一秒刷新20次的能力，我们利用它可以做出一些很高级的物品，例如计时器之类的东西。从该方法中我们可以拿到持有该ItemStack的实体（没错，Entity，甚至都非EntityLivingBase），以及“该物品是否被选中”（isSelected）。&lt;br /&gt;
* onEntityItemUpdate：决定当前ItemStack对应的EntityItem的刷新逻辑。&lt;br /&gt;
* onCreated：在该物品被合成出来后触发。&lt;br /&gt;
* getMaxItemUseDuration：用于指定物品被右键按住使用后，持续的最长时间——如右键弓后弓会逐渐拉伸至蓄满力状态，蓄满力后一段时间内，如果不松开右键，弓会一直紧绷着。注意这个方法的返回值是一个int值，单位实际上是tick。&lt;br /&gt;
* onPlayerStoppedUsing：配合着getMaxItemUseDuration使用的——即玩家松开右键后执行的游戏逻辑，原版的弓在这里即会召唤出一发箭矢实体（EntityArrow）。&lt;br /&gt;
* addInformation：@SideOnly(Side.CLIENT)，用于给ItemStack添加tooltip，具体为向一个List&amp;lt;String&amp;gt;中添加新元素的过程，一般是搭配&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;实现，甚至我们可以通过当前ItemStack的附加NBT信息来决定添加的tooltip。&lt;br /&gt;
* hasEffect：@SideOnly(Side.CLIENT)，用于决定该ItemStack是否有附魔光泽——没错，ItemStack有无附魔和有无附魔光泽的对应关系并不是写死的，只是Item类对该方法的默认实现即为：有附魔才有附魔光泽，你大可给你的Item子类在这里无脑返回一个true。&lt;br /&gt;
* getItemBurnTime：如果你想添加熔炉燃料，那么覆写这个方法即可。返回一个正整数，则默认它是一个熔炉燃料，且单位为tick；若返回0，则代表它不可燃；返回-1，则代表它的可燃状态交给原版Minecraft去决定。&lt;br /&gt;
* getIsRepairable：用于决定该ItemStack可被什么ItemStack修复。两个参数中，toRepair是被修复的，repair是修复材料。&lt;br /&gt;
&lt;br /&gt;
对于高级方块，Block类下有如下常用方法值得覆写：&lt;br /&gt;
* isPassable：决定实体是否可以从该方块中穿过，参考原版的门方块，就不难理解了。&lt;br /&gt;
* isReplacable：决定该方块是否可以在其他方块被放置时取代——如果不能，则方块会被放置在该方块的侧面。如原版的液体方块和空气方块在这里返回true。&lt;br /&gt;
* neighborChanged：覆写该方法，即可自定义该种方块的周围发生方块更新时执行的游戏逻辑。一般有两个用途：1.激发红石信号，如观察者方块；2.触发对该方块的状态的合法性检验（这里的“状态”不仅仅包括BlockState，是一种抽象的概念），如悬空的沙子在周围有方块更新时会立刻下坠。&lt;br /&gt;
* breakBlock：在方块被破坏后触发，注意不限于被玩家破坏（如果想要限定被玩家破坏才执行逻辑，可以考虑覆写onBlockDestroyedByPlayer），但使用&amp;lt;code&amp;gt;World#setBlockState&amp;lt;/code&amp;gt;替换该方块时，不会触发这个方法。&lt;br /&gt;
* onBlockActivated：玩家右键点击该方块后触发，注意两只手都会触发一次，所以最好先判定传入的EnumHand。原版的工作台和熔炉，在这里，就是让玩家打开相应的GUI界面。&lt;br /&gt;
* getStateForPlacement：上一节已经提到过，不再赘述。&lt;br /&gt;
* onBlockClicked：这个方法从MCP得到的名字可能会很有迷惑性，读者可能会以为它在左键、右键被按下时都会触发，实际上不对，只有左键点击它会触发该方法。&lt;br /&gt;
* onBlockPlacedBy：在某个生物实体放置该方块时被触发，注意不限于玩家，像末影人这种非玩家生物也可以。&lt;br /&gt;
* isFlammable、getFlammability等一系列方法：用于决定该方块是否可燃、燃烧能力等一系列和火方块相关的参数。按照Minecraft原版的设计，这些东西本来应该通过&amp;lt;code&amp;gt;BlockFire#setFireInfo&amp;lt;/code&amp;gt;这个公开方法指定，不过经过Forge的patch后，我们便可以在Block类中覆写方法指定了。&lt;br /&gt;
* getDrops：得到该方块所有可能的掉落物（以一个NonNullList&amp;lt;ItemStack&amp;gt;的形式呈现），由于每破坏一次该种方块，该方法就会调用一次，所以在这里可以利用利用随机数。&lt;br /&gt;
* getExpDrop：得到该方块被采集时，掉落的经验量。&lt;br /&gt;
* isReplacableOreGen：决定：在区块生成的过程中，已经被生成的该种方块是否可以被矿石取代。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55028</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55028"/>
		<updated>2025-01-03T12:36:01Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念|11.高级物品和方块 - 前置概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/12.高级物品和方块 - 常用的待覆写方法|12.高级物品和方块 - 常用的待覆写方法]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55027</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=55027"/>
		<updated>2025-01-03T12:35:31Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念|11.高级物品和方块 - 前置概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/12.高级物品和方块 - 常用的待覆写方法|12.常用的待覆写方法]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55026</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55026"/>
		<updated>2025-01-03T12:33:02Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
想必读者也看到了，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。3个返回值没有固定的含义，读者在使用前请先查看相关的代码。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3i和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，i代表int，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumActionResult typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
=== 在世界中放置方块 ===&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 常量名及含义 !! 数值（二进制形式）&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55020</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55020"/>
		<updated>2024-12-29T14:52:56Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 在世界中放置方块 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
对于高级物品，Item类有如下常用的方法值得覆写：&lt;br /&gt;
* onItemUseFirst、onItemUse：在物品被玩家手持，且右键非空气方块时触发，均返回一个EnumActionResult，前者先于后者被触发。对于前者而言，返回EnumActionResult.PASS，则代表会继续执行原版的相关逻辑。&lt;br /&gt;
* onItemUseFinish：该方法和上述2个方法有所区别——该方法限定的实体并不仅仅是玩家（EntityPlayer）了，而是所有生物（EntityLivingBase）。它返回一个ItemStack，代表某个ItemStack被使用后的结果。&lt;br /&gt;
* onItemRightClick：该方法会在玩家手持物品右键时触发——没错，它不要求玩家必须选中某个非空气方块，只要右键就行，实际上它也先于onItemUseFirst和onItemUse触发。Minecraft本身只提供了修改右键物品的逻辑的该方法，那么如果我们想干涉左键逻辑怎么办？考虑监听这两个事件：PlayerInteractEvent.LeftClickBlock和PlayerInteractEvent.LeftClickEmpty，具体在监听时可以判断玩家的手持物品，再写入相关逻辑，即可。&lt;br /&gt;
* canHarvestBlock：返回一个布尔值，注意该方法有2个重载，其中一个是原版的，还有一个是Forge塞进来的，当然推荐使用后者。用于判断某个BlockState是否可以被该Item对应的某个ItemStack挖掘并产生掉落物，原版中该方法的实现很丑陋，全是面向结果编程的味道，且鉴于Forge patch的&amp;lt;code&amp;gt;Block#setHarvestLevel&amp;lt;/code&amp;gt;这一方法的存在，一般你不该动这个方法，不过如果你想做出像剪刀那样比较特殊的挖掘、掉落效果，就可以覆写这个方法。&lt;br /&gt;
* onUpdate：这是一个比较重要的方法，它在游戏主循环中，每一tick都会被调用一次，也就是说理想状态下每秒钟内两个逻辑端各会调用该方法20次。因此如果我们覆写了该方法，便可以赋予相应的ItemStack一秒刷新20次的能力，我们利用它可以做出一些很高级的物品，例如计时器之类的东西。从该方法中我们可以拿到持有该ItemStack的实体（没错，Entity，甚至都非EntityLivingBase），以及“该物品是否被选中”（isSelected）。&lt;br /&gt;
* onEntityItemUpdate：决定当前ItemStack对应的EntityItem的刷新逻辑。&lt;br /&gt;
* onCreated：在该物品被合成出来后触发。&lt;br /&gt;
* getMaxItemUseDuration：用于指定物品被右键按住使用后，持续的最长时间——如右键弓后弓会逐渐拉伸至蓄满力状态，蓄满力后一段时间内，如果不松开右键，弓会一直紧绷着。注意这个方法的返回值是一个int值，单位实际上是tick。&lt;br /&gt;
* onPlayerStoppedUsing：配合着getMaxItemUseDuration使用的——即玩家松开右键后执行的游戏逻辑，原版的弓在这里即会召唤出一发箭矢实体（EntityArrow）。&lt;br /&gt;
* addInformation：@SideOnly(Side.CLIENT)，用于给ItemStack添加tooltip，具体为向一个List&amp;lt;String&amp;gt;中添加新元素的过程，一般是搭配&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;实现，甚至我们可以通过当前ItemStack的附加NBT信息来决定添加的tooltip。&lt;br /&gt;
* hasEffect：@SideOnly(Side.CLIENT)，用于决定该ItemStack是否有附魔光泽——没错，ItemStack有无附魔和有无附魔光泽的对应关系并不是写死的，只是Item类对该方法的默认实现即为：有附魔才有附魔光泽，你大可给你的Item子类在这里无脑返回一个true。&lt;br /&gt;
* getItemBurnTime：如果你想添加熔炉燃料，那么覆写这个方法即可。返回一个正整数，则默认它是一个熔炉燃料，且单位为tick；若返回0，则代表它不可燃；返回-1，则代表它的可燃状态交给原版Minecraft去决定。&lt;br /&gt;
* getIsRepairable：用于决定该ItemStack可被什么ItemStack修复。两个参数中，toRepair是被修复的，repair是修复材料。&lt;br /&gt;
&lt;br /&gt;
对于高级方块，Block类下有如下常用方法值得覆写：&lt;br /&gt;
* isPassable：决定实体是否可以从该方块中穿过，参考原版的门方块，就不难理解了。&lt;br /&gt;
* isReplacable：决定该方块是否可以在其他方块被放置时取代——如果不能，则方块会被放置在该方块的侧面。如原版的液体方块和空气方块在这里返回true。&lt;br /&gt;
* neighborChanged：覆写该方法，即可自定义该种方块的周围发生方块更新时执行的游戏逻辑。一般有两个用途：1.激发红石信号，如观察者方块；2.触发对该方块的状态的合法性检验（这里的“状态”不仅仅包括BlockState，是一种抽象的概念），如悬空的沙子在周围有方块更新时会立刻下坠。&lt;br /&gt;
* breakBlock：在方块被破坏后触发，注意不限于被玩家破坏（如果想要限定被玩家破坏才执行逻辑，可以考虑覆写onBlockDestroyedByPlayer），但使用&amp;lt;code&amp;gt;World#setBlockState&amp;lt;/code&amp;gt;替换该方块时，不会触发这个方法。&lt;br /&gt;
* onBlockActivated：玩家右键点击该方块后触发，注意两只手都会触发一次，所以最好先判定传入的EnumHand。原版的工作台和熔炉，在这里，就是让玩家打开相应的GUI界面。&lt;br /&gt;
* getStateForPlacement：上一节已经提到过，不再赘述。&lt;br /&gt;
* onBlockClicked：这个方法从MCP得到的名字可能会很有迷惑性，读者可能会以为它在左键、右键被按下时都会触发，实际上不对，只有左键点击它会触发该方法。&lt;br /&gt;
* onBlockPlacedBy：在某个生物实体放置该方块时被触发，注意不限于玩家，像末影人这种非玩家生物也可以。&lt;br /&gt;
* isFlammable、getFlammability等一系列方法：用于决定该方块是否可燃、燃烧能力等一系列和火方块相关的参数。按照Minecraft原版的设计，这些东西本来应该通过&amp;lt;code&amp;gt;BlockFire#setFireInfo&amp;lt;/code&amp;gt;这个公开方法指定，不过经过Forge的patch后，我们便可以在Block类中覆写方法指定了。&lt;br /&gt;
* getDrops：得到该方块所有可能的掉落物（以一个NonNullList&amp;lt;ItemStack&amp;gt;的形式呈现），由于每破坏一次该种方块，该方法就会调用一次，所以在这里可以利用利用随机数。&lt;br /&gt;
* getExpDrop：得到该方块被采集时，掉落的经验量。&lt;br /&gt;
* isReplacableOreGen：决定：在区块生成的过程中，已经被生成的该种方块是否可以被矿石取代。&lt;br /&gt;
&lt;br /&gt;
想必读者也看到了，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。3个返回值没有固定的含义，读者在使用前请先查看相关的代码。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3i和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，i代表int，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumActionResult typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
=== 在世界中放置方块 ===&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 常量名及含义 !! 数值（二进制形式）&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55019</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55019"/>
		<updated>2024-12-29T14:48:37Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* ActionResult&amp;lt;T&amp;gt; */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
对于高级物品，Item类有如下常用的方法值得覆写：&lt;br /&gt;
* onItemUseFirst、onItemUse：在物品被玩家手持，且右键非空气方块时触发，均返回一个EnumActionResult，前者先于后者被触发。对于前者而言，返回EnumActionResult.PASS，则代表会继续执行原版的相关逻辑。&lt;br /&gt;
* onItemUseFinish：该方法和上述2个方法有所区别——该方法限定的实体并不仅仅是玩家（EntityPlayer）了，而是所有生物（EntityLivingBase）。它返回一个ItemStack，代表某个ItemStack被使用后的结果。&lt;br /&gt;
* onItemRightClick：该方法会在玩家手持物品右键时触发——没错，它不要求玩家必须选中某个非空气方块，只要右键就行，实际上它也先于onItemUseFirst和onItemUse触发。Minecraft本身只提供了修改右键物品的逻辑的该方法，那么如果我们想干涉左键逻辑怎么办？考虑监听这两个事件：PlayerInteractEvent.LeftClickBlock和PlayerInteractEvent.LeftClickEmpty，具体在监听时可以判断玩家的手持物品，再写入相关逻辑，即可。&lt;br /&gt;
* canHarvestBlock：返回一个布尔值，注意该方法有2个重载，其中一个是原版的，还有一个是Forge塞进来的，当然推荐使用后者。用于判断某个BlockState是否可以被该Item对应的某个ItemStack挖掘并产生掉落物，原版中该方法的实现很丑陋，全是面向结果编程的味道，且鉴于Forge patch的&amp;lt;code&amp;gt;Block#setHarvestLevel&amp;lt;/code&amp;gt;这一方法的存在，一般你不该动这个方法，不过如果你想做出像剪刀那样比较特殊的挖掘、掉落效果，就可以覆写这个方法。&lt;br /&gt;
* onUpdate：这是一个比较重要的方法，它在游戏主循环中，每一tick都会被调用一次，也就是说理想状态下每秒钟内两个逻辑端各会调用该方法20次。因此如果我们覆写了该方法，便可以赋予相应的ItemStack一秒刷新20次的能力，我们利用它可以做出一些很高级的物品，例如计时器之类的东西。从该方法中我们可以拿到持有该ItemStack的实体（没错，Entity，甚至都非EntityLivingBase），以及“该物品是否被选中”（isSelected）。&lt;br /&gt;
* onEntityItemUpdate：决定当前ItemStack对应的EntityItem的刷新逻辑。&lt;br /&gt;
* onCreated：在该物品被合成出来后触发。&lt;br /&gt;
* getMaxItemUseDuration：用于指定物品被右键按住使用后，持续的最长时间——如右键弓后弓会逐渐拉伸至蓄满力状态，蓄满力后一段时间内，如果不松开右键，弓会一直紧绷着。注意这个方法的返回值是一个int值，单位实际上是tick。&lt;br /&gt;
* onPlayerStoppedUsing：配合着getMaxItemUseDuration使用的——即玩家松开右键后执行的游戏逻辑，原版的弓在这里即会召唤出一发箭矢实体（EntityArrow）。&lt;br /&gt;
* addInformation：@SideOnly(Side.CLIENT)，用于给ItemStack添加tooltip，具体为向一个List&amp;lt;String&amp;gt;中添加新元素的过程，一般是搭配&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;实现，甚至我们可以通过当前ItemStack的附加NBT信息来决定添加的tooltip。&lt;br /&gt;
* hasEffect：@SideOnly(Side.CLIENT)，用于决定该ItemStack是否有附魔光泽——没错，ItemStack有无附魔和有无附魔光泽的对应关系并不是写死的，只是Item类对该方法的默认实现即为：有附魔才有附魔光泽，你大可给你的Item子类在这里无脑返回一个true。&lt;br /&gt;
* getItemBurnTime：如果你想添加熔炉燃料，那么覆写这个方法即可。返回一个正整数，则默认它是一个熔炉燃料，且单位为tick；若返回0，则代表它不可燃；返回-1，则代表它的可燃状态交给原版Minecraft去决定。&lt;br /&gt;
* getIsRepairable：用于决定该ItemStack可被什么ItemStack修复。两个参数中，toRepair是被修复的，repair是修复材料。&lt;br /&gt;
&lt;br /&gt;
对于高级方块，Block类下有如下常用方法值得覆写：&lt;br /&gt;
* isPassable：决定实体是否可以从该方块中穿过，参考原版的门方块，就不难理解了。&lt;br /&gt;
* isReplacable：决定该方块是否可以在其他方块被放置时取代——如果不能，则方块会被放置在该方块的侧面。如原版的液体方块和空气方块在这里返回true。&lt;br /&gt;
* neighborChanged：覆写该方法，即可自定义该种方块的周围发生方块更新时执行的游戏逻辑。一般有两个用途：1.激发红石信号，如观察者方块；2.触发对该方块的状态的合法性检验（这里的“状态”不仅仅包括BlockState，是一种抽象的概念），如悬空的沙子在周围有方块更新时会立刻下坠。&lt;br /&gt;
* breakBlock：在方块被破坏后触发，注意不限于被玩家破坏（如果想要限定被玩家破坏才执行逻辑，可以考虑覆写onBlockDestroyedByPlayer），但使用&amp;lt;code&amp;gt;World#setBlockState&amp;lt;/code&amp;gt;替换该方块时，不会触发这个方法。&lt;br /&gt;
* onBlockActivated：玩家右键点击该方块后触发，注意两只手都会触发一次，所以最好先判定传入的EnumHand。原版的工作台和熔炉，在这里，就是让玩家打开相应的GUI界面。&lt;br /&gt;
* getStateForPlacement：上一节已经提到过，不再赘述。&lt;br /&gt;
* onBlockClicked：这个方法从MCP得到的名字可能会很有迷惑性，读者可能会以为它在左键、右键被按下时都会触发，实际上不对，只有左键点击它会触发该方法。&lt;br /&gt;
* onBlockPlacedBy：在某个生物实体放置该方块时被触发，注意不限于玩家，像末影人这种非玩家生物也可以。&lt;br /&gt;
* isFlammable、getFlammability等一系列方法：用于决定该方块是否可燃、燃烧能力等一系列和火方块相关的参数。按照Minecraft原版的设计，这些东西本来应该通过&amp;lt;code&amp;gt;BlockFire#setFireInfo&amp;lt;/code&amp;gt;这个公开方法指定，不过经过Forge的patch后，我们便可以在Block类中覆写方法指定了。&lt;br /&gt;
* getDrops：得到该方块所有可能的掉落物（以一个NonNullList&amp;lt;ItemStack&amp;gt;的形式呈现），由于每破坏一次该种方块，该方法就会调用一次，所以在这里可以利用利用随机数。&lt;br /&gt;
* getExpDrop：得到该方块被采集时，掉落的经验量。&lt;br /&gt;
* isReplacableOreGen：决定：在区块生成的过程中，已经被生成的该种方块是否可以被矿石取代。&lt;br /&gt;
&lt;br /&gt;
想必读者也看到了，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。3个返回值没有固定的含义，读者在使用前请先查看相关的代码。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3i和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，i代表int，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumActionResult typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
=== 在世界中放置方块 ===&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; || 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55018</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55018"/>
		<updated>2024-12-29T14:46:20Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
对于高级物品，Item类有如下常用的方法值得覆写：&lt;br /&gt;
* onItemUseFirst、onItemUse：在物品被玩家手持，且右键非空气方块时触发，均返回一个EnumActionResult，前者先于后者被触发。对于前者而言，返回EnumActionResult.PASS，则代表会继续执行原版的相关逻辑。&lt;br /&gt;
* onItemUseFinish：该方法和上述2个方法有所区别——该方法限定的实体并不仅仅是玩家（EntityPlayer）了，而是所有生物（EntityLivingBase）。它返回一个ItemStack，代表某个ItemStack被使用后的结果。&lt;br /&gt;
* onItemRightClick：该方法会在玩家手持物品右键时触发——没错，它不要求玩家必须选中某个非空气方块，只要右键就行，实际上它也先于onItemUseFirst和onItemUse触发。Minecraft本身只提供了修改右键物品的逻辑的该方法，那么如果我们想干涉左键逻辑怎么办？考虑监听这两个事件：PlayerInteractEvent.LeftClickBlock和PlayerInteractEvent.LeftClickEmpty，具体在监听时可以判断玩家的手持物品，再写入相关逻辑，即可。&lt;br /&gt;
* canHarvestBlock：返回一个布尔值，注意该方法有2个重载，其中一个是原版的，还有一个是Forge塞进来的，当然推荐使用后者。用于判断某个BlockState是否可以被该Item对应的某个ItemStack挖掘并产生掉落物，原版中该方法的实现很丑陋，全是面向结果编程的味道，且鉴于Forge patch的&amp;lt;code&amp;gt;Block#setHarvestLevel&amp;lt;/code&amp;gt;这一方法的存在，一般你不该动这个方法，不过如果你想做出像剪刀那样比较特殊的挖掘、掉落效果，就可以覆写这个方法。&lt;br /&gt;
* onUpdate：这是一个比较重要的方法，它在游戏主循环中，每一tick都会被调用一次，也就是说理想状态下每秒钟内两个逻辑端各会调用该方法20次。因此如果我们覆写了该方法，便可以赋予相应的ItemStack一秒刷新20次的能力，我们利用它可以做出一些很高级的物品，例如计时器之类的东西。从该方法中我们可以拿到持有该ItemStack的实体（没错，Entity，甚至都非EntityLivingBase），以及“该物品是否被选中”（isSelected）。&lt;br /&gt;
* onEntityItemUpdate：决定当前ItemStack对应的EntityItem的刷新逻辑。&lt;br /&gt;
* onCreated：在该物品被合成出来后触发。&lt;br /&gt;
* getMaxItemUseDuration：用于指定物品被右键按住使用后，持续的最长时间——如右键弓后弓会逐渐拉伸至蓄满力状态，蓄满力后一段时间内，如果不松开右键，弓会一直紧绷着。注意这个方法的返回值是一个int值，单位实际上是tick。&lt;br /&gt;
* onPlayerStoppedUsing：配合着getMaxItemUseDuration使用的——即玩家松开右键后执行的游戏逻辑，原版的弓在这里即会召唤出一发箭矢实体（EntityArrow）。&lt;br /&gt;
* addInformation：@SideOnly(Side.CLIENT)，用于给ItemStack添加tooltip，具体为向一个List&amp;lt;String&amp;gt;中添加新元素的过程，一般是搭配&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;实现，甚至我们可以通过当前ItemStack的附加NBT信息来决定添加的tooltip。&lt;br /&gt;
* hasEffect：@SideOnly(Side.CLIENT)，用于决定该ItemStack是否有附魔光泽——没错，ItemStack有无附魔和有无附魔光泽的对应关系并不是写死的，只是Item类对该方法的默认实现即为：有附魔才有附魔光泽，你大可给你的Item子类在这里无脑返回一个true。&lt;br /&gt;
* getItemBurnTime：如果你想添加熔炉燃料，那么覆写这个方法即可。返回一个正整数，则默认它是一个熔炉燃料，且单位为tick；若返回0，则代表它不可燃；返回-1，则代表它的可燃状态交给原版Minecraft去决定。&lt;br /&gt;
* getIsRepairable：用于决定该ItemStack可被什么ItemStack修复。两个参数中，toRepair是被修复的，repair是修复材料。&lt;br /&gt;
&lt;br /&gt;
对于高级方块，Block类下有如下常用方法值得覆写：&lt;br /&gt;
* isPassable：决定实体是否可以从该方块中穿过，参考原版的门方块，就不难理解了。&lt;br /&gt;
* isReplacable：决定该方块是否可以在其他方块被放置时取代——如果不能，则方块会被放置在该方块的侧面。如原版的液体方块和空气方块在这里返回true。&lt;br /&gt;
* neighborChanged：覆写该方法，即可自定义该种方块的周围发生方块更新时执行的游戏逻辑。一般有两个用途：1.激发红石信号，如观察者方块；2.触发对该方块的状态的合法性检验（这里的“状态”不仅仅包括BlockState，是一种抽象的概念），如悬空的沙子在周围有方块更新时会立刻下坠。&lt;br /&gt;
* breakBlock：在方块被破坏后触发，注意不限于被玩家破坏（如果想要限定被玩家破坏才执行逻辑，可以考虑覆写onBlockDestroyedByPlayer），但使用&amp;lt;code&amp;gt;World#setBlockState&amp;lt;/code&amp;gt;替换该方块时，不会触发这个方法。&lt;br /&gt;
* onBlockActivated：玩家右键点击该方块后触发，注意两只手都会触发一次，所以最好先判定传入的EnumHand。原版的工作台和熔炉，在这里，就是让玩家打开相应的GUI界面。&lt;br /&gt;
* getStateForPlacement：上一节已经提到过，不再赘述。&lt;br /&gt;
* onBlockClicked：这个方法从MCP得到的名字可能会很有迷惑性，读者可能会以为它在左键、右键被按下时都会触发，实际上不对，只有左键点击它会触发该方法。&lt;br /&gt;
* onBlockPlacedBy：在某个生物实体放置该方块时被触发，注意不限于玩家，像末影人这种非玩家生物也可以。&lt;br /&gt;
* isFlammable、getFlammability等一系列方法：用于决定该方块是否可燃、燃烧能力等一系列和火方块相关的参数。按照Minecraft原版的设计，这些东西本来应该通过&amp;lt;code&amp;gt;BlockFire#setFireInfo&amp;lt;/code&amp;gt;这个公开方法指定，不过经过Forge的patch后，我们便可以在Block类中覆写方法指定了。&lt;br /&gt;
* getDrops：得到该方块所有可能的掉落物（以一个NonNullList&amp;lt;ItemStack&amp;gt;的形式呈现），由于每破坏一次该种方块，该方法就会调用一次，所以在这里可以利用利用随机数。&lt;br /&gt;
* getExpDrop：得到该方块被采集时，掉落的经验量。&lt;br /&gt;
* isReplacableOreGen：决定：在区块生成的过程中，已经被生成的该种方块是否可以被矿石取代。&lt;br /&gt;
&lt;br /&gt;
想必读者也看到了，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。3个返回值没有固定的含义，读者在使用前请先查看相关的代码。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3i和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，i代表int，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
=== 在世界中放置方块 ===&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; || 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55017</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55017"/>
		<updated>2024-12-29T13:51:41Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
对于高级物品，Item类有如下常用的方法值得覆写：&lt;br /&gt;
* onItemUseFirst、onItemUse：在物品被玩家手持，且右键非空气方块时触发，均返回一个EnumActionResult，前者先于后者被触发。对于前者而言，返回EnumActionResult.PASS，则代表会继续执行原版的相关逻辑。&lt;br /&gt;
* onItemUseFinish：该方法和上述2个方法有所区别——该方法限定的实体并不仅仅是玩家（EntityPlayer）了，而是所有生物（EntityLivingBase）。它返回一个ItemStack，代表某个ItemStack被使用后的结果。&lt;br /&gt;
* onItemRightClick：该方法会在玩家手持物品右键时触发——没错，它不要求玩家必须选中某个非空气方块，只要右键就行，实际上它也先于onItemUseFirst和onItemUse触发。Minecraft本身只提供了修改右键物品的逻辑的该方法，那么如果我们想干涉左键逻辑怎么办？考虑监听这两个事件：PlayerInteractEvent.LeftClickBlock和PlayerInteractEvent.LeftClickEmpty，具体在监听时可以判断玩家的手持物品，再写入相关逻辑，即可。&lt;br /&gt;
* canHarvestBlock：返回一个布尔值，注意该方法有2个重载，其中一个是原版的，还有一个是Forge塞进来的，当然推荐使用后者。用于判断某个BlockState是否可以被该Item对应的某个ItemStack挖掘并产生掉落物，原版中该方法的实现很丑陋，全是面向结果编程的味道，且鉴于Forge patch的&amp;lt;code&amp;gt;Block#setHarvestLevel&amp;lt;/code&amp;gt;这一方法的存在，一般你不该动这个方法，不过如果你想做出像剪刀那样比较特殊的挖掘、掉落效果，就可以覆写这个方法。&lt;br /&gt;
* onUpdate：这是一个比较重要的方法，它在游戏主循环中，每一tick都会被调用一次，也就是说理想状态下每秒钟内两个逻辑端各会调用该方法20次。因此如果我们覆写了该方法，便可以赋予相应的ItemStack一秒刷新20次的能力，我们利用它可以做出一些很高级的物品，例如计时器之类的东西。从该方法中我们可以拿到持有该ItemStack的实体（没错，Entity，甚至都非EntityLivingBase），以及“该物品是否被选中”（isSelected）。&lt;br /&gt;
* onEntityItemUpdate：决定当前ItemStack对应的EntityItem的刷新逻辑。&lt;br /&gt;
* onCreated：在该物品被合成出来后触发。&lt;br /&gt;
* getMaxItemUseDuration：用于指定物品被右键按住使用后，持续的最长时间——如右键弓后弓会逐渐拉伸至蓄满力状态，蓄满力后一段时间内，如果不松开右键，弓会一直紧绷着。注意这个方法的返回值是一个int值，单位实际上是tick。&lt;br /&gt;
* onPlayerStoppedUsing：配合着getMaxItemUseDuration使用的——即玩家松开右键后执行的游戏逻辑，原版的弓在这里即会召唤出一发箭矢实体（EntityArrow）。&lt;br /&gt;
* addInformation：@SideOnly(Side.CLIENT)，用于给ItemStack添加tooltip，具体为向一个List&amp;lt;String&amp;gt;中添加新元素的过程，一般是搭配&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;实现，甚至我们可以通过当前ItemStack的附加NBT信息来决定添加的tooltip。&lt;br /&gt;
* hasEffect：@SideOnly(Side.CLIENT)，用于决定该ItemStack是否有附魔光泽——没错，ItemStack有无附魔和有无附魔光泽的对应关系并不是写死的，只是Item类对该方法的默认实现即为：有附魔才有附魔光泽，你大可给你的Item子类在这里无脑返回一个true。&lt;br /&gt;
* getItemBurnTime：如果你想添加熔炉燃料，那么覆写这个方法即可。返回一个正整数，则默认它是一个熔炉燃料，且单位为tick；若返回0，则代表它不可燃；返回-1，则代表它的可燃状态交给原版Minecraft去决定。&lt;br /&gt;
* getIsRepairable：用于决定该ItemStack可被什么ItemStack修复。两个参数中，toRepair是被修复的，repair是修复材料。&lt;br /&gt;
想必读者也看到了，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。3个返回值没有固定的含义，读者在使用前请先查看相关的代码。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3i和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，i代表int，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
=== 在世界中放置方块 ===&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; || 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55014</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55014"/>
		<updated>2024-12-27T18:19:10Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 在世界中放置方块 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
=== 在世界中放置方块 ===&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; || 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55013</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55013"/>
		<updated>2024-12-27T18:18:14Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 在世界中放置方块 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
== 在世界中放置方块 ==&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; || 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;br /&gt;
&lt;br /&gt;
== 注释与外部链接 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55012</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55012"/>
		<updated>2024-12-27T18:17:29Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 在世界中放置方块 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
== 在世界中放置方块 ==&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; || 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） || 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） || 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） || 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） || 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） || 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55011</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55011"/>
		<updated>2024-12-27T18:16:35Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* World */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;br /&gt;
其实在笔者看来，World类是个设计得比较奇怪的类，从字面意思上看，它就是代表着某个维度——这么理解的确是对的。然而，World类可不仅承载了维度这一概念，甚至还包括了存档这一概念——World中相关信息的保存路径、世界生成时选择的设置（如是否有奖励箱生成），甚至“是否开启了极限模式”这种全局通用设置，你都能从一个World对象中获取。Mojang显然是把“维度”和“存档”这两个概念揉到了一个类中——即World类。&lt;br /&gt;
&lt;br /&gt;
World类的设计极为复杂，相关的方法、用途也很多，这里只做简要说明。很多其他的东西，会在将来讲到添加新维度时，一并说明的。&lt;br /&gt;
&lt;br /&gt;
=== 判断当前逻辑端 ===&lt;br /&gt;
没错，我们终于说到了，判断当前所处的逻辑端的方法——检查非静态字段isRemote的值——是true，则代表当前为逻辑客户端，否则为逻辑服务端。虽然也有其它一些方法来判断逻辑段，但都没有这种方法好用，因为World对象在Minecraft各处代码中均可见到。&lt;br /&gt;
&lt;br /&gt;
== 在世界中放置方块 ==&lt;br /&gt;
在一个World中获取某坐标处的方块状态很简单，getBlockState这个方法即可胜任。与之类似地，放置方块，可以用setBlockState。&lt;br /&gt;
&lt;br /&gt;
但我们注意到：后者有2个重载，形参列表的区别在于：有一个重载较另一个重载而言，多了一个：&amp;lt;code&amp;gt;int flags&amp;lt;/code&amp;gt;。flags即标记，但它又是一个int类型的数——显然，它是一个幻数，具体含义到底如何，全看Mojang的底层设计如何了。如果去看看那个较为简单的方法，可以发现，它实际上是调用了另一个重载方法，并指定flags为3——为什么是3？&lt;br /&gt;
&lt;br /&gt;
显然，这很让人摸不着头脑。不过Forge在&amp;lt;code&amp;gt;Constants.BlockFlags&amp;lt;/code&amp;gt;类下提供了相应的常量，并通过注释和常量名说明了flags的具体含义。简单地说，Minecraft会从传入的幻数中获取很多信息，每一种信息的具体值都由传入的幻数对应的二进制数的每一位决定：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
| style=&amp;quot;font-weight:bold;&amp;quot; | 常量名及含义 | style=&amp;quot;font-weight:bold;&amp;quot; | 数值（二进制形式） |&lt;br /&gt;
|-&lt;br /&gt;
| NOTIFY_NEIGHBORS（邻近方块更新） | 0b00001 |&lt;br /&gt;
|-&lt;br /&gt;
| SEND_TO_CLIENTS（将相关数据同步到客户端） | 0b00010 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_RENDERER（该方块的渲染状态不会被更新） | 0b00100 |&lt;br /&gt;
|-&lt;br /&gt;
| RENDERER_MAIN_THREAD（让方块的渲染状态在游戏主线程中立刻被更新） | 0b01000 |&lt;br /&gt;
|-&lt;br /&gt;
| NO_OBSERVERS（该行为不会被观察者方块观测到） | 0b10000 |&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Minecraft的默认行为是采取前两项行为，因此默认的幻数值是把前两项行为对应的数进行按位或运算，即0b00011——实际上也就是上文提到的3。你可能会注意到，第三条和第四条是相冲突的，当发生冲突时，以第三条为准。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55010</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55010"/>
		<updated>2024-12-27T17:43:24Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* RayTraceResult */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
RayTrace，意思为“光线追踪”，如该词语的含义所言，RayTrace在Minecraft中的含义，即为“计算生物实体的视线，确定其视线选中的到底是一个实体，还是一个非空气方块，亦或者就是空气（即等价于什么都没选中）”。玩家在游戏中在准星指向的位置放置方块时，就会有RayTrace的过程。描述RayTrace结果的类型便是RayTraceResult。从一个RayTraceResult中，你可以拿到很多相关的有用信息。&lt;br /&gt;
&lt;br /&gt;
World类下有与RayTrace相关的一些方法，我们需要的时候可以直接拿来用。&lt;br /&gt;
&lt;br /&gt;
另外，实体的碰撞结果也是用RayTraceResult来描述的（最常见的例子就是实体投掷物砸中某个方块或实体）。&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55009</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55009"/>
		<updated>2024-12-27T17:30:39Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55005</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55005"/>
		<updated>2024-12-25T08:32:49Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* I18n和ITextComponent */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
在模组开发中，我们时常会遇到这样一种需求：给某个玩家发送聊天栏信息，很简单，用&amp;lt;code&amp;gt;EntityPlayer#sendMessage&amp;lt;/code&amp;gt;即可（注意不是sendStatusMessage），我们注意到这个方法接受一个ITextComponent，实际上出于本地化的需求与考虑，我们用的最多的该接口的实现是TextComponentTranslation。这个类的构造方法接受一个String——实际上就是用于充当该消息内容的本地化键名。我们在发送消息时，应该先判断逻辑端，否则消息会发送两次——由于我们处理逻辑一般是在逻辑服务端，因此发送消息一般也在这一端执行。&lt;br /&gt;
&lt;br /&gt;
如果我们在发送消息之外的场景中，还有本地化的需求怎么办？答案是使用&amp;lt;code&amp;gt;net.minecraft.client.resources.I18n#format&amp;lt;/code&amp;gt;，该方法返回一个本地化之后的String量，用法和TextComponentTranslation基本一样，但从该类的包名即可看出——该类只在物理客户端有意义，使用它时要尤其注意这一点。&lt;br /&gt;
&lt;br /&gt;
另外，你或许也注意到了，无论是TextComponentTranslation的构造方法还是&amp;lt;code&amp;gt;I18n#format&amp;lt;/code&amp;gt;，它都还可以接受一个Object变长参数，这实际上是为了满足一个需求：需要在本地化文本中插入动态的内容，具体用法类似于C语言中的printf函数，如字符串的占位符是&amp;lt;code&amp;gt;%s&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55004</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55004"/>
		<updated>2024-12-25T07:44:53Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55003</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55003"/>
		<updated>2024-12-25T07:39:11Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* DamageSource */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
这个类用于描述实体所受到的伤害类型——如虚空伤害、魔法伤害等等。这个类不是枚举类，所以我们可以自由地new新的DamageSource，供我们自己用。&lt;br /&gt;
&lt;br /&gt;
原版的所有已经定义好的DamageSource都在DamageSource类下以静态字段的形式呈现，需要的时候直接用即可。DamageSource主要有2个用途：第一个用途为参与一些判断，如有些生物会判断自己受到的伤害的DamageSource，并决定是否免疫它，如末影人就自动免疫弹射物造成的伤害；一系列特化版的保护附魔（如火焰保护、弹射物保护等）也是通过判断DamageSource来减免带有附魔的盔甲的穿戴者受到的伤害的。第二个用途是决定玩家死亡时的聊天栏提示信息——DamageSource的构造器中接受一个String参数：&amp;lt;code&amp;gt;damageType&amp;lt;/code&amp;gt;，如果玩家因为某种DamageSource死亡，那么聊天栏发送的消息的本地化键名为&amp;lt;code&amp;gt;&amp;quot;death.attack.&amp;quot; + damageType + &amp;quot;.player&amp;quot;&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55002</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55002"/>
		<updated>2024-12-25T07:22:05Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* BlockPos */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
BlockPos，顾名思义，即方块坐标（Block Position）。由于是用来描述方块的，因此它存储的三个坐标值也是int类型的——哪怕它有一个三个参数全为double的构造方法。不过注意，实体的三个坐标值则是double类型的，这需要区分一下。&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55001</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55001"/>
		<updated>2024-12-24T17:04:53Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* AxisAlignedBB */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
AxisAlignedBB，全称AxisAlignedBoundingBox，简称AABB。这个类描述的是一个长宽高都确定的长方体——在实际应用中用于指定某个方块的碰撞箱，以及鼠标指向某方块时产生的勾选框。这个类下有许多运算用方法，如取交集（intersect）、取并集（union），但实际上这里的取交集和取并集并不等同于数学意义上的交集与并集，具体是怎样的读者去看看它们的具体实现就好了，简单来说这两个方法都返回一个AxisAlignedBB，然而空间中两个长宽高都平行于坐标轴的长方体，其并出来的新的几何图形可不一定是一个标准的长方体，所以这两个方法实际上意义不大了。&lt;br /&gt;
&lt;br /&gt;
但实际上，Block类中用于获取碰撞箱的方法，返回值类型就是AxisAlignedBB，这是不是意味着我们的方块的碰撞箱只能是一个长方体了？实际上并不是，我们可以通过覆写&amp;lt;code&amp;gt;addCollisionBoxToList&amp;lt;/code&amp;gt;来实现，具体操作为向一个List&amp;lt;AxisAlignedBB&amp;gt;中添加新元素，这就很简单了。&lt;br /&gt;
&lt;br /&gt;
顺带一提，似乎Block类下几乎所有和碰撞箱有关的方法都被打上了@Deprecated注解，读者对此不要感到有疑虑，放心大胆地用即可。有的时候不要在乎这个@Deprecated，毕竟Block类下的&amp;lt;code&amp;gt;getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;getMetaFromState&amp;lt;/code&amp;gt;都被@Deprecated了——你总不能不覆写这两个方法吧？不覆写的话，注册多BlockState方块就会崩游戏了。实际上这都是因为Mojang即将在1.13大改这些类和相关方法，你一般会看到Forge对此写的一些注释如“to be removed in 1.13”，可问题是我们现在是在写1.12的模组，所以有的时候这些@Deprecated也挺让人不知所谓的。&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55000</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=55000"/>
		<updated>2024-12-24T16:42:10Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* ActionResult&amp;lt;T&amp;gt; */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
这是对EnumActionResult的一个扩展——有时候，Minecraft处理游戏逻辑时，在获取对某个对象的处理的结果时，不仅想知道处理是否成功（SUCCESS、PASS、FAIL这些结果），还想知道该对象的最终值，这个时候就需要用到这个类了，该类的构造很简单，就是&amp;lt;code&amp;gt;(EnumFacing typeIn, T value)&amp;lt;/code&amp;gt;。实际上我们一般能见到的是ActionResult&amp;lt;ItemStack&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54999</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54999"/>
		<updated>2024-12-24T15:53:47Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 一些向量类 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
Minecraft在&amp;lt;code&amp;gt;net.minecraft.util.math&amp;lt;/code&amp;gt;包下提供了三个向量类：Vec2f、Vec3f和Vec3d。实际上读者应该能很清楚地看出来，数字代表空间维度数，第一个是平面向量，后两个是空间向量；而字母代表坐标精度，f代表float，d代表double。一般而言，我们用的都是Vec3d。&lt;br /&gt;
&lt;br /&gt;
合理运用向量类能给不少问题中的数学运算带来极大的便利，如Vec3d下就有很多简化计算过程的好用方法，读者可以去自行察看。&lt;br /&gt;
&lt;br /&gt;
实际上，虽然这三个类被MCP取名为向量（Vector），然而它果真只能代表平面中或空间中一个向量或坐标吗？显然不是。它们本质上，其实是对有序数对或三维有序数组的简单封装而已。所以理论上它们也可以用来表示除向量和坐标之外的其他东西。比如，Vec3d也被经常用来表示维度的天空和虚空迷雾的颜色——在这里，Vec3d构造方法中传入的3个数值，其实就是相当于该颜色的R、G、B三个通道的各自的值。&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54998</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54998"/>
		<updated>2024-12-24T13:36:39Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 一些枚举类 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已），由&amp;lt;code&amp;gt;Item#getItemUseAction&amp;lt;/code&amp;gt;决定。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumBlockRenderType：顾名思义，这个枚举类用于描述方块的渲染类型——INVISIBLE、LIQUID、ENTITYBLOCK_ANIMATED、MODEL。&lt;br /&gt;
** MODEL：默认值，就是最普通的渲染类型——从指定的模型文件中读取出一个模型（实际上是通过一个ICustomModelLoader得到了一个IModel），并经历一系列加工过程，最后渲染出来。&lt;br /&gt;
** INVISIBLE：顾名思义，就是代表该方块什么都不渲染，如空气方块，如果你的模组中有一些方块纯粹是用于技术性用途，那可以设定成INVISIBLE这个渲染类型，再附加一个&amp;lt;code&amp;gt;setHardness(-1.0F)&amp;lt;/code&amp;gt;即可。&lt;br /&gt;
** LIQUID：用于流体方块的渲染，代表例子是原版的水方块。&lt;br /&gt;
** ENTITYBLOCK_ANIMATED：这个渲染类型很特殊，它单独起到的效果和INVISIBLE其实是大差不差的，只是它用于标记——该方块有TileEntity&amp;lt;ref&amp;gt;没错，EntityBlock指的其实就是TileEntity，实际上高版本它也叫BlockEntity，意思其实都一样，翻译成中文都是方块实体。&amp;lt;/ref&amp;gt;，且该方块的渲染&#039;&#039;&#039;全部&#039;&#039;&#039;由该TileEntity对应的一些相对接近于底层（实际上是lwjgl的OpenGL）的渲染代码来完成，包装成的东西名为TileEntitySpecialRenderer，一般简称TESR，TESR能帮我们实现很多单纯的模型文件做不到的效果，如：在方块表面渲染文字、动态贴图甚至实现动画效果。但是注意：用了TESR的方块实体，对应的方块的渲染类型，一般也还是MODEL。为何？因为这代表先让Minecraft默认的渲染机制为我们渲染好方块的大致轮廓和基础材质，再用TESR作补充；然而如果用了ENTITYBLOCK_ANIMATED，代表：方块的基础轮廓也是要我们手动去渲染的，典型的例子是原版的箱子。当它开启时，它的顶部的全部材质和四个侧面的一部分材质都会抬升并旋转一个角度——由于四个侧面都仅仅有一部分材质会参与这个过程，因此Minecraft的默认渲染机制确实不能胜任，所以才有必要用这个渲染类型。所以记住——除非你有特殊的需求，否则就算你用TESR渲染TileEntity，你也该用MODEL作为渲染类型而非ENTITYBLOCK_ANIMATED。有关TileEntity和TESR的内容，将会在后面讲到，这里先做了解即可。&lt;br /&gt;
* BlockRenderLayer：这个枚举类同样应用于方块，只是它用于指定方块的材质的特性。它有4个实例：SOLID、TRANSLUCENT、CUTOUT、CUTOUT_MIPPED。SOLID代表方块的材质完全不透明，TRANSLUCENT则代表半透明，CUTOUT则是材质中有一部分全透明而另外一部分完全不透明，至于CUTOUT_MIPPED，它是CUTOUT的抗锯齿版本，和CUTOUT实际上差别不是很大。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54997</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54997"/>
		<updated>2024-12-24T12:33:01Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 一些枚举类 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumAction：这个枚举类专用于物品，具体地说，是表示物品被使用时，产生的动画效果的“类型”（说是动画效果，实际上就是一层薄薄的贴图转了个角度后的一堆平移、伸缩或旋转变换而已）。原版有5种EnumAction，分别是：NONE、EAT、DRINK、BLOCK、BOW。很显然，后四者分别用于食物、水瓶和药水、方块的物品形式、弓。那么可能会有读者问了：如果我想自定义物品使用时的动画效果，怎么办，如何去做到？答案是通常情况下做不到，除非你用黑魔法。一般而言，新的EnumAction可以通过Forge提供的EnumHelper来通过反射生成，但是Minecraft具体处理这个EnumAction并实际产生动画的代码中，Forge实际上没有发布任何事件，所以理论上我们无法新增自定义的物品使用动画，除非你用Mixin大法，但除非你Modding水平够高，否则执意在Forge框架下开发时用Mixin实现自己的需求，还不如自己换个需求。&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54996</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54996"/>
		<updated>2024-12-24T12:17:03Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 一些枚举类 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
在给我们的高级的物品/方块实现一些功能时，我们会和很多枚举类打交道，如：&lt;br /&gt;
* EnumActionResult：这个枚举类有3个实例：SUCCESS、PASS和FAIL，它用于描述某个交互逻辑进行后，返回的结果（或者说执行结果）。其实它的意义不是那么大，因为它也就只是一个返回值而已，你可以在写一堆你自己的自定义逻辑后返回FAIL，本质上就是一个标记而已。不过对于物品的使用而言，SUCCESS和FAIL都不会在执行完主手上的物品的相关逻辑后，继续执行副手上对应物品的相关逻辑，而PASS则会，这个特性需要小小地注意一下。&lt;br /&gt;
* EnumFacing：这是一个表示方向的枚举类，可以表示东、南、西、北、上、下六个方向，前面已经提过，此处不再赘述。&lt;br /&gt;
* EnumHand和EnumHandSide：二者都用于表示主手和副手，不知道Mojang出于怎样的考量写出了这两个定位几乎一样的枚举类，反正用的时候也没区别，注意一下到底是哪个类型就行。&lt;br /&gt;
* EntityEquipmentSlot：用于描述一个有物品栏的生物的物品栏槽位种类，总共有6种：主手、副手、头部、胸部、腿部、脚部。&lt;br /&gt;
&lt;br /&gt;
以上就是一些常用的枚举类，多亏了MCP，它们的类名和实例名都很容易让人顾名思义，用起来也比较简单，这里就不多说了。&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54995</id>
		<title>用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/11.%E9%AB%98%E7%BA%A7%E7%89%A9%E5%93%81%E5%92%8C%E6%96%B9%E5%9D%97_-_%E5%89%8D%E7%BD%AE%E6%A6%82%E5%BF%B5&amp;diff=54995"/>
		<updated>2024-12-24T09:47:15Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;首先，笔者想对能看到这里的读者，道一声恭喜！因为能学到这里，读者应该已经掌握了创建一个新物品或新方块的基本的标准化流程，且已经熟悉了相关的重要概念（如ItemStack和NBT格式的运用），这已经超越了许多新手modder了。不过应该也有读者存有疑问：之前学习的东西，充其量只能搞出一些没有任何额外行为和高级功能的，仅能被当作合成材料或建材使用的物品/方块，那么那些知名模组中的看起来很高级的物品/方块，都是怎么做出来的呢？&lt;br /&gt;
&lt;br /&gt;
很简单，如果我们排除掉使用TileEntity的方块，那么剩下的那些高级的物品/方块，几乎都是通过覆写Item类或Block类下的若干方法实现的，这些方法有的是Minecraft原生支持的，还有一些是Forge塞进去的。你可能会看到Minecraft有些原生支持的方法被@Deprecated了，那可能是因为Forge觉得这个方法提供的形参不够多，对于对应功能的控制还不够精细，因此patch了一个更为强大的同功能重载方法。另外，如果你实在找不到可以覆写的方法，那么可以考虑监听一些事件。&lt;br /&gt;
&lt;br /&gt;
然而，覆写这些方法通常涉及到一些很重要也比较复杂的类，这些类比较琐碎，应用也非常广泛，如果真的按照“讲到了对应的专题再详细讲”这个原则安排内容，会导致一系列问题——例如，World类是一个非常重要的类，用到的地方超级多，但是读者想必也不希望掌握如何创建一个新维度后，再回来写高级的物品/方块吧？因此这一节就专门讲这些琐碎的类以及它们涉及到的一些重要概念。&lt;br /&gt;
== 一些枚举类 ==&lt;br /&gt;
&lt;br /&gt;
== 一些向量类 ==&lt;br /&gt;
&lt;br /&gt;
== ActionResult&amp;lt;T&amp;gt; ==&lt;br /&gt;
&lt;br /&gt;
== AxisAlignedBB ==&lt;br /&gt;
&lt;br /&gt;
== BlockPos ==&lt;br /&gt;
&lt;br /&gt;
== DamageSource ==&lt;br /&gt;
&lt;br /&gt;
== EntityPlayer ==&lt;br /&gt;
&lt;br /&gt;
== I18n和ITextComponent ==&lt;br /&gt;
&lt;br /&gt;
== MinecraftServer ==&lt;br /&gt;
&lt;br /&gt;
== RayTraceResult ==&lt;br /&gt;
&lt;br /&gt;
== World ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=54994</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=54994"/>
		<updated>2024-12-24T07:42:00Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/11.高级物品和方块 - 前置概念|11.高级物品和方块 - 前置概念]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54993</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54993"/>
		<updated>2024-12-23T11:15:04Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* Forge BlockStates V1模型文件格式 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉有4种方块状态——朝向有4种：水平方向的东、南、西、北，而实际上熔炉的燃烧/熄灭这一属性不是通过IProperty控制的，而是通过构造方法中的一个boolean参数控制的——即熄灭的熔炉和燃烧的熔炉实际上不是同一个Block，而它们各自有4种方块状态。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
3种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
此外，PropertyEnum还有一个针对方块朝向的特化版：PropertyDirection，本质上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;。实际上关于这个类，你可以找到一些可以直接拿来用的公开字段，如&amp;lt;code&amp;gt;BlockHorizontal.FACING&amp;lt;/code&amp;gt;，这个属性只允许水平方向上的EnumFacing——即东、南、西、北四个方向。&lt;br /&gt;
&lt;br /&gt;
弄好了你所需要的的IProperty字段后，就要通过覆写Block类下的一个方法来告知Minecraft方块状态系统：这个方块的BlockState可以包含这些IProperty：&lt;br /&gt;
&lt;br /&gt;
 @Override&lt;br /&gt;
 public BlockStateContainer createBlockState(){&lt;br /&gt;
     return new BlockStateContainer(this, property1, property2, ...);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
BlockStateContainer类构造方法中有个变长参数，以用于传入我们声明好的IProperty。实际上这个方法的返回值，会在Block类构造方法中，被传入一个名为blockState的字段。&lt;br /&gt;
&lt;br /&gt;
然后我们就该声明该方块的默认方块状态了（Default BlockState），通过setDefaultState来实现。对于BlockState中没有IProperty的方块，方块状态只有一种，因此我们也不需要设定默认的方块状态了——Minecraft已经帮我们设定好了。但是对于多BlockState方块，我们必须手动设定。&lt;br /&gt;
&lt;br /&gt;
那有读者就问了：我们如何能拿到一个带有特定属性且属性值被显式指定的一个IBlockState呢？所以首先我们先来讲讲IBlockState中的一些常用方法。&lt;br /&gt;
&lt;br /&gt;
说是一些，其实也就三个，其中有两个是修改属性值的：withProperty和cycleProperty。前者用于显式指定某个IBlockState中的某一属性的值，后者用于循环某个IBlockState中某一属性所有可能的值。最常用的方法是withProperty。两个方法均符合链式调用规则。此外还有一个：getValue，其作用显而易见。&lt;br /&gt;
&lt;br /&gt;
然后我们就可以设定该方块默认的方块状态了：&lt;br /&gt;
&lt;br /&gt;
 this.setDefaultState(this.blockState.getBaseState().withProperty(property1, defaultValue1).withProperty(property2, defaultValue2)....;&lt;br /&gt;
&lt;br /&gt;
然后Block类下的protected字段：defaultState就会把传入的这个IBlockState缓存下来，需要的时候用getDefaultState获取即可。所以我们可以看到，一个方块的默认状态，也是满足重复利用实例的原则的。但是非默认的方块状态便不一样了，用到它们的时候一般是在defaultState的基础上再次withProperty或者cycleProperty，因此其他方块状态，便不能用==运算符号判断是否相等了——只有默认的方块状态可以这么做。&lt;br /&gt;
&lt;br /&gt;
在什么情况下，给方块设定什么样的状态，都是没有定式的，全看你自己的需求。但特别地，如果你的方块有一个PropertyDirection属性，且你希望这个属性随着玩家放置方块时的的朝向变化，那么一般你只需要覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;方法即可，具体的实现如何，可以去参考原版的一些有方向属性的方块在这一块的代码。&lt;br /&gt;
&lt;br /&gt;
那么，工作是不是已经做完了呢？当然没有，我们毕竟还要写相应的JSON文件嘛，不过读者可能会觉得，仅仅是为了测试的话，没必要立刻就写JSON文件，可以直接顶着紫黑块的压力，按F3查看它的属性值！好的，这么做确实可以，然而注册完方块，runClient之后读者会发现代码根本跑不起来，会崩溃——且抛出一个IllegalArgumentException：&amp;quot;Don&#039;t know how to convert [state] back into data...&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
好的，所以笔者还需要讲一个很重要的概念：方块的meta值。是不是很眼熟？ItemStack就有meta值啊。只是在ItemStack中，meta值兼任损害值和类型序数两个概念，由hasSubtypes字段控制它的具体含义。但对于方块而言，没损害值这一说，所以方块的meta值就代表它的一个类型。这么说，方块的meta岂不是和它的BlockState作用重合了？是这样的，在Minecraft 1.8版本之前，BlockState系统还不存在，那时候方块被附加上不同属性后得到的“状态”就是用方块的meta值来描述的——玩家在世界中和方块交互时，以及Minecraft内部机制序列化、反序列化地图时，用的都是方块的meta值。但BlockState系统加入后，meta值便只被用于序列化、反序列化存档地图了。显然我们需要给一个方块的所有BlockState都各自指定一个meta值，否则Minecraft根本就不知道如何序列化或反序列化这种多BlockState方块。我们覆写&amp;lt;code&amp;gt;Block#getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;Block#getMetaFromState&amp;lt;/code&amp;gt;这两个方法，即可，runClient后，游戏便能正常进入了。&lt;br /&gt;
&lt;br /&gt;
和ItemStack不同，方块的meta值只能是0 - 15这个范围内的整数——最多只有16种可能；而在99%的情况下，所有BlockState和所有meta值之间的关系都是一一对应的，因此&#039;&#039;&#039;通常情况下&#039;&#039;&#039;，一个方块的BlockState最多只有16种。但也有例外——比如栅栏、红石粉、火焰这些方块，它们实际上有着成百上千种BlockState，那么这是如何做到的呢？它们实际上都只指定了部分BlockState的meta值，同时覆写了&amp;lt;code&amp;gt;Block#getActualState&amp;lt;/code&amp;gt;方法，该方法能根据周围的方块的方块状态，计算出它本身该是什么方块，这一部分算出来的BlockState实际上是不参与序列化和反序列化的。这个手段实际上很有局限性，为何？能这么做的方块，它的特点也很明确：它本身的BlockState确实会随着它周围的方块的BlockState的变化而变化，那对于箱子这种能存储物品（实际上是ItemStack）的方块呢？就没辙了，毕竟你总不可能不打开箱子，根据它周围的方块状态就确定它里面装的到底都有啥吧（笑），而且因为ItemStack本身有附加NBT，因此可以这么说，同一种物品的ItemStack有无限种可能。所以，指望着BlockState系统来完成这件事是没辙了。&lt;br /&gt;
&lt;br /&gt;
那有没有解决办法呢？&amp;lt;s&amp;gt;废话，当然有。&amp;lt;/s&amp;gt;解决方案是给世界上的每一个箱子方块都创建一个方块实体（TileEntity），这会在之后的教程中讲到，TileEntity并不是享元对象，因此我们可以通过给对应的TileEntity类追加实例变量的方式来存储理论上无限的数据。一个方块拥有了TileEntity不意味着它就不需要多种方块状态了，实际上箱子的朝向这种信息还是通过IProperty字段来定义的。TileEntity也有其缺点：内存和性能开销比较大。&lt;br /&gt;
&lt;br /&gt;
除非你真的在写类似于原版的栅栏、红石粉这些方块的方块，否则不要用getActualState，它实际上是个hack，很不好用，也很复杂。该用TileEntity就用TileEntity。另外，不是所有方块的属性都必须以IProperty的形式呈现。如：箱子有普通材质和圣诞节材质两种材质，这难道也应该是一个PropertyBool吗？大错特错。实际上这个是在相关的渲染代码中根据当前系统时间被临时判断出来的，因为这个真的只影响视觉，不影响任何（反）序列化过程、游戏逻辑。&#039;&#039;&#039;只有当方块的某个特性影响到了实际的游戏逻辑，或造成了存档内数据的改变，该特性才应该被作为一个IProperty对待。&#039;&#039;&#039;否则，你只需临时作个判断就好了。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;br /&gt;
现在，blockstates JSON文件中的那个“variants”应该很好理解了——它实际上是囊括了该方块的所有IProperty。对于那些单BlockState方块而言，它们没有任何的额外IProperty，因此我们直接开个“normal”，再来指定它的模型文件路径即可，就这么简单。&lt;br /&gt;
&lt;br /&gt;
但是对于多BlockState方块而言，显然我们期望不同的方块状态对应不同的样子，比如两种熔炉方块的外观会随着朝向的不同发生变化。这个时候就需要在variants下列出所有可能的IProperty的不同值之间的所有可能的组合情况了，我们来看看原版的熄灭的熔炉方块的blockstates JSON文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing=north&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
        &amp;quot;facing=south&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
        &amp;quot;facing=west&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
        &amp;quot;facing=east&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
熄灭的熔炉方块有且只有一个PropertyDirection，它被构造时传入的name是&amp;lt;code&amp;gt;&amp;quot;facing&amp;quot;&amp;lt;/code&amp;gt;，而它实际上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;，EnumFacing实现了IStringSerializable，因此对于该方块，我们就可以列出&amp;quot;facing=[A certain EnumFacing instance.getName()]&amp;quot;这种式子表示一个BlockState，再指定它的具体模型情况即可。这里我们注意到朝向实际上不影响方块的材质，本质上模型只是转了一定的角度而已，因此这里model不变，而y（即方块模型绕过方块上下两面中心点的直线旋转的角度）则有所变化。注意这里指定模型文件路径时并没有指定域名（domain），那是因为这是原版blockstates文件，不写域名的话，域名默认为minecraft，而对于我们的模组的blockstates文件，域名必须得写——就是我们的模组的modid。&lt;br /&gt;
&lt;br /&gt;
=== Forge BlockStates V1模型文件格式 ===&lt;br /&gt;
可能已经有读者注意到，原版的blockstates JSON文件格式是有优化空间的，比如在上面那个例子中，同样的“facing=”这种东西重复了四次，而且每种情况下模型文件实际上是相同的，只是绕y轴旋转的角度有所变化，但我们却依然要重复书写同样的模型文件路径，这实际上有些冗余。&lt;br /&gt;
&lt;br /&gt;
而且当一个方块拥有的IProperty数量不只是一个时，情况往往会变得更加糟糕——比如再来一个有四种可能值的IProperty的话，我们真的就要把总共十六种情况全部列出来，真的就要在variants里写上十六行相差不大的东西。这听起来就让人头皮发麻。&lt;br /&gt;
&lt;br /&gt;
所幸，Forge给我们提供了一个基于原版格式的blockstates文件格式——Forge BlockStates V1格式，让我们用它来重写原版的这一blockstates文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;forge_marker&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;defaults&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing&amp;quot;: {&lt;br /&gt;
            &amp;quot;north&amp;quot;: { &amp;quot;y&amp;quot;: 0 },&lt;br /&gt;
            &amp;quot;south&amp;quot;: { &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
            &amp;quot;west&amp;quot;: { &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
            &amp;quot;east&amp;quot;: { &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
首先，&amp;lt;code&amp;gt;&amp;quot;forge_marker&amp;quot;: 1&amp;lt;/code&amp;gt;代表这是一个符合Forge BlockStates V1格式的blockstates文件，这会告知Forge来处理它。然后我们可以通过设定defaults来设定该blockstates文件的一些默认值——毕竟四个方块状态共用一个模型文件，因此我们只需把它当成默认值写入即可。&lt;br /&gt;
&lt;br /&gt;
然后就是variants中IProperty写法的简化了，也很容易让人明白。如果有多个IProperty，只需分开写即可，Forge会自动求出它们的所有可能值的所有可能的组合情况的。这就大大简化了blockstates的模型映射。&lt;br /&gt;
&lt;br /&gt;
值得注意的是，虽然名字叫Forge BlockStates V1，但实际上这也可以用于方块的物品形式的模型的指定。还记得ModelResourceLocation构造方法中那个String类型的variantIn吗？我们传入的是&amp;lt;code&amp;gt;&amp;quot;inventory&amp;quot;&amp;lt;/code&amp;gt;，所以这个inventory实际上就是一个variant，换言之，你只需要在variants中这么写即可：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    ...&lt;br /&gt;
    &amp;quot;inventory&amp;quot;: [{&lt;br /&gt;
        &amp;quot;transform&amp;quot;: &amp;quot;forge:default-block&amp;quot;&lt;br /&gt;
    }],&lt;br /&gt;
    ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
甚至于和方块没什么关系的普通物品我们也可以写在blockstates文件中，只需要把transform改成forge:default-item，再指定一下textures即可。不过并不推荐这么做，因为普通的物品和方块既然没关系，那么放在blockstates文件中既多此一举，也容易造成歧义。&lt;br /&gt;
&lt;br /&gt;
== 方块的Meta-hack ==&lt;br /&gt;
类似于ItemStack，方块也是可以有Meta-hack的——实际上是用同一个Block的不同BlockState及其对应的meta代表设定上不同的多种方块（实际上在代码层面都对应同一个Block对象）。和ItemStack的Meta-hack类似，方块的Meta-hack也是不推荐使用的。&lt;br /&gt;
&lt;br /&gt;
首先我们需要一个区分方块类型的IProperty，一般是用PropertyEnum，用到的枚举类一般作为静态内部类的形式出现在方块类中；再设定该方块的hasSubtypes为true；然后在blockstates文件中，针对这个variant分别指定各自不同的模型文件路径，即可。没错，你没看错，就这么简单。&lt;br /&gt;
&lt;br /&gt;
然而使用Meta-hack的方块对应的物品却不是这么简单——因为ItemBlock类的默认实现很可能不能满足我们的需求，为何？因为ItemBlock的默认逻辑是：若传入的Block的hasSubtypes为true，那么使用ItemBlock对应的某个ItemStack放置该方块时，该ItemStack的meta值和放置的BlockState对应的meta值，是相等的。因此如果你的方块只有这一个区分类型的IProperty，倒还没问题；但如果你同时还有其他几个IProperty呢，比如一个决定朝向的PropertyDirection？那就糟了，该ItemBlock的meta会影响到放置出来的方块的朝向，这显然不是我们所期望的，我们所期望的应当是：由放置该方块的实体的朝向来决定该方块的朝向。因此这个时候你就得覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;才行，这个方法会传入对应的ItemStack，由此我们就可以手动指定符合我们自己的需求的BlockState和ItemStack的各自的metadata的正确对应关系。&lt;br /&gt;
&lt;br /&gt;
另外，我们还得覆写&amp;lt;code&amp;gt;Block#getSubBlocks&amp;lt;/code&amp;gt;方法，它的用途和之前提到过的getSubItems差不多，实际上ItemBlock类中的getSubItems方法就是代理给了getSubBlocks的。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54992</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54992"/>
		<updated>2024-12-23T10:48:54Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 模型文件的书写 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉有4种方块状态——朝向有4种：水平方向的东、南、西、北，而实际上熔炉的燃烧/熄灭这一属性不是通过IProperty控制的，而是通过构造方法中的一个boolean参数控制的——即熄灭的熔炉和燃烧的熔炉实际上不是同一个Block，而它们各自有4种方块状态。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
3种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
此外，PropertyEnum还有一个针对方块朝向的特化版：PropertyDirection，本质上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;。实际上关于这个类，你可以找到一些可以直接拿来用的公开字段，如&amp;lt;code&amp;gt;BlockHorizontal.FACING&amp;lt;/code&amp;gt;，这个属性只允许水平方向上的EnumFacing——即东、南、西、北四个方向。&lt;br /&gt;
&lt;br /&gt;
弄好了你所需要的的IProperty字段后，就要通过覆写Block类下的一个方法来告知Minecraft方块状态系统：这个方块的BlockState可以包含这些IProperty：&lt;br /&gt;
&lt;br /&gt;
 @Override&lt;br /&gt;
 public BlockStateContainer createBlockState(){&lt;br /&gt;
     return new BlockStateContainer(this, property1, property2, ...);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
BlockStateContainer类构造方法中有个变长参数，以用于传入我们声明好的IProperty。实际上这个方法的返回值，会在Block类构造方法中，被传入一个名为blockState的字段。&lt;br /&gt;
&lt;br /&gt;
然后我们就该声明该方块的默认方块状态了（Default BlockState），通过setDefaultState来实现。对于BlockState中没有IProperty的方块，方块状态只有一种，因此我们也不需要设定默认的方块状态了——Minecraft已经帮我们设定好了。但是对于多BlockState方块，我们必须手动设定。&lt;br /&gt;
&lt;br /&gt;
那有读者就问了：我们如何能拿到一个带有特定属性且属性值被显式指定的一个IBlockState呢？所以首先我们先来讲讲IBlockState中的一些常用方法。&lt;br /&gt;
&lt;br /&gt;
说是一些，其实也就三个，其中有两个是修改属性值的：withProperty和cycleProperty。前者用于显式指定某个IBlockState中的某一属性的值，后者用于循环某个IBlockState中某一属性所有可能的值。最常用的方法是withProperty。两个方法均符合链式调用规则。此外还有一个：getValue，其作用显而易见。&lt;br /&gt;
&lt;br /&gt;
然后我们就可以设定该方块默认的方块状态了：&lt;br /&gt;
&lt;br /&gt;
 this.setDefaultState(this.blockState.getBaseState().withProperty(property1, defaultValue1).withProperty(property2, defaultValue2)....;&lt;br /&gt;
&lt;br /&gt;
然后Block类下的protected字段：defaultState就会把传入的这个IBlockState缓存下来，需要的时候用getDefaultState获取即可。所以我们可以看到，一个方块的默认状态，也是满足重复利用实例的原则的。但是非默认的方块状态便不一样了，用到它们的时候一般是在defaultState的基础上再次withProperty或者cycleProperty，因此其他方块状态，便不能用==运算符号判断是否相等了——只有默认的方块状态可以这么做。&lt;br /&gt;
&lt;br /&gt;
在什么情况下，给方块设定什么样的状态，都是没有定式的，全看你自己的需求。但特别地，如果你的方块有一个PropertyDirection属性，且你希望这个属性随着玩家放置方块时的的朝向变化，那么一般你只需要覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;方法即可，具体的实现如何，可以去参考原版的一些有方向属性的方块在这一块的代码。&lt;br /&gt;
&lt;br /&gt;
那么，工作是不是已经做完了呢？当然没有，我们毕竟还要写相应的JSON文件嘛，不过读者可能会觉得，仅仅是为了测试的话，没必要立刻就写JSON文件，可以直接顶着紫黑块的压力，按F3查看它的属性值！好的，这么做确实可以，然而注册完方块，runClient之后读者会发现代码根本跑不起来，会崩溃——且抛出一个IllegalArgumentException：&amp;quot;Don&#039;t know how to convert [state] back into data...&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
好的，所以笔者还需要讲一个很重要的概念：方块的meta值。是不是很眼熟？ItemStack就有meta值啊。只是在ItemStack中，meta值兼任损害值和类型序数两个概念，由hasSubtypes字段控制它的具体含义。但对于方块而言，没损害值这一说，所以方块的meta值就代表它的一个类型。这么说，方块的meta岂不是和它的BlockState作用重合了？是这样的，在Minecraft 1.8版本之前，BlockState系统还不存在，那时候方块被附加上不同属性后得到的“状态”就是用方块的meta值来描述的——玩家在世界中和方块交互时，以及Minecraft内部机制序列化、反序列化地图时，用的都是方块的meta值。但BlockState系统加入后，meta值便只被用于序列化、反序列化存档地图了。显然我们需要给一个方块的所有BlockState都各自指定一个meta值，否则Minecraft根本就不知道如何序列化或反序列化这种多BlockState方块。我们覆写&amp;lt;code&amp;gt;Block#getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;Block#getMetaFromState&amp;lt;/code&amp;gt;这两个方法，即可，runClient后，游戏便能正常进入了。&lt;br /&gt;
&lt;br /&gt;
和ItemStack不同，方块的meta值只能是0 - 15这个范围内的整数——最多只有16种可能；而在99%的情况下，所有BlockState和所有meta值之间的关系都是一一对应的，因此&#039;&#039;&#039;通常情况下&#039;&#039;&#039;，一个方块的BlockState最多只有16种。但也有例外——比如栅栏、红石粉、火焰这些方块，它们实际上有着成百上千种BlockState，那么这是如何做到的呢？它们实际上都只指定了部分BlockState的meta值，同时覆写了&amp;lt;code&amp;gt;Block#getActualState&amp;lt;/code&amp;gt;方法，该方法能根据周围的方块的方块状态，计算出它本身该是什么方块，这一部分算出来的BlockState实际上是不参与序列化和反序列化的。这个手段实际上很有局限性，为何？能这么做的方块，它的特点也很明确：它本身的BlockState确实会随着它周围的方块的BlockState的变化而变化，那对于箱子这种能存储物品（实际上是ItemStack）的方块呢？就没辙了，毕竟你总不可能不打开箱子，根据它周围的方块状态就确定它里面装的到底都有啥吧（笑），而且因为ItemStack本身有附加NBT，因此可以这么说，同一种物品的ItemStack有无限种可能。所以，指望着BlockState系统来完成这件事是没辙了。&lt;br /&gt;
&lt;br /&gt;
那有没有解决办法呢？&amp;lt;s&amp;gt;废话，当然有。&amp;lt;/s&amp;gt;解决方案是给世界上的每一个箱子方块都创建一个方块实体（TileEntity），这会在之后的教程中讲到，TileEntity并不是享元对象，因此我们可以通过给对应的TileEntity类追加实例变量的方式来存储理论上无限的数据。一个方块拥有了TileEntity不意味着它就不需要多种方块状态了，实际上箱子的朝向这种信息还是通过IProperty字段来定义的。TileEntity也有其缺点：内存和性能开销比较大。&lt;br /&gt;
&lt;br /&gt;
除非你真的在写类似于原版的栅栏、红石粉这些方块的方块，否则不要用getActualState，它实际上是个hack，很不好用，也很复杂。该用TileEntity就用TileEntity。另外，不是所有方块的属性都必须以IProperty的形式呈现。如：箱子有普通材质和圣诞节材质两种材质，这难道也应该是一个PropertyBool吗？大错特错。实际上这个是在相关的渲染代码中根据当前系统时间被临时判断出来的，因为这个真的只影响视觉，不影响任何（反）序列化过程、游戏逻辑。&#039;&#039;&#039;只有当方块的某个特性影响到了实际的游戏逻辑，或造成了存档内数据的改变，该特性才应该被作为一个IProperty对待。&#039;&#039;&#039;否则，你只需临时作个判断就好了。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;br /&gt;
现在，blockstates JSON文件中的那个“variants”应该很好理解了——它实际上是囊括了该方块的所有IProperty。对于那些单BlockState方块而言，它们没有任何的额外IProperty，因此我们直接开个“normal”，再来指定它的模型文件路径即可，就这么简单。&lt;br /&gt;
&lt;br /&gt;
但是对于多BlockState方块而言，显然我们期望不同的方块状态对应不同的样子，比如两种熔炉方块的外观会随着朝向的不同发生变化。这个时候就需要在variants下列出所有可能的IProperty的不同值之间的所有可能的组合情况了，我们来看看原版的熄灭的熔炉方块的blockstates JSON文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing=north&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
        &amp;quot;facing=south&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
        &amp;quot;facing=west&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
        &amp;quot;facing=east&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
熄灭的熔炉方块有且只有一个PropertyDirection，它被构造时传入的name是&amp;lt;code&amp;gt;&amp;quot;facing&amp;quot;&amp;lt;/code&amp;gt;，而它实际上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;，EnumFacing实现了IStringSerializable，因此对于该方块，我们就可以列出&amp;quot;facing=[A certain EnumFacing instance.getName()]&amp;quot;这种式子表示一个BlockState，再指定它的具体模型情况即可。这里我们注意到朝向实际上不影响方块的材质，本质上模型只是转了一定的角度而已，因此这里model不变，而y（即方块模型绕过方块上下两面中心点的直线旋转的角度）则有所变化。注意这里指定模型文件路径时并没有指定域名（domain），那是因为这是原版blockstates文件，不写域名的话，域名默认为minecraft，而对于我们的模组的blockstates文件，域名必须得写——就是我们的模组的modid。&lt;br /&gt;
&lt;br /&gt;
=== Forge BlockStates V1模型文件格式 ===&lt;br /&gt;
可能已经有读者注意到，原版的blockstates JSON文件格式是有优化空间的，比如在上面那个例子中，同样的“facing=”这种东西重复了四次，而且每种情况下模型文件实际上是相同的，只是绕y轴旋转的角度有所变化，但我们却依然要重复书写同样的模型文件路径，这实际上有些冗余。&lt;br /&gt;
&lt;br /&gt;
而且当一个方块拥有的IProperty数量不只是一个时，情况往往会变得更加糟糕——比如再来一个有四种可能值的IProperty的话，我们真的就要把总共十六种情况全部列出来，真的就要在variants里写上十六行相差不大的东西。这听起来就让人头皮发麻。&lt;br /&gt;
&lt;br /&gt;
所幸，Forge给我们提供了一个基于原版格式的blockstates文件格式——Forge BlockStates V1格式，让我们用它来重写原版的这一blockstates文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;forge_marker&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;defaults&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing&amp;quot;: {&lt;br /&gt;
            &amp;quot;north&amp;quot;: { &amp;quot;y&amp;quot;: 0 },&lt;br /&gt;
            &amp;quot;south&amp;quot;: { &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
            &amp;quot;west&amp;quot;: { &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
            &amp;quot;east&amp;quot;: { &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
首先，&amp;lt;code&amp;gt;&amp;quot;forge_marker&amp;quot;: 1&amp;lt;/code&amp;gt;代表这是一个符合Forge BlockStates V1格式的blockstates文件，这会告知Forge来处理它。然后我们可以通过设定defaults来设定该blockstates文件的一些默认值——毕竟四个方块状态共用一个模型文件，因此我们只需把它当成默认值写入即可。&lt;br /&gt;
&lt;br /&gt;
然后就是variants中IProperty写法的简化了，也很容易让人明白。如果有多个IProperty，只需分开写即可，Forge会自动求出它们的所有可能值的所有可能的组合情况的。这就大大简化了blockstates的模型映射。&lt;br /&gt;
&lt;br /&gt;
值得注意的是，虽然名字叫Forge BlockStates V1，但实际上这也可以用于方块的物品形式的模型的指定。还记得ModelResourceLocation构造方法中那个String类型的variantIn吗？我们传入的是&amp;lt;code&amp;gt;&amp;quot;inventory&amp;quot;&amp;lt;/code&amp;gt;，所以这个inventory实际上就是一个variant，换言之，你只需要在variants中这么写即可：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    ...&lt;br /&gt;
    &amp;quot;inventory&amp;quot;: [{&lt;br /&gt;
        &amp;quot;transform&amp;quot;: &amp;quot;forge:default-block&amp;quot;&lt;br /&gt;
    }]&lt;br /&gt;
    ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
甚至于和方块没什么关系的普通物品我们也可以写在blockstates文件中，只需要把transform改成forge:default-item，再指定一下textures即可。不过并不推荐这么做，因为普通的物品和方块既然没关系，那么放在blockstates文件中既多此一举，也容易造成歧义。&lt;br /&gt;
&lt;br /&gt;
== 方块的Meta-hack ==&lt;br /&gt;
类似于ItemStack，方块也是可以有Meta-hack的——实际上是用同一个Block的不同BlockState及其对应的meta代表设定上不同的多种方块（实际上在代码层面都对应同一个Block对象）。和ItemStack的Meta-hack类似，方块的Meta-hack也是不推荐使用的。&lt;br /&gt;
&lt;br /&gt;
首先我们需要一个区分方块类型的IProperty，一般是用PropertyEnum，用到的枚举类一般作为静态内部类的形式出现在方块类中；再设定该方块的hasSubtypes为true；然后在blockstates文件中，针对这个variant分别指定各自不同的模型文件路径，即可。没错，你没看错，就这么简单。&lt;br /&gt;
&lt;br /&gt;
然而使用Meta-hack的方块对应的物品却不是这么简单——因为ItemBlock类的默认实现很可能不能满足我们的需求，为何？因为ItemBlock的默认逻辑是：若传入的Block的hasSubtypes为true，那么使用ItemBlock对应的某个ItemStack放置该方块时，该ItemStack的meta值和放置的BlockState对应的meta值，是相等的。因此如果你的方块只有这一个区分类型的IProperty，倒还没问题；但如果你同时还有其他几个IProperty呢，比如一个决定朝向的PropertyDirection？那就糟了，该ItemBlock的meta会影响到放置出来的方块的朝向，这显然不是我们所期望的，我们所期望的应当是：由放置该方块的实体的朝向来决定该方块的朝向。因此这个时候你就得覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;才行，这个方法会传入对应的ItemStack，由此我们就可以手动指定符合我们自己的需求的BlockState和ItemStack的各自的metadata的正确对应关系。&lt;br /&gt;
&lt;br /&gt;
另外，我们还得覆写&amp;lt;code&amp;gt;Block#getSubBlocks&amp;lt;/code&amp;gt;方法，它的用途和之前提到过的getSubItems差不多，实际上ItemBlock类中的getSubItems方法就是代理给了getSubBlocks的。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54991</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54991"/>
		<updated>2024-12-23T08:43:10Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 模型文件的书写 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉有4种方块状态——朝向有4种：水平方向的东、南、西、北，而实际上熔炉的燃烧/熄灭这一属性不是通过IProperty控制的，而是通过构造方法中的一个boolean参数控制的——即熄灭的熔炉和燃烧的熔炉实际上不是同一个Block，而它们各自有4种方块状态。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
3种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
此外，PropertyEnum还有一个针对方块朝向的特化版：PropertyDirection，本质上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;。实际上关于这个类，你可以找到一些可以直接拿来用的公开字段，如&amp;lt;code&amp;gt;BlockHorizontal.FACING&amp;lt;/code&amp;gt;，这个属性只允许水平方向上的EnumFacing——即东、南、西、北四个方向。&lt;br /&gt;
&lt;br /&gt;
弄好了你所需要的的IProperty字段后，就要通过覆写Block类下的一个方法来告知Minecraft方块状态系统：这个方块的BlockState可以包含这些IProperty：&lt;br /&gt;
&lt;br /&gt;
 @Override&lt;br /&gt;
 public BlockStateContainer createBlockState(){&lt;br /&gt;
     return new BlockStateContainer(this, property1, property2, ...);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
BlockStateContainer类构造方法中有个变长参数，以用于传入我们声明好的IProperty。实际上这个方法的返回值，会在Block类构造方法中，被传入一个名为blockState的字段。&lt;br /&gt;
&lt;br /&gt;
然后我们就该声明该方块的默认方块状态了（Default BlockState），通过setDefaultState来实现。对于BlockState中没有IProperty的方块，方块状态只有一种，因此我们也不需要设定默认的方块状态了——Minecraft已经帮我们设定好了。但是对于多BlockState方块，我们必须手动设定。&lt;br /&gt;
&lt;br /&gt;
那有读者就问了：我们如何能拿到一个带有特定属性且属性值被显式指定的一个IBlockState呢？所以首先我们先来讲讲IBlockState中的一些常用方法。&lt;br /&gt;
&lt;br /&gt;
说是一些，其实也就三个，其中有两个是修改属性值的：withProperty和cycleProperty。前者用于显式指定某个IBlockState中的某一属性的值，后者用于循环某个IBlockState中某一属性所有可能的值。最常用的方法是withProperty。两个方法均符合链式调用规则。此外还有一个：getValue，其作用显而易见。&lt;br /&gt;
&lt;br /&gt;
然后我们就可以设定该方块默认的方块状态了：&lt;br /&gt;
&lt;br /&gt;
 this.setDefaultState(this.blockState.getBaseState().withProperty(property1, defaultValue1).withProperty(property2, defaultValue2)....;&lt;br /&gt;
&lt;br /&gt;
然后Block类下的protected字段：defaultState就会把传入的这个IBlockState缓存下来，需要的时候用getDefaultState获取即可。所以我们可以看到，一个方块的默认状态，也是满足重复利用实例的原则的。但是非默认的方块状态便不一样了，用到它们的时候一般是在defaultState的基础上再次withProperty或者cycleProperty，因此其他方块状态，便不能用==运算符号判断是否相等了——只有默认的方块状态可以这么做。&lt;br /&gt;
&lt;br /&gt;
在什么情况下，给方块设定什么样的状态，都是没有定式的，全看你自己的需求。但特别地，如果你的方块有一个PropertyDirection属性，且你希望这个属性随着玩家放置方块时的的朝向变化，那么一般你只需要覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;方法即可，具体的实现如何，可以去参考原版的一些有方向属性的方块在这一块的代码。&lt;br /&gt;
&lt;br /&gt;
那么，工作是不是已经做完了呢？当然没有，我们毕竟还要写相应的JSON文件嘛，不过读者可能会觉得，仅仅是为了测试的话，没必要立刻就写JSON文件，可以直接顶着紫黑块的压力，按F3查看它的属性值！好的，这么做确实可以，然而注册完方块，runClient之后读者会发现代码根本跑不起来，会崩溃——且抛出一个IllegalArgumentException：&amp;quot;Don&#039;t know how to convert [state] back into data...&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
好的，所以笔者还需要讲一个很重要的概念：方块的meta值。是不是很眼熟？ItemStack就有meta值啊。只是在ItemStack中，meta值兼任损害值和类型序数两个概念，由hasSubtypes字段控制它的具体含义。但对于方块而言，没损害值这一说，所以方块的meta值就代表它的一个类型。这么说，方块的meta岂不是和它的BlockState作用重合了？是这样的，在Minecraft 1.8版本之前，BlockState系统还不存在，那时候方块被附加上不同属性后得到的“状态”就是用方块的meta值来描述的——玩家在世界中和方块交互时，以及Minecraft内部机制序列化、反序列化地图时，用的都是方块的meta值。但BlockState系统加入后，meta值便只被用于序列化、反序列化存档地图了。显然我们需要给一个方块的所有BlockState都各自指定一个meta值，否则Minecraft根本就不知道如何序列化或反序列化这种多BlockState方块。我们覆写&amp;lt;code&amp;gt;Block#getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;Block#getMetaFromState&amp;lt;/code&amp;gt;这两个方法，即可，runClient后，游戏便能正常进入了。&lt;br /&gt;
&lt;br /&gt;
和ItemStack不同，方块的meta值只能是0 - 15这个范围内的整数——最多只有16种可能；而在99%的情况下，所有BlockState和所有meta值之间的关系都是一一对应的，因此&#039;&#039;&#039;通常情况下&#039;&#039;&#039;，一个方块的BlockState最多只有16种。但也有例外——比如栅栏、红石粉、火焰这些方块，它们实际上有着成百上千种BlockState，那么这是如何做到的呢？它们实际上都只指定了部分BlockState的meta值，同时覆写了&amp;lt;code&amp;gt;Block#getActualState&amp;lt;/code&amp;gt;方法，该方法能根据周围的方块的方块状态，计算出它本身该是什么方块，这一部分算出来的BlockState实际上是不参与序列化和反序列化的。这个手段实际上很有局限性，为何？能这么做的方块，它的特点也很明确：它本身的BlockState确实会随着它周围的方块的BlockState的变化而变化，那对于箱子这种能存储物品（实际上是ItemStack）的方块呢？就没辙了，毕竟你总不可能不打开箱子，根据它周围的方块状态就确定它里面装的到底都有啥吧（笑），而且因为ItemStack本身有附加NBT，因此可以这么说，同一种物品的ItemStack有无限种可能。所以，指望着BlockState系统来完成这件事是没辙了。&lt;br /&gt;
&lt;br /&gt;
那有没有解决办法呢？&amp;lt;s&amp;gt;废话，当然有。&amp;lt;/s&amp;gt;解决方案是给世界上的每一个箱子方块都创建一个方块实体（TileEntity），这会在之后的教程中讲到，TileEntity并不是享元对象，因此我们可以通过给对应的TileEntity类追加实例变量的方式来存储理论上无限的数据。一个方块拥有了TileEntity不意味着它就不需要多种方块状态了，实际上箱子的朝向这种信息还是通过IProperty字段来定义的。TileEntity也有其缺点：内存和性能开销比较大。&lt;br /&gt;
&lt;br /&gt;
除非你真的在写类似于原版的栅栏、红石粉这些方块的方块，否则不要用getActualState，它实际上是个hack，很不好用，也很复杂。该用TileEntity就用TileEntity。另外，不是所有方块的属性都必须以IProperty的形式呈现。如：箱子有普通材质和圣诞节材质两种材质，这难道也应该是一个PropertyBool吗？大错特错。实际上这个是在相关的渲染代码中根据当前系统时间被临时判断出来的，因为这个真的只影响视觉，不影响任何（反）序列化过程、游戏逻辑。&#039;&#039;&#039;只有当方块的某个特性影响到了实际的游戏逻辑，或造成了存档内数据的改变，该特性才应该被作为一个IProperty对待。&#039;&#039;&#039;否则，你只需临时作个判断就好了。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;br /&gt;
现在，blockstates JSON文件中的那个“variants”应该很好理解了——它实际上是囊括了该方块的所有IProperty。对于那些单BlockState方块而言，它们没有任何的额外IProperty，因此我们直接开个“normal”，再来指定它的模型文件路径即可，就这么简单。&lt;br /&gt;
&lt;br /&gt;
但是对于多BlockState方块而言，显然我们期望不同的方块状态对应不同的样子，比如两种熔炉方块的外观会随着朝向的不同发生变化。这个时候就需要在variants下列出所有可能的IProperty的不同值之间的所有可能的组合情况了，我们来看看原版的熄灭的熔炉方块的blockstates JSON文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing=north&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
        &amp;quot;facing=south&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
        &amp;quot;facing=west&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
        &amp;quot;facing=east&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
熄灭的熔炉方块有且只有一个PropertyDirection，它被构造时传入的name是&amp;lt;code&amp;gt;&amp;quot;facing&amp;quot;&amp;lt;/code&amp;gt;，而它实际上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;，EnumFacing实现了IStringSerializable，因此对于该方块，我们就可以列出&amp;quot;facing=[A certain EnumFacing instance.getName()]&amp;quot;这种式子表示一个BlockState，再指定它的具体模型情况即可。这里我们注意到朝向实际上不影响方块的材质，本质上模型只是转了一定的角度而已，因此这里model不变，而y（即方块模型绕过方块上下两面中心点的直线旋转的角度）则有所变化。注意这里指定模型文件路径时并没有指定域名（domain），那是因为这是原版blockstates文件，不写域名的话，域名默认为minecraft，而对于我们的模组的blockstates文件，域名必须得写——就是我们的模组的modid。&lt;br /&gt;
&lt;br /&gt;
=== Forge BlockStates V1模型文件格式 ===&lt;br /&gt;
可能已经有读者注意到，原版的blockstates JSON文件格式是有优化空间的，比如在上面那个例子中，同样的“facing=”这种东西重复了四次，而且每种情况下模型文件实际上是相同的，只是绕y轴旋转的角度有所变化，但我们却依然要重复书写同样的模型文件路径，这实际上有些冗余。&lt;br /&gt;
&lt;br /&gt;
而且当一个方块拥有的IProperty数量不只是一个时，情况往往会变得更加糟糕——比如再来一个有四种可能值的IProperty的话，我们真的就要把总共十六种情况全部列出来，真的就要在variants里写上十六行相差不大的东西。这听起来就让人头皮发麻。&lt;br /&gt;
&lt;br /&gt;
所幸，Forge给我们提供了一个基于原版格式的blockstates文件格式——Forge BlockStates V1格式，让我们用它来重写原版的这一blockstates文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;forge_marker&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;defaults&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing&amp;quot;: {&lt;br /&gt;
            &amp;quot;north&amp;quot;: { &amp;quot;y&amp;quot;: 0 },&lt;br /&gt;
            &amp;quot;south&amp;quot;: { &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
            &amp;quot;west&amp;quot;: { &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
            &amp;quot;east&amp;quot;: { &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
首先，&amp;lt;code&amp;gt;&amp;quot;forge_marker&amp;quot;: 1&amp;lt;/code&amp;gt;代表这是一个符合Forge BlockStates V1格式的blockstates文件，这会告知Forge来处理它。然后我们可以通过设定defaults来设定该blockstates文件的一些默认值——毕竟四个方块状态共用一个模型文件，因此我们只需把它当成默认值写入即可。&lt;br /&gt;
&lt;br /&gt;
然后就是variants中IProperty写法的简化了，也很容易让人明白。如果有多个IProperty，只需分开写即可，Forge会自动求出它们的所有可能值的所有可能的组合情况的。这就大大简化了blockstates的模型映射。&lt;br /&gt;
&lt;br /&gt;
值得注意的是，虽然名字叫Forge BlockStates V1，但实际上这也可以用于方块的物品形式的模型的指定。还记得ModelResourceLocation构造方法中那个String类型的variantIn吗？我们传入的是&amp;lt;code&amp;gt;&amp;quot;inventory&amp;quot;&amp;lt;/code&amp;gt;，所以这个inventory实际上就是一个variant，换言之，你只需要在variants中这么写即可：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    ...&lt;br /&gt;
    &amp;quot;inventory&amp;quot;: [{&lt;br /&gt;
        &amp;quot;transform&amp;quot;: &amp;quot;forge:default-block&amp;quot;&lt;br /&gt;
    }]&lt;br /&gt;
    ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
甚至于和方块没什么关系的普通物品我们也可以写在blockstates文件中，只需要把transform改成forge:default-item，再指定一下textures即可。不过并不推荐这么做，因为普通的物品和方块既然没关系，那么放在blockstates文件中既多此一举，也容易造成歧义。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54990</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54990"/>
		<updated>2024-12-23T08:32:57Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 模型文件的书写 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉有4种方块状态——朝向有4种：水平方向的东、南、西、北，而实际上熔炉的燃烧/熄灭这一属性不是通过IProperty控制的，而是通过构造方法中的一个boolean参数控制的——即熄灭的熔炉和燃烧的熔炉实际上不是同一个Block，而它们各自有4种方块状态。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
3种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
此外，PropertyEnum还有一个针对方块朝向的特化版：PropertyDirection，本质上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;。实际上关于这个类，你可以找到一些可以直接拿来用的公开字段，如&amp;lt;code&amp;gt;BlockHorizontal.FACING&amp;lt;/code&amp;gt;，这个属性只允许水平方向上的EnumFacing——即东、南、西、北四个方向。&lt;br /&gt;
&lt;br /&gt;
弄好了你所需要的的IProperty字段后，就要通过覆写Block类下的一个方法来告知Minecraft方块状态系统：这个方块的BlockState可以包含这些IProperty：&lt;br /&gt;
&lt;br /&gt;
 @Override&lt;br /&gt;
 public BlockStateContainer createBlockState(){&lt;br /&gt;
     return new BlockStateContainer(this, property1, property2, ...);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
BlockStateContainer类构造方法中有个变长参数，以用于传入我们声明好的IProperty。实际上这个方法的返回值，会在Block类构造方法中，被传入一个名为blockState的字段。&lt;br /&gt;
&lt;br /&gt;
然后我们就该声明该方块的默认方块状态了（Default BlockState），通过setDefaultState来实现。对于BlockState中没有IProperty的方块，方块状态只有一种，因此我们也不需要设定默认的方块状态了——Minecraft已经帮我们设定好了。但是对于多BlockState方块，我们必须手动设定。&lt;br /&gt;
&lt;br /&gt;
那有读者就问了：我们如何能拿到一个带有特定属性且属性值被显式指定的一个IBlockState呢？所以首先我们先来讲讲IBlockState中的一些常用方法。&lt;br /&gt;
&lt;br /&gt;
说是一些，其实也就三个，其中有两个是修改属性值的：withProperty和cycleProperty。前者用于显式指定某个IBlockState中的某一属性的值，后者用于循环某个IBlockState中某一属性所有可能的值。最常用的方法是withProperty。两个方法均符合链式调用规则。此外还有一个：getValue，其作用显而易见。&lt;br /&gt;
&lt;br /&gt;
然后我们就可以设定该方块默认的方块状态了：&lt;br /&gt;
&lt;br /&gt;
 this.setDefaultState(this.blockState.getBaseState().withProperty(property1, defaultValue1).withProperty(property2, defaultValue2)....;&lt;br /&gt;
&lt;br /&gt;
然后Block类下的protected字段：defaultState就会把传入的这个IBlockState缓存下来，需要的时候用getDefaultState获取即可。所以我们可以看到，一个方块的默认状态，也是满足重复利用实例的原则的。但是非默认的方块状态便不一样了，用到它们的时候一般是在defaultState的基础上再次withProperty或者cycleProperty，因此其他方块状态，便不能用==运算符号判断是否相等了——只有默认的方块状态可以这么做。&lt;br /&gt;
&lt;br /&gt;
在什么情况下，给方块设定什么样的状态，都是没有定式的，全看你自己的需求。但特别地，如果你的方块有一个PropertyDirection属性，且你希望这个属性随着玩家放置方块时的的朝向变化，那么一般你只需要覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;方法即可，具体的实现如何，可以去参考原版的一些有方向属性的方块在这一块的代码。&lt;br /&gt;
&lt;br /&gt;
那么，工作是不是已经做完了呢？当然没有，我们毕竟还要写相应的JSON文件嘛，不过读者可能会觉得，仅仅是为了测试的话，没必要立刻就写JSON文件，可以直接顶着紫黑块的压力，按F3查看它的属性值！好的，这么做确实可以，然而注册完方块，runClient之后读者会发现代码根本跑不起来，会崩溃——且抛出一个IllegalArgumentException：&amp;quot;Don&#039;t know how to convert [state] back into data...&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
好的，所以笔者还需要讲一个很重要的概念：方块的meta值。是不是很眼熟？ItemStack就有meta值啊。只是在ItemStack中，meta值兼任损害值和类型序数两个概念，由hasSubtypes字段控制它的具体含义。但对于方块而言，没损害值这一说，所以方块的meta值就代表它的一个类型。这么说，方块的meta岂不是和它的BlockState作用重合了？是这样的，在Minecraft 1.8版本之前，BlockState系统还不存在，那时候方块被附加上不同属性后得到的“状态”就是用方块的meta值来描述的——玩家在世界中和方块交互时，以及Minecraft内部机制序列化、反序列化地图时，用的都是方块的meta值。但BlockState系统加入后，meta值便只被用于序列化、反序列化存档地图了。显然我们需要给一个方块的所有BlockState都各自指定一个meta值，否则Minecraft根本就不知道如何序列化或反序列化这种多BlockState方块。我们覆写&amp;lt;code&amp;gt;Block#getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;Block#getMetaFromState&amp;lt;/code&amp;gt;这两个方法，即可，runClient后，游戏便能正常进入了。&lt;br /&gt;
&lt;br /&gt;
和ItemStack不同，方块的meta值只能是0 - 15这个范围内的整数——最多只有16种可能；而在99%的情况下，所有BlockState和所有meta值之间的关系都是一一对应的，因此&#039;&#039;&#039;通常情况下&#039;&#039;&#039;，一个方块的BlockState最多只有16种。但也有例外——比如栅栏、红石粉、火焰这些方块，它们实际上有着成百上千种BlockState，那么这是如何做到的呢？它们实际上都只指定了部分BlockState的meta值，同时覆写了&amp;lt;code&amp;gt;Block#getActualState&amp;lt;/code&amp;gt;方法，该方法能根据周围的方块的方块状态，计算出它本身该是什么方块，这一部分算出来的BlockState实际上是不参与序列化和反序列化的。这个手段实际上很有局限性，为何？能这么做的方块，它的特点也很明确：它本身的BlockState确实会随着它周围的方块的BlockState的变化而变化，那对于箱子这种能存储物品（实际上是ItemStack）的方块呢？就没辙了，毕竟你总不可能不打开箱子，根据它周围的方块状态就确定它里面装的到底都有啥吧（笑），而且因为ItemStack本身有附加NBT，因此可以这么说，同一种物品的ItemStack有无限种可能。所以，指望着BlockState系统来完成这件事是没辙了。&lt;br /&gt;
&lt;br /&gt;
那有没有解决办法呢？&amp;lt;s&amp;gt;废话，当然有。&amp;lt;/s&amp;gt;解决方案是给世界上的每一个箱子方块都创建一个方块实体（TileEntity），这会在之后的教程中讲到，TileEntity并不是享元对象，因此我们可以通过给对应的TileEntity类追加实例变量的方式来存储理论上无限的数据。一个方块拥有了TileEntity不意味着它就不需要多种方块状态了，实际上箱子的朝向这种信息还是通过IProperty字段来定义的。TileEntity也有其缺点：内存和性能开销比较大。&lt;br /&gt;
&lt;br /&gt;
除非你真的在写类似于原版的栅栏、红石粉这些方块的方块，否则不要用getActualState，它实际上是个hack，很不好用，也很复杂。该用TileEntity就用TileEntity。另外，不是所有方块的属性都必须以IProperty的形式呈现。如：箱子有普通材质和圣诞节材质两种材质，这难道也应该是一个PropertyBool吗？大错特错。实际上这个是在相关的渲染代码中根据当前系统时间被临时判断出来的，因为这个真的只影响视觉，不影响任何（反）序列化过程、游戏逻辑。&#039;&#039;&#039;只有当方块的某个特性影响到了实际的游戏逻辑，或造成了存档内数据的改变，该特性才应该被作为一个IProperty对待。&#039;&#039;&#039;否则，你只需临时作个判断就好了。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;br /&gt;
现在，blockstates JSON文件中的那个“variants”应该很好理解了——它实际上是囊括了该方块的所有IProperty。对于那些单BlockState方块而言，它们没有任何的额外IProperty，因此我们直接开个“normal”，再来指定它的模型文件路径即可，就这么简单。&lt;br /&gt;
&lt;br /&gt;
但是对于多BlockState方块而言，显然我们期望不同的方块状态对应不同的样子，比如两种熔炉方块的外观会随着朝向的不同发生变化。这个时候就需要在variants下列出所有可能的IProperty的不同值之间的所有可能的组合情况了，我们来看看原版的熄灭的熔炉方块的blockstates JSON文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing=north&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
        &amp;quot;facing=south&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
        &amp;quot;facing=west&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
        &amp;quot;facing=east&amp;quot;:  { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot;, &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
熄灭的熔炉方块有且只有一个PropertyDirection，它被构造时传入的name是&amp;lt;code&amp;gt;&amp;quot;facing&amp;quot;&amp;lt;/code&amp;gt;，而它实际上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;，EnumFacing实现了IStringSerializable，因此对于该方块，我们就可以列出&amp;quot;facing=[A certain EnumFacing instance.getName()]&amp;quot;这种式子表示一个BlockState，再指定它的具体模型情况即可。这里我们注意到朝向实际上不影响方块的材质，本质上模型只是转了一定的角度而已，因此这里model不变，而y（即方块模型绕过方块上下两面中心点的直线旋转的角度）则有所变化。注意这里指定模型文件路径时并没有指定域名（domain），那是因为这是原版blockstates文件，不写域名的话，域名默认为minecraft，而对于我们的模组的blockstates文件，域名必须得写——就是我们的模组的modid。&lt;br /&gt;
&lt;br /&gt;
=== Forge BlockStates V1模型文件格式 ===&lt;br /&gt;
可能已经有读者注意到，原版的blockstates JSON文件格式是有优化空间的，比如在上面那个例子中，同样的“facing=”这种东西重复了四次，而且每种情况下模型文件实际上是相同的，只是绕y轴旋转的角度有所变化，但我们却依然要重复书写同样的模型文件路径，这实际上有些冗余。&lt;br /&gt;
&lt;br /&gt;
而且当一个方块拥有的IProperty数量不只是一个时，情况往往会变得更加糟糕——比如再来一个有四种可能值的IProperty的话，我们真的就要把总共十六种情况全部列出来，真的就要在variants里写上十六行相差不大的东西。这听起来就让人头皮发麻。&lt;br /&gt;
&lt;br /&gt;
所幸，Forge给我们提供了一个基于原版格式的blockstates文件格式——Forge BlockStates V1格式，让我们用它来重写原版的这一blockstates文件：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;forge_marker&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;defaults&amp;quot;: { &amp;quot;model&amp;quot;: &amp;quot;furnace&amp;quot; },&lt;br /&gt;
    &amp;quot;variants&amp;quot;: {&lt;br /&gt;
        &amp;quot;facing&amp;quot;: {&lt;br /&gt;
            &amp;quot;north&amp;quot;: { &amp;quot;y&amp;quot;: 0 },&lt;br /&gt;
            &amp;quot;south&amp;quot;: { &amp;quot;y&amp;quot;: 180 },&lt;br /&gt;
            &amp;quot;west&amp;quot;: { &amp;quot;y&amp;quot;: 270 },&lt;br /&gt;
            &amp;quot;east&amp;quot;: { &amp;quot;y&amp;quot;: 90 }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
首先，&amp;lt;code&amp;gt;&amp;quot;forge_marker&amp;quot;: 1&amp;lt;/code&amp;gt;代表这是一个符合Forge BlockStates V1格式的blockstates文件，这会告知Forge来处理它。然后我们可以通过设定defaults来设定该blockstates文件的一些默认值——毕竟四个方块状态共用一个模型文件，因此我们只需把它当成默认值写入即可。&lt;br /&gt;
&lt;br /&gt;
然后就是variants中IProperty写法的简化了，也很容易让人明白。如果有多个IProperty，只需分开写即可，Forge会自动求出它们的所有可能值的所有可能的组合情况的。这就大大简化了blockstates的模型映射。&lt;br /&gt;
&lt;br /&gt;
值得注意的是，虽然名字叫Forge BlockStates V1，但实际上这也可以用于方块的物品形式的模型的指定。还记得ModelResourceLocation构造方法中那个String类型的variantsIn吗？我们传入的是&amp;lt;code&amp;gt;&amp;quot;inventory&amp;quot;&amp;lt;/code&amp;gt;，所以这个inventory实际上就是一个variant，换言之，你只需要在variants中这么写即可：&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    ...&lt;br /&gt;
    &amp;quot;inventory&amp;quot;: [{&lt;br /&gt;
        &amp;quot;transform&amp;quot;: &amp;quot;forge:default-block&amp;quot;&lt;br /&gt;
    }]&lt;br /&gt;
    ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
甚至于和方块没什么关系的普通物品我们也可以写在blockstates文件中，只需要把transform改成forge:default-item，再指定一下textures即可。不过并不推荐这么做，因为普通的物品和方块既然没关系，那么放在blockstates文件中既多此一举，也容易造成歧义。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54989</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54989"/>
		<updated>2024-12-23T07:31:22Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉有4种方块状态——朝向有4种：水平方向的东、南、西、北，而实际上熔炉的燃烧/熄灭这一属性不是通过IProperty控制的，而是通过构造方法中的一个boolean参数控制的——即熄灭的熔炉和燃烧的熔炉实际上不是同一个Block，而它们各自有4种方块状态。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
3种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
此外，PropertyEnum还有一个针对方块朝向的特化版：PropertyDirection，本质上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;。实际上关于这个类，你可以找到一些可以直接拿来用的公开字段，如&amp;lt;code&amp;gt;BlockHorizontal.FACING&amp;lt;/code&amp;gt;，这个属性只允许水平方向上的EnumFacing——即东、南、西、北四个方向。&lt;br /&gt;
&lt;br /&gt;
弄好了你所需要的的IProperty字段后，就要通过覆写Block类下的一个方法来告知Minecraft方块状态系统：这个方块的BlockState可以包含这些IProperty：&lt;br /&gt;
&lt;br /&gt;
 @Override&lt;br /&gt;
 public BlockStateContainer createBlockState(){&lt;br /&gt;
     return new BlockStateContainer(this, property1, property2, ...);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
BlockStateContainer类构造方法中有个变长参数，以用于传入我们声明好的IProperty。实际上这个方法的返回值，会在Block类构造方法中，被传入一个名为blockState的字段。&lt;br /&gt;
&lt;br /&gt;
然后我们就该声明该方块的默认方块状态了（Default BlockState），通过setDefaultState来实现。对于BlockState中没有IProperty的方块，方块状态只有一种，因此我们也不需要设定默认的方块状态了——Minecraft已经帮我们设定好了。但是对于多BlockState方块，我们必须手动设定。&lt;br /&gt;
&lt;br /&gt;
那有读者就问了：我们如何能拿到一个带有特定属性且属性值被显式指定的一个IBlockState呢？所以首先我们先来讲讲IBlockState中的一些常用方法。&lt;br /&gt;
&lt;br /&gt;
说是一些，其实也就三个，其中有两个是修改属性值的：withProperty和cycleProperty。前者用于显式指定某个IBlockState中的某一属性的值，后者用于循环某个IBlockState中某一属性所有可能的值。最常用的方法是withProperty。两个方法均符合链式调用规则。此外还有一个：getValue，其作用显而易见。&lt;br /&gt;
&lt;br /&gt;
然后我们就可以设定该方块默认的方块状态了：&lt;br /&gt;
&lt;br /&gt;
 this.setDefaultState(this.blockState.getBaseState().withProperty(property1, defaultValue1).withProperty(property2, defaultValue2)....;&lt;br /&gt;
&lt;br /&gt;
然后Block类下的protected字段：defaultState就会把传入的这个IBlockState缓存下来，需要的时候用getDefaultState获取即可。所以我们可以看到，一个方块的默认状态，也是满足重复利用实例的原则的。但是非默认的方块状态便不一样了，用到它们的时候一般是在defaultState的基础上再次withProperty或者cycleProperty，因此其他方块状态，便不能用==运算符号判断是否相等了——只有默认的方块状态可以这么做。&lt;br /&gt;
&lt;br /&gt;
在什么情况下，给方块设定什么样的状态，都是没有定式的，全看你自己的需求。但特别地，如果你的方块有一个PropertyDirection属性，且你希望这个属性随着玩家放置方块时的的朝向变化，那么一般你只需要覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;方法即可，具体的实现如何，可以去参考原版的一些有方向属性的方块在这一块的代码。&lt;br /&gt;
&lt;br /&gt;
那么，工作是不是已经做完了呢？当然没有，我们毕竟还要写相应的JSON文件嘛，不过读者可能会觉得，仅仅是为了测试的话，没必要立刻就写JSON文件，可以直接顶着紫黑块的压力，按F3查看它的属性值！好的，这么做确实可以，然而注册完方块，runClient之后读者会发现代码根本跑不起来，会崩溃——且抛出一个IllegalArgumentException：&amp;quot;Don&#039;t know how to convert [state] back into data...&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
好的，所以笔者还需要讲一个很重要的概念：方块的meta值。是不是很眼熟？ItemStack就有meta值啊。只是在ItemStack中，meta值兼任损害值和类型序数两个概念，由hasSubtypes字段控制它的具体含义。但对于方块而言，没损害值这一说，所以方块的meta值就代表它的一个类型。这么说，方块的meta岂不是和它的BlockState作用重合了？是这样的，在Minecraft 1.8版本之前，BlockState系统还不存在，那时候方块被附加上不同属性后得到的“状态”就是用方块的meta值来描述的——玩家在世界中和方块交互时，以及Minecraft内部机制序列化、反序列化地图时，用的都是方块的meta值。但BlockState系统加入后，meta值便只被用于序列化、反序列化存档地图了。显然我们需要给一个方块的所有BlockState都各自指定一个meta值，否则Minecraft根本就不知道如何序列化或反序列化这种多BlockState方块。我们覆写&amp;lt;code&amp;gt;Block#getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;Block#getMetaFromState&amp;lt;/code&amp;gt;这两个方法，即可，runClient后，游戏便能正常进入了。&lt;br /&gt;
&lt;br /&gt;
和ItemStack不同，方块的meta值只能是0 - 15这个范围内的整数——最多只有16种可能；而在99%的情况下，所有BlockState和所有meta值之间的关系都是一一对应的，因此&#039;&#039;&#039;通常情况下&#039;&#039;&#039;，一个方块的BlockState最多只有16种。但也有例外——比如栅栏、红石粉、火焰这些方块，它们实际上有着成百上千种BlockState，那么这是如何做到的呢？它们实际上都只指定了部分BlockState的meta值，同时覆写了&amp;lt;code&amp;gt;Block#getActualState&amp;lt;/code&amp;gt;方法，该方法能根据周围的方块的方块状态，计算出它本身该是什么方块，这一部分算出来的BlockState实际上是不参与序列化和反序列化的。这个手段实际上很有局限性，为何？能这么做的方块，它的特点也很明确：它本身的BlockState确实会随着它周围的方块的BlockState的变化而变化，那对于箱子这种能存储物品（实际上是ItemStack）的方块呢？就没辙了，毕竟你总不可能不打开箱子，根据它周围的方块状态就确定它里面装的到底都有啥吧（笑），而且因为ItemStack本身有附加NBT，因此可以这么说，同一种物品的ItemStack有无限种可能。所以，指望着BlockState系统来完成这件事是没辙了。&lt;br /&gt;
&lt;br /&gt;
那有没有解决办法呢？&amp;lt;s&amp;gt;废话，当然有。&amp;lt;/s&amp;gt;解决方案是给世界上的每一个箱子方块都创建一个方块实体（TileEntity），这会在之后的教程中讲到，TileEntity并不是享元对象，因此我们可以通过给对应的TileEntity类追加实例变量的方式来存储理论上无限的数据。一个方块拥有了TileEntity不意味着它就不需要多种方块状态了，实际上箱子的朝向这种信息还是通过IProperty字段来定义的。TileEntity也有其缺点：内存和性能开销比较大。&lt;br /&gt;
&lt;br /&gt;
除非你真的在写类似于原版的栅栏、红石粉这些方块的方块，否则不要用getActualState，它实际上是个hack，很不好用，也很复杂。该用TileEntity就用TileEntity。另外，不是所有方块的属性都必须以IProperty的形式呈现。如：箱子有普通材质和圣诞节材质两种材质，这难道也应该是一个PropertyBool吗？大错特错。实际上这个是在相关的渲染代码中根据当前系统时间被临时判断出来的，因为这个真的只影响视觉，不影响任何（反）序列化过程、游戏逻辑。&#039;&#039;&#039;只有当方块的某个特性影响到了实际的游戏逻辑，或造成了存档内数据的改变，该特性才应该被作为一个IProperty对待。&#039;&#039;&#039;否则，你只需临时作个判断就好了。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54988</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54988"/>
		<updated>2024-12-22T15:42:35Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 给方块附加属性 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉就有8种方块状态——朝向有4种，工作状态2种：熄灭/燃烧，乘起来就是8种。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
3种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
此外，PropertyEnum还有一个针对方块朝向的特化版：PropertyDirection，本质上是一个PropertyEnum&amp;lt;EnumFacing&amp;gt;。实际上关于这个类，你可以找到一些可以直接拿来用的公开字段，如&amp;lt;code&amp;gt;BlockHorizontal.FACING&amp;lt;/code&amp;gt;，这个属性只允许水平方向上的EnumFacing——即东、南、西、北四个方向。&lt;br /&gt;
&lt;br /&gt;
弄好了你所需要的的IProperty字段后，就要通过覆写Block类下的一个方法来告知Minecraft方块状态系统：这个方块的BlockState可以包含这些IProperty：&lt;br /&gt;
&lt;br /&gt;
 @Override&lt;br /&gt;
 public BlockStateContainer createBlockState(){&lt;br /&gt;
     return new BlockStateContainer(this, property1, property2, ...);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
BlockStateContainer类构造方法中有个变长参数，以用于传入我们声明好的IProperty。实际上这个方法的返回值，会在Block类构造方法中，被传入一个名为blockState的字段。&lt;br /&gt;
&lt;br /&gt;
然后我们就该声明该方块的默认方块状态了（Default BlockState），通过setDefaultState来实现。对于BlockState中没有IProperty的方块，方块状态只有一种，因此我们也不需要设定默认的方块状态了——Minecraft已经帮我们设定好了。但是对于多BlockState方块，我们必须手动设定。&lt;br /&gt;
&lt;br /&gt;
那有读者就问了：我们如何能拿到一个带有特定属性且属性值被显式指定的一个IBlockState呢？所以首先我们先来讲讲IBlockState中的一些常用方法。&lt;br /&gt;
&lt;br /&gt;
说是一些，其实也就三个，其中有两个是修改属性值的：withProperty和cycleProperty。前者用于显式指定某个IBlockState中的某一属性的值，后者用于循环某个IBlockState中某一属性所有可能的值。最常用的方法是withProperty。两个方法均符合链式调用规则。此外还有一个：getValue，其作用显而易见。&lt;br /&gt;
&lt;br /&gt;
然后我们就可以设定该方块默认的方块状态了：&lt;br /&gt;
&lt;br /&gt;
 this.setDefaultState(this.blockState.getBaseState().withProperty(property1, defaultValue1).withProperty(property2, defaultValue2)....;&lt;br /&gt;
&lt;br /&gt;
然后Block类下的protected字段：defaultState就会把传入的这个IBlockState缓存下来，需要的时候用getDefaultState获取即可。所以我们可以看到，一个方块的默认状态，也是满足重复利用实例的原则的。但是非默认的方块状态便不一样了，用到它们的时候一般是在defaultState的基础上再次withProperty或者cycleProperty，因此其他方块状态，便不能用==运算符号判断是否相等了——只有默认的方块状态可以这么做。&lt;br /&gt;
&lt;br /&gt;
在什么情况下，给方块设定什么样的状态，都是没有定式的，全看你自己的需求。但特别地，如果你的方块有一个PropertyDirection属性，且你希望这个属性随着玩家放置方块时的的朝向变化，那么一般你只需要覆写&amp;lt;code&amp;gt;Block#getStateForPlacement&amp;lt;/code&amp;gt;方法即可，具体的实现如何，可以去参考原版的一些有方向属性的方块在这一块的代码。&lt;br /&gt;
&lt;br /&gt;
那么，工作是不是已经做完了呢？当然没有，我们毕竟还要写相应的JSON文件嘛，不过读者可能会觉得，仅仅是为了测试的话，没必要立刻就写JSON文件，可以直接顶着紫黑块的压力，按F3查看它的属性值！好的，这么做确实可以，然而注册完方块，runClient之后读者会发现代码根本跑不起来，会崩溃——且抛出一个IllegalArgumentException：&amp;quot;Don&#039;t know how to convert [state] back into data...&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
好的，所以笔者还需要讲一个很重要的概念：方块的meta值。是不是很眼熟？ItemStack就有meta值啊。只是在ItemStack中，meta值兼任损害值和类型序数两个概念，由hasSubtypes字段控制它的具体含义。但对于方块而言，没损害值这一说，所以方块的meta值就代表它的一个类型。这么说，方块的meta岂不是和它的BlockState作用重合了？是这样的，在Minecraft 1.8版本之前，BlockState系统还不存在，那时候方块被附加上不同属性后得到的“状态”就是用方块的meta值来描述的——玩家在世界中和方块交互时，以及Minecraft内部机制序列化、反序列化地图时，用的都是方块的meta值。但BlockState系统加入后，meta值便只被用于序列化、反序列化存档地图了。显然我们需要给一个方块的所有BlockState都各自指定一个meta值，否则Minecraft根本就不知道如何序列化或反序列化这种多BlockState方块。我们覆写&amp;lt;code&amp;gt;Block#getStateFromMeta&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;Block#getMetaFromState&amp;lt;/code&amp;gt;这两个方法，即可，runClient后，游戏便能正常进入了。&lt;br /&gt;
&lt;br /&gt;
和ItemStack不同，方块的meta值只能是0 - 15这个范围内的整数——最多只有16种可能；而在99%的情况下，所有BlockState和所有meta值之间的关系都是一一对应的，因此&#039;&#039;&#039;通常情况下&#039;&#039;&#039;，一个方块的BlockState最多只有16种。但也有例外——比如栅栏、红石粉、火焰这些方块，它们实际上有着成百上千种BlockState，那么这是如何做到的呢？它们实际上都只指定了部分BlockState的meta值，同时覆写了&amp;lt;code&amp;gt;Block#getActualState&amp;lt;/code&amp;gt;方法，该方法能根据周围的方块的方块状态，计算出它本身该是什么方块，这一部分算出来的BlockState实际上是不参与序列化和反序列化的。这个手段实际上很有局限性，为何？能这么做的方块，它的特点也很明确：它本身的BlockState确实会随着它周围的方块的BlockState的变化而变化，那对于箱子这种能存储物品（实际上是ItemStack）的方块呢？就没辙了，毕竟你总不可能不打开箱子，根据它周围的方块状态就确定它里面装的到底都有啥吧（笑），而且因为ItemStack本身有附加NBT，因此可以这么说，同一种物品的ItemStack有无限种可能。所以，指望着BlockState系统来完成这件事是没辙了。&lt;br /&gt;
&lt;br /&gt;
那有没有解决办法呢？&amp;lt;s&amp;gt;废话，当然有。&amp;lt;/s&amp;gt;解决方案是给世界上的每一个箱子方块都创建一个方块实体（TileEntity），这会在之后的教程中讲到，TileEntity并不是享元对象，因此我们可以通过给对应的TileEntity类追加实例变量的方式来存储理论上无限的数据。一个方块拥有了TileEntity不意味着它就不需要多种方块状态了，实际上箱子的朝向这种信息还是通过IProperty字段来定义的。TileEntity也有其缺点：内存和性能开销比较大。&lt;br /&gt;
&lt;br /&gt;
除非你真的在写类似于原版的栅栏、红石粉这些方块的方块，否则不要用getActualState，它实际上是个hack，很不好用，也很复杂。该用TileEntity就用TileEntity。另外，不是所有方块的属性都必须以IProperty的形式呈现。如：箱子有普通材质和圣诞节材质两种材质，这难道也应该是一个PropertyBool吗？大错特错。实际上这个是在相关的渲染代码中根据当前系统时间被临时判断出来的，因为这个真的只影响视觉，不影响任何（反）序列化过程、游戏逻辑。&#039;&#039;&#039;只有当方块的某个特性影响到了实际的游戏逻辑，或造成了存档内数据的改变，该特性才应该被作为一个IProperty对待。&#039;&#039;&#039;否则，你只需临时作个判断就好了。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54987</id>
		<title>用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/10.BlockState%E7%B3%BB%E7%BB%9F%E5%8F%8AForge_BlockStates_V1%E6%A8%A1%E5%9E%8B%E6%A0%BC%E5%BC%8F&amp;diff=54987"/>
		<updated>2024-12-22T13:30:07Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。  很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;和Item一样，我们和世界中的方块进行交互时，实际上交互的也并非Block本身，而是它的某个方块状态（BlockState）。BlockState的存在允许我们为世界中实际存在的某个方块附加一些属性：一个或多个IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;。方块状态对象都是接口IBlockState的实例。&lt;br /&gt;
&lt;br /&gt;
很多教程都会说ItemStack和IBlockState是相似的，其实不然：一来，ItemStack的使用是随建随用，而IBlockState甚至一定程度上遵循享元设计模式；二来，同一种物品对应的ItemStack有无数多种，而同一种方块的方块状态种类是可以被穷尽的。&lt;br /&gt;
&lt;br /&gt;
原版中有很多多BlockState的方块，如熔炉就有8种方块状态——朝向有4种，工作状态2种：熄灭/燃烧，乘起来就是8种。&lt;br /&gt;
&lt;br /&gt;
在本节教程中，笔者将完整地讲述创建一个多BlockState方块的全过程。&lt;br /&gt;
&lt;br /&gt;
== 给方块附加属性 ==&lt;br /&gt;
首先我们需要在对应的方块类中添加相应的静态IProperty&amp;lt;T extends Comparable&amp;lt;T&amp;gt;&amp;gt;字段。实际上我们完全不需要重新实现这个接口，用Minecraft提供的一系列默认实现就好了。默认实现一共有3种：PropertyInteger、PropertyBool和PropertyEnum&amp;lt;T extends Enum &amp;amp; IStringSerializable&amp;gt;。顾名思义，它们分别用于描述整型、布尔型和枚举型的属性。值得注意的是，它们的构造方法都是protected修饰的，那么我们如何构造它们呢？答案是利用它们的静态方法create。&lt;br /&gt;
&lt;br /&gt;
三种IProperty的默认实现类的对象的构造，都要求一个String参数，用于设定该属性的名称，这个参数只能由小写字母和下划线组成，我们会在方块的blockstates JSON文件中看到它的身影。对于PropertyInteger，我们还需要指定该属性的最大值和最小值。对于PropertyEnum，我们需要一个实现了IStringSerializable接口的枚举类（这个接口只有一个方法：&amp;lt;code&amp;gt;String getName()&amp;lt;/code&amp;gt;，也是为blockstates JSON文件服务的）；该属性会默认接受该枚举类的所有枚举值，但我们也可以显式指定哪些值会被允许，如传入一个数组或Collection，甚至一个Predicate，都是可以的。&lt;br /&gt;
&lt;br /&gt;
== 模型文件的书写 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=54986</id>
		<title>用户:MashKJo/1.12.2模组开发教程</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B&amp;diff=54986"/>
		<updated>2024-12-22T12:45:48Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​// Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[用户:MashKJo/1.12.2模组开发教程/绪论|绪论]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/1.构建开发环境|1.构建开发环境]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/2.基础概念|2.基础概念]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/3.Mod主类与代理详解|3.Mod主类与代理详解]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统|4.Forge事件系统]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/5.第一个物品|5.第一个物品]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack|6.ItemStack和Meta-hack]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/7.自定义创造模式物品栏|7.自定义创造模式物品栏]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用|8.NBT系统的使用]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/9.第一个方块|9.第一个方块]]&lt;br /&gt;
* [[用户:MashKJo/1.12.2模组开发教程/10.BlockState系统及Forge BlockStates V1模型格式|10.BlockState系统及Forge BlockStates V1模型格式]]&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54982</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54982"/>
		<updated>2024-12-16T10:36:15Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 添加一个新方块的流程 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
* setLightOpacity：传入一个int量，用于设定方块的透光度，传入的数值越大透光度越低。完全不透光的方块，该值为255；完全透光则为0；部分透光则在两者之间，如树叶的lightOpacity为1。该值默认由一个三目运算决定，具体为：如果该方块为渲染意义上的完整方块，lightOpacity为255，否则为0&lt;br /&gt;
* setLightLevel：传入一个float量，设置方块的光照等级，注意实际的光照值要把这个传入的参数乘以15。具体设定为多少，读者可以去查阅原版中会发光的方块的源代码，这块是怎么设定的&lt;br /&gt;
* setResistance：传入一个float量，用于设置方块的爆炸抗性，实际抗性值为传入参数乘以3的结果&lt;br /&gt;
* setHardness：传入一个float量，用于设置方块的挖掘硬度，注意这个setter也有可能影响爆炸抗性。特别地，还有一个该setter的特化版：setBlockUnbreakable，会把hardness设定为-1.0F，即让该方块变得像基岩一样不可破坏&lt;br /&gt;
* setUnlocalizedName、setCreativeTab：作用类似于Item类中的同名方法。注意：这里如果给setUnlocalizedName传入&amp;lt;code&amp;gt;&amp;quot;examplename&amp;quot;&amp;lt;/code&amp;gt;，那么最终的本地化键名不是&amp;lt;code&amp;gt;item.examplename.name&amp;lt;/code&amp;gt;，也不是&amp;lt;code&amp;gt;block.examplename.name&amp;lt;/code&amp;gt;，而是&amp;lt;code&amp;gt;tile.examplename.name&amp;lt;/code&amp;gt;&lt;br /&gt;
* setHarvestLevel：形参列表为&amp;lt;code&amp;gt;(String toolClass, int level)&amp;lt;/code&amp;gt;，用于设定该方块的挖掘等级和应被什么种类的工具挖掘。实际上这个方法是Forge塞进去的——没错，Minecraft原版甚至没有挖掘等级这一概念，原版中某些方块需要特定等级的工具才能挖掘的例子纯属硬编码的结果。toolClass这个String代表工具类型，如&amp;lt;code&amp;gt;&amp;quot;pickaxe&amp;quot;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;quot;axe&amp;quot;&amp;lt;/code&amp;gt;等等。而level的规则是：木制/金制工具的等级为0，石制为1，铁制为2，钻石工具则为3，-1则代表空手挖掘。另外注意：该方法的返回值类型为void，即不符合链式调用规则&lt;br /&gt;
&lt;br /&gt;
例如，笔者新增了一个方块：红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/java/net/tutorial_mod/block/BlockRuby.java:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 package net.tutorial_mod.block;&lt;br /&gt;
 &lt;br /&gt;
 import net.minecraft.block.Block;&lt;br /&gt;
 import net.minecraft.block.material.MapColor;&lt;br /&gt;
 import net.minecraft.block.material.Material;&lt;br /&gt;
 import net.tutorial_mod.TutorialMod;&lt;br /&gt;
 &lt;br /&gt;
 public class BlockRuby extends Block{&lt;br /&gt;
    public BlockRuby(){&lt;br /&gt;
        super(Material.ROCK, MapColor.RED);&lt;br /&gt;
        this.setRegistryName(TutorialMod.MODID, &amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setUnlocalizedName(&amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setCreativeTab(TutorialMod.TutorialModTab);&lt;br /&gt;
        this.setHarvestLevel(&amp;quot;pickaxe&amp;quot;, 2);&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
最后，先实例化，监听RegistryEvent.Register&amp;lt;Block&amp;gt;事件，把我们的新方块注册进游戏。&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
进入游戏后，读者可能会感到奇怪：创造模式物品栏里，并没有我们的新方块，用/give指令也得不到，只有用/setblock指令才能把该方块放置在世界中。那是因为我们还没有注册该方块的物品形式。&lt;br /&gt;
&lt;br /&gt;
方块的物品形式首先是物品，实际上是ItemBlock——这是Item的一个子类，构造器接受一个Block参数的传入，这种设计很易于理解。我们直接实例化ItemBlock类，再通过监听RegistryEvent.Register&amp;lt;Item&amp;gt;事件即可把它注册进游戏。注意：我们不需要给方块的物品形式的实例调用一系列setter方法，但必须调用setRegistryName——且它的registryName和对应方块的registryName必须相同。&lt;br /&gt;
&lt;br /&gt;
可能会有读者对前面Block类的setUnlocalizedName和setCreativeTab这两个setter感到奇怪——因为其实这两个方法只对物品有意义——毕竟放在世界中的方块需要什么对应的创造模式物品栏？是这样，这两个方法是为ItemBlock服务的。我们用我们的Block对象构造出一个ItemBlock后，该ItemBlock对象中的对应的创造模式物品栏、unlocalizedName这些信息会自动等于对应的Block对象中的对应信息的，这也是为什么我们不需要给ItemBlock调用一大堆的setter。&lt;br /&gt;
&lt;br /&gt;
ItemBlock类中覆写了Item类中的一些方法，导致了ItemBlock对象的通用逻辑为：玩家在世界中对着非空气方块右键时，如果坐标合法（如Y坐标在0~255这个范围内），就会在该处放置一个对应的方块，同时手上的ItemStack的数量这一字段减一。除非你真的有特殊需求，否则你只需要直接实例化ItemBlock类得到方块的物品形式就好了，不用去先继承ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好了，现在再运行游戏，应该可以在创造模式物品栏中看到我们的方块的物品形式了。&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;br /&gt;
和物品不同，方块的模型文件，并不是通过代码来指定的，而是通过&amp;lt;code&amp;gt;assets/[modid]/blockstates&amp;lt;/code&amp;gt;路径下的与方块注册名同名的.json文件来指定的，例如，对于我们给前文添加的红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/blockstates/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;:{&lt;br /&gt;
        &amp;quot;normal&amp;quot;:{&lt;br /&gt;
            &amp;quot;model&amp;quot;:&amp;quot;tutorial_mod:ruby_block&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
这个文件的各部分内容都是什么意思，会在后面讲到，不过有一处是显而易见的：该文件把我们的方块的模型文件映射到了&amp;lt;code&amp;gt;assets/[modid]/models/block/ruby_block.json&amp;lt;/code&amp;gt;这个文件。那么，再来看看模型文件的内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/models/block/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;parent&amp;quot;: &amp;quot;block/cube_all&amp;quot;,&lt;br /&gt;
    &amp;quot;textures&amp;quot;:{&lt;br /&gt;
        &amp;quot;all&amp;quot;: &amp;quot;tutorial_mod:blocks/ruby_block&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
由于我们要添加的红宝石块是类似于原版的铁块、金块等方块的——六个面的材质都一样，所以我们直接指定该模型文件的父模型为&amp;lt;code&amp;gt;assets/minecraft/models/block/cube_all.json&amp;lt;/code&amp;gt;，然后我们就可以指定所有（all）面的材质文件了。我们可以看出，&amp;quot;parent&amp;quot;用于指定模型的父模型文件。之前讲过的物品模型文件，其父模型文件实际上是&amp;lt;code&amp;gt;assets/minecraft/models/item/generated.json&amp;lt;/code&amp;gt;，这个模型文件主要设定了物品材质的默认几何变换。&lt;br /&gt;
&lt;br /&gt;
读者翻阅原版的模型文件时，会发现这些模型的最终父模型是&amp;quot;builtin/generated&amp;quot;，这实际上不指代一个具体的模型文件，它的意思是“交给相关的渲染代码处理”。&lt;br /&gt;
&lt;br /&gt;
如果我想新增一个六个面材质不是全相同的方块呢？你可以继承原版其他的模型文件，如：&amp;lt;code&amp;gt;block/cube_column&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;block/cube_directional&amp;lt;/code&amp;gt;等等。或者你就可以直接继承&amp;lt;code&amp;gt;block/cube&amp;lt;/code&amp;gt;，然后你手动指定方块的六个面的材质和粒子效果材质的文件路径，也可以，但是一般没必要。&lt;br /&gt;
&lt;br /&gt;
把绘制好的材质文件放到对应路径中，进入游戏，就可以看到世界中的方块的材质已经被正确渲染了。&lt;br /&gt;
&lt;br /&gt;
但是，方块的物品形式仍然是紫黑色的，我们还需要给它声明模型。首先用&amp;lt;code&amp;gt;ModelLoader#setCustomModelResourceLocation&amp;lt;/code&amp;gt;将它和一个ModelResourceLocation绑定，再：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/models/item/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;parent&amp;quot;: &amp;quot;tutorial_mod:block/ruby_block&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
没错，我们把它的父模型指定为之前的方块模型即可，MC的相关代码会自动把该物品的样子渲染成方块斜着放的样子的。&lt;br /&gt;
&lt;br /&gt;
现在进入游戏测试，应该真的没什么问题了。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54981</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54981"/>
		<updated>2024-12-16T10:35:47Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 添加一个新方块的流程 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
* setLightOpacity：传入一个int量，用于设定方块的透光度，传入的数值越大透光度越低。完全不透光的方块，该值为255；完全透光则为0；部分透光则在两者之间，如树叶的lightOpacity为1。该值默认由一个三目运算决定，具体为：如果该方块为渲染意义上的完整方块，lightOpacity为255，否则为0&lt;br /&gt;
* setLightLevel：传入一个float量，设置方块的光照等级，注意实际的光照值要把这个传入的参数乘以15。具体设定为多少，读者可以去查阅原版中会发光的方块的源代码，这块是怎么设定的&lt;br /&gt;
* setResistance：传入一个float量，用于设置方块的爆炸抗性，实际抗性值为传入参数乘以3的结果&lt;br /&gt;
* setHardness：传入一个float量，用于设置方块的挖掘硬度，注意这个setter也有可能影响爆炸抗性。特别地，还有一个该setter的特化版：setBlockUnbreakable，会把hardness设定为-1.0F，即让该方块变得像基岩一样不可破坏&lt;br /&gt;
* setUnlocalizedName、setCreativeTab：作用类似于Item类中的同名方法。注意：这里如果给setUnlocalizedName传入&amp;lt;code&amp;gt;&amp;quot;examplename&amp;quot;&amp;lt;/code&amp;gt;，那么最终的本地化键名不是&amp;lt;code&amp;gt;item.examplename.name&amp;lt;/code&amp;gt;，也不是&amp;lt;code&amp;gt;block.examplename.name&amp;lt;/code&amp;gt;，而是&amp;lt;code&amp;gt;tile.examplename.name&amp;lt;/code&amp;gt;&lt;br /&gt;
* setHarvestLevel：形参列表为&amp;lt;code&amp;gt;(String toolClass, int level)&amp;lt;/code&amp;gt;，用于设定该方块的挖掘等级和应被什么种类的工具挖掘。实际上这个方法是Forge塞进去的——没错，Minecraft原版甚至没有挖掘等级这一概念，原版中某些方块需要特定等级的工具才能挖掘的例子纯属硬编码的结果。toolClass这个String代表工具类型，如&amp;lt;code&amp;gt;&amp;quot;picakxe&amp;quot;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;quot;axe&amp;quot;&amp;lt;/code&amp;gt;等等。而level的规则是：木制/金制工具的等级为0，石制为1，铁制为2，钻石工具则为3，-1则代表空手挖掘。另外注意：该方法的返回值类型为void，即不符合链式调用规则&lt;br /&gt;
&lt;br /&gt;
例如，笔者新增了一个方块：红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/java/net/tutorial_mod/block/BlockRuby.java:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 package net.tutorial_mod.block;&lt;br /&gt;
 &lt;br /&gt;
 import net.minecraft.block.Block;&lt;br /&gt;
 import net.minecraft.block.material.MapColor;&lt;br /&gt;
 import net.minecraft.block.material.Material;&lt;br /&gt;
 import net.tutorial_mod.TutorialMod;&lt;br /&gt;
 &lt;br /&gt;
 public class BlockRuby extends Block{&lt;br /&gt;
    public BlockRuby(){&lt;br /&gt;
        super(Material.ROCK, MapColor.RED);&lt;br /&gt;
        this.setRegistryName(TutorialMod.MODID, &amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setUnlocalizedName(&amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setCreativeTab(TutorialMod.TutorialModTab);&lt;br /&gt;
        this.setHarvestLevel(&amp;quot;pickaxe&amp;quot;, 2);&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
最后，先实例化，监听RegistryEvent.Register&amp;lt;Block&amp;gt;事件，把我们的新方块注册进游戏。&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
进入游戏后，读者可能会感到奇怪：创造模式物品栏里，并没有我们的新方块，用/give指令也得不到，只有用/setblock指令才能把该方块放置在世界中。那是因为我们还没有注册该方块的物品形式。&lt;br /&gt;
&lt;br /&gt;
方块的物品形式首先是物品，实际上是ItemBlock——这是Item的一个子类，构造器接受一个Block参数的传入，这种设计很易于理解。我们直接实例化ItemBlock类，再通过监听RegistryEvent.Register&amp;lt;Item&amp;gt;事件即可把它注册进游戏。注意：我们不需要给方块的物品形式的实例调用一系列setter方法，但必须调用setRegistryName——且它的registryName和对应方块的registryName必须相同。&lt;br /&gt;
&lt;br /&gt;
可能会有读者对前面Block类的setUnlocalizedName和setCreativeTab这两个setter感到奇怪——因为其实这两个方法只对物品有意义——毕竟放在世界中的方块需要什么对应的创造模式物品栏？是这样，这两个方法是为ItemBlock服务的。我们用我们的Block对象构造出一个ItemBlock后，该ItemBlock对象中的对应的创造模式物品栏、unlocalizedName这些信息会自动等于对应的Block对象中的对应信息的，这也是为什么我们不需要给ItemBlock调用一大堆的setter。&lt;br /&gt;
&lt;br /&gt;
ItemBlock类中覆写了Item类中的一些方法，导致了ItemBlock对象的通用逻辑为：玩家在世界中对着非空气方块右键时，如果坐标合法（如Y坐标在0~255这个范围内），就会在该处放置一个对应的方块，同时手上的ItemStack的数量这一字段减一。除非你真的有特殊需求，否则你只需要直接实例化ItemBlock类得到方块的物品形式就好了，不用去先继承ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好了，现在再运行游戏，应该可以在创造模式物品栏中看到我们的方块的物品形式了。&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;br /&gt;
和物品不同，方块的模型文件，并不是通过代码来指定的，而是通过&amp;lt;code&amp;gt;assets/[modid]/blockstates&amp;lt;/code&amp;gt;路径下的与方块注册名同名的.json文件来指定的，例如，对于我们给前文添加的红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/blockstates/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;:{&lt;br /&gt;
        &amp;quot;normal&amp;quot;:{&lt;br /&gt;
            &amp;quot;model&amp;quot;:&amp;quot;tutorial_mod:ruby_block&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
这个文件的各部分内容都是什么意思，会在后面讲到，不过有一处是显而易见的：该文件把我们的方块的模型文件映射到了&amp;lt;code&amp;gt;assets/[modid]/models/block/ruby_block.json&amp;lt;/code&amp;gt;这个文件。那么，再来看看模型文件的内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/models/block/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;parent&amp;quot;: &amp;quot;block/cube_all&amp;quot;,&lt;br /&gt;
    &amp;quot;textures&amp;quot;:{&lt;br /&gt;
        &amp;quot;all&amp;quot;: &amp;quot;tutorial_mod:blocks/ruby_block&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
由于我们要添加的红宝石块是类似于原版的铁块、金块等方块的——六个面的材质都一样，所以我们直接指定该模型文件的父模型为&amp;lt;code&amp;gt;assets/minecraft/models/block/cube_all.json&amp;lt;/code&amp;gt;，然后我们就可以指定所有（all）面的材质文件了。我们可以看出，&amp;quot;parent&amp;quot;用于指定模型的父模型文件。之前讲过的物品模型文件，其父模型文件实际上是&amp;lt;code&amp;gt;assets/minecraft/models/item/generated.json&amp;lt;/code&amp;gt;，这个模型文件主要设定了物品材质的默认几何变换。&lt;br /&gt;
&lt;br /&gt;
读者翻阅原版的模型文件时，会发现这些模型的最终父模型是&amp;quot;builtin/generated&amp;quot;，这实际上不指代一个具体的模型文件，它的意思是“交给相关的渲染代码处理”。&lt;br /&gt;
&lt;br /&gt;
如果我想新增一个六个面材质不是全相同的方块呢？你可以继承原版其他的模型文件，如：&amp;lt;code&amp;gt;block/cube_column&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;block/cube_directional&amp;lt;/code&amp;gt;等等。或者你就可以直接继承&amp;lt;code&amp;gt;block/cube&amp;lt;/code&amp;gt;，然后你手动指定方块的六个面的材质和粒子效果材质的文件路径，也可以，但是一般没必要。&lt;br /&gt;
&lt;br /&gt;
把绘制好的材质文件放到对应路径中，进入游戏，就可以看到世界中的方块的材质已经被正确渲染了。&lt;br /&gt;
&lt;br /&gt;
但是，方块的物品形式仍然是紫黑色的，我们还需要给它声明模型。首先用&amp;lt;code&amp;gt;ModelLoader#setCustomModelResourceLocation&amp;lt;/code&amp;gt;将它和一个ModelResourceLocation绑定，再：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/models/item/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;parent&amp;quot;: &amp;quot;tutorial_mod:block/ruby_block&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
没错，我们把它的父模型指定为之前的方块模型即可，MC的相关代码会自动把该物品的样子渲染成方块斜着放的样子的。&lt;br /&gt;
&lt;br /&gt;
现在进入游戏测试，应该真的没什么问题了。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54980</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54980"/>
		<updated>2024-12-16T10:32:55Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 为方块和ItemBlock提供模型和材质文件 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
* setLightOpacity：传入一个int量，用于设定方块的透光度，传入的数值越大透光度越低。完全不透光的方块，该值为255；完全透光则为0；部分透光则在两者之间，如树叶的lightOpacity为1。该值默认由一个三目运算决定，具体为：如果该方块为渲染意义上的完整方块，lightOpacity为255，否则为0&lt;br /&gt;
* setLightLevel：传入一个float量，设置方块的光照等级，注意实际的光照值要把这个传入的参数乘以15。具体设定为多少，读者可以去查阅原版中会发光的方块的源代码，这块是怎么设定的&lt;br /&gt;
* setResistance：传入一个float量，用于设置方块的爆炸抗性，实际抗性值为传入参数乘以3的结果&lt;br /&gt;
* setHardness：传入一个float量，用于设置方块的挖掘硬度，注意这个setter也有可能影响爆炸抗性。特别地，还有一个该setter的特化版：setBlockUnbreakable，会把hardness设定为-1.0F，即让该方块变得像基岩一样不可破坏&lt;br /&gt;
* setUnlocalizedName、setCreativeTab：作用类似于Item类中的同名方法&lt;br /&gt;
* setHarvestLevel：形参列表为&amp;lt;code&amp;gt;(String toolClass, int level)&amp;lt;/code&amp;gt;，用于设定该方块的挖掘等级和应被什么种类的工具挖掘。实际上这个方法是Forge塞进去的——没错，Minecraft原版甚至没有挖掘等级这一概念，原版中某些方块需要特定等级的工具才能挖掘的例子纯属硬编码的结果。toolClass这个String代表工具类型，如&amp;lt;code&amp;gt;&amp;quot;picakxe&amp;quot;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;quot;axe&amp;quot;&amp;lt;/code&amp;gt;等等。而level的规则是：木制/金制工具的等级为0，石制为1，铁制为2，钻石工具则为3，-1则代表空手挖掘。另外注意：该方法的返回值类型为void，即不符合链式调用规则&lt;br /&gt;
&lt;br /&gt;
例如，笔者新增了一个方块：红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/java/net/tutorial_mod/block/BlockRuby.java:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 package net.tutorial_mod.block;&lt;br /&gt;
 &lt;br /&gt;
 import net.minecraft.block.Block;&lt;br /&gt;
 import net.minecraft.block.material.MapColor;&lt;br /&gt;
 import net.minecraft.block.material.Material;&lt;br /&gt;
 import net.tutorial_mod.TutorialMod;&lt;br /&gt;
 &lt;br /&gt;
 public class BlockRuby extends Block{&lt;br /&gt;
    public BlockRuby(){&lt;br /&gt;
        super(Material.ROCK, MapColor.RED);&lt;br /&gt;
        this.setRegistryName(TutorialMod.MODID, &amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setUnlocalizedName(&amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setCreativeTab(TutorialMod.TutorialModTab);&lt;br /&gt;
        this.setHarvestLevel(&amp;quot;pickaxe&amp;quot;, 2);&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
最后，先实例化，监听RegistryEvent.Register&amp;lt;Block&amp;gt;事件，把我们的新方块注册进游戏。&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
进入游戏后，读者可能会感到奇怪：创造模式物品栏里，并没有我们的新方块，用/give指令也得不到，只有用/setblock指令才能把该方块放置在世界中。那是因为我们还没有注册该方块的物品形式。&lt;br /&gt;
&lt;br /&gt;
方块的物品形式首先是物品，实际上是ItemBlock——这是Item的一个子类，构造器接受一个Block参数的传入，这种设计很易于理解。我们直接实例化ItemBlock类，再通过监听RegistryEvent.Register&amp;lt;Item&amp;gt;事件即可把它注册进游戏。注意：我们不需要给方块的物品形式的实例调用一系列setter方法，但必须调用setRegistryName——且它的registryName和对应方块的registryName必须相同。&lt;br /&gt;
&lt;br /&gt;
可能会有读者对前面Block类的setUnlocalizedName和setCreativeTab这两个setter感到奇怪——因为其实这两个方法只对物品有意义——毕竟放在世界中的方块需要什么对应的创造模式物品栏？是这样，这两个方法是为ItemBlock服务的。我们用我们的Block对象构造出一个ItemBlock后，该ItemBlock对象中的对应的创造模式物品栏、unlocalizedName这些信息会自动等于对应的Block对象中的对应信息的，这也是为什么我们不需要给ItemBlock调用一大堆的setter。&lt;br /&gt;
&lt;br /&gt;
ItemBlock类中覆写了Item类中的一些方法，导致了ItemBlock对象的通用逻辑为：玩家在世界中对着非空气方块右键时，如果坐标合法（如Y坐标在0~255这个范围内），就会在该处放置一个对应的方块，同时手上的ItemStack的数量这一字段减一。除非你真的有特殊需求，否则你只需要直接实例化ItemBlock类得到方块的物品形式就好了，不用去先继承ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好了，现在再运行游戏，应该可以在创造模式物品栏中看到我们的方块的物品形式了。&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;br /&gt;
和物品不同，方块的模型文件，并不是通过代码来指定的，而是通过&amp;lt;code&amp;gt;assets/[modid]/blockstates&amp;lt;/code&amp;gt;路径下的与方块注册名同名的.json文件来指定的，例如，对于我们给前文添加的红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/blockstates/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;variants&amp;quot;:{&lt;br /&gt;
        &amp;quot;normal&amp;quot;:{&lt;br /&gt;
            &amp;quot;model&amp;quot;:&amp;quot;tutorial_mod:ruby_block&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
这个文件的各部分内容都是什么意思，会在后面讲到，不过有一处是显而易见的：该文件把我们的方块的模型文件映射到了&amp;lt;code&amp;gt;assets/[modid]/models/block/ruby_block.json&amp;lt;/code&amp;gt;这个文件。那么，再来看看模型文件的内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/models/block/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;parent&amp;quot;: &amp;quot;block/cube_all&amp;quot;,&lt;br /&gt;
    &amp;quot;textures&amp;quot;:{&lt;br /&gt;
        &amp;quot;all&amp;quot;: &amp;quot;tutorial_mod:blocks/ruby_block&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
由于我们要添加的红宝石块是类似于原版的铁块、金块等方块的——六个面的材质都一样，所以我们直接指定该模型文件的父模型为&amp;lt;code&amp;gt;assets/minecraft/models/block/cube_all.json&amp;lt;/code&amp;gt;，然后我们就可以指定所有（all）面的材质文件了。我们可以看出，&amp;quot;parent&amp;quot;用于指定模型的父模型文件。之前讲过的物品模型文件，其父模型文件实际上是&amp;lt;code&amp;gt;assets/minecraft/models/item/generated.json&amp;lt;/code&amp;gt;，这个模型文件主要设定了物品材质的默认几何变换。&lt;br /&gt;
&lt;br /&gt;
读者翻阅原版的模型文件时，会发现这些模型的最终父模型是&amp;quot;builtin/generated&amp;quot;，这实际上不指代一个具体的模型文件，它的意思是“交给相关的渲染代码处理”。&lt;br /&gt;
&lt;br /&gt;
如果我想新增一个六个面材质不是全相同的方块呢？你可以继承原版其他的模型文件，如：&amp;lt;code&amp;gt;block/cube_column&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;block/cube_directional&amp;lt;/code&amp;gt;等等。或者你就可以直接继承&amp;lt;code&amp;gt;block/cube&amp;lt;/code&amp;gt;，然后你手动指定方块的六个面的材质和粒子效果材质的文件路径，也可以，但是一般没必要。&lt;br /&gt;
&lt;br /&gt;
把绘制好的材质文件放到对应路径中，进入游戏，就可以看到世界中的方块的材质已经被正确渲染了。&lt;br /&gt;
&lt;br /&gt;
但是，方块的物品形式仍然是紫黑色的，我们还需要给它声明模型。首先用&amp;lt;code&amp;gt;ModelLoader#setCustomModelResourceLocation&amp;lt;/code&amp;gt;将它和一个ModelResourceLocation绑定，再：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/resources/assets/tutorial_mod/models/item/ruby_block.json:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    &amp;quot;parent&amp;quot;: &amp;quot;tutorial_mod:block/ruby_block&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
没错，我们把它的父模型指定为之前的方块模型即可，MC的相关代码会自动把该物品的样子渲染成方块斜着放的样子的。&lt;br /&gt;
&lt;br /&gt;
现在进入游戏测试，应该真的没什么问题了。&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54979</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54979"/>
		<updated>2024-12-16T09:26:48Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 添加方块对应的物品形式 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
* setLightOpacity：传入一个int量，用于设定方块的透光度，传入的数值越大透光度越低。完全不透光的方块，该值为255；完全透光则为0；部分透光则在两者之间，如树叶的lightOpacity为1。该值默认由一个三目运算决定，具体为：如果该方块为渲染意义上的完整方块，lightOpacity为255，否则为0&lt;br /&gt;
* setLightLevel：传入一个float量，设置方块的光照等级，注意实际的光照值要把这个传入的参数乘以15。具体设定为多少，读者可以去查阅原版中会发光的方块的源代码，这块是怎么设定的&lt;br /&gt;
* setResistance：传入一个float量，用于设置方块的爆炸抗性，实际抗性值为传入参数乘以3的结果&lt;br /&gt;
* setHardness：传入一个float量，用于设置方块的挖掘硬度，注意这个setter也有可能影响爆炸抗性。特别地，还有一个该setter的特化版：setBlockUnbreakable，会把hardness设定为-1.0F，即让该方块变得像基岩一样不可破坏&lt;br /&gt;
* setUnlocalizedName、setCreativeTab：作用类似于Item类中的同名方法&lt;br /&gt;
* setHarvestLevel：形参列表为&amp;lt;code&amp;gt;(String toolClass, int level)&amp;lt;/code&amp;gt;，用于设定该方块的挖掘等级和应被什么种类的工具挖掘。实际上这个方法是Forge塞进去的——没错，Minecraft原版甚至没有挖掘等级这一概念，原版中某些方块需要特定等级的工具才能挖掘的例子纯属硬编码的结果。toolClass这个String代表工具类型，如&amp;lt;code&amp;gt;&amp;quot;picakxe&amp;quot;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;quot;axe&amp;quot;&amp;lt;/code&amp;gt;等等。而level的规则是：木制/金制工具的等级为0，石制为1，铁制为2，钻石工具则为3，-1则代表空手挖掘。另外注意：该方法的返回值类型为void，即不符合链式调用规则&lt;br /&gt;
&lt;br /&gt;
例如，笔者新增了一个方块：红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/java/net/tutorial_mod/block/BlockRuby.java:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 package net.tutorial_mod.block;&lt;br /&gt;
 &lt;br /&gt;
 import net.minecraft.block.Block;&lt;br /&gt;
 import net.minecraft.block.material.MapColor;&lt;br /&gt;
 import net.minecraft.block.material.Material;&lt;br /&gt;
 import net.tutorial_mod.TutorialMod;&lt;br /&gt;
 &lt;br /&gt;
 public class BlockRuby extends Block{&lt;br /&gt;
    public BlockRuby(){&lt;br /&gt;
        super(Material.ROCK, MapColor.RED);&lt;br /&gt;
        this.setRegistryName(TutorialMod.MODID, &amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setUnlocalizedName(&amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setCreativeTab(TutorialMod.TutorialModTab);&lt;br /&gt;
        this.setHarvestLevel(&amp;quot;pickaxe&amp;quot;, 2);&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
最后，先实例化，监听RegistryEvent.Register&amp;lt;Block&amp;gt;事件，把我们的新方块注册进游戏。&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
进入游戏后，读者可能会感到奇怪：创造模式物品栏里，并没有我们的新方块，用/give指令也得不到，只有用/setblock指令才能把该方块放置在世界中。那是因为我们还没有注册该方块的物品形式。&lt;br /&gt;
&lt;br /&gt;
方块的物品形式首先是物品，实际上是ItemBlock——这是Item的一个子类，构造器接受一个Block参数的传入，这种设计很易于理解。我们直接实例化ItemBlock类，再通过监听RegistryEvent.Register&amp;lt;Item&amp;gt;事件即可把它注册进游戏。注意：我们不需要给方块的物品形式的实例调用一系列setter方法，但必须调用setRegistryName——且它的registryName和对应方块的registryName必须相同。&lt;br /&gt;
&lt;br /&gt;
可能会有读者对前面Block类的setUnlocalizedName和setCreativeTab这两个setter感到奇怪——因为其实这两个方法只对物品有意义——毕竟放在世界中的方块需要什么对应的创造模式物品栏？是这样，这两个方法是为ItemBlock服务的。我们用我们的Block对象构造出一个ItemBlock后，该ItemBlock对象中的对应的创造模式物品栏、unlocalizedName这些信息会自动等于对应的Block对象中的对应信息的，这也是为什么我们不需要给ItemBlock调用一大堆的setter。&lt;br /&gt;
&lt;br /&gt;
ItemBlock类中覆写了Item类中的一些方法，导致了ItemBlock对象的通用逻辑为：玩家在世界中对着非空气方块右键时，如果坐标合法（如Y坐标在0~255这个范围内），就会在该处放置一个对应的方块，同时手上的ItemStack的数量这一字段减一。除非你真的有特殊需求，否则你只需要直接实例化ItemBlock类得到方块的物品形式就好了，不用去先继承ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好了，现在再运行游戏，应该可以在创造模式物品栏中看到我们的方块的物品形式了。&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54978</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54978"/>
		<updated>2024-12-16T09:12:25Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 添加一个新方块的流程 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
* setLightOpacity：传入一个int量，用于设定方块的透光度，传入的数值越大透光度越低。完全不透光的方块，该值为255；完全透光则为0；部分透光则在两者之间，如树叶的lightOpacity为1。该值默认由一个三目运算决定，具体为：如果该方块为渲染意义上的完整方块，lightOpacity为255，否则为0&lt;br /&gt;
* setLightLevel：传入一个float量，设置方块的光照等级，注意实际的光照值要把这个传入的参数乘以15。具体设定为多少，读者可以去查阅原版中会发光的方块的源代码，这块是怎么设定的&lt;br /&gt;
* setResistance：传入一个float量，用于设置方块的爆炸抗性，实际抗性值为传入参数乘以3的结果&lt;br /&gt;
* setHardness：传入一个float量，用于设置方块的挖掘硬度，注意这个setter也有可能影响爆炸抗性。特别地，还有一个该setter的特化版：setBlockUnbreakable，会把hardness设定为-1.0F，即让该方块变得像基岩一样不可破坏&lt;br /&gt;
* setUnlocalizedName、setCreativeTab：作用类似于Item类中的同名方法&lt;br /&gt;
* setHarvestLevel：形参列表为&amp;lt;code&amp;gt;(String toolClass, int level)&amp;lt;/code&amp;gt;，用于设定该方块的挖掘等级和应被什么种类的工具挖掘。实际上这个方法是Forge塞进去的——没错，Minecraft原版甚至没有挖掘等级这一概念，原版中某些方块需要特定等级的工具才能挖掘的例子纯属硬编码的结果。toolClass这个String代表工具类型，如&amp;lt;code&amp;gt;&amp;quot;picakxe&amp;quot;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;quot;axe&amp;quot;&amp;lt;/code&amp;gt;等等。而level的规则是：木制/金制工具的等级为0，石制为1，铁制为2，钻石工具则为3，-1则代表空手挖掘。另外注意：该方法的返回值类型为void，即不符合链式调用规则&lt;br /&gt;
&lt;br /&gt;
例如，笔者新增了一个方块：红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/java/net/tutorial_mod/block/BlockRuby.java:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 package net.tutorial_mod.block;&lt;br /&gt;
 &lt;br /&gt;
 import net.minecraft.block.Block;&lt;br /&gt;
 import net.minecraft.block.material.MapColor;&lt;br /&gt;
 import net.minecraft.block.material.Material;&lt;br /&gt;
 import net.tutorial_mod.TutorialMod;&lt;br /&gt;
 &lt;br /&gt;
 public class BlockRuby extends Block{&lt;br /&gt;
    public BlockRuby(){&lt;br /&gt;
        super(Material.ROCK, MapColor.RED);&lt;br /&gt;
        this.setRegistryName(TutorialMod.MODID, &amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setUnlocalizedName(&amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setCreativeTab(TutorialMod.TutorialModTab);&lt;br /&gt;
        this.setHarvestLevel(&amp;quot;pickaxe&amp;quot;, 2);&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
最后，先实例化，监听RegistryEvent.Register&amp;lt;Block&amp;gt;事件，把我们的新方块注册进游戏。&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54977</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54977"/>
		<updated>2024-12-16T09:10:44Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​/* 添加一个新方块的流程 */ // Edit via Wikiplus&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
* setLightOpacity：传入一个int量，用于设定方块的透光度，传入的数值越大透光度越低。完全不透光的方块，该值为255；完全透光则为0；部分透光则在两者之间，如树叶的lightOpacity为1。该值默认由一个三目运算决定，具体为：如果该方块为渲染意义上的完整方块，lightOpacity为255，否则为0&lt;br /&gt;
* setLightLevel：传入一个float量，设置方块的光照等级，注意实际的光照值要把这个传入的参数乘以15。具体设定为多少，读者可以去查阅原版中会发光的方块的源代码，这块是怎么设定的&lt;br /&gt;
* setResistance：传入一个float量，用于设置方块的爆炸抗性，实际抗性值为传入参数乘以3的结果&lt;br /&gt;
* setHardness：传入一个float量，用于设置方块的挖掘硬度，注意这个setter也有可能影响爆炸抗性。特别地，还有一个该setter的特化版：setBlockUnbreakable，会把hardness设定为-1.0F，即让该方块变得像基岩一样不可破坏&lt;br /&gt;
* setUnlocalizedName、setCreativeTab：作用类似于Item类中的同名方法&lt;br /&gt;
* setHarvestLevel：形参列表为&amp;lt;code&amp;gt;(String toolClass, int level)&amp;lt;/code&amp;gt;，用于设定该方块的挖掘等级和应被什么种类的工具挖掘。实际上这个方法是Forge塞进去的——没错，Minecraft原版甚至没有挖掘等级这一概念，原版中某些方块需要特定等级的工具才能挖掘的例子纯属硬编码的结果。toolClass这个String代表工具类型，如&amp;lt;code&amp;gt;&amp;quot;picakxe&amp;quot;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;quot;axe&amp;quot;&amp;lt;/code&amp;gt;等等。而level的规则是：木制/金制工具的等级为0，石制为1，铁制为2，钻石工具则为3，-1则代表空手挖掘。另外注意：该方法的返回值类型为void，即不符合链式调用规则&lt;br /&gt;
&lt;br /&gt;
例如，笔者新增了一个方块：红宝石块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;src/main/java/net/tutorial_mod/block/BlockRuby.java:&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 package net.tutorial_mod.block;&lt;br /&gt;
 &lt;br /&gt;
 import net.minecraft.block.Block;&lt;br /&gt;
 import net.minecraft.block.material.MapColor;&lt;br /&gt;
 import net.minecraft.block.material.Material;&lt;br /&gt;
 import net.tutorial_mod.TutorialMod;&lt;br /&gt;
 &lt;br /&gt;
 public class BlockRuby extends Block{&lt;br /&gt;
    public BlockRuby(){&lt;br /&gt;
        super(Material.ROCK, MapColor.RED);&lt;br /&gt;
        this.setRegistryName(TutorialMod.MODID, &amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setUnlocalizedName(&amp;quot;ruby_block&amp;quot;);&lt;br /&gt;
        this.setCreativeTab(TutorialMod.TutorialModTab);&lt;br /&gt;
        this.setHarvestLevel(&amp;quot;pickaxe&amp;quot;, 2);&lt;br /&gt;
    }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
最后，监听RegistryEvent.Register&amp;lt;Block&amp;gt;事件，把我们的新方块注册进游戏。&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
	<entry>
		<id>https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54976</id>
		<title>用户:MashKJo/1.12.2模组开发教程/9.第一个方块</title>
		<link rel="alternate" type="text/html" href="https://mcbbs.wiki/index.php?title=%E7%94%A8%E6%88%B7:MashKJo/1.12.2%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/9.%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%B9%E5%9D%97&amp;diff=54976"/>
		<updated>2024-12-16T08:34:47Z</updated>

		<summary type="html">&lt;p&gt;MashKJo：​创建页面，内容为“在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。  好，区分了这两个概念后，我们就可以开始了。  == 添加一个新方块的流程 == 所有方…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;在开始制作我们的第一个方块前，笔者先区分一下两个概念：方块（Block）和方块的物品形式（ItemBlock）——平时，我们说“方块”，是同时涵盖了这两个概念的：广义上的方块，被放置在世界中时，即为Block；而在物品栏中时，它则是一种特殊的物品，也即ItemBlock。&lt;br /&gt;
&lt;br /&gt;
好，区分了这两个概念后，我们就可以开始了。&lt;br /&gt;
&lt;br /&gt;
== 添加一个新方块的流程 ==&lt;br /&gt;
所有方块都是&amp;lt;code&amp;gt;net.minecraft.block.Block&amp;lt;/code&amp;gt;类的实例，该类和Item类不同，它没有零参构造器，我们在构造Block实例时，有2个构造器可供选择：&amp;lt;code&amp;gt;(Material materialIn)&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;(Material materialIn, MapColor mapColorIn)&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
MapColor用于指示地图上显示该方块所用的颜色，这个类的构造方法是private的，且它要求构造MapColor对象时指定该对象对应的序数，该类有许多静态MapColor字段供你使用，Mojang在这个类里把硬编码体现得淋漓尽致（捂脸）。&lt;br /&gt;
&lt;br /&gt;
Material用于描述方块的“质地”，这个类纯粹用于封装许多和方块有关的信息，它能影响方块的许多属性，这个类的构造方法倒是public的，其中要求传入一个MapColor。不过我们也没必要新建Material，直接复用原版的即可。&lt;br /&gt;
&lt;br /&gt;
上述的第一个构造方法，实际效果为&amp;lt;code&amp;gt;this(materialIn, materialIn.getMaterialMapColor());&amp;lt;/code&amp;gt;。读者根据自己的需求自行选择合适的构造器，填入合适的参数就好。&lt;br /&gt;
&lt;br /&gt;
想必读者肯定猜到了，Block类也有很多getter和setter可以用。与Item的类似，Block类的getter牵扯到BlockState这一概念，因此后面再讲，现在只讲setter：&lt;br /&gt;
&lt;br /&gt;
== 添加方块对应的物品形式 ==&lt;br /&gt;
&lt;br /&gt;
== 为方块和ItemBlock提供模型和材质文件 ==&lt;/div&gt;</summary>
		<author><name>MashKJo</name></author>
	</entry>
</feed>