我的世界Java版1.21.4的Fabric模组开发教程(二十三)创建生物(下)实体在游戏中的实现(1)

这是适用于Minecraft Java版1.21.4的Fabric模组开发系列教程专栏第二十三章------创建生物(下)实体在游戏中的实现(1)。如果还未开始制作生物的外观和动画,请参考我的世界Java版1.21.4的Fabric模组开发教程(二十二)创建生物(上)实体外观与动画设计。想要阅读其他内容,请查看或订阅上面的专栏。

在上一章节中,我们完成了测试生物(test_entity) 的外观与动画设计,并导出了实体模型文件、实体动画文件和实体平面展开图。接下来,我们将使用这些文件,将生物真正的加入到游戏中。

即使在前一章中完成了创建实体的准备工作,想要在游戏中添加实体依然非常繁琐。一般,我们按照下面每个步骤需要完成的具体工作为顺序依次推进:

  • 注册实体模型层
    • 创建实体模型层静态常量;
    • 编写实体渲染状态类;
    • 导入实体模型类;
  • 注册实体及其模型渲染器
    • 创建实体注册键静态常量;
    • 编写实体类;
    • 注册实体;
    • 编写实体模型渲染器类;
  • 注册实体属性
    • 定义实体属性;
  • 完成创建实体的其他工作;

为实体添加动画也是必不可少的步骤。尤其是生物行走时,如果没有动画,生物会直接向四面八方平移。要为实体添加动画,请按照以下步骤推进:

  • 导入实体动画类;
  • 在实体模型类中完成动画设置;

简单的说,在游戏中添加实体就是分别注册实体模型层、实体渲染器和实体属性并设置动画的过程,所有代码均需要在客户端模块中完成。

如果已经完成了注册实体模型层和注册实体及其模型渲染器两节内容,请参考我的世界Java版1.21.4的Fabric模组开发教程(二十四)创建生物(下)实体在游戏中的实现(2)。

第一节------注册实体模型层

实体模型层的注册大致分为创建实体模型层静态常量(1.1)、编写实体渲染状态类(1.2)、导入实体模型类(1.3) 以及注册实体模型层(1.4) 四个步骤。

1.1创建实体模型层静态常量

实体模型层的注册是创建生物的第一步。我们可以创建一个实体模型层类,在其中声明实体模型层静态常量,用于为指定生物的模型层注册。


实体模型层记录类EntityModelLayer

EntityModelLayer记录类用于为指定生物创建实体模型层,使其在游戏中正常显示。一个生物可以有一个或多个实体模型层,多个实体模型层可以为生物的不同部位提供渲染空间。

实体模型层记录类中只有一个带参构造方法用于创建其对象,参数列表已经在类头处体现:

java 复制代码
@Environment(EnvType.CLIENT)
public record EntityModelLayer(Identifier id, String name) {
	public String toString() {
		return this.id + "#" + this.name;
	}
}
  • Identifier id:标识符,传递一个Identifier对象;
  • String name:实体模型层名称,可以指定实体的部位,如果模型层只用来创建一个实体,此处应传递"main";

1.在客户端模块中创建entity目录,然后在其中创建实体模型层类ModModelLayers

2.在类中声明名为TEST_ENTITY的实体模型层静态常量,然后调用EntityModelLayer记录类的构造方法对其初始化;

java 复制代码
public static final EntityModelLayer TEST_ENTITY = new EntityModelLayer(Identifier.of(FabricDocsReference.MOD_ID,"test_entity"),"main");

构造方法中传递两个参数,第一个参数传递了Identifier.of()方法返回的标识符对象,其中提供模组Id和实体模型层标识符的路径test_entity,第二个参数传递一个字符串,代表实体模型层的名称main

1.2编写实体渲染状态类

实体渲染状态类是1.21.4版本中新增的用于为渲染器提供当前实体渲染状态的类,也是实体渲染器类中需要继承的类。


生物实体渲染状态类LivingEntityRenderState

LivingEntityRenderState类用于捕获实体渲染前的每一帧供实体渲染器读取。在创建指定实体的渲染状态类时需要继承此类。此版本以前的其他版本不需要用到此API。

类中仅声明诸多简单的变量,代表实体渲染的状态属性。


entity目录中创建TestEntityRenderState类,使其继承LivingEntityRenderState类;

java 复制代码
public class TestEntityRenderState extends LivingEntityRenderState {}

测试生物(test_entity)不需要任何用于存储渲染状态的变量,类体可以为空。不过,创建实体渲染状态类是必要的,因为实体模型类和实体渲染器类中会用到此类。

1.3导入实体模型类

实体模型类是决定实体外观的核心类。一般需要提前通过Blockbench制作实体模型,然后导出为Java文件,稍作修改后在此处使用。


实体模型类EntityModel<T extends EntityRenderState>

EntityModel类用于定义实体的几何形状与动画,存储了实体形状外观与动画的核心数据。其泛型通常为实体渲染状态类。一般,实体模型类需要继承此类以完成实体模型的创建。

继承抽象类EntityModel的类需要实现其中的setAngles()方法:

java 复制代码
public void setAngles(T state) {
	this.resetTransforms();
}

在指定的实体模型类中此方法将被重写,用于定义实体的动画;

纹理模型数据类TexturedModelData

TexturedModelData类是由Blockbench生成的Java代码中使用到的API,用于存储有纹理的实体模型数据供实体模型注册时使用

其构造方法已被私有化,因此,需要调用静态方法of()来创建此类的对象:

java 复制代码
public static TexturedModelData of(ModelData partData, int textureWidth, int textureHeight) {
	return new TexturedModelData(partData, new TextureDimensions(textureWidth, textureHeight));
}

其中需要传递三个参数:

  • ModelData partData:模型数据对象;
  • int textureWidth:纹理宽度;
  • int textureHeight:纹理高度;

可以看到方法中调用了构造方法,其中传递了模型数据对象和TextureDimensions类的构造方法;

纹理尺寸类TextureDimensions

TextureDimensions类用于定义实体模型纹理图像的尺寸 ,可以存储纹理的宽度和高度。在TexturedModelData类的构造方法中调用了此类的构造方法,用于确定纹理图像的尺寸。

模型数据类ModelData和模型组件数据类ModelPartData

ModelData类用于存储实体所有部位的数据 ,属于模型的根容器。ModelPartData类用于存储实体单个部位的数据,包括几何模型和位置等数据。这些API均被Blockbench生成的代码中使用,用于将Blockbench中编辑好的实体模型在游戏中渲染出来。

想要获取ModelData的对象,可以直接调用ModelData类的无参构造方法。想要获取ModelPartData的对象则需要调用ModelData对象的getRoot()方法:

java 复制代码
public ModelPartData getRoot() {
	return this.data;
}

用于获取当前模型数据对象的根容器;

调用ModelPartData对象的addChild()方法,可以在根容器中添加实体各部位的模型数据:

java 复制代码
public ModelPartData addChild(String name, ModelPartBuilder builder, ModelTransform rotationData) {
	ModelPartData modelPartData = new ModelPartData(builder.build(), rotationData);
	return this.addChild(name, modelPartData);
}

一般,这些方法由Blockbench生成,无需手动编写。


1.准备好已经导出的实体模型Java类;

2.在entity目录中创建TestModel类,将代码粘贴到此处。在类头中修改类名为TestModel并使其继承EntityModel<TestEntityRenderState>TestEntityRenderState是之前创建好的实体渲染状态类;

java 复制代码
public class TestModel extends EntityModel<TestEntityRenderState> {

    public TestModel(ModelPart root) {
        super(root);
    }

    public static TexturedModelData getTexturedModelData() {
        ModelData modelData = new ModelData();
        ModelPartData modelPartData = modelData.getRoot();
        modelPartData.addChild("head", ModelPartBuilder.create().uv(0, 0).cuboid(-7.0F, -8.0F, -2.0F, 8.0F, 8.0F, 8.0F, new Dilation(0.0F)), ModelTransform.pivot(3.0F, 0.0F, -2.0F));
        modelPartData.addChild("body", ModelPartBuilder.create().uv(0, 16).cuboid(-7.0F, -2.0F, -2.0F, 8.0F, 12.0F, 4.0F, new Dilation(0.0F)), ModelTransform.pivot(3.0F, 2.0F, 0.0F));
        modelPartData.addChild("right_arm", ModelPartBuilder.create().uv(24, 16).cuboid(-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new Dilation(0.0F)), ModelTransform.pivot(-5.0F, 2.0F, 0.0F));
        modelPartData.addChild("left_arm", ModelPartBuilder.create().uv(0, 32).cuboid(-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new Dilation(0.0F)), ModelTransform.pivot(7.0F, 2.0F, 0.0F));
        modelPartData.addChild("right_leg", ModelPartBuilder.create().uv(16, 32).cuboid(-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new Dilation(0.0F)), ModelTransform.pivot(-1.0F, 14.0F, 0.0F));
        modelPartData.addChild("left_leg", ModelPartBuilder.create().uv(32, 0).cuboid(-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new Dilation(0.0F)), ModelTransform.pivot(3.0F, 14.0F, 0.0F));
        return TexturedModelData.of(modelData, 64, 64);
    }

    @Override
    public void setAngles(TestEntityRenderState state) {}
}

类中只需保留三个方法:

  • 构造方法:其中需要调用其超类的构造方法;
  • getTexturedModelData() :核心方法,用于将模型数据打包为TexturedModelData对象,供注册实体模型层时使用;
  • setAngles():用于为实体添加动画,在下文将展开讲解;

其他内容均可以删除。部分生成的代码可能与当前版本使用的API版本不相符,部分方法的参数列表需手动修改。

1.4注册实体模型层

注册实体模型层的准备工作已经完毕,可以在客户端入口点类中开始注册。


实体模型层注册类EntityModelLayerRegistry

EntityModelLayerRegistry类是专门用于注册实体模型层的工具类 ,其中的静态方法registerModelLayer()是注册实体模型层的唯一方法:

java 复制代码
public static void registerModelLayer(EntityModelLayer modelLayer, TexturedModelDataProvider provider) {
	...
	EntityModelLayersAccessor.getLayers().add(modelLayer);
}

部分方法体已省略,需要传递两个参数:

  • EntityModelLayer modelLayer:实体模型层对象;
  • TexturedModelDataProvider provider:纹理模型数据提供器对象;

纹理模型数据提供器函数式接口TexturedModelDataProvider

TexturedModelDataProvider接口在EntityModelLayerRegistry内部声明,用于接收纹理模型数据对象 。接口被@FunctionalInterface修饰,代表其中方法的返回值可以通过表达式传递;

java 复制代码
@FunctionalInterface
public interface TexturedModelDataProvider {
	TexturedModelData createModelData();
}

其对象出现在EntityModelLayerRegistry类静态方法registerModelLayer()的参数列表中,用于注册实体模型层。


在客户端入口点类的onInitializeClient()方法中直接调用registerModelLayer()方法,完成实体模型层的注册;

java 复制代码
@Override
public void onInitializeClient() {
	EntityModelLayerRegistry.registerModelLayer(ModModelLayers.TEST_ENTITY, TestModel::getTexturedModelData);
}

其中分别传递了实体模型层对象TEST_ENTITY以及TestModel类中的getTexturedModelData()方法的表达式。

第二节------注册实体及其模型渲染器

实体及其模型渲染器的注册大致分为创建实体注册键静态常量(2.1)、编写实体类(2.2)、注册实体(2.3)、编写实体模型渲染器类(2.4) 以及注册实体模型渲染器(2.5) 五个步骤。

2.1创建实体注册键静态常量

我们需要创建实体注册类,在其中声明实体注册键常量供注册实体时使用。


实体类型类EntityType和实体类型构造器内部类EntityType.Builder

EntityType类是Minecraft中所有实体的唯一标识符和类型定义 ,在实体注册过程和实体类中广泛使用,其对象在注册实体和实体类构造方法中频繁出现。EntityType.Builder内部类声明在EntityType类内部,是专门用于构建EntityType对象的内部类,也同样是注册实体过程中必须用到的类之一。

内部类EntityType.Builder的构造方法已被私有化,想创建其对象需要调用静态方法create()

java 复制代码
public static <T extends Entity> EntityType.Builder<T> create(EntityType.EntityFactory<T> factory, SpawnGroup spawnGroup) {
	return new EntityType.Builder<>(factory, spawnGroup);
}

其中传递两个参数:

  • EntityType.EntityFactory<T> factoryEntityType.EntityFactory<T>类型代表需要传递一个能够返回EntityType对象的表达式;
  • SpawnGroup spawnGroup:生成组枚举常量;

完成后,还需要调用其对象的build()方法完成EntityType对象的构造;

java 复制代码
public EntityType<T> build(RegistryKey<EntityType<?>> registryKey) {
	if (this.saveable) {
		Util.getChoiceType(TypeReferences.ENTITY_TREE, registryKey.getValue().toString());
	}
	return new EntityType<>(...);
}

方法支持链式调用,其中传递一个指定生物EntityType类型的注册键对象;

生成组枚举类SpawnGroup

枚举类SpawnGroup用于对实体进行分类,在注册实体时会使用到其中的枚举常量,这些常量包括:

  • MONSTER:怪物;
  • CREATURE:动物;
  • AMBIENT:环境生物;
  • AXOLOTLS:美西螈;
  • UNDERGROUND_WATER_CREATURE:地下水生生物;
  • WATER_CREATURE:水生生物;
  • WATER_AMBIENT:水下环境生物;
  • MISC:其他;

这些常量也用于控制实体在生物群系的生成。


1.在客户端入口点类所在目录直接创建实体注册类ModEntities

2.声明类型为RegistryKey<EntityType<?>>的注册键静态常量;

java 复制代码
public static final RegistryKey<EntityType<?>> TEST_ENTITY_REGISTRY_KEY = RegistryKey.of(RegistryKeys.ENTITY_TYPE, Identifier.of(FabricDocsReference.MOD_ID,"entity_test"));

调用RegistryKey.of()方法,其中分别传递注册键类型为实体类型RegistryKeys.ENTITY_TYPE,以及Identifier.of()方法返回的由模组Id与路径entity_test组成的标识符对象。

关于RegistryKey.of()方法的详细用法请参考我的世界Java版1.21.4的Fabric模组开发教程(五)创建高级物品:盔甲

关于Identifier类的详细用法说明请参考我的世界Java版1.21.4的Fabric模组开发教程(二)创建物品

2.2编写实体类

现在,我们开始为测试生物(test_entity)编写实体类。实体类用于定义实体属性与具体行为,是创建实体过程中的关键类。一般,实体类需要继承已有的实体父类来确定实体的大致类型。


动物实体类AnimalEntity

AnimalEntity类是Minecraft中的动物实体类,也是所有被动/友好动物的基类。游戏中所有带有动物性质的实体都继承了此类,继承此类通常至少要重写以下两个方法:

  • isBreedingItem(ItemStack stack):用于判断给定的物品是否能触发该动物的繁殖行为,一般直接返回false
  • createChild(ServerWorld world, PassiveEntity entity):用于创建此实体的一个幼年实体,一般返回null

1.在entity/custom目录中创建TestEntity类,作为测试生物的实体类;

2.使其继承AnimalEntity类并重写相关方法,使测试生物具有动物实体的性质与行为;

java 复制代码
public class TestEntity extends AnimalEntity {

    public TestEntity(EntityType<? extends AnimalEntity> entityType, World world) {
        super(entityType, world);
    }

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

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

2.3注册实体

现在,我们可以通过实体类将测试生物(test_entity)在游戏注册表中注册,这些代码需要添加到实体注册类中,即ModEntities类;

声明静态常量TEST_ENTITY并调用Register.registry()方法。关于Register.registry()方法的详细用法说明,请参考我的世界Java版1.21.4的Fabric模组开发教程(二)创建物品

java 复制代码
public static final EntityType<TestEntity> TEST_ENTITY = Registry.register(
        Registries.ENTITY_TYPE,
        Identifier.of(FabricDocsReference.MOD_ID,"test_entity"),
        EntityType.Builder.create(TestEntity::new, SpawnGroup.AMBIENT).build(TEST_ENTITY_REGISTRY_KEY));

方法中传递三个参数,第一个是注册实体的固定写法Registries.ENTITY_TYPE,第二个参数传递Identifier.of()方法返回的由模组Id和路径组成的标识符对象,第三个参数中首先调用内部类EntityType.Builder的静态方法create()创建实体类型构造器对象,其中传递实体类构造方法的表达式TestEntity::new以及生成组枚举常量SpawnGroup.AMBIENT,然后调用build()方法完成EntityType对象的构造,其中传递实体注册键对象。

2.4编写实体模型渲染器类

为了使测试生物(test_entity)在世界中正常渲染,还需要编写实体模型渲染器类。


生物实体渲染器类MobEntityRenderer

MobEntityRenderer是Minecraft中所有可移动生物的基础渲染器类,用于提供在游戏中渲染实体所需的标准框架和通用功能。一般创建实体模型渲染器类需要先继承此类。

MobEntityRenderer类的全类名为:

java 复制代码
public abstract class MobEntityRenderer<T extends MobEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>>
	extends LivingEntityRenderer<T, S, M>{...}

其中的泛型包括:

  • T extends MobEntity:实体类,需要继承MobEntity类及其子类;
  • S extends LivingEntityRenderState:实体渲染状态类,需要继承LivingEntityRenderState类;
  • M extends EntityModel<? super S>:实体模型类,需要继承EntityModel<? super S>类,这里的<? super S>代表实体渲染状态类;

其构造方法将在继承此类的实体渲染器类中被调用:

java 复制代码
public MobEntityRenderer(EntityRendererFactory.Context context, M entityModel, float f) {
	super(context, entityModel, f);
}

需要传递三个参数:

  • EntityRendererFactory.Context context:实体渲染器工厂上下文对象;
  • M entityModel:实体模型类对象,需要调用实体模型类的构造方法;
  • float f:实体阴影大小,单位为方块;

实体渲染器工厂上下文内部类EntityRendererFactory.Context

EntityRendererFactory.Context类声明在EntityRendererFactory类内部,属于实体渲染期间的工具类,包含了渲染一个实体所需的所有依赖和资源。其对象在实体渲染器类中构造方法的参数列表中出现。

一般,最常用的方法为getPart(),用于通过模型层获取模型部件;

java 复制代码
public ModelPart getPart(EntityModelLayer layer) {
	return this.entityModels.getModelPart(layer);
}

方法需要传递一个实体模型层对象作为参数。


继承了MobEntityRenderer类的实体渲染器类中至少要添加以下内容:

  • 实体展开图(UV图)的标识符对象 :UV图应当提前导出并存储在服务器模块的textures/entity目录中,用于渲染实体各部件上的贴图;
  • 构造方法:其中调用其超类的构造方法;
  • createRenderState():方法重写自超类,用于返回实体渲染状态对象;
  • getTexture():方法重写自超类,用于获取UV图的标识符对象;

1.创建实体渲染器类TestModelRenderer并使其继承MobEntityRenderer

java 复制代码
public class TestModelRenderer extends MobEntityRenderer<TestEntity, TestEntityRenderState,TestModel> {}

泛型中要依次添加实体类TestEntity、实体渲染状态类TestEntityRenderState以及实体模型类TestModel

2.声明名为TEXTURE的实体展开图(UV图)标识符对象静态常量,类型为Identifier

java 复制代码
private static final Identifier TEXTURE = Identifier.of(FabricDocsReference.MOD_ID,"textures/entity/test.png");

调用Identifier.of()方法对其初始化,其中分别传递模组Id和贴图在项目中的路径,指定贴图的位置;

3.声明构造方法,方法体中调用其父类的构造方法;

java 复制代码
public TestModelRenderer(EntityRendererFactory.Context context) {
    super(context,new TestModel(context.getPart(ModModelLayers.TEST_ENTITY)),0.5f);
}

其中首先传递实体渲染器工厂上下文对象context,然后调用实体模型类的构造方法,来获取实体模型对象,构造方法中调用context.getPart()方法,其中继续传递实体模型层对象来获取模型部件对象,最后设置实体阴影为0.5个方块的大小;

4.重写createRenderState()方法,其中直接返回实体渲染状态对象;

java 复制代码
@Override
public TestEntityRenderState createRenderState() {
    return new TestEntityRenderState();
}

5.重写getTexture()方法,其中返回实体展开图(UV图)标识符对象TEXTURE

java 复制代码
@Override
public Identifier getTexture(TestEntityRenderState state) {
    return TEXTURE;
}

2.5注册实体模型渲染器

最后,还要在客户端入口点类中注册测试生物(test_entity)的实体模型渲染器。


实体渲染器注册类EntityRendererRegistry

EntityRendererRegistry是专门用于注册实体模型渲染器的工具类,是实体创建过程中一定会用到的类。

类中只有一个静态方法register(),用于注册实体模型渲染器:

java 复制代码
public static <E extends Entity> void register(EntityType<? extends E> entityType, EntityRendererFactory<E> entityRendererFactory) {
	EntityRendererRegistryImpl.register(entityType, entityRendererFactory);
}

其中需要传递两个参数:

  • EntityType<? extends E> entityType:实体类型对象;
  • EntityRendererFactory<E> entityRendererFactory:此处需要传递一个返回实体模型渲染器对象的表达式;

在客户端入口点类的onInitializeClient()方法中直接注册实体模型渲染器;

java 复制代码
EntityRendererRegistry.register(ModEntities.TEST_ENTITY, TestModelRenderer::new);

调用EntityRendererRegistry类的静态方法register()完成实体模型渲染器的注册,其中分别传递实体类型对象ModEntities.TEST_ENTITY以及实体模型渲染器类构造方法的表达式TestModelRenderer::new

本章小结

本章详细阐述了注册实体第一节和第二节的所有工作,完成了实体模型层、实体渲染器以及实体本身的注册。由于篇幅原因,第三节和第四节的内容将在下一章展开。本章内容难度较大且操作繁琐,需要开发者有一定的耐心。在下一章,我们将定义实体属性,完成创建生物的最后一步,然后启动游戏开始测试生物在游戏中的表现,并进行生物创建完毕的善后工作。感谢各位的阅读,有兴趣可以订阅此专栏!

相关推荐
温柔一只鬼.2 小时前
GUI学习——day2
java·开发语言·学习
东离与糖宝2 小时前
Spring Boot 3 + Qwen 3.5 最佳实践:从接口调用到 RAG 向量检索一站式开发
java·人工智能
零雲2 小时前
java面试:Spring是如何解决循环依赖问题的
java·spring·面试
云边散步2 小时前
godot2D游戏教程系列二(10)
笔记·学习·游戏·游戏开发
饕餮争锋3 小时前
Java泛型介绍
java·开发语言
程序媛徐师姐3 小时前
Java基于SSM的即时空教室查询小程序,附源码+文档说明
java·微信小程序·小程序·ssm·即时空教室查询小程序·java即时空教室查询小程序·即时空教室查询微信小程序
努力长头发的程序猿3 小时前
在Unity当中使用GameFrameworkX框架的知识点
java·unity·游戏引擎
季明洵3 小时前
二叉树的最小深度、完全二叉树的节点个数、平衡二叉树、路径总和、从中序与后序遍历序列构造二叉树
java·数据结构·算法·leetcode·二叉树
AD钙奶-lalala3 小时前
SpringBoot 4.0.3配置Swagger
java·spring boot·后端