创建一个实体

实体(Entity)是游戏世界里的一种可以根据附加的逻辑移动的物体,包括:

  • 矿车

生物实体(Living Entity)是拥有生命值,并且可以造成伤害的实体。 为了实现不同的功能,生物实体有着不同的分支类型,其中有:

  • HostileEntity 敌对实体,用于僵尸、苦力怕、骷髅等
  • AnimalEntity 动物实体,用于羊、牛、猪等
  • WaterCreatureEntity 水生物实体,用于可以游泳的实体
  • FishEntity 鱼实体,用于鱼(使用 WaterCreatureEntity 代替 schooling 行为)

具体继承、使用哪个取决于你的需求和目的。 随着开发进度的推进,对于特定的任务,实体的逻辑变得越来越具体、简明。有以下两种继承自 LivingEntity 的实体:

在这篇教程中,我们将研究创建一个继承自 PathAwareEntity的方块实体. 这个实体将拥有模型和贴图。移动和物理将会在以后的教程中介绍。

创建并注册一个实体

创建一个继承自 PathAwareEntity 的类,这个类是我们创建的实体的大脑和主体

/*
 * 我们的方块实体继承自 PathAwareEntity, 它继承自 MobEntity, 而 MobEntity 继承自 LivingEntity。
 *
 * LivingEntity 拥有生命值,并且可以造成伤害。
 * MobEntity 具有AI逻辑和移动控制。
 * PathAwareEntity 提供额外的寻路系统和轻微调整拴绳行为.
 */
public class CubeEntity extends PathAwareEntity {
 
    public CubeEntity(EntityType<? extends PathAwareEntity> entityType, World world) {
        super(entityType, world);
    }
}

你可以在 ENTITY_TYPE 注册类别下注册这个实体。 Fabric提供一个 FabricEntityTypeBuilder 类型,它继承自原版 EntityType.Builder。Fabric提供的Builder类内置的额外方法用于配置实体的 追踪数值(tracking values) 。

public class EntityTesting implements ModInitializer {
 
    /*
     * 使用 "entitytesting:cube" 作为ID注册我们的实体。
     *
     * 这个实体注册在了 SpawnGroup#CREATURE 类别下,大多数的动物和友好或中立的生物都注册在这个类别下。
     * 它有一个 .75 * .75 (或12个像素宽,即一个方块的3/4)大小的碰撞体积。
     */
    public static final EntityType<CubeEntity> CUBE = Registry.register(
            Registry.ENTITY_TYPE,
            new Identifier("entitytesting", "cube"),
            FabricEntityTypeBuilder.create(SpawnGroup.CREATURE, CubeEntity::new).dimensions(EntityDimensions.fixed(0.75f, 0.75f)).build()
    );
 
    @Override
    public void onInitialize() {
 
    }
}

实体需要 属性(Attributes) 和 渲染器(Renderer) 才能起作用。

注册实体的属性

属性定义了一个生物拥有的基本数值:它有多少生命值?它能造成多少伤害?它有默认的装备栏吗?

大部分的原版实体都有一个静态方法用于返回它们的属性(比如说 ZombieEntity#createZombieAttributes )。 我们的自制实体没有任何独立的数值,目前,我们可以直接使用 MobEntity#createMobAttributes

原版里有一个 DefaultAttributeRegistry 类用于注册这些属性。 想要使用它并不容易,所以Fabric提供一个 FabricDefaultAttributeRegistry 类。 你应该在你的Mod的初始化部分的某处注册默认的属性:

public class EntityTesting implements ModInitializer {
 
    public static final EntityType<CubeEntity> CUBE = [...];
 
    @Override
    public void onInitialize() {
        /*
         * 注册我们方块实体的默认属性。
         * 属性是一个生物当前状态的数值,其中有攻击伤害和生命值等。
         * 如果实体没有及时注册适当的属性,则游戏将崩溃。
         *
         * 在1.15中,它是通过重写实体类内部的方法来完成的。
         * 大部分的原版实体都有一个静态方法(例如,ZombieEntity#createZombieAttributes)用于初始化它们的属性。
         */
        FabricDefaultAttributeRegistry.register(CUBE, CubeEntity.createMobAttributes());
    }
}

注册实体的渲染器

最后一个需要注册的是实体的渲染器。渲染器一般通过提供模型来决定实体的 外观。 MobEntityRenderer 是生物实体最好的选择。继承这个类需要重写一个用于提供贴图的方法,和三个用于父类构造的参数:

下面的代码展示了一个简单的实体渲染器,它的阴影大小是0.5f, 贴图的路径为 resources/assets/entitytesting/textures/entity/cube/cube.png. 注意:用到的贴图和模型将在下一步创建。

/*
 * 一个用来提供模型、阴影大小和贴图的渲染器
 */
public class CubeEntityRenderer extends MobEntityRenderer<CubeEntity, CubeEntityModel> {
 
    public CubeEntityRenderer(EntityRendererFactory.Context context) {
        super(context, new CubeEntityModel(context.getPart(EntityTestingClient.MODEL_CUBE_LAYER)), 0.5f);
    }
 
    @Override
    public Identifier getTexture(CubeEntity entity) {
        return new Identifier("entitytesting", "textures/entity/cube/cube.png");
    }
}

客户端初始化类中使用 EntityRendererRegistry 来注册这个渲染器:

@Environment(EnvType.CLIENT)
public class EntityTestingClient implements ClientModInitializer {
    public static final EntityModelLayer MODEL_CUBE_LAYER = new EntityModelLayer(new Identifier("entitytesting", "cube"), "main");
    @Override
    public void onInitializeClient() {
        /*
         * 方块实体渲染器的注册,提供模型、阴影大小和贴图的渲染器。
         *
         * 实体渲染器也可以在实体基于上下文进行渲染前(EndermanEntityRenderer#render) 操作模型。
         */
        EntityRendererRegistry.INSTANCE.register(EntityTesting.CUBE, (context) -> {
            return new CubeEntityRenderer(context);
        });
        // 在 1.17, 使用 EntityRendererRegistry.register (见下文) 而不是EntityRendererRegistry.INSTANCE.register (见上文)
        EntityRendererRegistry.register(EntityTesting.CUBE, (context) -> {
            return new CubeEntityRenderer(context);
        });
 
        EntityModelLayerRegistry.registerModelLayer(MODEL_CUBE_LAYER, CubeEntityModel::getTexturedModelData);
    }
}

创建模型和材质

完成实体创建的最后一步是创建模型和贴图。 模型定义了实体的 结构 ,而贴图提供了实体的颜色。

标准的模型在类的顶部提供并在构造方法中初始化“部位(parts)”, 或 ModelPart 对象, 然后在 render 方法中渲染它们。 注意 setAngles 和 render 是 EntityModel 类的抽象方法,必须重写。

public class CubeEntityModel extends EntityModel<CubeEntity> {
 
    private final ModelPart base;
 
    public CubeEntityModel(ModelPart modelPart) {
        this.base = modelPart.getChild(EntityModelPartNames.CUBE);
    }
 
    [...]
}

在创建一个部位之后,我们需要为他添加一个形状(shape)。 我们必须在root添加一个子节点。新部位的贴图位置位于 .uv,它的偏移值位于 .cuboid的前三个数字中, 它的尺寸(size)位于 .cuboid的后三个数字中。注意,模型的原点从拐角处开始,所以你需要一些偏移让它居中:

public class CubeEntityModel extends EntityModel<CubeEntity> {
 
    private final ModelPart base;
 
    public CubeEntityModel() {
        [...]
    }
 
    public static TexturedModelData getTexturedModelData() {
        ModelData modelData = new ModelData();
        ModelPartData modelPartData = modelData.getRoot();
        modelPartData.addChild(EntityModelPartNames.CUBE, ModelPartBuilder.create().uv(0, 0).cuboid(-6F, 12F, -6F, 12F, 12F, 12F), ModelTransform.pivot(0F, 0F, 0F));
    }

我们的实体模型现在有了一个12x12x12的方块(大约一个方块的75%),并以0, 0, 0为中心。 setAngles 用于模型的动画,但是目前我们留空。 render 用来告诉游戏我们的实体出现在哪。注意标准的原版模型通常看起来比它们的碰撞体积更大,因此,我们在这里把模型变小。

public class CubeEntityModel extends EntityModel<CubeEntity> {
 
    private final ModelPart base;
 
    public CubeEntityModel() [...]
 
    public static TexturedModelData getTexturedModelData() [...]
 
    @Override
    public void setAngles(CubeEntity entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) {
    }
 
    @Override
    public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay, float red, float green, float blue, float alpha) {
        ImmutableList.of(this.base).forEach((modelRenderer) -> {
            modelRenderer.render(matrices, vertices, light, overlay, red, green, blue, alpha);
        });
    }
}

我们还需要添加一个贴图文件来完成它。默认的贴图大小是64宽、32高的; 你可以通过改变 texturedModelData 里的 返回值(return) 来改变它。 我们使用64×64的贴图:

public class CubeEntityModel extends EntityModel<CubeEntity> {
 
    private final ModelPart base;
 
    [...]
 
    public static TexturedModelData getTexturedModelData() {
        [...]
        return TexturedModelData.of(modelData, 64, 64);
    }
 
    [...]
} 

生成你的实体

确保把你的 client 入口加进 fabric.mod.json。你可以像这样做:

  "entrypoints": {
    "main": [
      "mod.fabricmc.entitytesting.EntityTesting"
    ],
    "client": [
      "mod.fabricmc.entitytesting.EntityTestingClient"
    ]
  },

你可以在游戏内使用 /summon entitytesting:cube 来生成你的实体,按下F3+b可以查看它的碰撞体积:

如果你的实体不是继承自 LivingEntity,你必须创建一个自己的生成包处理器。要么用网络API, 或者使用 mixin 的 ClientPlayNetworkHandler#onEntitySpawn

添加任务 & 活动(activities)

相关文章:
添加活动: https://fabricmc.net/wiki/villager_activities

Last modification:August 18, 2022
如果喜欢,打赏几分钱呗:)