MC Forge1.20.1 mod开发学习笔记(个人向)

前言

之前一直就很想做forge开发,但是没有时间

趁着春节假期,打算好好学一下

主要基于北山_Besson的教程

复习

java,两年半没写了,还是生疏了很多,基础的东西还好,一些关键的语法已经忘了

final和static

类(实例)、抽象类(模板)、接口(方法要求)

lambda表达式,(参数) -> {语句块}

设计模式

单例模式、工厂模式:简单工厂、工厂方法、抽象工厂

策略模式、状态模式、观察者模式(事件监听器)

配置

jdk17,win+path添加环境变量

Java Downloads | Oracle

forge的mdk

Downloads for Minecraft Forge for Minecraft 1.20.1

给IDEA换源:gradle/wrapper/gradle-wrapper.properties

复制代码
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.8-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

加载反混淆映射:gradle.properties

将mapping_channel改为parchment

去羊皮纸官网Getting Started查询对应的映射版本号

把mapping_version改为2023.09.03-1.20.1

添加反混淆的仓库:settings.gradle

(添加 maven{url='https://maven.parchmentmc.org'} )

复制代码
pluginManagement {
    repositories {
        gradlePluginPortal()
        maven {
            name = 'MinecraftForge'
            url = 'https://maven.minecraftforge.net/'
        }
        maven { url = 'https://maven.parchmentmc.org' }
    }
}

plugins {
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}

在build.gradle中添加反混淆插件:

(添加 id 'org.parchmentmc.librarian.forgegradle' version '1.+' )

复制代码
plugins {
    id 'eclipse'
    id 'idea'
    id 'maven-publish'
    id 'net.minecraftforge.gradle' version '[6.0,6.2)'
    id 'org.parchmentmc.librarian.forgegradle' version '1.+'
}

然后把下面minecraft{}中的mapping channels改一下:

复制代码
mappings channel: 'parchment', version: '2023.09.03-1.20.1'

最后调整一下maven构建的线程数,设置->构建、执行、部署->构建工具->maven

线程计数改为10C(十线程)

然后在maven->运行程序,勾选 "将IDE构建/运行委托给maven"

点旁边的大象,有个刷新按钮,重新构建一下项目

然后打开下面的forgegradle runs,双击runClient运行

源码阅读

阅读反编译、反混淆后的MC源代码

在外部库中,找net开头的有mapped_parchment的库

数据文件在这个库:

解读所有源码的工程量相当的大,只需要看几个重要的,比如Items和Blocks,看一下他们的注册流程(双击shift,可以快速搜索类查看代码)

我们阅读一下实例代码中,变量BLOCKS到变量EXAMPLE_TAB之间的内容

DeferredRegister:延迟注册器,避免一些并发注册的问题,让mod中需要注册的内容在forge注册之后进行注册

RegistryObject:注册完之后会变成一个实际的对象

IEventBus:事件总线,把DeferredRegister注册到总线上时会调用事件总线的注册方法。

可以发现,它就是定义了三个延迟注册器,分别注册Block、Item、CreativeModeTab

然后分别调用三个延迟注册器,注册了对应的方块、方块物品、物品、创造模式物品栏

读完后可以删掉这些示例内容,因为我们后续做物品、方块、物品栏注册会分到不同的文件中去写,这样看起来就会规整许多

开始

一般开始编写mod需要有一个有趣的idea,当然,如果只是为了练习,随便取什么名字都可以

想好我们的mod名称,我们可以右键src/main/java/com/example/examplemod/ExampleMod,进行重构->重命名,改为新的名字,同时右键使用重构,修改ExampleMod中的MOD_ID的值

我这里改为了TestMod,MOD_ID为test_mod

可能后面还需要看看gradle.properties中最下面的部分有没有同步更改,最下面第二个变量表示MOD在列表中显示的名称。

注册物品

首先MC中物品和方块是不一样的,方块更像是特殊的物品,它既能够带在背包中,作为合成材料,也具有模型,能够放置出来。而物品一般只能作为掉落物和合成材料。

方块一般需要同时注册Blocks和Items,而物品就简单一些,只用注册Items。

首先新建item文件夹,然后新建类ModItems

模仿实例中注册物品的过程,先建立延迟注册器ITEMS,然后注册物品。

最后写register方法,让我们的TestMod类中来调用这个register方法,完成所有物品的注册。

代码如下:item/ModItems.java

java 复制代码
public class ModItems {
    public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, TestMod.MOD_ID);
    public static final RegistryObject<Item> CANDY_STICK =
            ITEMS.register("candy_stick", () -> new Item(new Item.Properties()));
    public static final RegistryObject<Item> CANDY_STAFF =
            ITEMS.register("candy_staff", () -> new Item(new Item.Properties()));

    public static void register(IEventBus eventBus) {
        ITEMS.register(eventBus);
    }
}

这里可能会令人感到疑惑,ITEMS都是用的register方法,为啥会有不同的效果。

因为如果它检测到传入的参数时IEventBus时,它会走另一条逻辑,把自己注册了的内容打包给eventBus注册。源代码如下:

而普通物品的注册逻辑是这样的:

java 复制代码
public <I extends T> RegistryObject<I> register(String name, Supplier<? extends I> sup) {
        if (this.seenRegisterEvent) {
            throw new IllegalStateException("Cannot register new entries to DeferredRegister after RegisterEvent has been fired.");
        } else {
            Objects.requireNonNull(name);
            Objects.requireNonNull(sup);
            ResourceLocation key = new ResourceLocation(this.modid, name);
            if (this.registryKey != null) {
                RegistryObject<I> ret = this.optionalRegistry ? RegistryObject.createOptional(key, this.registryKey, this.modid) : RegistryObject.create(key, this.registryKey, this.modid);
                if (this.entries.putIfAbsent(ret, sup) != null) {
                    throw new IllegalArgumentException("Duplicate registration " + name);
                } else {
                    return ret;
                }
            } else {
                throw new IllegalStateException("Could not create RegistryObject in DeferredRegister");
            }
        }
    }

可以看到,里面通过ResourceLocation来定位对应物品的资源(图标、材质模型、音效)

然后调用RegistryObject的create方法,最后把创建的RegistryObject返回出来

接下来我们需要准备物品的资源

resources资源(models与textures)

textures包含物品的纹理,也就是显示的图标

models就是物品的模型,lang就是语言文件,blockstates就是方块的状态文件。

首先将绘制的物品图标放在resources/assets/test_mod/textures/item中

然后在models/item中新建各个物品的json文件,名称必须与我们注册的物品名称相同

以candy_staff.json为例:

java 复制代码
{
  "parent": "minecraft:item/generated",
  "textures": {
    "layer0": "test_mod:item/candy_staff"
  }
}

item/generated一般表示2D物品渲染,layer0就是基础层(可以定义layer1、layer2进行叠加渲染)

(item/handheld表示手持工具的渲染,item/handheld_rod表示手持杆状工具的渲染)

在models中,item主要管手持物(一三人称、左右手)、掉落物、物品栏、物品展示框的渲染

而block主要管方块的渲染,以candy_clock.json为例

java 复制代码
{
  "parent": "block/cube_all",
  "textures": {
    "all": "test_mod:block/candy_block"
  }
}

这里用cube_all是因为该方块所有的面都相同,都使用同一个纹理

还有很多种方块渲染模式,AI一下就能知道

注册方块

其实操作也是差不多的,只不过需要在注册时同时注册方块的物品,否则,如果只有方块,没有对应物品会出一些奇怪的问题。

这里写了一个registerBlock函数,来一起注册方块和方块物品。

java 复制代码
public class ModBlocks {
    public static final DeferredRegister<Block> BLOCKS
            = DeferredRegister.create(ForgeRegistries.BLOCKS, TestMod.MOD_ID);

    public static final RegistryObject<Block> CANDY_BLOCK
            = registerBlock("candy_block", () -> new Block(BlockBehaviour.Properties.of().strength(1.0f, 1.0f)));

    private static <T extends Block> void registerBlockItems(String name, RegistryObject<T> block) {
        ModItems.ITEMS.register(name, () -> new BlockItem(block.get(), new Item.Properties()));
    }
    private static <T extends Block> RegistryObject<T> registerBlock(String name, Supplier<T> block) {
        RegistryObject<T> blocks = BLOCKS.register(name, block);
        registerBlockItems(name, blocks);
        return blocks;
    }

    public static void register(IEventBus eventBus) {
        BLOCKS.register(eventBus);
    }
}

然后我们依然需要去准备方块的资源,定义方块的模型

除此之外,我们还需要在resources中设置方块的blockstate,这里本来是交给一些比较复杂的方块的(比如有朝向、有开关两种状态的方块)

不过这里只是改变模型的渲染效果,无法改变碰撞箱。

代码:resources/assets/test_mod/blockstates/candy_block.json

java 复制代码
{
  "variants": {
    "": {
      "model": "test_mod:block/candy_block"
    }
  }
}

注册创造模式物品栏

也是类似的,先创建延迟注册器,但创建物品栏的时候差别较大

这里采用的是建造者模式来构建CreativeModeTab,icon就是物品栏的图标,title就是物品栏的名称,displayItems就是物品栏中进行展示的物品,最后进行一个.build完成建造。

java 复制代码
public class ModCreativeModeTabs {
    public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS
            = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, TestMod.MOD_ID);

    public static final RegistryObject<CreativeModeTab> TEST_TAB
            = CREATIVE_MODE_TABS.register("test_tab", () -> CreativeModeTab.builder()
                .icon(() -> new ItemStack(ModItems.CANDY_STICK.get()))
                .title(Component.translatable("itemGroup.test_tab"))
                .displayItems((parameters, output) -> {
                    output.accept(ModItems.CANDY_STICK.get());
                    output.accept(ModItems.CANDY_STAFF.get());
                    output.accept(ModBlocks.CANDY_BLOCK.get());
                })
                .build()
            );
    public static void register(IEventBus eventBus) {
        CREATIVE_MODE_TABS.register(eventBus);
    }
}

注意到这里有个Component.translatable("itemGroup.test_tab"),这里其实是为了方便后续文本翻译,在lang文件中使用该名称查询对应物品栏的翻译。

resources资源(lang)

在lang文件夹下新建en_us.json,里面根据你注册时的物品名称与命名空间来定位物品

一般物品就是 "item.mod名称.物品名称",方块就是 "block.mod名称.物品名称"

java 复制代码
{
  "item.test_mod.candy_stick": "Candy Stick",
  "item.test_mod.candy_staff": "Candy Staff",

  "block.test_mod.candy_block": "Candy Block",

  "itemGroup.test_tab": "Test Tab"
}

运行结果

由于我们还没定义糖块的texture,它无法显示正常的纹理。

相关推荐
蒸蒸yyyyzwd2 小时前
cpp学习笔记
笔记·学习
来两个炸鸡腿2 小时前
【Datawhale组队学习202602】Easy-Vibe task03 动手做出原型
人工智能·学习·大模型·vibe coding
浅念-2 小时前
C++ STL vector
java·开发语言·c++·经验分享·笔记·学习·算法
qyhua2 小时前
春节怀旧:翻出 20 年前的 VB6 书籍与老 CPU 记忆
笔记·其他
winfreedoms2 小时前
ROS2主题通讯——黑马程序员ROS2课程上课笔记(2)
笔记
锅包一切2 小时前
【蓝桥杯JavaScript基础入门】二、JavaScript关键特性
开发语言·前端·javascript·学习·蓝桥杯
was17213 小时前
你的私有知识库:自托管 Markdown 笔记方案 NoteDiscovery
笔记·云原生·自部署
前路不黑暗@13 小时前
Java项目:Java脚手架项目的文件服务(八)
java·开发语言·spring boot·学习·spring cloud·docker·maven
崎岖Qiu13 小时前
【计算机网络 | 第十一篇】图解交换机的自学习功能
网络·学习·计算机网络