我的世界Java版1.21.4的Fabric模组开发教程(七)创建自定义魔咒效果

这是适用于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>>的表达式,作用是为类型为OMapCodec​​对象。方法体中调用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,类型为EnchantmentLevelBasedValuegetter()方法为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类是一个抽象类,有很多类包括PlayerEntityLivingEntityItemEntity等类均继承了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.BuilderEnchantment.DefinitionEnchantment.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”的附魔。

本章小结

本章详细阐述了创建自定义魔咒效果的全部过程,供广大网友为游戏添加自己的魔咒提供参考。本文篇幅巨大,难度较高,还望各位仔细研读,有兴趣可以订阅此专栏,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值