本篇教程的视频
本篇教程的源代码
介绍
前面我们已经学习了如何自定义交易,那么假设你还不满足,希望有更多职业的村民,那么就继续这里的教程
在这篇教程中,我们将学习如何自定义村民,包括新增村民职业、交易等
村民的职业是通过工作站点来定义的,一开始生成他们的时候并没有职业,也不能交易
但如果有相关的工作站点,那么村民就会根据工作站点来转换职业(除了绿袍),更换其自身的贴图,同时也与玩家可以进行交易
查看源代码
首先村民要定义一个职业,再根据其职业内容来定义交易
所以我们先来查看VillagerProfession
这个类,这个类是定义了原版的村民职业
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static final VillagerProfession NONE = register("none", PointOfInterestType.NONE, IS_ACQUIRABLE_JOB_SITE, null); public static final VillagerProfession ARMORER = register("armorer", PointOfInterestTypes.ARMORER, SoundEvents.ENTITY_VILLAGER_WORK_ARMORER); public static final VillagerProfession BUTCHER = register("butcher", PointOfInterestTypes.BUTCHER, SoundEvents.ENTITY_VILLAGER_WORK_BUTCHER); public static final VillagerProfession CARTOGRAPHER = register( "cartographer", PointOfInterestTypes.CARTOGRAPHER, SoundEvents.ENTITY_VILLAGER_WORK_CARTOGRAPHER ); public static final VillagerProfession CLERIC = register("cleric", PointOfInterestTypes.CLERIC, SoundEvents.ENTITY_VILLAGER_WORK_CLERIC); public static final VillagerProfession FARMER = register( "farmer", PointOfInterestTypes.FARMER, ImmutableSet.of(Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT_SEEDS, Items.BONE_MEAL), ImmutableSet.of(Blocks.FARMLAND), SoundEvents.ENTITY_VILLAGER_WORK_FARMER ); ...
|
我们可以看到这些注册语句,当然不同的职业注册的方法也不尽相同,不过其基本的参数是注册名
、工作站点
、声音
不过我们可以看到农民的注册语句多了两个参数,这两个参数分别是可收集物品
、辅助工作站点
,因为农民得种地,同时又会收集成熟的农作物
其他职业的注册也是类似的
这里我们方便起见,就以盔甲商 ARMORER
为例,我们来看看它的注册方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| private static VillagerProfession register(String id, RegistryKey<PointOfInterestType> heldWorkstation, @Nullable SoundEvent workSound) { return register(id, entry -> entry.matchesKey(heldWorkstation), entry -> entry.matchesKey(heldWorkstation), workSound); }
private static VillagerProfession register( String id, Predicate<RegistryEntry<PointOfInterestType>> heldWorkstation, Predicate<RegistryEntry<PointOfInterestType>> acquirableWorkstation, @Nullable SoundEvent workSound ) { return register(id, heldWorkstation, acquirableWorkstation, ImmutableSet.of(), ImmutableSet.of(), workSound); }
private static VillagerProfession register( String id, RegistryKey<PointOfInterestType> heldWorkstation, ImmutableSet<Item> gatherableItems, ImmutableSet<Block> secondaryJobSites, @Nullable SoundEvent workSound ) { return register(id, entry -> entry.matchesKey(heldWorkstation), entry -> entry.matchesKey(heldWorkstation), gatherableItems, secondaryJobSites, workSound); }
private static VillagerProfession register( String id, Predicate<RegistryEntry<PointOfInterestType>> heldWorkstation, Predicate<RegistryEntry<PointOfInterestType>> acquirableWorkstation, ImmutableSet<Item> gatherableItems, ImmutableSet<Block> secondaryJobSites, @Nullable SoundEvent workSound ) { return Registry.register( Registries.VILLAGER_PROFESSION, Identifier.ofVanilla(id), new VillagerProfession(id, heldWorkstation, acquirableWorkstation, gatherableItems, secondaryJobSites, workSound) ); }
|
当然,它的注册方法也是和Items
的一样,层层叠叠的,最后的那个才是我们要用的,上面这一堆都可以进行整合
接下来我们再看看盔甲商的工作站点PointOfInterestTypes.ARMORER
这个是在PointOfInterestTypes
这个类中定义的
1 2 3 4 5 6
| public static final RegistryKey<PointOfInterestType> ARMORER = of("armorer"); ... private static RegistryKey<PointOfInterestType> of(String id) { return RegistryKey.of(RegistryKeys.POINT_OF_INTEREST_TYPE, Identifier.ofVanilla(id)); } ...
|
这个是盔甲商的工作站点注册的语句及其注册方法
但是你是否发现了一个问题,它对应的方块呢?盔甲商对应的工作站应该是高炉
然鹅,上面的两个语句都没有提到任何的方块,这是为什么呢?
我们再往下翻,我们可以看到下面的一个语句
1 2 3 4 5 6 7 8 9 10
| public static PointOfInterestType registerAndGetDefault(Registry<PointOfInterestType> registry) { register(registry, ARMORER, getStatesOfBlock(Blocks.BLAST_FURNACE), 1, 1); ... }
private static PointOfInterestType register( Registry<PointOfInterestType> registry, RegistryKey<PointOfInterestType> key, Set<BlockState> states, int ticketCount, int searchDistance ) { ... }
|
在registerAndGetDefault
这个方法中,我们可以看到ARMORER
对应的工作站点是BLAST_FURNACE
(高炉),工作站点的具体方块是在这里定义的
后面的两个数字分别是容纳数量
和搜索距离
(单位当然不是1格方块,具体得挖底层)
容纳数量
基本上是1
,即一个工作站只能对应一个村民
我们也可以在这个方法中看到其他的一些工作站(也可以叫兴趣点)
1
| register(registry, MEETING, getStatesOfBlock(Blocks.BELL), 32, 6);
|
这是村民集会的地点,也就是村庄里的钟,它最高可以容纳32人
1
| register(registry, BEEHIVE, getStatesOfBlock(Blocks.BEEHIVE), 0, 1);
|
当然也有其他生物的,比如蜂巢,不过他们的容纳数量为0,因为这不是给人使的,给蜜蜂的
不过,最要紧的是它的注册方法是私有的,我们无法直接调用,但是Fabric提供了API
,我们可以直接使用
注册职业
创建ModVillagers类
1 2 3
| public class ModVillagers { }
|
首先我们创建一个ModVillagers
类,这个类用来存放我们自定义的职业
随后我们将上面的注册方法进行整合,结合我们自己的命名空间来
1 2 3 4 5
| private static VillagerProfession register(String id, RegistryKey<PointOfInterestType> heldWorkstation, @Nullable SoundEvent workSound) { return Registry.register(Registries.VILLAGER_PROFESSION, Identifier.of(TutorialMod.MOD_ID, id), new VillagerProfession(id, entry -> entry.matchesKey(heldWorkstation), entry -> entry.matchesKey(heldWorkstation), ImmutableSet.of(), ImmutableSet.of(), workSound)); }
|
当然,这里是默认让村民不会收集物品,也不会有辅助工作站点,如果你想让村民有这些功能,可以自行修改
那么还有一个是工作站点对应方块的注册
1 2 3
| private static PointOfInterestType registerPointOfInterestType(String id, Block block) { return PointOfInterestHelper.register(Identifier.of(TutorialMod.MOD_ID, id), 1, 1, block); }
|
这里的PointOfInterestHelper
是Fabric
提供的API
,我们直接使用它来注册我们的站点对应的方块
不过它的结构和原版的还是有区别的,这里少了RegistryKey<PointOfInterestType>
这个类型的参数
创建ModPointOfInterestTypes类
最后在注册村民职业之前,我们首先还需要注册工作站点
1 2 3 4 5 6
| public class ModPointOfInterestTypes { public static final RegistryKey<PointOfInterestType> ICE_ETHER_KEY = of("ice_ether_poi"); private static RegistryKey<PointOfInterestType> of(String id) { return RegistryKey.of(RegistryKeys.POINT_OF_INTEREST_TYPE, Identifier.of(TutorialMod.MOD_ID, id)); } }
|
这里我们创建一个ModPointOfInterestTypes
类,这个类用来存放我们自定义的工作站点
这里的注册方法也是和原版一样的,不过记得要改命名空间
而后我们注册一个工作站点ICE_ETHER_KEY
,另外对应的方块我们放到ModVillagers
类中注册
最后,也不要忘了初始化的方法
1 2 3
| public static void registerModVillagers() {
}
|
并在模组主类中调用这个方法
1
| ModVillagers.registerModVillagers();
|
注册职业和工作站点对应的方块
接下来我们利用前面的两个方法来注册相关内容
1 2 3 4
| public static final VillagerProfession ICE_ETHER_MASTER = register("ice_ether_master", ModPointOfInterestTypes.ICE_ETHER_KEY, SoundEvents.ENTITY_VILLAGER_WORK_ARMORER);
public static final PointOfInterestType ICE_ETHER_POI = registerPointOfInterestType("ice_ether_poi", ModBlocks.ICE_ETHER_BLOCK);
|
这里我们注册了一个ICE_ETHER_MASTER
职业和ICE_ETHER_POI
工作站点
其中ICE_ETHER_POI
对应的方块是ICE_ETHER_BLOCK
值得一提的是,这里的ICE_ETHER_POI
和前面ModPointOfInterestTypes
类中的ICE_ETHER_KEY
的注册名一定一定是要一致的
,不然无法注册成功(源代码中是直接调用的,也就没有这个问题)
编写交易内容
这里我们新增了一个村民的职业,但是他还没有能够交易的内容,我们还得定义它
1 2 3 4 5 6 7 8
| TradeOfferHelper.registerVillagerOffers(ModVillagers.ICE_ETHER_MASTER, 1, factories -> { factories.add(new TradeOffers.SellItemFactory(ModItems.ICE_ETHER, 2, 9, 12, 2, 0.5f)); factories.add(new TradeOffers.BuyItemFactory(ModItems.RAW_ICE_ETHER, 2, 9, 12, 2)); }); TradeOfferHelper.registerVillagerOffers(ModVillagers.ICE_ETHER_MASTER, 2, factories -> { factories.add(new TradeOffers.SellItemFactory(ModBlocks.ICE_ETHER_BLOCK.asItem(), 4, 16, 12, 4, 0.5f)); factories.add(new TradeOffers.BuyItemFactory(ModBlocks.RAW_ICE_ETHER_BLOCK.asItem(), 4, 16, 12, 4)); });
|
方法是和前一篇教程一样的,这里我们先写两个等级的,简单按照你自己的想法写一下即可
数据文件
语言文件
1
| translationBuilder.add("entity.minecraft.villager.ice_ether_master", "Ice Ether Master");
|
这里我们写一下村民的语言文件,这个是显示在交易的GUI
上的村民的名字
PointTag
我们假设其他文件都搞定了,但是我们进入游戏生成一个村民,即便把ICE_ETHER_BLOCK
放他边上,他也没有转换职业
因为我们还得写一个PointOfInterestTypeTag
这个东西既不是物品的标签ItemTag
,也不是方块的标签BlockTag
,而是独属于工作站(兴趣点)的标签,Minecraft其实还有各种各样的标签,可以好好研究研究
在图文教程的后面,我们还将补充画的相关内容,这个时候也有一个标签得写
那废话不多说,这里来写这个标签的数据生成类
我们创建ModPointTagProvider
,继承TagProvider<PointOfInterestType>
1 2 3 4 5 6 7 8 9 10
| public class ModPointTagProvider extends TagProvider<PointOfInterestType> { public ModPointTagProvider(DataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookupFuture) { super(output, RegistryKeys.POINT_OF_INTEREST_TYPE, registryLookupFuture); }
@Override protected void configure(RegistryWrapper.WrapperLookup lookup) { } }
|
这里的super函数直接改写好了,改成RegistryKeys.POINT_OF_INTEREST_TYPE
,因为我们这里要生成的标签就是它这个类型的
而后我们就可以在configure
里写我们的Tag了
1 2
| getOrCreateTagBuilder(PointOfInterestTypeTags.ACQUIRABLE_JOB_SITE) .addOptional(Identifier.of(TutorialMod.MOD_ID, "ice_ether_poi"));
|
这里我们要加的不是方块,也不是物品,而是Identifier
这个类型的参数
这个名字就是我们前面注册用的名字,一定一定要一致
,否则还是无效的
材质文件
村民属于实体,不过他们的职业对应的材质是他们的第二层贴图,我们将对应的材质放在textures/entity/villager/profession
下,不要搞错位置
我们去看原版的那些材质的时候,其实也可以发现,在profession
下的贴图并不包括整个村民的贴图
那么在此之后,我们就可以进入游戏去测试了