本篇教程的视频

(待发布)

本篇教程的源代码

(待发布)

本篇教程目标

为模组添加依赖于GeckoLib的方块和物品

介绍

GeckoLib,也就是壁虎库,我们熟知的YSM模组也是用了GeckoLib作为核心开发的

简单来说,GeckoLib是一个用于创建模型和动画的库,可以让开发者在Java版中使用基岩版的模型和动画,其仓库参见:GeckoLib

在我们需要制作一些高精度模型或者复杂动画时,GeckoLib是一个不错的选择

我这里所说的高精度模型,也就是没有那22.5度旋转限制和单轴旋转限制的模型;不过呢,这只是对于我们这个系列的1.20.1而言的

这里我多说几句,在后面的版本中,从1.21.6开始,原版的模型不再受22.5度的旋转限制了;
1.21.11开始,也不再受单轴旋转限制了,所以在高版本中,原版的模型就可以支持较高精度了(所以一步到位,可以直接去尝试26.1的开发)

那么这篇教程的话,也是作为我们方块实体系列的前置,因为GeckoLib的方块模型加载方式,其实就是用方块实体来实现的

当然这篇教程只是让我们的模组加载GeckoLib的模型,方块实体的那些真正的处理逻辑我们到后面再说

添加依赖

那么GecokoLib作为我们模组的依赖,首先我们得添加它

build.gradle中添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
repositories {
maven {
name = 'GeckoLib'
url 'https://dl.cloudsmith.io/public/geckolib3/geckolib/maven/'
content {
includeGroupByRegex("software\\.bernie.*")
includeGroup("com.eliotlash.mclib")
}
}
}

dependencies {
modImplementation("software.bernie.geckolib:geckolib-fabric-${minecraft_version}:${geckolib_version}")
implementation("com.eliotlash.mclib:mclib:20")
}

这里的geckolib_version需要我们到gradle.properties中添加版本信息,版本可以在模组站上找到,可以找一个下载量高的版本(因为用的人多)

1
geckolib_version=4.7.1.2

而后就是重载Gradle项目,另外再运行一下genSources

关于建模和动画

安装插件

GeckoLib相关的模型和动画自然还是用我们的老朋友Blockbench来完成,但需要安装插件GeckoLib Models & Animations

新建项目

然后在我们的开始界面就会有一个GeckoLib Animated Model选项,点击创建新模型

而后会弹出一个小窗口,Model Type可以直接选择Item,因为会多一个显示调整标签页,方便调整它在物品栏、手持视角的显示,这个类型不会影响我们最终的模型类型

至于实体,我们以后有机会再讲吧,现阶段的教程没有生物实体的教程计划

导出模型

在我们做好模型后,下一步就是导出了,导出的内容包括模型文件本身、材质文件、显示文件、动画文件(如果有)

  • 文件 -> 导出 -> Export GeckoLib Model,这是导出模型文件,我们会得到一个.geo.json文件;
  • 文件 -> 导出 -> Export GeckoLib Display Settings,这是导出显示文件,也就是我们在显示调整中调整好的显示信息,我们会得到一个.json文件;
  • 文件 -> 导出 -> Export GeckoLib Animations,这是导出动画文件,我们会得到一个.animation.json文件

贴图也记得保存

另外,强烈建议将项目本身(.bbmodel)也进行保存,因为导出的.geo.json文件虽然可以用Blockbench打开,但会缺失一些东西,不方便我们再编辑

导出这些文件之后,将这些文件放置到我们项目assets目录下的相应位置,具体位置如下:

  • assets/<modid>/geo下放置.geo.json文件
  • assets/<modid>/textures/block(如果是物品则放item)下放置.png贴图
  • assets/<modid>/animations下放置.animation.json文件
  • assets/<modid>/models/block(如果是物品则放item)下放置.json文件

鉴于本教程的视频教程尚未推出,这里先放一个临时仓库,大家如果需要文件,可自取:
Tutorial Mod Re Template 1.20.1-fab

编写代码

那么接下来我们就开始写代码,鉴于方块物品的流程相似,我们这里只跑一次添加方块的流程

总的来说,我们的整个流程是这样的:

  1. 创建方块类(Block);
  2. 注册方块(ModBlocks);
  3. 创建方块实体类(BlockEntity);
  4. 注册方块实体(ModBlockEntities);
  5. 创建物品类(Item);
  6. 注册物品(ModItems);
  7. 创建方块模型 & 物品模型类(Model);
  8. 创建方块渲染器 & 物品渲染器类(Renderer),并完善物品类;
  9. 客户端类方块渲染器注册(ClientModInitializer);
  10. 其他数据文件(DataGen);

方块的整体流程就这样,内容比较多,但也不是很复杂

如果是物品的话,其实从第5步开始就好了,和方块的流程一样

主类初始化

不过在正式开始之前,我们需要向主类添加一行代码

1
GeckoLib.initialize();

没有它的话,GeckoLib相关的内容就无法完成初始化,我们的模型会无法正确加载

创建方块类

这里我们新建一个PortableOriginiumRigBlock,继承自BlockWithEntity

1
2
3
4
5
6
7
8
9
10
public class PortableOriginiumRigBlock extends BlockWithEntity {
public PortableOriginiumRigBlock(Settings settings) {
super(settings);
}

@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return null;
}
}

创建构造函数,并重写createBlockEntity方法

显然,这里的便携源石矿机是从终末地工业模组中拿过来的,后续我们将在它的基础上进行我们的方块实体系列

等我们写了方块实体之后,再来完善createBlockEntity方法的返回值

注册方块

接下来到ModBlocks中添加方块

1
2
3
4
5
6
7
public static final Block PORTABLE_ORIGINIUM_RIG = registerBlocksWithoutItem("portable_originium_rig",
new PortableOriginiumRigBlock(AbstractBlock.Settings.create().strength(0.5f).nonOpaque()));


private static Block registerBlocksWithoutItem(String name, Block block) {
return Registry.register(Registries.BLOCK, new Identifier(TutorialModRe.MOD_ID, name), block);
}

这里我们再加一个方法,因为在这里我们不需要注册物品,物品等会单独去注册

然后使用registerBlocksWithoutItem方法注册方块,里面内容跟之前一样,这里我就不再赘述

创建方块实体类

接下来我们创建方块实体类PortableOriginiumRigBlockEntity,继承BlockEnity,实现GeoBlockEntity接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PortableOriginiumRigBlockEntity extends BlockEntity implements GeoBlockEntity {
public PortableOriginiumRigBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}

@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {

}

@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return null;
}
}

创建构造函数,并重写相关方法

然后我们来创建一些东西

1
2
3
4
5
6
7
8
9
10
11
12
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);

@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "controller", 0,
state -> state.setAndContinue(RawAnimation.begin().thenLoop("working"))));
}

@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return cache;
}

AnimatableInstanceCacheGeckoLib提供的一个缓存类,用于缓存动画实例(应该是为了降低性能损耗的),这里我们使用GeckoLibUtil.createInstanceCache(this)创建一个缓存实例,并返回

registerControllers方法中,我们创建了一个AnimationController,并添加了一个AnimationController,用于控制动画播放,这里指向working动画(在本篇教程提供的动画文件中,其实还有一个idle动画,我们留到后面方块实体的教程再说吧)

注册方块实体

我们到ModBlockEntities类中,添加以下内容(这个类我们之前写过了,这里就不再赘述了)

1
2
public static final BlockEntityType<PortableOriginiumRigBlockEntity> PORTABLE_ORIGINIUM_RIG = create("portable_originium_rig",
BlockEntityType.Builder.create(PortableOriginiumRigBlockEntity::new, ModBlocks.PORTABLE_ORIGINIUM_RIG));

当然现在还会报错,我们还需要改写一些方块实体类的构造函数

1
2
3
public PortableOriginiumRigBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.PORTABLE_ORIGINIUM_RIG, pos, state);
}

这样就好了

另外,我们还需要修改方块类中的createBlockEntity方法

1
2
3
4
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new PortableOriginiumRigBlockEntity(pos, state);
}

创建物品类

这里我们新建一个PortableOriginiumRigItem,继承BlockItem,并实现GeoItem接口

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
public class PortableOriginiumRigItem extends BlockItem implements GeoItem {
public PortableOriginiumRigItem(Block block, Settings settings) {
super(block, settings);
}

@Override
public void createRenderer(Consumer<Object> consumer) {

}

@Override
public Supplier<Object> getRenderProvider() {
return null;
}

@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {

}

@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return null;
}
}

创建构造函数,并重写相关方法

这里我们需要向构造函数中添加一行语句

1
2
3
4
public PortableOriginiumRigItem(Block block, Settings settings) {
super(block, settings);
SingletonGeoAnimatable.registerSyncedAnimatable(this);
}

这里我们调用了SingletonGeoAnimatable.registerSyncedAnimatable(this)方法,用于注册同步动画

然后创建cacherenderProvider,并重写相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private final AnimatableInstanceCache cache = new SingletonAnimatableInstanceCache(this);

private final Supplier<Object> renderProvider = GeoItem.makeRenderer(this);

@Override
public Supplier<Object> getRenderProvider() {
return renderProvider;
}

@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "controller", 0,
state -> state.setAndContinue(RawAnimation.begin().thenLoop("working"))));
}

@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return cache;
}

createRenderer方法等我们写完渲染器类再来完善

注册物品

我们在ModItems类中添加以下内容

1
2
public static final Item PORTABLE_ORIGINIUM_RIG_ITEM = registerItems("portable_originium_rig",
new PortableOriginiumRigItem(ModBlocks.PORTABLE_ORIGINIUM_RIG, new Item.Settings()));

另外不要忘记将其添加到物品栏

1
entries.add(ModItems.PORTABLE_ORIGINIUM_RIG_ITEM);

创建方块模型 & 物品模型类

接下来我们要创建两个类,一个是PortableOriginiumModel,一个是PortableOriginiumItemModel,都继承GeoModel,但泛型不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PortableOriginiumModel extends GeoModel<PortableOriginiumRigBlockEntity> {
@Override
public Identifier getModelResource(PortableOriginiumRigBlockEntity animatable) {
return new Identifier(TutorialModRe.MOD_ID, "geo/portable_originium_rig.geo.json");
}

@Override
public Identifier getTextureResource(PortableOriginiumRigBlockEntity animatable) {
return new Identifier(TutorialModRe.MOD_ID, "textures/block/portable_originium_rig.png");
}

@Override
public Identifier getAnimationResource(PortableOriginiumRigBlockEntity animatable) {
return new Identifier(TutorialModRe.MOD_ID, "animations/portable_originium_rig.animation.json");
}
}

重写getModelResourcegetTextureResourcegetAnimationResource方法,其实就是告诉程序模型、纹理和动画文件的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PortableOriginiumItemModel extends GeoModel<PortableOriginiumRigItem> {
@Override
public Identifier getModelResource(PortableOriginiumRigItem animatable) {
return new Identifier(TutorialModRe.MOD_ID, "geo/portable_originium_rig.geo.json");
}

@Override
public Identifier getTextureResource(PortableOriginiumRigItem animatable) {
return new Identifier(TutorialModRe.MOD_ID, "textures/block/portable_originium_rig.png");
}

@Override
public Identifier getAnimationResource(PortableOriginiumRigItem animatable) {
return new Identifier(TutorialModRe.MOD_ID, "animations/portable_originium_rig.animation.json");
}
}

物品模型类也一样,泛型是PortableOriginiumRigItem

创建方块 & 物品渲染器类

有了模型类之后,渲染器就可以渲染了,接下来我们先写方块的渲染器类,创建一个PortableOriginiumRigRenderer类,继承GeoBlockRenderer,泛型是PortableOriginiumRigBlockEntity

1
2
3
4
5
public class PortableOriginiumRigRenderer extends GeoBlockRenderer<PortableOriginiumRigBlockEntity> {
public PortableOriginiumRigRenderer(BlockEntityRendererFactory.Context context) {
super(new PortableOriginiumModel());
}
}

构造函数中传入模型类,记得将构造函数的参数改为BlockEntityRendererFactory.Context context

方块的渲染器类还需要在客户端类中进行注册

1
BlockEntityRendererFactories.register(ModBlockEntities.PORTABLE_ORIGINIUM_RIG, PortableOriginiumRigRenderer::new);

接下来我们写物品的渲染器类,创建一个PortableOriginiumRigItemRenderer类,继承GeoItemRenderer,泛型是PortableOriginiumRigItem

1
2
3
4
5
public class PortableOriginiumRigItemRenderer extends GeoItemRenderer<PortableOriginiumRigItem> {
public PortableOriginiumRigItemRenderer() {
super(new PortableOriginiumItemModel());
}
}

构造函数不需要参数,然后传入模型类

然后,在物品类的createRenderer方法我们就可以写了

1
2
3
4
5
6
7
8
9
10
@Override
public void createRenderer(Consumer<Object> consumer) {
consumer.accept(new RenderProvider() {
private final PortableOriginiumRigItemRenderer renderer = new PortableOriginiumRigItemRenderer();
@Override
public BuiltinModelItemRenderer getCustomRenderer() {
return renderer;
}
});
}

这里我们传入了一个RenderProvider,然后创建了一个PortableOriginiumRigItemRenderer对象,并返回给BuiltinModelItemRenderer

那么到这里为止,代码部分就这样结束了,接下来还有一些数据文件

这整套流程确实有点多,稍微有点复杂,不过习惯就好了

数据文件

那么数据文件我们还是交给数据生成去跑

语言文件

1
translationBuilder.add(ModItems.PORTABLE_ORIGINIUM_RIG_ITEM, "Portable Originium Rig");

战利品列表

1
addDrop(ModBlocks.PORTABLE_ORIGINIUM_RIG);

模型文件

1
blockStateModelGenerator.registerSimpleState(ModBlocks.PORTABLE_ORIGINIUM_RIG);

在此之后,我们就可以跑数据生成了,然后运行游戏进行测试