这是适用于Minecraft Java版1.21.4的Fabric模组开发系列教程专栏第七章——创建自定义魔咒效果。想要阅读其他内容,请查看或订阅上面的专栏。
魔咒效果(EnchantmentEffect) 指的是使用附魔台给物品附魔,或者使用附魔书和铁砧给物品附魔后,附加给物品的额外魔咒条目,从而在一定程度上增强物品的某些属性。想要创建一个魔咒效果,详细的来讲,通常需要完成以下步骤:
- 创建自定义魔咒效果记录类;
- 创建
Codec
; - 编写自定义魔咒效果逻辑;
- 创建自定义魔咒注册键对象;
- 在游戏注册表中注册自定义魔咒效果;
- 创建自定义魔咒JSON数据文件生成类;
- 创建数据生成入口点类
- 执行数据生成
- 在语言文件中为自定义魔咒添加翻译键值对并启动游戏测试
在本章,我们将创建一个原版游戏中没有的魔咒效果——支援(irongolem_support_effect)。可以将魔咒添加到可攻击其他实体的物品上,效果是当使用带有魔咒的武器攻击其他实体(尤其是敌对生物)时,召唤一个铁傀儡帮助玩家。
创建自定义魔咒效果记录类
在自定义魔咒效果记录类中,我们需要编写魔咒效果的详细逻辑并创建魔咒效果的Codec
。
在com/example/test
目录中创建enchantment
文件夹,用于存放魔咒相关Java类。在其中创建effect
文件夹,用于存放自定义魔咒效果Java类。最后,在effect
文件夹中创建记录IronGolemSupportEnchantmentEffect.java
;
public record IronGolemSupportEnchantmentEffect
(EnchantmentLevelBasedValue amount) implements EnchantmentEntityEffect {...}
添加一个类型为EnchantmentLevelBasedValue
的参数,代表附魔等级比例值。然后实现EnchantmentEntityEffect
接口。
附魔等级比例值接口EnchantmentLevelBasedValue
一般,EnchantmentLevelBasedValue
是创建自定义魔咒效果记录类中传递的参数,用于根据附魔等级计算一些随附魔等级改变而变化的数值,如伤害、速度等数值。稍后,我们可以根据这个amount
数值修改用带有此魔咒的武器攻击其他实体时召唤的铁傀儡数量。接口中有很多处理复杂数值变化的方法;
例如linear()
方法用于使数值随附魔等级线性变化,其中传递两个参数;
static EnchantmentLevelBasedValue.Linear linear(float base, float perLevelAboveFirst) {
return new EnchantmentLevelBasedValue.Linear(base, perLevelAboveFirst);
}
float base
:基础成本。最低级附魔需要花费的经验值级别;perLevelAboveFirst
:每级递增成本。附魔等级每高一级需要花费的经验值级别,计算方法为:需要花费的经验值级别 = 基础成本 + 每级递增成本 * (附魔等级 - 1)
附魔实体效果接口EnchantmentEntityEffect
EnchantmentEntityEffect
接口用于创建被自定义魔咒效果附魔的武器对其他实体施加的效果,尤其是在攻击其他实体时。通常,自定义魔咒效果记录类可以实现EnchantmentEntityEffect
接口,最常见的写法是在自定义魔咒记录中重写接口的apply()
方法;
@Override
void apply(ServerWorld world, int level, EnchantmentEffectContext context, Entity user, Vec3d pos);
方法中提供了许多可用的参数,进而使开发者可以深度自定义魔咒对实体的影响。另外,在创建了Codec
后,还需要重写getCodec()
方法,用于返回Codec
;
@Override
MapCodec<? extends EnchantmentEntityEffect> getCodec();
然后重写apply()
和getCodec()
方法;
public record IronGolemSupportEnchantmentEffect(EnchantmentLevelBasedValue amount) implements EnchantmentEntityEffect {
@Override
public void apply(ServerWorld world, int level, EnchantmentEffectContext context, Entity target, Vec3d pos) {}
@Override
public MapCodec<? extends EnchantmentEntityEffect> getCodec() {}
}
创建Codec
Codec
于1.16版本引入。根据Fabric官方文档所述,引入Codec
的概念是为了将对象序列化或反序列化,或者说是为了编码和解码例如JSON、NBT等的数据结构。Codec
的用途比较广泛,除了创建自定义魔咒效果过程中使用以外,在创建自定义数据组件还需要使用Codec
。
这里使用Codec的原因是因为将自定义魔咒效果注册到游戏注册表时的,某些属性以JSON/NBT形式存储(记录类中的EnchantmentLevelBasedValue
参数等),需要使用Codec
对其进行序列化和反序列化,从而实现数据之间的转换。
适用于记录类的Codec构造器类RecordCodecBuilder
RecordCodecBuilder
类用于为自定义魔咒效果记录类(和其他需要创建Codec的类)创建Codec并对其中的属性完成数据转换。通常,调用RecordCodecBuilder.mapCodec()
方法为当前记录类创建Codec对象,方法中传递一个函数式;
public static <O> MapCodec<O> mapCodec(final Function<Instance<O>, ? extends App<Mu<O>, O>> builder) {
return build(builder.apply(instance()));
}
其中的函数式代表接受Instance<O>
并返回App<Mu<O>, O>>
的表达式,作用是为类型为O
的MapCodec
对象。方法体中调用build()
方法,用于构造MapCodec
对象中详细的编码和解码逻辑;
public static <O> MapCodec<O> build(final App<Mu<O>, O> builderBox) {
final RecordCodecBuilder<O, O> builder = unbox(builderBox);
return new MapCodec<O>() {
@Override
public <T> DataResult<O> decode(final DynamicOps<T> ops, final MapLike<T> input) {...}
@Override
public <T> RecordBuilder<T> encode(final O input, final DynamicOps<T> ops, final RecordBuilder<T> prefix) {...}
//...
};
}
可以看到build()
方法中直接返回了一个匿名MapCodec<O>
对象,并在其中重写了其父类MapDecoder<A>
和MapEncoder<A>
中对应的decode()
和encode()
方法,进行了更加详细的编码/解码过程。此处省略了方法体和部分方法;
尽管mapCodec()
方法中的参数可以写成一个匿名内部类,在正式开发情况中,也只会写成一个函数表达式,其基本框架为:
RecordCodecBuilder.mapCodec(
instance -> instance.group(
EnchantmentLevelBasedValue.CODEC.fieldOf(...).forGetter(...)
).apply(instance, O::new)
调用instance.group()
方法用于定义要序列化的字段及其对应的Codec
。然后使用EnchantmentLevelBasedValue.CODEC
常量确定需要序列化字段的类型,如果是其他类型的数据,需要使用Codec
接口中的类型常量;接着使用带有链式调用性质的fieldOf()
方法确定字段在记录类中的名字和forGetter()
方法确定记录类中的字段getter()
方法,此处传递一个函数表达式;最后调用apply()
方法再次确定构造MapCodec
对象的类型。
1.在自定义魔咒效果记录类IronGolemSupportEnchantmentEffect
中声明静态常量CODEC
,类型为MapCodec<IronGolemSupportEnchantmentEffect>
,并且使用RecordCodecBuilder.mapCodec()
方法为其构造MapCodec
对象;
public static final MapCodec<IronGolemSupportEnchantmentEffect> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(
EnchantmentLevelBasedValue.CODEC.fieldOf("amount").forGetter(IronGolemSupportEnchantmentEffect::amount)
).apply(instance, IronGolemSupportEnchantmentEffect::new)
);
确定需要序列化的字段为amount
,类型为EnchantmentLevelBasedValue
,getter()
方法为IronGolemSupportEnchantmentEffect::amount
,最后确认构造的Codec
类型为IronGolemSupportEnchantmentEffect::new
。
2.使getCodec()
方法返回MapCodec<IronGolemSupportEnchantmentEffect>
对象CODEC
;
@Override
public MapCodec<? extends EnchantmentEntityEffect> getCodec() {
return CODEC;
}
编写自定义魔咒效果逻辑
现在,我们开始编写自定义魔咒效果——支援(irongolem_support_effect) 被附魔在其他武器上时,玩家(或任意实体)攻击其他实体时的行为和效果逻辑,即攻击其他实体时,在目标实体位置上生成铁傀儡。
所有的逻辑都将写在IronGolemSupportEnchantmentEffect
记录类的apply()
方法中;
@Override
public void apply(ServerWorld world, int level, EnchantmentEffectContext context, Entity target, Vec3d pos) {}
方法中提供了5个可用的参数:
ServerWorld world
:服务器世界对象;int level
:附魔等级;EnchantmentEffectContext context
:附魔效果上下文对象;Entity target
:目标实体对象,即被攻击的实体;Vec3d pos
:目标实体的位置坐标;
服务器世界类ServerWorld
ServerWorld
类是World
的子类,代表整个服务器世界环境。它的对象可以控制并管理服务器世界中的一切数据,例如存储世界基本信息、方块数据、实体数据和区块数据等,此外还可以执行服务器世界中的一些逻辑,例如生成实体、执行指令、处理事件等,是Minecraft中强大的原生API。
ServerWorld
对象提供了许多方法,这里讲解几个常用的方法:
-
setWeather(int clearDuration, int rainDuration, boolean raining, boolean thundering)
:设置世界当前天气及其持续的时间。传递四个参数:int clearDuration
:晴天持续的时间,即在指定时间后恢复为雷暴/雨天;int rainDuration
:雨天/雷暴持续的时间。即在指定时间后恢复为晴天;boolean raining
:是否下雨。可以直接传递布尔值;boolean thundering
:是否打雷。可以直接传递布尔值;
所有持续时间的单位为秒;
-
setTimeOfDay(long timeOfDay)
:设置世界当前时间。传递一个长整型数据,指的是当前世界时间的游戏刻,取值范围在0~23999之间。参考值:日出:6000,正午:12000,日落:18000; -
getEntitiesByClass(Class<T> entityClass, Box box, Predicate<? super T> predicate)
:(继承自EntityView
接口)用于在指定区域内查找符合特定条件的实体,返回一个传递三个参数:Class<T> entityClass
:指定要查找的实体类型的字节码;Box box
:指定搜索范围。传递一个Box
对象,表示一个三维空间区域;Predicate<? super T> predicate
:指定额外的筛选条件。传递一个实体对象和条件语句的函数表达式;
Box
类的详细用法说明将在接下来的章节中提及; -
getPlayers(Predicate<? super ServerPlayerEntity> predicate, int limit)
:获取当前世界所有玩家,返回一个List<ServerPlayerEntity>
对象。传递两个参数:Predicate<? super ServerPlayerEntity> predicate
:(可选)传递一个玩家对象和条件语句的函数表达式;int limit
:(可选)获取玩家的数量上限;
-
spawnEntity(Entity entity)
:渲染实体到当前世界。传递一个Entity
对象,代表指定的实体对象;(需要在本章中使用)
在后续章节中使用到的方法将在此处补充。
魔咒效果上下文记录EnchantmentEffectContext
EnchantmentEffectContext
记录封装了附魔效果的上下文信息。其中存储类附魔效果的来源、目标、装备槽位、持有者以及装备损坏回调等数据,是apply()
方法的参数之一。
通常我们使用EnchantmentEffectContext
对象的owner()
方法来获取魔咒效果施加的来源(通常为玩家),方法返回一个LivingEntity
对象;
实体类Entity
Entity
类代表存在于世界中可移动或交互的对象,包括玩家、怪物、动物、矿车、盔甲架和掉落物等。Entity
类是一个抽象类,有很多类包括PlayerEntity
、LivingEntity
和ItemEntity
等类均继承了Entity
类,作为其子类,定义了每种实体不同的属性和行为。
Entity
对象同样提供了许多方法,这里讲解目前可能需要用到的方法:
setPosition(Vec3d pos)
:设置实体坐标。传递一个Vec3d
对象,其中提供了一个三维坐标;getBlockPos()
:获取实体脚下方块的坐标,返回一个BlockPos
对象,其中提供了一个方块的三维坐标;
此外,在本章中即将用到的IronGolemEntity
代表铁傀儡的实体类,也是Entity
类的子类。其中有着专属于配置铁傀儡的属性和行为的方法:
setPlayerCreated(boolean playerCreated)
:设置铁傀儡是否由玩家创建。直接传递一个布尔值;- 构造方法
new IronGolemEntity(EntityType<? extends IronGolemEntity> entityType, World world)
:用于构造一个铁傀儡实体对象。传递两个参数:EntityType<? extends IronGolemEntity> entityType
:实体类型。传递一个EntityType
类的静态常量,此处固定的写法为EntityType.IRON_GOLEM
;World world
:世界对象。指定实体生成的世界。
在后续章节中使用到的方法将在此处补充。
三维向量类Vec3d
Vec3d
类用于表示三维空间中的点或方向。在游戏中,通常可以用于表示为玩家、实体和方块的三维坐标,也可以进行三维空间中的相关计算,例如面积、向量计算等。
通常,可以通过PlayerEntity
对象中的一些方法获取玩家位置坐标和视角向量等信息。在本章中apply()
方法参数中的Vec3d
对象用于确定目标实体的位置坐标,暂时没有用到对象的其他方法。
想要在攻击其他实体时,在目标实体的位置上生成铁傀儡,首先我们需要创建一个铁傀儡实体对象,然后设置其渲染的位置,最后将铁傀儡渲染到游戏中。
1.在apply()
方法中,声明变量entity
,类型为IronGolemEntity
,并使用构造方法初始化对象;
IronGolemEntity entity = new IronGolemEntity(EntityType.IRON_GOLEM,world);
构造方法传递两个参数,第一个参数是铁傀儡实体类型常量EntityType.IRON_GOLEM
,是创建铁傀儡对象时的固定写法;第二个参数代表将实体在指定世界中渲染,使用apply()
方法参数中的world
变量即可;
2.然后使用IronGolemEntity
对象的setPosition()
方法指定实体渲染到世界中的具体位置,传递一个Vec3d
对象,即apply()
方法参数中的pos
变量;
entity.setPosition(pos);
3.最后使用World
对象的spawnEntity()
方法将实体渲染到当前世界当中,传递一个IronGolemEntity
对象;
world.spawnEntity(entity);
4.基本的魔咒效果已经编写完成。但是还需要处理一些细节;
- 避免铁傀儡对玩家产生仇恨
由于在攻击其他实体时会在实体原地直接召唤铁傀儡,当剑出现横扫攻击时,可能会误伤铁傀儡,此时铁傀儡会开始向你发起攻击。为了规避这种情形,可以调用IronGolemEntity
对象的setPlayerCreated()
方法;
方法直接传递一个布尔值为entity.setPlayerCreated(true);
true
,代表铁傀儡由玩家创建,任何情况下都不会对玩家展开攻击; - 处理特殊情况
对于一些没有生命的实体(盔甲架、船等),攻击时是不需要召唤铁傀儡的。此时,可以通过target
变量判断目标实体是否为LivingEntity
对象,即有生命的实体。另外还要判断目标实体是否为铁傀儡本身。以防万一,可以调用context.owner()
方法判断施加魔咒效果的实体(通常为玩家)是否存在;if (target instanceof LivingEntity && !(target instanceof IronGolemEntity)){ if(context.owner() != null) { //渲染实体逻辑 } }
- 根据附魔等级调整渲染铁傀儡的数量
我们可以通过level
变量获取当前魔咒的附魔等级,然后根据附魔等级和附魔等级比例值计算生成的铁傀儡的数量。调用EnchantmentLevelBasedValue
对象的getValue()
方法,获取计算后渲染铁傀儡的数量,存储在变量numStrikes
中,然后为渲染实体代码片段添加循环,实现召唤多个铁傀儡(附魔等级大于等于II的情况);if (target instanceof LivingEntity && !(target instanceof IronGolemEntity)) { if (context.owner() != null) { float numStrikes = this.amount.getValue(level); for (float i = 0; i < numStrikes; i++) { //渲染实体逻辑 } } }
至此,自定义魔咒效果记录类的编写正式完成,完整的代码如下:
public record IronGolemSupportEnchantmentEffect(EnchantmentLevelBasedValue amount) implements EnchantmentEntityEffect {
public static final MapCodec<IronGolemSupportEnchantmentEffect> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(
EnchantmentLevelBasedValue.CODEC.fieldOf("amount").forGetter(IronGolemSupportEnchantmentEffect::amount)
).apply(instance, IronGolemSupportEnchantmentEffect::new)
);
@Override
public void apply(ServerWorld world, int level, EnchantmentEffectContext context, Entity target, Vec3d pos) {
if (target instanceof LivingEntity && !(target instanceof IronGolemEntity)) {
if (context.owner() != null) {
float numStrikes = this.amount.getValue(level);
for (float i = 0; i < numStrikes; i++) {
IronGolemEntity entity = new IronGolemEntity(EntityType.IRON_GOLEM,world);
entity.setPlayerCreated(true);
entity.setPosition(pos);
world.spawnEntity(entity);
}
}
}
}
@Override
public MapCodec<? extends EnchantmentEntityEffect> getCodec() {
return CODEC;
}
}
创建自定义魔咒注册键对象
现在,可以开始在游戏注册表中注册自定义魔咒效果前的准备工作。首先便是使用RegistryKey.of()
方法创建魔咒的注册键对象。
在com/example/test/utils
目录中创建ModEnchantmentEffects
类,用于注册魔咒。声明静态常量SUPPORTING
作为注册键对象,类型为RegistryKey<Enchantment>
;
public static final RegistryKey<Enchantment> SUPPORTING =
RegistryKey.of(RegistryKeys.ENCHANTMENT,Identifier.of(FabricDocsReference.MOD_ID,"supporting"));
注册键对象以RegistryKeys.ENCHANTMENT
作为注册键,代表注册类型,是创建魔咒注册键对象的固定写法,然后调用Identifier.of()
方法创建标识符,标识符为模组Id和路径supporting
构成;
关于Registry.of()
方法的详细用法请参考我的世界Java版1.21.4的Fabric模组开发教程(五)创建高级物品:盔甲;
关于Identifier
类的详细用法说明请参考我的世界Java版1.21.4的Fabric模组开发教程(二)创建物品。
在游戏注册表中注册自定义魔咒效果
注册自定义魔咒效果的过程与之前注册的其他物品不太相同。因为魔咒效果在游戏中的部分配置是JSON文件的形式,此时便需要Codec进行序列化和反序列化,实现数据灵活的转换。在注册时,也同样要用到自定义魔咒效果记录类中的Codec。
1.在ModEnchantmentEffects
类中声明静态变量IRONGOLEM_SUPPORT_EFFECT
用于注册自定义魔咒效果,类型为MapCodec<IronGolemSupportEnchantmentEffect>
,使用Registry.register()
方法初始化变量;
public static MapCodec<IronGolemSupportEnchantmentEffect> IRONGOLEM_SUPPORT_EFFECT =
Registry.register(Registries.ENCHANTMENT_ENTITY_EFFECT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "irongolem_support_effect"), IronGolemSupportEnchantmentEffect.CODEC);
register()
方法中首先传递自定义魔咒效果的注册键类型Registries.ENCHANTMENT_ENTITY_EFFECT_TYPE
,是固定写法,接着使用Identifier.of()
方法创建由模组Id和路径组成的标识符,路径为irongolem_support_effect
,注意要和魔咒路径区分,最后传递IronGolemSupportEnchantmentEffect.CODEC
常量用于编/码对象。
关于原生Register.registry()
方法的详细用法说明,请参考我的世界Java版1.21.4的Fabric模组开发教程(二)创建物品。
2.创建静态方法registerModEnchantmentEffects()
作为初始化此类的工具方法;
public static void registerModEnchantmentEffects() {
Test.LOGGER.info("Registering EnchantmentEffects for" + FabricDocsReference.MOD_ID);
}
实际上方法体可以为空,此处使用了日志记录。
3.在入口点类onInitialize()
方法中调用registerModEnchantmentEffects()
方法,用于注册魔咒效果对象;
public void onInitialize() {
//...
ModEnchantmentEffects.registerModEnchantmentEffects();
//...
}
启动游戏后,已经注册的魔咒对应的附魔书会自动出现在创造模式物品栏中。
创建自定义魔咒的JSON数据文件生成类
目前,自定义魔咒的注册仍需要使用JSON配置文件来完成,但考虑到编写JSON文件的复杂程度,我们可以创建一个自定义魔咒的JSON文件生成类。通过这个类可以生成创建自定义魔咒对应的JSON文件,避免了手工编写JSON文件带来的诸多问题。
1.在com/example/test
目录中创建datagen
文件夹,用于存放数据生成相关Java类。在其中创建EnchantmentGenerator.java
,并继承FabricDynamicRegistryProvider
类并实现相关方法;
public class EnchantmentGenerator extends FabricDynamicRegistryProvider {
public EnchantmentGenerator(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
super(output, registriesFuture);
}
@Override
protected void configure(RegistryWrapper.WrapperLookup registries, Entries entries) {}
@Override
public String getName() {
return null;
}
}
Fabric动态注册表供应接口FabricDynamicRegistryProvider
FabricDynamicRegistryProvider
接口是一种用于生成动态注册表对象(例如生物群系、魔咒或维度)的JSON数据文件提供程序。通常,为了生成JSON文件,我们需要使数据生成类继承FabricDynamicRegistryProvider
接口。一般需要重写三个方法:
- 构造方法
EnchantmentGenerator(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture)
:不需要手动调用,传递数据生成器和注册表构建器两个参数; configure(RegistryWrapper.WrapperLookup registries, Entries entries)
:用于编写自定义数据生成逻辑;getName()
:返回当前数据生成器名称(仅用于调试);
注册表封装器的封装查询内部接口RegistryWrapper.WrapperLookup
RegistryWrapper.WrapperLookup
接口在RegistryWrapper
接口内部声明,用于在数据生成阶段访问和操作动态注册表。它通常出现在FabricDynamicRegistryProvider
接口方法configure()
的参数列表中供开发者使用。
比较重要的方法是RegistryWrapper.WrapperLookup
对象的getOrThrow()
方法,用于从动态注册表中获取指定注册表的包装器,借此来限制魔咒可以附魔的物品范围。
default <T> RegistryWrapper.Impl<T> getOrThrow(RegistryKey<? extends Registry<? extends T>> registryRef) {
return (RegistryWrapper.Impl<T>)this.getOptional(registryRef)
.orElseThrow(() -> new IllegalStateException("Registry " + registryRef.getValue() + " not found"));
}
传递一个注册键作为参数,如果指定的注册键不存在,将抛出IllegalStateException
异常。返回值为RegistryWrapper.Impl
对象,然后还需要调用RegistryEntryLookup<T>
对象内部接口RegistryLookup
的方法getOrThrow()
,方法传递一个ItemTags
类的静态常量,进一步确定可附魔的物品范围。
Fabric动态注册表条目内部类FabricDynamicRegistryProvider.Entries
FabricDynamicRegistryProvider.Entries
类一般在数据文件生成类中使用,用于写入自定义数据到JSON等配置文件中。它将在数据文件生成类继承了FabricDynamicRegistryProvider
接口后实现的configure()
方法的参数列表中出现。
通常,想要写入一组自定义数据并生成JSON文件,需要调用其对象的add()
方法;
public <T> RegistryEntry<T> add(RegistryKey<T> key, T object, ResourceCondition... conditions) {
return getQueuedEntries(key).add(key, object, conditions);
}
传递三个参数:
RegistryKey<T> key
:指定注册键对象;T object
:指定注册的对象实例,在数据生成类文件中,此处固定传递一个Enchantment.Builder
对象,用于构造Enchantment
对象;ResourceCondition... conditions
:(可选参数)指定资源条件。
魔咒记录Enchantment
、魔咒构造器内部类Enchantment.Builder
、魔咒定义内部类Enchantment.Definition
和魔咒等级成本内部类Enchantment.Cost
Enchantment
记录用于管理游戏中的附魔系统。其中声明了多个内部类,包括Enchantment.Builder
、Enchantment.Definition
和Enchantment.Cost
等类,三者都需要在生成数据过程中使用。
builder()
Enchantment.Builder
类的对象可以通过调用Enchantment.builder()
方法获取;
public static Enchantment.Builder builder(Enchantment.Definition definition) {
return new Enchantment.Builder(definition);
}
方法传递一个Enchantment.Definition
类的对象,返回Enchantment
对象;
addEffect()
此外,在稍后还需要调用Enchantment.Builder
对象的addEffect()
方法,用于为魔咒添加带有目标和作用对象的附魔效果;
public <E> Enchantment.Builder addEffect(
ComponentType<List<TargetedEnchantmentEffect<E>>> type,
EnchantmentEffectTarget enchanted,
EnchantmentEffectTarget affected,
E effect
) {
...
}
方法传递了四个参数:
ComponentType<List<TargetedEnchantmentEffect<E>>> type
:指定触发时机。需要传递EnchantmentEffectComponentTypes
接口中的变量;EnchantmentEffectTarget enchanted
:指定触发者。固定传递枚举常量EnchantmentEffectTarget.ATTACKER
;EnchantmentEffectTarget affected
:指定受影响者。固定传递枚举常量EnchantmentEffectTarget.VICTIM
;E effect
:指定自定义魔咒效果记录类,此处可以指定附魔等级比例值的计算方法;
leveledCost()
Enchantment.Cost
类的对象可以通过调用Enchantment.leveledCost()
方法获取,用于配置魔咒每个级别的附魔成本;
public static Enchantment.Cost leveledCost(int base, int perLevel) {
return new Enchantment.Cost(base, perLevel);
}
方法传递两个参数:
int base
:指定基础成本,即最低等级的附魔(I级)需要消耗的经验级别;int perLevel
:指定每级递增成本。每当附魔等级增加一级需要消耗的经验级别;
definition()
Enchantment.Definition
类的对象可以通过调用Enchantment.definition()
方法获取,用于配置魔咒的属性;
public static Enchantment.Definition definition(
RegistryEntryList<Item> supportedItems,
int weight,
int maxLevel,
Enchantment.Cost minCost,
Enchantment.Cost maxCost,
int anvilCost,
AttributeModifierSlot... slots
) {
return new Enchantment.Definition(supportedItems, Optional.empty(), weight, maxLevel, minCost, maxCost, anvilCost, List.of(slots));
}
其中参数包括:
RegistryEntryList<Item> supportedItems
:指定可以使用此魔咒附魔的物品,通常调用RegistryWrapper.WrapperLookup
对象的getOrThrow()
方法和RegistryEntryLookup<T>
对象内部接口RegistryLookup
的方法getOrThrow()
;int weight
:指定附魔权重,即魔咒出现在附魔台上的概率;int maxLevel
:指定附魔的最高等级;Enchantment.Cost minCost
:指定最低等级附魔花费的经验级别数;Enchantment.Cost maxCost
:指定最高等级附魔花费的经验级别数;int anvilCost
:指定使用铁砧合并带有此魔咒的武器时花费的经验级别数;AttributeModifierSlot... slots
:指定附魔物品的所在装备槽位,一般传递枚举类AttributeModifierSlot
中的枚举常量;
2.封装一个register()
方法,用于将自定义魔咒注册到注册表中;
private void register(Entries entries, Enchantment.Builder builder, ResourceCondition ...resourceCondition) {
entries.add(ModEnchantmentEffects.SUPPORTING,
builder.build(ModEnchantmentEffects.SUPPORTING.getValue()),
resourceCondition);
}
参数列表为动态注册表条目内部类对象entries
、附魔构造器内部类对象builder
和资源条件对象resourceCondition
。在方法体中调用entries.add()
方法,首先传递自定义魔咒效果注册键对象ModEnchantmentEffects.SUPPORTING
,然后传递附魔构造器对象的build()
方法返回的Enchantment
对象,其中调用注册键对象的getValue()
方法返回自定义魔咒效果的标识符,最后直接传递ResourceCondition
对象。
3.在configure()
方法中调用register()
方法,开始注册自定义魔咒;
protected void configure(RegistryWrapper.WrapperLookup registries, Entries entries) {
register(entries, Enchantment.builder(
Enchantment.definition(
registries.getOrThrow(RegistryKeys.ITEM).getOrThrow(ItemTags.WEAPON_ENCHANTABLE),
10,
3,
Enchantment.leveledCost(1, 1),
Enchantment.leveledCost(2, 4),
5,
AttributeModifierSlot.HAND)
).addEffect(
EnchantmentEffectComponentTypes.POST_ATTACK,
EnchantmentEffectTarget.ATTACKER,
EnchantmentEffectTarget.VICTIM,
new IronGolemSupportEnchantmentEffect(
EnchantmentLevelBasedValue.linear(1f, 1f))
));
}
方法中第一个参数直接传递entries
,第二个参数调用Enchantment.builder()
方法返回Enchantment.Builder
对象,在其中调用Enchantment.definition()
方法返回Enchantment.Definition
对象,其中的参数按顺序分别设置了可以被附魔的物品范围为ItemTags.WEAPON_ENCHANTABLE
(武器)、附魔台中出现此魔咒的概率为10、附魔的最高等级为3、使用附魔台附魔时最低级所花费的经验级别数为1(基础成本)+1(每级递增成本)*(1(等级)-1)=1、使用附魔台附魔时最高级所花费的经验级别数为2(基础成本)+4(每级递增成本)*(5(等级)-1)=18、在铁砧中合并带有此魔咒的武器时所花费的经验级别数为5和指定附魔物品的所在装备槽位为AttributeModifierSlot.HAND
(手);
然后调用addEffect()
方法添加带有目标和作用对象的附魔效果,其中的参数按顺序分别设置了触发时机为EnchantmentEffectComponentTypes.POST_ATTACK
(攻击后)、触发者为EnchantmentEffectTarget.ATTACKER
、受影响者为EnchantmentEffectTarget.VICTIM
和自定义魔咒效果对象,在记录类的构造方法中EnchantmentLevelBasedValue.linear()
方法配置铁傀儡基础召唤个数(1)和随附魔等级提升每次增加的个数(1)。
创建数据生成入口点类
在最后,我们还需要将编写好的数据生成类在数据生成入口点类中进行配置,可以确定在执行数据生成时哪些自定义数据需要进行数据生成。
数据生成入口点内部函数式接口DataGeneratorEntrypoint
DataGeneratorEntrypoint
接口是Fabric API中为开发者提供数据生成功能的入口点,和整个模组的入口点类相似。一般,数据生成入口点类需要实现DataGeneratorEntrypoint
接口并实现onInitializeDataGenerator()
方法。
void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator);
只有一个类型为FabricDataGenerator
的参数。
Fabric数据生成器类FabricDataGenerator
及其程序包内部类FabricDataGenerator.Pack
FabricDataGenerator
类用于在数据生成入口点类中创建自定义数据生成器。通常出现在数据生成入口点类的onInitializeDataGenerator()
参数列表中。
通常,创建一个自定义数据生成器首先要调用FabricDataGenerator.createPack()
方法来获取FabricDataGenerator.Pack
内部类对象,然后使用FabricDataGenerator.Pack
内部类对象的addProvider()
方法添加自定义数据生成类的对象。
1.在com/example/test/datagen
目录中创建FabricDocsReferenceDataGenerator
类作为数据生成入口点类,然后实现DataGeneratorEntrypoint
接口并重写onInitializeDataGenerator()
方法;
public class FabricDocsReferenceDataGenerator implements DataGeneratorEntrypoint {
@Override
public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {}
}
2.在方法中声明pack
对象作为数据生成器程序包,类型为FabricDataGenerator.Pack
,调用FabricDataGenerator
对象的createPack()
方法对其初始化;
FabricDataGenerator.Pack pack = fabricDataGenerator.createPack();
3.调用FabricDataGenerator.Pack
对象的addProvider()
方法,用于添加自定义数据生成器的提供程序,传递自定义生成器类的对象的构造方法的函数式
pack.addProvider(EnchantmentGenerator::new);
执行数据生成
完成数据生成入口点类的编写后,可以开始生成自定义魔咒的JSON配置文件了。在开始之前,要确保你的项目已经启用数据生成。
关于启用项目的数据生成说明,请参考我的世界Java版1.21.4的Fabric模组开发教程(二)创建物品中的“为物品添加合成配方”小节。
1.打开Gradle窗格,在其中点击“执行Gradle任务”;
2.执行gradlew runDatagen
命令,开始数据生成;
3.运行窗格中显示“BUILD SUCCESSFUL”,说明执行成功;
4.现在可以在generated/data/test/enchantment目录中找到生成的supporting.json
文件;
{
"anvil_cost": 5,
"description": {
"translate": "enchantment.test.supporting"
},
"effects": {
"minecraft:post_attack": [
{
"affected": "victim",
"effect": {
"type": "test:irongolem_support_effect",
"amount": {
"type": "minecraft:linear",
"base": 1.0,
"per_level_above_first": 1.0
}
},
"enchanted": "attacker"
}
]
},
"max_cost": {
"base": 2,
"per_level_above_first": 4
},
"max_level": 3,
"min_cost": {
"base": 1,
"per_level_above_first": 1
},
"slots": [
"hand"
],
"supported_items": "#minecraft:enchantable/weapon",
"weight": 10
}
其中的设置和自定义数据生成类中的配置相同。
在语言文件中为自定义魔咒添加翻译键值对并启动游戏测试
最后的最后,还需要为魔咒添加中文翻译;
1.在assets/test/lang/zh_cn.json
文件中添加一条键值对;
{
...
"enchantment.test.supporting": "支援"
...
}
2.启动游戏开始测试功能。在创造模式物品栏中搜索“支援”,可以看到对应的附魔书;
在“原材料”物品组中;
2.使用铁砧为“铁剑”附魔;
3.使用铁剑攻击其他实体,会在原地召唤铁傀儡;
使用带有“支援II”附魔的武器攻击实体会召唤2个铁傀儡;
此外,也可以主手持“铁剑”,然后使用指令完成附魔;
给予玩家主手持有的武器“支援II”的附魔。
本章小结
本章详细阐述了创建自定义魔咒效果的全部过程,供广大网友为游戏添加自己的魔咒提供参考。本文篇幅巨大,难度较高,还望各位仔细研读,有兴趣可以订阅此专栏,谢谢!