本篇教程的视频:

本篇教程源代码

GitHub地址:TutorialMod-Entity-1.21

介绍

本篇教程我们将为我们的模组添加生物实体,这个实体是一个简单的生物

当然本篇教程我们并不会我们的生物实体添加动画,在后面的教程中我们会讲到

然后本篇及之后的两篇教程都将使用原版的方法来写生物实体及其动画的关键帧系统,并不涉及GeckoLib这个库

想使用GeckoLib的话,可以参考GeckoLib的官方文档

生物实体建模

这里我们还是使用Blockbench来建模,当然,Blockbench也是有GeckoLib的插件的,也就是GeckoLib Animation Utils

但是我们这里不使用GeckoLib,所以我们在新建文件时选择模组版实体,图标是有一个咖啡杯的那个,不要选错了

而后我们还需要安装一些插件,我们要安装Animated Java(这个是用来做动画的)、Fabric Modded Entity(这个是用于导出Java文件时,使用yarn映射的)、Animation to Java Converter(这个将动画导出为原版的关键帧系统的Java文件)这三个插件

建模的事情我就不多说了,不过建议每个肢体的部分都给个分组,这样做动画也好做

导出模型

在导出之前,我们先看一下文件 -> 项目中的导出版本,我们选择Fabric 1.17+,不然导出的映射是有问题的

导出模型时,我们选择导出 -> 导出为Java版实体

之后我们就可以得到一个Java文件

生物实体

创建一些类

我们现在就回到我们的模组中,创建一些类

创建ModEntities类,这个类用于实体的注册

1
2
3
public class ModEntities {

}

创建ModModelLayers类,这个是实体模型的渲染层设置

1
2
3
public class ModModelLayers {

}

创建TigerRenderer类,这个是实体的渲染器

1
2
3
public class TigerRenderer {

}

创建TigerEntity类,这个是我们的实体类,继承AnimalEntity,实现相应的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TigerEntity extends AnimalEntity {
public TigerEntity(EntityType<? extends AnimalEntity> entityType, World world) {
super(entityType, world);
}

@Override
public boolean isBreedingItem(ItemStack stack) {
return false;
}

@Nullable
@Override
public PassiveEntity createChild(ServerWorld world, PassiveEntity entity) {
return null;
}
}

这个实体类是实现实体的一些逻辑方法的,比如各种各样的目标

导入我们刚刚导出的Java文件,这个文件是我们的实体模型类,这里的话是TigerModel

当然,这里还得改一些东西

改写TigerModel类

导入的这个类是有一些问题的,我们需要改写一下

1
2
3
public class TigerModel<T extends TigerEntity> extends SinglePartEntityModel<T> {
...
}

我们让TigerModel继承SinglePartEntityModel,这个是用于单一部位的实体模型

同时TigerModel的泛型参数为TigerEntity,这个是我们的实体类

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
public class TigerModel<T extends TigerEntity> extends SinglePartEntityModel<T> {

private final ModelPart tiger;
private final ModelPart head;

public TigerModel(ModelPart root) {
this.tiger = root.getChild("tiger");
this.head = tiger.getChild("head");
}
public static TexturedModelData getTexturedModelData() {
...
}

@Override
public ModelPart getPart() {
return this.tiger;
}

@Override
public void setAngles(T entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) {

}

@Override
public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay, int color) {
tiger.render(matrices, vertices, light, overlay, color);
}
}

模型的部分上就留下了tiger(这是我的主文件夹,参见视频教程前面侧边栏中的内容)和head(这是头部的文件夹)两个部分,这两个部分是我们的实体的主体和头部

其他的全部删去

构造方法也要改写,因为他们所在的文件位置是不一样的,这里也用不同方法来获取其位置

getTexturedModelData不用管他,那一串就留着就行

下面的getPart方法是获取主体的方法,这里我们返回tiger,也就是我们的主体

setAngles方法是设置实体的角度的,这里我们不做处理,这个是后面用于设置头部动画的

render方法是渲染实体的方法,这里我们只渲染主体(在1.20中,后面的color是按RGB拆开写的,这里就合在一起了)

设置实体渲染层

我们到ModModelLayers类中,设置实体的渲染层

1
2
3
4
public class ModModelLayers {
public static final EntityModelLayer TIGER =
new EntityModelLayer(Identifier.of(TutorialMod.MOD_ID, "tiger"), "main");
}

这里我们设置了一个TIGER的渲染层,这个渲染层的IDtutorialmod:tiger,名字是main(这个只是个名字,取什么都没关系,我们这个模型也就只有一层渲染层)

设置实体渲染器

我们到TigerRenderer类中,设置实体的渲染器

1
2
3
public class TigerRenderer extends MobEntityRenderer<TigerEntity, TigerModel<TigerEntity>> {

}

这个类要继承MobEntityRenderer,泛型参数为我们的实体类和实体模型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TigerRenderer extends MobEntityRenderer<TigerEntity, TigerModel<TigerEntity>> {

public static final Identifier TEXTURE = Identifier.of(TutorialMod.MOD_ID, "textures/entity/tiger.png");

public TigerRenderer(EntityRendererFactory.Context context) {
super(context, new TigerModel<>(context.getPart(ModModelLayers.TIGER)), 0.5f);
}

@Override
public Identifier getTexture(TigerEntity entity) {
return TEXTURE;
}

@Override
public void render(TigerEntity livingEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) {
if (livingEntity.isBaby()) {
matrixStack.scale(0.5F, 0.5F, 0.5F);
} else {
matrixStack.scale(1.0F, 1.0F, 1.0F);
}
super.render(livingEntity, f, g, matrixStack, vertexConsumerProvider, i);
}
}

而后我们编写其中的内容

首先设置一个TEXTURE,这个是我们的实体的材质

构造方法我们也直接改写,去掉原来的TigerModel<TigerEntity> entityModelfloat f,直接在下面的方法中指定我们的实体模型数值大小(这个其实是实体的阴影大小

getTexture方法是获取实体的材质的方法,这里我们返回TEXTURE

render方法是渲染实体的方法,这里我们判断实体是否是baby,如果是baby,那么就缩小0.5倍,否则就是原大小(实际情况是,建模的时候就不大,导致后面的小生物更小了)

设置实体类

接下去我们在TigerEntity中设置实体的一些属性

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
38
39
public class TigerEntity extends AnimalEntity {
public TigerEntity(EntityType<? extends AnimalEntity> entityType, World world) {
super(entityType, world);
}

@Override
public void tick() {
super.tick();
}

@Override
protected void initGoals() {
this.goalSelector.add(0, new SwimGoal(this));
this.goalSelector.add(1, new AnimalMateGoal(this, 1.0D));
this.goalSelector.add(2, new TemptGoal(this, 1.25D, Ingredient.ofItems(Items.BEEF), false));
this.goalSelector.add(3, new FollowParentGoal(this, 1.25D));
this.goalSelector.add(4, new WanderAroundGoal(this, 1.0D));
this.goalSelector.add(5, new LookAtEntityGoal(this, PlayerEntity.class, 3.0f));
this.goalSelector.add(6, new LookAroundGoal(this));
}
public static DefaultAttributeContainer.Builder createTigerAttributes() {
return MobEntity.createMobAttributes()
.add(EntityAttributes.GENERIC_MAX_HEALTH, 200)
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.3f)
.add(EntityAttributes.GENERIC_ARMOR, 0.5f)
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 10);
}

@Override
public boolean isBreedingItem(ItemStack stack) {
return stack.isOf(Items.BEEF);
}

@Nullable
@Override
public PassiveEntity createChild(ServerWorld world, PassiveEntity entity) {
return ModEntities.TIGER.create(world);
}
}

我们加一个tick方法,这个方法是实体的tick方法

initGoals方法是实体的目标,使用goalSelector添加目标,其参数为优先级目标,优先级的数值越小,优先级越高

这里我们添加了SwimGoal(游泳)、AnimalMateGoal(繁殖)、TemptGoal(诱惑)、FollowParentGoal(跟随父母)、WanderAroundGoal(漫游)、LookAtEntityGoal(看着玩家)、LookAroundGoal(看周围)

再下面是createTigerAttributes方法,这个是设置实体的属性,这里我们设置了最大生命值移动速度护甲攻击力

当然,这个方法是要到模组主类进行初始化的

1
FabricDefaultAttributeRegistry.register(ModEntities.TIGER, TigerEntity.createTigerAttributes());

这里我们注册了TIGER实体的属性

isBreedingItem方法是判断物品是否是繁殖食物的方法,这里我们判断物品是否是牛肉

createChild方法是创建实体的方法,这里我们返回一个新的TigerEntity

注册实体

我们到ModEntities类中,注册我们的实体

1
2
3
4
5
public class ModEntities {
public static final EntityType<TigerEntity> TIGER = Registry.register(Registries.ENTITY_TYPE,
Identifier.of(TutorialMod.MOD_ID, "tiger"),
EntityType.Builder.create(TigerEntity::new, SpawnGroup.CREATURE).dimensions(1f,1f).build());
}

这里我们注册了一个TIGER实体,IDtutorialmod:tiger,实体的构造方法为TigerEntity::new,实体的生成组为CREATURE(一般生物),实体的大小为1f,1f(这个是碰撞箱的大小)

客户端注册实体和渲染器

最后,我们还得到客户端类中注册我们的实体和渲染器

1
2
EntityModelLayerRegistry.registerModelLayer(ModModelLayers.TIGER, TigerModel::getTexturedModelData);
EntityRendererRegistry.register(ModEntities.TIGER, TigerRenderer::new);

在此之后,我们将模型的贴图文件放置在对应位置(也就是上面写的那一串),前面就可以启动游戏进行测试了

当然,因为我们还没有写实体的刷怪蛋,所以我们只能通过summon指令来召唤实体