本篇教程由作者设定使用 CC BY-NC-ND 协议。
(这个教程会持续更新)
如果你想制作mod但是不懂java?不知道怎么配环境?寻找了网上的教程的但是不知道说的是什么?这个教程能详细的告诉你怎么制作mod
本教程分为
模组构建篇
新手篇(
是个新手就能理解,不怎么需要java的基础)
基础篇(内容稍微有一点难,需要一点java的基础)
应用篇(十分的困难,需要理解java的写法)
JAVA篇(学习java的基本知识)
模组篇(如果写模组的前置和联动的话值得看)
mixin篇(想修改minecraft的内容的)
需要的东西
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(中文的本地化键)
都有哪个国家的本地化键
南非荷兰语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关键字)
引用: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();就行了
函数
有人就说:”我每次设置值那么麻烦,就没有别的方法了吗?“,当然有了!函数啊,用函数啊。
在定义函数时有严格的方法
函数名要和class名相同
不能要有任何返回值
这样当生成实例时将自动执行函数。
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();执行
我把重点都写在图里了,可以参考(如果需要用这个图来说明时需要引用这个教程)
第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 超类 {}
在这里为了说明继承了会发生什么。在这里我要写代码然后讲解。
首先创造了一个新的实例apple1并且给apple1赋予了Red Apple,然后把刚才的Red Apple输出,就是这么简单的代码。聪明的你一定发现了”为什么Apple.java没有任何代码还能执行代码?“这个就是继承的特性!
”将超类的所有代码都继承到子类“
所以执行时就是超类加上子类的代码了!
注意!
在我写注意之前我需要给你看这样的图
在执行代码时如果有继承关系时:
呼叫子类执行子类的方法:因为子类包含子类的方法所以是是可行的!
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仓库
本地仓库(Local Repository):
这是每个开发者机器上存放Maven依赖的地方。
Maven默认的本地仓库位置是用户的home目录下的.m2/repository文件夹。
开发者可以自定义本地仓库的位置,通过在settings.xml配置文件中设置<localRepository>标签。
远程仓库(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
项目ID就是蓝色的那个部分,所以是1014304
文件ID就是红色的那个部分,所以是5405930
把它们合起来就是
curse.maven:changedplus-1014304:5405930
然后
dependencies {
implementation fg.deobf("curse.maven:changedplus-1014304:5405930")
}
注:fg.deobf是用来反编译的
mixin篇
关于mixin是什么,ChatGPT是这么说的:
那么在minecraft里mixin是用来干什么的?
答:修改minecraft的
有人就会想修改minecraft的不就是模组吗?不是的,完全不是一个概念。我举个例子:
我想要能飞的船:
没有mixin:重新创造一个船
有mixin:在原版的船的基础上在进行添加代码
就比如在某站上看到的能吃的“岩浆桶“,就是修改了岩浆桶的属性将岩浆桶设置为可以吃。
那么当然不限制于船什么的,在这个教程里主要讲解如何运用mixin。