本篇教程由作者设定使用 CC BY-NC-ND 协议。

(这个教程会持续更新)

如果你想制作mod但是不懂java?不知道怎么配环境?寻找了网上的教程的但是不知道说的是什么?这个教程能详细的告诉你怎么制作mod

本教程分为

模组构建篇

新手篇(

是个新手就能理解,不怎么需要java的基础)

基础篇(内容稍微有一点难,需要一点java的基础)

应用篇(十分的困难,需要理解java的写法)

JAVA篇(学习java的基本知识)

模组篇(如果写模组的前置和联动的话值得看)

mixin篇(想修改minecraft的内容的)

需要的东西

IDEA

forge[MDK](不能直接下载,需要点击MDK)

Blockbench (建模用)

构建模组

IDEA

按照指示下载即可(把所有的勾了!)

开始构建模组

如果把一下的做了你的屏幕应该会出现这两个东西

InterlliJ IDEA(IDEA版本)

forge-(游戏版本)-(forge版本)-mdk.zip

如果有就说明你成功了。如果没有,那就是哪里出错了。

把zip的压缩给解开放在(给你的模组起名字)文件夹(之后把(给你的模组起名字)文件夹为模组文件夹),打开IDEA把模组文件夹放进去。

环境变量(如果出现因为java版本不对遇到的问题建议看,否则可以跳过)

打开IDEA之后右键模组文件夹,打开【打开模块设置】,点击SDK的右边。下载JDK,下载最新的JDK。然后右键{此电脑},点击属性。

点击高级系统设置,点击环境变量。

需要你设置的有

Path

JAVA_HOME

第一个需要把你下载的JDK的bin当做路径,第二个就是java路径

比如说

(Path)C:\.gradle\jdks\adoptium-17-x64-hotspot-windows\jdk-17.0.11+9\bin

(JAVA_HOME)C:\.gradle\jdks\adoptium-17-x64-hotspot-windows\jdk-17.0.11+9

设置模组

打开IDEA把进行这样的操作

src/main/java/com/example/examplemod/examplemod.java(保留examplemod.java然后把包括com在内的删掉改成)

src/main/java/(你的域名)/examplemod.java(然后把examplemod.java的名称改成你的模组名称)

这个可留可不留,如果你是新手的话建议留

      src/main/resources/META-INF/mods.toml(修改成以下的格式)

# 这是一个示例 mods.toml 文件。它包含与加载模组相关的数据。
# 有几个强制字段 (#mandatory),以及许多可选字段 (#optional)。
# 总体格式是标准的 TOML 格式,v0.5.0。
# 请注意,此文件中有几个 TOML 列表。
# 在此处查找有关 toml 格式的更多信息:https://github.com/toml-lang/toml
# 要加载的模组加载器类型的名称 - 对于常规的 FML @Mod 模组,应该是 javafml
modLoader = "javafml" #mandatory
# 与该模组加载器匹配的版本范围 - 对于常规的 FML @Mod,它将是 Forge 的版本
loaderVersion = "[40,)" #mandatory 这通常会被 Forge 每个 Minecraft 版本提升一次。请参阅我们的下载页面以获取版本列表。
# 模组的许可证。这是强制性元数据,可更轻松地理解您的再分发属性。
# 请在 https://choosealicense.com/ 上查看您的选项。All rights reserved 是默认的版权声明,因此在此处默认为此。
license = "All rights reserved"
# 当此模组出现问题时,可用于引导人们的 URL
issueTrackerURL = "(如果模组出现了问题,可以在这给链接报告的东西,可不加" #optional
# 一系列模组 - 允许的数量由各个模组加载器确定
[[mods]] #mandatory
# 模组的 modid
modId = "(模组名)" #mandatory
# 模组的版本号 - 此处可以使用一些众所周知的 ${} 变量,或者直接硬编码
# ${file.jarVersion} 将替换从模组的 JAR 文件元数据中读取的 Implementation-Version 的值
# 有关如何在构建过程中完全自动填充此内容的相关 build.gradle 脚本,请参阅。
version = "(你的模组版本)" #mandatory
# 模组的显示名称
displayName = "(模组名(可以写汉字))" #mandatory
# 用于查询此模组的更新的 URL。请参阅 JSON 更新规范 https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
updateJSONURL = "(更新的时候的链接,可以不加)" #optional
# 此模组的“主页”URL,在模组 UI 中显示
displayURL = "(模组的主页链接,可以不加)" #optional
# 用于显示的包含在模组 JAR 根目录中的标志的文件名
logoFile = "(模组的图像)" #optional
# 在模组 UI 中显示的文本字段
credits = "(制作团队,可以不加)" #optional
# 在模组 UI 中显示的文本字段
authors = "(作者)" #optional
# 模组的描述文本(多行!)(#mandatory)
description = '''(简介)'''
# 一个依赖项 - 使用 . 来指示特定 modid 的依赖关系。依赖项是可选的。
#从这里往下写这个模组的依赖,forge和minecraft是必须的!!!
[[dependencies.changedplus]] #optional
# 依赖项的 modid
modId = "forge" #mandatory
# 此依赖项是否必须存在 - 如果不是,则必须指定以下顺序
mandatory = true #mandatory
# 依赖项的版本范围
versionRange = "[40,)" #mandatory
# 依赖项的排序关系 - 如果关系不是强制性的,则必须在之后指定
ordering = "NONE"
# 此依赖项应用的侧面 - BOTH、CLIENT 或 SERVER
side = "BOTH"
# 这是另一个依赖项
[[dependencies.changedplus]]
modId = "minecraft"
mandatory = true
# 此版本范围声明了当前 Minecraft 版本的最低版本,但不包括下一个主要版本
versionRange = "[1.18.2,1.19)"
ordering = "NONE"
side = "BOTH"

如果是新手的话这么设置可以,但是你想加模组前置之类的看后续的应用篇。

(模组的图像指的是{.png}把你想放进去的图像放进src/main/resources里)

右击java,新建,目录,写你的域名(啥都可以,可以是你的B站主页,github,curseforge等等,如果没有写个[com.你的名字.你的模组名称])

如何打包模组?

以我的模组为例子

buildscript {
    repositories {
        // 这些仓库仅用于 Gradle 插件,将任何其他仓库放在下面的 repository 块中
        maven { url = 'https://maven.minecraftforge.net' }
        mavenCentral()
    }
    dependencies {
        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
    }
}

// 只编辑此行以下的内容,上面的代码添加并启用了 Forge 设置所需的内容。
plugins {
    id 'eclipse'
    id 'maven-publish'
}
apply plugin: 'net.minecraftforge.gradle'


version = '0.0.0' //你的模组版本
group = 'github.com.gengyoubo.changedplus' //域名
archivesBaseName = 'changedplus'//模组名字,没有别的了

// Mojang 在 1.18+ 版本中向最终用户提供 Java 17,因此您的模组应该针对 Java 17。
java.toolchain.languageVersion = JavaLanguageVersion.of(17)

println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}"
minecraft {
    // 映射可以随时更改,并且必须采用以下格式。
    // 渠道:版本:
    // official   MCVersion             Mojang 映射文件中的官方字段/方法名称
    // parchment  YYYY.MM.DD-MCVersion  基于官方的开放社区参数名称和 javadocs
    //
    // 在使用 'official' 或 'parchment' 映射时,您必须注意 Mojang 许可证。
    // 请在此处查看更多信息:https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
    //
    // Parchment 是由 ParchmentMC 维护的非官方项目,与 MinecraftForge 分开
    // 使用它们的映射需要进行其他设置:https://github.com/ParchmentMC/Parchment/wiki/Getting-Started
    //
    // 使用非默认映射时请自行承担风险。它们可能不总是有效。
    // 更改映射后,只需重新运行设置任务即可更新您的工作区。
    mappings channel: 'official', version: '1.18.2'

    // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') // 目前,此位置无法从默认值更改。

    // 默认的运行配置。
    // 可以根据需要进行调整、删除或复制。
    runs {
        client {
            workingDirectory project.file('run')

            // 用户开发环境中推荐的日志数据
            // 标记可以按需添加/删除,以逗号分隔。
            // "SCAN": 用于扫描模组。
            // "REGISTRIES": 用于触发注册表事件。
            // "REGISTRYDUMP": 用于获取所有注册表的内容。
            property 'forge.logging.markers', 'REGISTRIES'

            // 控制台的推荐日志级别
            // 您可以在这里设置各种级别。
            // 请阅读:https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
            property 'forge.logging.console.level', 'debug'

            // 逗号分隔的命名空间列表,用于加载游戏测试。空白 = 所有命名空间。
            property 'forge.enabledGameTestNamespaces', 'examplemod'

            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }

        server {
            workingDirectory project.file('run')

            property 'forge.logging.markers', 'REGISTRIES'

            property 'forge.logging.console.level', 'debug'

            // 逗号分隔的命名空间列表,用于加载游戏测试。空白 = 所有命名空间。
            property 'forge.enabledGameTestNamespaces', 'examplemod'

            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }

        // 此运行配置启动 GameTestServer 并运行所有已注册的游戏测试,然后退出。
        // 默认情况下,当没有提供游戏测试时,服务器将崩溃。
        // 游戏测试系统默认也启用了其他运行配置下的 /test 命令。
        gameTestServer {
            workingDirectory project.file('run')

            // 用户开发环境中推荐的日志数据
            // 标记可以按需添加/删除,以逗号分隔。
            // "SCAN": 用于扫描模组。
            // "REGISTRIES": 用于触发注册表事件。
            // "REGISTRYDUMP": 用于获取所有注册表的内容。
            property 'forge.logging.markers', 'REGISTRIES'

            // 控制台的推荐日志级别
            // 您可以在这里设置各种级别。
            // 请阅读:https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
            property 'forge.logging.console.level', 'debug'

            // 逗号分隔的命名空间列表,用于加载游戏测试。空白 = 所有命名空间。
            property 'forge.enabledGameTestNamespaces', 'examplemod'

            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }

        data {
            workingDirectory project.file('run')

            property 'forge.logging.markers', 'REGISTRIES'

            property 'forge.logging.console.level', 'debug'

            // 指定数据生成的模组 id、输出结果的资源和查找现有资源的位置。
            args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')

            mods {
                examplemod {
                    source sourceSets.main
                }
            }
        }
    }
}

// 包含由数据生成器生成的资源。
sourceSets.main.resources { srcDir 'src/generated/resources' }

repositories {
    // 将依赖项的仓库放在这里
    // ForgeGradle 会自动为您添加 Forge maven 和 Maven Central

    // 如果在 ./libs 中有模组 jar 依赖项,您可以将它们声明为仓库,如下所示:
    // flatDir {
    //     dir 'libs'
    // }
}

dependencies {
    // 指定要使用的 Minecraft 版本。如果此组不是 'net.minecraft',则假定该依赖项是 ForgeGradle 的 'patcher' 依赖项,并将应用其补丁。
    // 用户开发时间是一个特殊名称,并且将对其进行各种转换。
    minecraft 'net.minecraftforge:forge:1.18.2-40.2.0'

    // 真实模组非混淆依赖项示例 - 这些将被重映射为当前映射
    // compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // 将 JEI API 添加为编译时依赖项
    // runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // 将完整的 JEI 模组添加为运行时依赖项
    // implementation fg.deobf("com.tterrag.registrate:Registrate:MC${mc_version}-${registrate_version}") // 将 Registrate 添加为依赖项

    // 使用 ./libs 中的模组 jar 的示例
    // implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}")

    // 更多信息...
    // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
    // http://www.gradle.org/docs/current/userguide/dependency_management.html
}

// 示例以将属性添加到清单以供运行时读取。
jar {
    manifest {
        attributes([
                "Specification-Title"     : "examplemod",
                "Specification-Vendor"    : "examplemodsareus",
                "Specification-Version"   : "1", // 我们是我们自己的版本 1
                "Implementation-Title"    : project.name,
                "Implementation-Version"  : project.jar.archiveVersion,
                "Implementation-Vendor"   : "examplemodsareus",
                "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
        ])
    }
}

// 示例配置以使用 maven-publish 插件进行发布
// 这是重新混淆 jar 文件的首选方法
jar.finalizedBy('reobfJar')
// 但是,如果您处于多项目构建中,开发时间需要未混淆的 jar 文件,因此您可以在发布时延迟混淆,方法是
// publish.dependsOn('reobfJar')

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact jar
        }
    }
    repositories {
        maven {
            url "file://${project.projectDir}/mcmodsrepo"
        }
    }
}

tasks.withType(JavaCompile).configureEach {
    options.encoding = 'UTF-8' // 使用 UTF-8 字符集进行 Java 编译
}

然后点击大象(🐘),点击Tasks,build,build

如果是新手的话这么设置可以,但是你想加模组前置之类和mixin的看后续的应用篇。

新手篇

如果你能正常的启动我的世界的话,恭喜你!你有资格创造你的模组,接下来才是刚开始!!!

写进入新的世界的时候的信息

如图,我的例子

package github.com.gengyoubo.changedplus;

import net.minecraft.Util;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod("changedplus")
@Mod.EventBusSubscriber
//public 公用的函数
//class 属性
//Main 函数的名字,这里是文件的名字,对应了Main.java
public class Main  {

    @SubscribeEvent
    //static 静态函数
    //void 从这里开始调用
    public static void playerJoinWorld(PlayerEvent.PlayerLoggedInEvent event){
        Player player = event.getPlayer();
        Level level =player.level;
        //从这里写进入世界的时候的信息
        //player.sendMessage(new TextComponent("信息"), Util.NIL_UUID);
        player.sendMessage(new TextComponent("欢迎来到胶兽的世界!"+player.getDisplayName().getString()+",你准备好被兽化了吗?"), Util.NIL_UUID);
        //如果想添加玩家的名称时,需要在"的外面写+player.getDisplayName().getString()+
    }
}

注意,将文本直接写进代码里的叫做硬编码。我不怎么推荐将文本直接写入文本,这个时候就要用到本地化键了。(在基础篇会写)

写一个物品

想要制作一个物品需要

编写物品属性

注册物品

注册物品属性

建模

我来一个一个教

编写物品属性

例子

package github.com.gengyoubo.changedplus.item;

import net.minecraft.world.item.Item;

// 单个物品函数
public class Latex extends Item {
    public Latex()
    {
        super(new Item.Properties().tab(ChangedPlusTab.getInstance()));
    }

}

你需要创建这样的格式

src.main.java.你的域名.item.物品名.java

注册物品(属性)

你可以一个一个注册,但是你要是觉得麻烦可以制造一个注册表

package github.com.gengyoubo.changedplus.item;

import net.minecraft.world.item.Item;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

// 物品注册表
@Mod.EventBusSubscriber(modid = "changedplus", bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModEventSubscriber {
    // 注册物品
    @SubscribeEvent
    public static void onRegisterItems(final RegistryEvent.Register<Item> event) {
        registerItem(event, new Latex_sword(), "latex_sword");
        registerItem(event, new Latex(), "latex");
        registerItem(event, new latex_stick(), "latex_stick");
        registerItem(event, new long_latex_stick(), "long_latex_stick");
        // 以registerItem的格式注册物品↑
    }
    //一键注册物品的属性
    private static void registerItem(RegistryEvent.Register<Item> event, Item item, String name) {
        item.setRegistryName("changedplus", name);
        event.getRegistry().register(item);
    }
}

太便利了,这个直接套公式呀!!!

文件路径和上面同理

建模

你要是会用blockbench的话你就应该会写如何建模(如果连建模都不会我会考虑再建一个教程)

基础篇

基础篇里主要讲的是比较难,但是必须要会的地方!!!

本地化键

前言

你在游玩的时候有没有用过自动汉化更新模组,它的原理就是进入游戏的时候把汉化包(带有本地化键的资源包)下载出来。然后你只需要加载这个整合包就好,其实这些都是背后的汉化大佬的帮助才能游玩汉化过的模组。

在本地化键里,你不仅要学会如何写java,你还会写json。但是不要担心,你只需要会写文本路径就好。

格式

在本地化键里有两种,一个是有路径的本地化键,一个是没有路径的本地化键。

有路径的是这么写的

"item.changedplus.latex_sword": "胶剑"

这个是指定的,不能随便改

没有路径的是这么写的

"message.welcome": "欢迎来到胶兽的世界! %s, 你准备好被兽化了吗?",

这个只要按照规定去写,就不会出现大问题

还有比较特殊的

"item.changedplus.latex_sword.tooltip": "当前的攻击力: %s",

这时候有人会想“啊?这里哪里特殊了?这不是和有路径的一模一样吗?”但是你先别急,你看代码。

//部分片段
@Override
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> tooltip, TooltipFlag flag) {
    super.appendHoverText(stack, world, tooltip, flag);
    CompoundTag tag = stack.getTag();
    int currentAttackDamage = BASE_ATTACK_DAMAGE;
    if (tag != null && tag.contains("CurrentAttackDamage")) {
        currentAttackDamage = tag.getInt("CurrentAttackDamage");
    }
    tooltip.add(new TranslatableComponent("item.changedplus.latex_sword.tooltip",5 + currentAttackDamage));
}
//部分片段

这个和前两个有什么不一样的?

前半部分的item.changedplus.latex_sword是固定的。

后半部分的tooltip是可以改的。

十分的复杂,但是为什么没在应用篇就是因为这个还不算复杂,只要知道写法就能用,从这里正式的开始如何写本地化键

有路径的本地化键

有路径的基本格式是这样的

(总类).(模组ID).(名称).(自定义)

总类:模组的总类比如说物品就是item,方块就是block,成就就是advancements,还有创造模式选项卡itemGroup

模组ID:基于你的文件夹的名称

名称:物品的名称,方块的名称等等(如果没有可以不用写,比如说创造模式选项卡,成就等)

自定义:这里有一点难,和其他的不一样。如果是写物品的详细信息的话,它就是这种格式

"item.changedplus.long_latex_stick.detailed_info": "给生物施加击退5和1分钟的缓慢5"

如果是成就的话它就是这种格式

"advancements.changedplus.root.title": "欢迎来到changedplus"

不知道?那么看下一个

特殊的有路径的本地化键

给物品详细信息的物品的本地化键(这里只是介绍一下,在应用篇里会教你如何写)

"item.changedplus.long_latex_stick.detailed_info": "给生物施加击退5和1分钟的缓慢5"

成就(如何写成就请看新手篇)

我们来复习一下,在成就里有根成就root和子成就child,它们的写法是

advancements.changedplus.root/child.title/description

title就是成就的标题

description就是成就的副标题

没有路径的本地化键

(自定义).(自定义).(自定义).(自定义).......

就像这样,你可以自己起名字

写好的本地化键需要放哪里?

确认一下你的格式是否是json格式然后放进

src/main/resources/assets/lang

这里需要两个json,需要按照下面的起名字,缺一个都不行

一个是英语en.us.json(英语的本地化键)

一个是中文zh.cn.json(中文的本地化键)

都有哪个国家的本地化键

(引用语言 - 中文 Minecraft Wiki

南非荷兰语af_za

阿拉伯语ar_sa

阿斯图里亚斯语ast_es

阿塞拜疆语az_az

巴什基尔语ba_ru

巴伐利亚语bar

白俄罗斯语be_by

保加利亚语bg_bg

布列塔尼语br_fr

布拉邦特语brb

波斯尼亚语bs_ba

加泰罗尼亚语ca_es

捷克语cs_cz

威尔士语cy_gb

丹麦语da_dk

奥地利德语de_at

瑞士德语de_ch

德语de_de

希腊语el_gr

澳大利亚英语en_au

加拿大英语en_ca

英式英语en_gb

新西兰英语en_nz

海盗英语en_pt

颠倒英语en_ud

美式英语en_us

纯粹英语enp

莎士比亚风格英语enws

世界语eo_uy

阿根廷西班牙语es_ar

智利西班牙语es_cl

厄瓜多尔西班牙语es_ec

西班牙语es_es

墨西哥西班牙语es_mx

乌拉圭西班牙语es_uy

委内瑞拉西班牙语es_ve

安达卢西亚语esan

爱沙尼亚语et_ee

巴斯克语eu_es

波斯语fa_ir

芬兰语fi_fi

菲律宾语fil_ph

法罗语fo_fo

加拿大法语fr_ca

法语fr_fr

东法兰克语fra_de

弗留利语fur_it

弗里斯兰语fy_nl

爱尔兰语ga_ie

苏格兰盖尔语gd_gb

加利西亚语gl_es

夏威夷语haw_us

希伯来语he_il

印地语hi_in

克罗地亚语hr_hr

匈牙利语hu_hu

亚美尼亚语hy_am

印尼语id_id

伊博语ig_ng

伊多语io_en

冰岛语is_is

斯拉夫共通语isv

意大利语it_it

日语ja_jp

逻辑语jbo_en

格鲁吉亚语ka_ge

哈萨克语kk_kz

卡纳达语kn_in

韩语/朝鲜语ko_kr

科隆语/利普里安语ksh

康沃尔语kw_gb

拉丁语la_la

卢森堡语lb_lu

林堡语li_li

伦巴第语lmo

老挝语lo_la

小猫皮钦语lol_us

立陶宛语lt_lt

拉脱维亚语lv_lv

文言文lzh

马其顿语mk_mk

蒙古语mn_mn

马来语ms_my

马耳他语mt_mt

纳瓦特尔语nah

低地德语nds_de

弗拉芒语nl_be

荷兰语nl_nl

挪威尼诺斯克语nn_no

巴克摩挪威语no_no

奥克语oc_fr

艾尔夫达伦语ovd

波兰语pl_pl

巴西葡萄牙语pt_br

葡萄牙语pt_pt

昆雅语qya_aa

罗马尼亚语ro_ro

俄语(革命前)rpr

俄语ru_ru

卢森尼亚语ry_ua

雅库特语sah_sah

北萨米语se_no

斯洛伐克语sk_sk

斯洛文尼亚语sl_si

索马里语so_so

阿尔巴尼亚语sq_al

塞尔维亚语(拉丁字母)sr_cs

塞尔维亚语(西里尔字母)sr_sp

瑞典语sv_se

上萨克森德语sxu

西里西亚语szl

泰米尔语ta_in

泰语th_th

他加禄语tl_ph

克林贡语tlh_aa

道本语tok

土耳其语tr_tr

鞑靼语tt_ru

乌克兰语uk_ua

瓦伦西亚语val_es

威尼斯语vec_it

越南语vi_vn

维奥沙语vp_vl

意第绪语yi_de

约鲁巴语yo_ng

简体中文zh_cn

繁体中文(香港)zh_hk

繁体中文(台湾)zh_tw

马来语(爪夷文)zlm_arab

应用篇

你要是学到了这里,那就说明你可以制作简单的模组了,恭喜你,但是想要制作高质量的模组需要更难的知识。在应用篇里,你会接触到特别复杂的函数(比如说只用特定的种族的生物可以使用某某),加油吧!

种族识别

前言

为什么会有种族识别?这个真的会用吗?你是否是这么想的?我也以为这个不需要,但是制造某某专属武器之类的东西就需要种族识别函数,比如说如果你是人类的话武器的攻击力才10点,但是胶兽使用武器的话攻击力就是100点,看起来很难,其实一点也不简单废话文学(确信,首先需要做支持胶兽的附属模组,然后需要写判断,然后还要写攻击力。对了,攻击力是无法直接修改的,需要依靠动态dmg来修改,动态dmg的话后面会讲.

条件函数

例子:如果持有武器的人是胶兽,返回A。如果不是,返回B

public void check(LivingEntity entity) {
        LatexVariant<?> variant = LatexVariant.getEntityVariant(entity);
        if (variant != null) {
        info("Entity variant exists for entity: {}", entity.getName().getString());
        } else {
        info("Entity variant exists for entity: {}", entity.getName().getString());         
        }
    }

如果需要引用函数,需要用event.

动态dmg

前言

有一个人想修改武器的攻击力,但是修改不了,为什么?因为物品的基本信息包括攻击力已经被注册了,不能再修改了,那该怎么办?于是他就想,我不去修改武器的攻击力,只要写攻击力增加或者是减少了多少,这不就变相的修改了武器的攻击力了吗?这就是动态dmg!

在这里我会教你如何写动态dmg。

但是写好的动态dmg不能直接用,需要有个接受动态dmg的函数。

动态dmg

需要的代码

武器本身的攻击力和提升的攻击力

识别函数

事件监听函数

NBT函数

武器本身的攻击力和提升的攻击力

必须是静态final,所以实际应该是这么写的

public class Latex_sword extends SwordItem {
    private static final int BASE_ATTACK_DAMAGE = 10; // 基础攻击力
    private static final int ADDITIONAL_ATTACK_DAMAGE = 25; // 额外攻击力

BASE_ATTACK_DAMAGE会在武器信息里调用,ADDITIONAL_ATTACK_DAMAGE会在之后的函数调用。

识别函数

在种族识别中,我教你如何写识别函数,把它利用起来

@SubscribeEvent
public static void onEntityHurt(LivingHurtEvent event) {
    if (event.getSource().getDirectEntity() instanceof LivingEntity) {
        LivingEntity attacker = (LivingEntity) event.getSource().getDirectEntity();
        Item item = attacker.getMainHandItem().getItem();
        //下面是种族识别函数
        if (item instanceof Latex_sword) {
            LatexVariant<?> variant = LatexVariant.getEntityVariant(attacker);
            if (variant != null) {
                // 如果攻击者是latex player或者是NPC latex,增加攻击力
                event.setAmount(event.getAmount() + ADDITIONAL_ATTACK_DAMAGE);
            }
        }
    }
}

事件监听函数

实时刷新当前的攻击力

@SubscribeEvent
public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
    if (event.phase == TickEvent.Phase.END) {
        ItemStack stack = event.player.getMainHandItem();
        if (stack.getItem() instanceof Latex_sword) {
            updateAttackDamage(stack, event.player);
        }
    }
}

NBT函数

给武器添加伤害的幅度

private static void updateAttackDamage(ItemStack stack, LivingEntity entity) {
    int currentAttackDamage = calculateCurrentAttackDamage(entity);

    CompoundTag tag = stack.getOrCreateTag();
    tag.putInt("CurrentAttackDamage", currentAttackDamage);
    stack.setTag(tag);
}

条件函数

引用了识别函数

private static int calculateCurrentAttackDamage(LivingEntity entity) {
    int currentAttackDamage = BASE_ATTACK_DAMAGE;
    if (LatexVariant.getEntityVariant(entity) != null) {
        currentAttackDamage += ADDITIONAL_ATTACK_DAMAGE;
    }
    return currentAttackDamage;
}

JAVA篇

这个教程是一个如何写java的教程

理论篇

这里学习java的知识,但是内容复杂,建议从实战篇开始学!

第零章

在java里有5种的类

Class类(属性)也叫C类,大部分模组的java文件就是C类。

Interface类(接口)也叫I类,不怎么用。

Record类(记录)也叫R类。
Enum类(枚举)也叫E类。

@interface类(注解)也叫@类。
C类

C类的基本写法是这样的

public class C {
}

I类

I类的基本写法是这样的

public interface I {
}

R类

R类的基本写法是这样的

public record R() {
}

E类

E类的基本写法是这样的

public enum E {
}

@类

@类的基本写法是这样的

public @interface AT {
}

叠加类

叠加类就是可以叠加的类,比如说

public class C{
    public class A {}
    public class B {}
}

详细会在后面写。

第一章

第一节(基本概念1)

在学习JAVA之前一定要了解这些概念。

对象:对象是类的一个实例,有状态和行为。

类:类是一个模板,它描述一类对象的行为和状态。

方法:方法就是行为,一个类可以有很多方法。

实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。

用图描述就是这样

类----对象------方法
 |          |--------方法
 |          |--------方法
 |          |--------实例变量
 |          |--------实例变量
 |          
 |------对象

Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

关于 Java 标识符,有以下几点需要注意:

所有的标识符都应该以字母(A-Z 或者 a-z),美元符($)、或者下划线(_)开始

Apple

首字符之后可以是字母(A-Z 或者 a-z),美元符($)、下划线(_)或数字的任何字符组合

Apple

关键字不能用作标识符

public

标识符是大小写敏感的

Apple

apple

ApPlE

aPpLe

Java可以使用修饰符来修饰类中方法和属性。主要有两类修饰符:

访问控制修饰符 : default, public , protected, private

非访问控制修饰符 : final, abstract, static, synchronized

Java 中主要有如下几种类型的变量

局部变量

类变量(静态变量)

成员变量(非静态变量)

数组是储存在堆上的对象,可以保存多个同类型变量

这个会在后面详细说明

Java 5.0引入了枚举,枚举限制变量只能是预先设定好的值。使用枚举可以减少代码中的 bug。

第一节(JAVA关键字)


如何制作mod?(forge版)(只支持1.18.X)-第1张图片

如何制作mod?(forge版)(只支持1.18.X)-第2张图片

如何制作mod?(forge版)(只支持1.18.X)-第3张图片引用:https://www.runoob.com/java/java-basic-syntax.html

第一节(注释)

注释是很重要的东西,可以说明这个方法是如何运作的。有三种方法.

/* *
    *  这是第一种
    */
 
 
 
 //这是第三种·

第一节(基本概念2)

在 Java 中,一个类可以由其他类派生。如果你要创建一个类,而且已经存在一个类具有你所需要的属性或方法,那么你可以将新创建的类继承该类。

利用继承的方法,可以重用已存在类的方法和属性,而不用重写这些代码。被继承的类称为超类(super class),派生类称为子类(sub class)

没错!super man的那个超类

在 Java 中,接口可理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。

接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类。

第二节(对象和类1)

那么在第一节里说了对象是什么,但是究竟是什么?我举个例子

比如有这样一个文章

我买了很多水果,有苹果香蕉......

水果就是类

苹果香蕉就是对象

那么还有这样的文章

苹果,很种在土里可以长出苹果树......

苹果的状态(也就是特征)有

苹果的行为(也就是特性)有种在土里可以长出苹果树

那么在代码方面是什么呢?

A:对象的状态就是属性,行为通过方法体现。

我在第一节讲过方法就是行为那么,那么实例变量又是什么?

A:状态

第二节(对象和类2)

一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。

  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。

  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。

实战篇

在这里进行实际的操作!内容简单,适合新手学!也可以当作复习用!

第0章(需要准备的东西)

你需要新创建一个文件夹,给文件夹起个名字(gengyoubo),然后用IDEA把文件夹打开,创造一个Main.java

第1章

第1节(java的基本知识)

class Main {
  public static void main(String[] args) {
    System.out.println("");
  }
}

把""里的替换成"Hello World"吧!如果代码没有问题,将会输出

Hello World

第2节

在这里我们需要知道学习java的时候必须要记住的东西!

首先想要输出"耿悠博"的话,代码应该是

System.out.println("耿悠博");

而不是

System.out.println(耿悠博);

因为在java里,没有耿优博的代码,所以需要用""让系统知道你想要输出的字符串。

这个代码(System.out.println())的意思是[输出()里的东西]

最重要的就是,代码的最后需要加;

第3节

在java里有这个代码

System.out.println(114514);
System.out.println("114514");

如果运行上面的代码将会以数值输出114514,而运行下面的代码会以字符串输出114514。

数值可以计算,但是字符串不行,所以需要转换(在第5节中讲)

在java里有这样的运算符

a+b

a-b

a*b

a/b

a%b

从左往右介绍。计算a+b,计算a-b,计算a*(乘)b,计算a/(除)b,计算a/b的余。(在理论篇里会讲)

第4节

在这里,讲解+的另外一种用处,那就是连接字符串。

比如"我是"+"耿悠博",就会输出 我是耿悠博

第5节(变量)

"耿悠博"是属于字符串(String)型,114514是属于整数(int)型(有其他的代替方法,但是这节不讲其他的方法)

这些"型"需要定义:

String name;

int number;

如果是字符串的变量的话需要在前面加String,如果是数值的话就是int(浮点数是例外)

然后在再下一行写变量里的东西

String name;
name ="耿悠博";
int number;
number =114514;

能不能再简单的写呢?

String name="耿悠博";
int number=114514;

定义变量的同时赋值的行为叫做初始化,然后定义完的变量可以直接使用,像

System.out.println(name);

注意:已经定义过的变量不许再次定义!

int number=114514;
int number=114514;//这是错误的例子
number=114514;//这是正确的例子

(大量的同类型的变量可以在一个地方定义)

int a=1,b=1,c=4,d=5,e=1,f=4;

(只要把型换一下就能用这个型的定义方式)

(顺便说一下,这种变量叫做局部变量,只定义不赋值的是实例变量,用static关键字声明的变量叫做类变量,在方法里定义的变量叫做参数变量,等等等等,下面介绍如何写?

写变量最重要的是 型 变量名

局部变量

int number =114514;

成员变量(实例变量)

int number;

静态变量(类变量)

static int number;

参数变量

public void function(int number);

......然后当然可以把计算值并且定义

int a =1;
a=a+1;

当然这也适用于+-*/%

也可以省略

a +=1;

当然这也适用于+-*/%

如果是加或者是减1的话

a ++;
a --;

当然还用其他的,这些东西在理论篇学吧!

数值除了整数还有小数(float(单精数,单浮点)和double(双精数,双浮点))这里讲解double

比如说

double number=114.514

计算时

a =5/2;

输出的只是2,因为默认是整数,所以需要这么写

a =5.0/2.0;
//或者是
a =5d/2d;
//或者是
a =(double)5/(double)2;//这种数值前面的(型)或者是数值后面的(型的省略)只写一个就行(如果你是强迫症的话当我没说)

就行了

第6节(类型转换)

你不小心写了这个

System.out.println("刚满"+18+"岁");

但是不用担心,输出时会把数值自动转换成字符串。

然后你又不小心写了这个

a =5.0/2

当然这个也不用担心,计算时如果是小数的话会自动转换成double。

这个叫做自动类型转换

也有强制类型转换,比如说往系统里输入数字,但是是文本,还想计算,可以用这个(但是没(什么)用)

作业1

输出

我的名字是”“

今年”“岁了

”“里写你认为合适的字符串或者是数值。

第2章

第1节

我:你喜欢我吗?

路人甲:喜欢!

路人丙:不喜欢!

如果在代码的层面里看,我对系统提问你喜欢我吗?如果喜欢!返回true,如果不喜欢!返回false。

这就是布尔值(boolean),只有true和flase。但是可以用这两个值来进行条件语句

从定义变量开始!

boolean DoYouLike=true;

可以赋值,也可以用布尔表达式来求true或者是false。

boolean check = 1+1==2;

布尔表达式就是1+1==2了(当然意思就是1+1等于2吗?true)

如果把==换成!=就是(1+1等于2以外的数值吗?false)

可以比较大小

1<2

(<,>,<=,>=这四种)

还有

和&&

或||

否!

总结下来一共有3种,但是其实应该有6种

  • 算术运算符(+-*/%)

  • 关系运算符(==,!=,<,>,<=,>=)

  • 位运算(&&,||,!)

  • 逻辑运算符

  • 赋值运算符

  • 其他运算符

其他的就在理论篇中讲解

第2节

开始写路人到底喜不喜欢我的代码

if

if (like==1){
System.out.println("我喜欢你");
}

like==1就是布尔表达式了,但是没有写1以外的值会跳过if

else else if

if (like==1){
System.out.println("我喜欢你");
}else{
System.out.println("讨厌");
}

这就是1以外的时候

if (like==1){
System.out.println("我喜欢你");
}else if(like==2){
System.out.println("讨厌");
}

这就是2的时候

switch

switch(like){
    case 1:
    System.out.println("我喜欢你!");
    break;
    case 2:
    System.out.println("讨厌!");
    break;
    default:
    break;
}

如果不写break或者是continue的话会导致程序有问题,具体的作用会在后面写!

第3节

要想要程序循环需要while和for

while

while(true){
System.out.println(6);
}

(如果没有办法跳出循环会无线循环,需要写布尔表达式的条件或者是写什么时候需要break)

for

for(int i =1;i==1;i=1){
System.out.println(6);
}

for的结构是 数值的定义;布尔表达式;数值的变化

break:结束switch,while,for

continue:跳过switch,while,for

第4节

在第一章里虽然可以用

int a=1,b=1,c=4,d=5,e=1,f=4;

的方式定义并赋值,但是我认为还是太麻烦了!所以需要数组

int[] numbers={1,1,4,5,1,4,};(String同理)

它的编号从左数0,1,2,3,4,5的6个,需要哪个时

int[0] numbers=114514;

如果想要知道numbers里有几个元素时写numbers.length

当然,还是太麻烦了。所以居然有加强的for

for(int number:numbers)

型 变量 数组名

这个意思就是循环(元素的个数)次

作业2

int[]={1,4,6,9,13,16};

算出基数的和和偶数的和

第3章(方法和class)

第1节

在第2章为止都是基础知识,从这里开始才是重头戏,首先你要知道什么是方法,那么就先从最开始的开始讲吧

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

第一行会在第二节会讲,这节主要是讲方法。

方法主要有两种,有返回值的方法和没有返回值的方法

有返回值的

public static int function(){

return result;
}

首先public static int function的意思是返回值为int型(也就是整数型)的方法。那么知道返回值为int型,我们要输出int型的值,

return result的意思是返回result。如果上面的返回值为int型,result必须是int。String型也是同理。

没有返回值的

public static void function(){
}

首先public static void function的意思是没有返回值的方法(void)。既然没有返回值那么也不用写return result。

有返回值的方法一般都是“我给你A,返回了B”。有点像结账系统。

没有返回值的方法是“使用A”。类似使用物品的动作。

写方法是为了容易看代码,所以推荐使用。那么我该如何使用呢?

返回值为void的方法

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

(话说,public static void main(String[] args)的方法是从编译器来的,它的具体的方式就是它)

首先我想实行一个能输出"Hello,World!"的方法。方法部分可以这么写

public static void Hello(){
    System.out.println("Hello, World!");
}

因为是只是输出方法,所以不要返回值,所以返回值是void(无)。

如何调用方法呢?

public class Main {
    Hello();
}

那么把它结合一下就是

public class Main {
    public static void main(String[] args) {
    Hello();
}
    public static void Hello(){
    System.out.println("Hello, World!");
}
}

方法和其他的不一样,不讲究摆放循序,但是只能在class Main{}内部

如果我不是想调用方法,而是给方法一点值该怎么办?你如果知道我在第二章说了什么接下来你就能理解我说的话了。

首先是调用方法的代码部分

function(1);
//单个值
function(1,1,4,5,1,4,);
//多个值

首先调用方法时,值可以是1个,也可以是1个以上。型也可以是任何

public static void function(int number){...}
public static void function(int number1,int number2,int number3,int number4,int number5,int number6){...}

在方法中需要接受传过来的值,然后定义参数变量。

返回值为非void的方法

首先调用方法的部分就不一样

int number =function();
int number =function(a);

你也可以把它看成一种变量。这个时候就容易理解了。

那么方法部分

public static int function(){...}
public static int function(int a){...}

有返回值的方法不要忘了用ruturn返回!

重写与重载

重写:

跳到第四章

重载:

很简单,方法名一样,但是参数的数量不一样,这样就可以编译代码

第2节

class和方法类似,只不过在文件之间使用方法。但是这还是方法。

从最开始的看

public class Main {
    public static void main(String[] args)  {
            System.out.println("Hello, World!");
    }
}

如果我想在别的文件输出Hello,World该怎么办?

假设我有SubMain.java

//Main.java
public class Main {
    public static void main(String[] args)  {
        SubMain.function();
    }
}
//SubMain.java
public class SubMain {
    public static void function(){
        System.out.println("Hello, World!");
    }
}

你知道了如何调用SubMain.function方法,其实你也应该知道System.out.println("Hello,World!")是什么了,调用系统类 System 中的标准输出对象 out 中的方法 println()。

import

如果我想要导入库该怎么办呢?

import java.lang.Math;

这个链接最好上网上找一找。

作业3

和作业2一样,只不过把方法放在SubMain.java

第3章

第1节(面向对象)

对象是什么?当然不是女朋友男朋友的对象了,它指的是目标物(object)。

那么我们为什么要学对象呢?这个是因为为了方便修改代码了。

那么面向对象又是什么?对象当然就是object了,那么面向在英语里有(在......的中心),把它们结合起来就是在目标物的中心,既然是目标物的中心,当然我们要做的就是写以目标物为中心的代码了(这里的部分不要多想)

那么我们又回到了对象是什么这个问题?简单的来说就是个"东西,物品,个体"比如说人,书,车......等等那些包含信息的东西叫做对象。

当然对象不止有信息,它还有行为。

在学面向对象的知识时务必要知道的东西就是类和实例。

实例你可以理解为对象,类就是设计图。就像是有了设计图才能制造出车。

那么对象拥有信息和行为,那么那些东西该如何定义呢。这就是设计图的工作(也就是类)

class类(C类)的定义在第二章的第二节中讲过,但是我在再写一下

class 类名{

}

但是需要注意的是实例也好类也好都是用C类定义。然后在这里类一般指的是设计图,并不是什么什么类的类)

接下来为了方便,实例部分标注为Main.java,类部分标注为Apple.java

首先我们需要从类里生成实例。

class Main{
    public static void main(String[] args){
    new Apple();
    }
}
class Apple{

}

然后我们需要声明并赋值,但是以往都需要定义是什么型,但是这种情况就不需要。

Apple apple=new Apple();

类型 变量名 类名

但是刚才也说过,但是以往都需要定义是什么型,但是唯独只有实例的导入是不一样的,因为是实例的导入,所以类名会直接变成类型。

当然实例可不是只能生成一个,可以是无数个。

接下来,我们需要让apple输出I'm apple的句子。这个时候在类里,我们可以写

public void passage(){
    System.out.println("I'm apple");
}

然后我想让实例做passage的行为。

apple.passage();

这个apple就是刚才定义的变量。

类字段和实例字段

实例字段就是对象的信息。所以我们现在需要定义apple的颜色

class Apple{
    public String color;
}

现在我们有了字段,这个时候还没有颜色,我们需要导入。

现在我们要导入颜色!

class Main{
    public static void main(String[] args){
    Apple apple=new Apple();
    apple.name ="red";
    }
}

如果要使用的话只需要使用apple.name就行!

但是我们发现,每个apple的颜色都不一样,如何让apple能说出对应的颜色呢?

this。它有【这个】的意思。

class Apple{
    public String color;
    public void passage(){
    System.out.println("My color is"+this.color);
    }
}

然后输出passage方法(执行passage方法)时,就用apple.passage();就行了

函数

有人就说:”我每次设置值那么麻烦,就没有别的方法了吗?“,当然有了!函数啊,用函数啊。

在定义函数时有严格的方法

  1. 函数名要和class名相同

  2. 不能要有任何返回值

这样当生成实例时将自动执行函数。

Apple(){
    
}

然后最重要的就来了!如果要设置每个apple时就需要(类)参数变量,你一定见过!

class Main{
    public static void main(String[] args){
    Apple apple=new Apple("red");
    }
}

然后再类里

class Apple{
    public String color;
    Apple(String color){
    this.color=color;
    }
}

对象方法

public int apple(){
    return 5;
}

当你想要计算或者是组合字符串时用,然后你想使用限定的方法时

int like=this.like();

这个时候就是用当前值进行计算

类字段

有实例字段也就有类字段。

当是类字段时

public static 型 变量名;

没错,当是类字段时一定要加static。

类字段与对象字段的区别就时类字段会保存,但是对象字段用完就会消失。

然后还有类方法。

public static 型 方法名(){}

当函数的内容重复时,为了方便第一个保持原来,第二个把重复的部分用this();执行

我把重点都写在图里了,可以参考(如果需要用这个图来说明时需要引用这个教程)

如何制作mod?(forge版)(只支持1.18.X)-第4张图片

第2节(封闭性)

人都有能展示的东西和不能展示的东西

比如说能展示的东西就是:脸,眼睛,嘴巴,等等等等

不能展示的东西就是:小j(非常抱歉,由于以下原因暂时无法播放......)

所以能展示的东西就用public,不能展示的就用private了。

在public里,在class里也好在class外都能用!

在private里,只能在class里用!

(虽然什么都没写,但是还有default ,这个可写可不写)

如何设置是public和private?

你觉得是隐私的就用private,除此之外就是public(在学访问修饰符之前都可以这么想)

第4章(基础最终章)

第1节(继承)

在上一章中,我们知道了如何创造对象和类,然后我们创造了Apple类,这个教程本来想创造Banana类,但是这里就有一个问题,Apple类和Banana类有重复的代码,如果只是一个两个的话还好,但是如果有很多个的话,到后面就不好维护了,而且说不定还忘记了!那么为了方便维护。我们要使用继承这个功能来写代码!在这个之前我们要知道这两个重要的概念

超类(super class)也叫父类或者是基类。

子类(sub class)也叫派生类或者是继承类。

被继承的叫做超类,继承的叫做子类。(但是在实际写代码的时候只需要知道有超类和子类就行了......)

我们要了解如何继承。为了演示,我需要创建Main.java,Apple.java,Banana.java,Fruit.java这四个文件!(但是主要的代码是围绕这Apple.java,Banana.java和Fruit.java进行!)

首先我们需要知道哪个是超类,哪个是子类。在这里,我们知道:

超类:Fruit.java

子类:Apple.java,Banana.java

实例:Main.java

如果想要继承Frult,就需要继承的关键字extends(顺便说一下,extends在英语里有扩展的意思所以Apple.java和Banana.java就是以fruit.java为基础开始往外扩展。)

class 子类 extends 超类 {}

在这里为了说明继承了会发生什么。在这里我要写代码然后讲解。

如何制作mod?(forge版)(只支持1.18.X)-第5张图片

如何制作mod?(forge版)(只支持1.18.X)-第6张图片

如何制作mod?(forge版)(只支持1.18.X)-第7张图片

首先创造了一个新的实例apple1并且给apple1赋予了Red Apple,然后把刚才的Red Apple输出,就是这么简单的代码。聪明的你一定发现了”为什么Apple.java没有任何代码还能执行代码?“这个就是继承的特性!

”将超类的所有代码都继承到子类“

所以执行时就是超类加上子类的代码了!

注意!

在我写注意之前我需要给你看这样的图

如何制作mod?(forge版)(只支持1.18.X)-第8张图片

如何制作mod?(forge版)(只支持1.18.X)-第9张图片

在执行代码时如果有继承关系时:

呼叫子类执行子类的方法:因为子类包含子类的方法所以是是可行的!

Apple apple1=new Apple();
functionSub();

呼叫子类执行超类的方法:因为子类继承了超类的方法所以是可行的!

Apple apple1=new Apple();
functionSuper();

呼叫超类执行子类的方法:因为超类不包含子类的方法所以是不可行的!

Fruit fruit=new Fruit();
functionSub();

呼叫超类执行超类的方法:因为超类包含超类的方法所以是可行的!

Fruit fruit=new Fruit();
functionSuper();

重写

当有继承关系的类时,如果子类有和超类一样的方法时将会覆盖变成子类的方法

super

当你想执行超类的方法时,只需要写:

super(方法名)就可以了!

然后如果子类函数如果想要执行超类的函数时:

super(参数)就可以了!

protected访问符

当你想要让超类和它的子类使用时,在它的超类设置protected使用就行!但是如果protected是在子类时就用不了变量了!

第2节(抽象与抽象类)

你想象一下你给同学们发了作业,但是有些人写作业的方式都不一样:有的人是老老实实写的,有的人是问老师,有的人是抄的......

在这里我们发现了共同点:

都是做作业,但是每个人的做的方法都是不一样。这个就是抽象。

想要给方法设置一个抽象的方法需要给超类设置一个抽象的方法(abstract)

public abstract void function();

因为不知道具体的执行的方法,方法的内容是空的。但是制定的方法一定要在子类中写具体的执行的方法!不然会报错!!!

public void function(){}

准确的来说既然写了抽象方法,必须要重写!不然会报错!

然后类方法中有一个是抽象方法就必须是抽象类!

 abstract class Fruit{}

第3节(多态)

有这样的文件people.java,他有想买apple又想买banana聪明的你一定会写

public void buy (Apple apple){......}
public void buy (Banana banana){......}

你写的代码时是对的!但是还是像之前的一样,一个两个还好,如果有很多的方法维护起来就很麻烦。为了避免这样的麻烦,直接合并!

public void buy(Fruit fruit){......}

然后在主文件里

people gengyoubo=new people("gengyoubo");
Apple apple=new Apple();
Banana banana=new Banana();
//或者是
//Fruit apple=new Apple();
//Fruit banana=new Banana();
gengyoubo.buy(apple);
gengyoubo.buy(banana);

为什么能这么操作?在子类继承了超类了之后子类既是apple类,也是fruit类。所以可以使用!

模组篇

你有没有想过?我如何能和这个模组联动,如何能和这个模组当附属?这个教程能告诉你!

模组关系的定义

如果你没有看的话建议你看

[#8] 模组关系的定义 - 常见问题 - MC百科社群 - MC百科|最大的Minecraft中文MOD百科 (mcmod.cn)

build.gradle

在构建篇里有提到build.gradle,那么让我对模组部分说明一下

maven仓库

Maven仓库是一个存储了项目构建过程中所需的所有库文件(如jar包)的集中位置。在Maven项目中,仓库分为两种类型:

  1. 本地仓库(Local Repository):

    • 这是每个开发者机器上存放Maven依赖的地方。

    • Maven默认的本地仓库位置是用户的home目录下的.m2/repository文件夹。

    • 开发者可以自定义本地仓库的位置,通过在settings.xml配置文件中设置<localRepository>标签。

  2. 远程仓库(Remote Repository):

    • Maven中央仓库是最著名的远程仓库之一,它包含了大量的公共库和插件。

    • 远程仓库可以是私有的,也可以是公共的,用于存放那些不在中央仓库中的依赖。

    • Maven允许配置多个远程仓库,并且可以指定它们之间的搜索顺序。

    这个看起来不重要,但是在使用时非常重要,尤其是远程仓库(Remote Repository)

    我们使用模组需要有三种方法

    第一种就是使用作者的专门的maven仓库

    第二种就是自己从外部导入模组

    第三种就是使用curseforge之类的模组百科来导入

    我主要来讲第三种

    curseforge的cursemaven仓库

    首先maven仓库是这么写的

    repositories {
       maven {
           url "https://cursemaven.com"
       }
    }

    Gradle版本为5以上

    repositories {
       maven {
           url "https://cursemaven.com"
           content {
               includeGroup "curse.maven"
           }
       }
    }

    Gradle版本为6以上

    repositories {
       exclusiveContent {
           forRepository {
               maven {
                   url "https://cursemaven.com"
               }
           }
           filter {
               includeGroup "curse.maven"
           }
       }
    }

    依赖格式为

    curse.maven:<descriptor>-<projectid>:<fileids>

    <descriptor>:给你的文件起名字,最好是能一眼就知道的

    <projectid>:要添加的文件的项目 ID 

        <fileids>-> 要添加为的文件的文件 ID 

什么是项目 ID ?文件ID?请看VCR

如何制作mod?(forge版)(只支持1.18.X)-第10张图片

项目ID就是蓝色的那个部分,所以是1014304

文件ID就是红色的那个部分,所以是5405930

把它们合起来就是

curse.maven:changedplus-1014304:5405930

然后

dependencies {
    implementation fg.deobf("curse.maven:changedplus-1014304:5405930")
}

注:fg.deobf是用来反编译的

mixin篇

关于mixin是什么,ChatGPT是这么说的:

如何制作mod?(forge版)(只支持1.18.X)-第11张图片

如何制作mod?(forge版)(只支持1.18.X)-第12张图片

如何制作mod?(forge版)(只支持1.18.X)-第13张图片

如何制作mod?(forge版)(只支持1.18.X)-第14张图片

如何制作mod?(forge版)(只支持1.18.X)-第15张图片那么在minecraft里mixin是用来干什么的?

答:修改minecraft的

有人就会想修改minecraft的不就是模组吗?不是的,完全不是一个概念。我举个例子:

我想要能飞的船:

没有mixin:重新创造一个船

有mixin:在原版的船的基础上在进行添加代码

就比如在某站上看到的能吃的“岩浆桶“,就是修改了岩浆桶的属性将岩浆桶设置为可以吃。

那么当然不限制于船什么的,在这个教程里主要讲解如何运用mixin。