我的世界1.20.1forge模组开发(5)——自定义火球,HUD绘制

这期再来讲讲上一期没讲完的东西,怎么新建一个火球,这个火球和原版的火球不一样,他不会破坏方块,被点燃的实体不会停止燃烧,这就需要新建一个火球类继承原版的火球,冷却时间在下方显示,要绘制HUD然后动态显示冷却时间。

一、实体的定义 

在我的世界中,实体(entity)是游戏中可以互动的虚拟物体,如生物、怪物、玩家角色等。实体可以移动、进行攻击、被攻击、进行交互等操作。与实体不同,投掷物(projectile)是一种特殊的实体,通常是由玩家或某些生物投掷出去的物体,如箭矢、火球等。

在我的世界中,实体分为多种类型,包括但不限于:

  1. 生物类实体:如动物、怪物以及玩家本身都属于生物类实体,它们可以移动、进行攻击等操作。

  2. 物品实体:如掉落的物品、放置的物品等,可以被玩家拾取或进行交互。

  3. 方块实体:如推动的方块、活塞推出的方块等,也属于实体的一种。

总的来说,在我的世界中,实体是游戏中重要的元素之一,玩家与实体的互动和操作是游戏的核心内容之一。

二、新建实体

在 Minecraft 1.20.1 Forge 模组开发中,创建一个新的实体涉及多个步骤。

首先需要定义一个继承自 `Entity` 类的新实体类。

public class ExampleEntity extends Entity {
    private static final EntityDataAccessor<Integer> DATA_EXAMPLE_VALUE = SynchedEntityData.defineId(ExampleEntity.class, EntityDataSerializers.INT);

    public ExampleEntity(EntityType<?> type, Level world) {
        super(type, world);
    }

    @Override
    protected void defineSynchedData() {
        this.entityData.define(DATA_EXAMPLE_VALUE, 0);
    }

    @Override
    protected void readAdditionalSaveData(CompoundTag compoundTag) {
        this.entityData.set(DATA_EXAMPLE_VALUE, compoundTag.getInt("ExampleValue"));
    }

    @Override
    protected void addAdditionalSaveData(CompoundTag compoundTag) {
        compoundTag.putInt("ExampleValue", this.entityData.get(DATA_EXAMPLE_VALUE));
    }
}

代码功能总结:

这段代码定义了一个名为ExampleEntity的类,这个类继承自Minecraft中的Entity类。它主要用于在Minecraft中创建一个自定义实体,并实现了同步数据和保存/加载额外数据的功能。下面是对代码的详细分解:

  1. 类定义和继承

    public class ExampleEntity extends Entity {
    
    • ExampleEntity类继承了Entity类,意味着它是一个实体对象,可以存在于Minecraft的世界中。
    • public关键字表示这个类可以被其他类访问。
  2. 定义同步数据

    private static final EntityDataAccessor<Integer> DATA_EXAMPLE_VALUE = SynchedEntityData.defineId(ExampleEntity.class, EntityDataSerializers.INT);
    
    • EntityDataAccessor<Integer>是一个泛型接口,用于访问实体的同步数据。
    • DATA_EXAMPLE_VALUE是一个静态常量,用于存储这个实体的一个整型同步变量。
    • SynchedEntityData.defineId方法用于为实体定义一个唯一标识符,这个标识符用于在网络中同步数据。
    • EntityDataSerializers.INT指定这个同步变量的数据类型为整型。
  3. 构造方法

    public ExampleEntity(EntityType<?> type, Level world) {
        super(type, world);
    }
    
    • 构造方法用于创建ExampleEntity对象。
    • EntityType<?> type表示该实体的类型,Level world表示该实体所在的Minecraft世界。
    • super(type, world)调用了父类Entity的构造方法,初始化了实体的基本属性。
  4. 定义同步数据值

    @Override
    protected void defineSynchedData() {
        this.entityData.define(DATA_EXAMPLE_VALUE, 0);
    }
    
    • 重写了defineSynchedData()方法,用于定义实体的同步数据。
    • this.entityData.define(DATA_EXAMPLE_VALUE, 0)使用之前定义的DATA_EXAMPLE_VALUE标识符,将一个初始值为0的整型数据添加到entityData中。entityData是一个SynchedEntityData对象,用于管理实体的同步数据。
  5. 读取附加保存数据

    @Override
    protected void readAdditionalSaveData(CompoundTag compoundTag) {
        this.entityData.set(DATA_EXAMPLE_VALUE, compoundTag.getInt("ExampleValue"));
    }
    
    • 重写了readAdditionalSaveData()方法,用于从CompoundTag对象中读取实体的额外保存数据。
    • CompoundTag是一种数据结构,可以存储多种类型的数据,这里用于存储实体的状态信息。
    • compoundTag.getInt("ExampleValue")CompoundTag中读取名为ExampleValue的整数值,并将其设置到实体的同步数据中。
  6. 添加附加保存数据

    @Override
    protected void addAdditionalSaveData(CompoundTag compoundTag) {
        compoundTag.putInt("ExampleValue", this.entityData.get(DATA_EXAMPLE_VALUE));
    }
    
    • 重写了addAdditionalSaveData()方法,用于将实体的额外数据保存到CompoundTag对象中。
    • this.entityData.get(DATA_EXAMPLE_VALUE)从实体的同步数据中获取DATA_EXAMPLE_VALUE的值。
    • compoundTag.putInt("ExampleValue", ...)将获取到的值保存到CompoundTag对象中,键名为ExampleValue

总结

这段代码的主要功能是定义一个自定义的Minecraft实体ExampleEntity,并实现了一个整型同步变量DATA_EXAMPLE_VALUE的同步和保存/加载功能。同步功能使得客户端和服务器上的实体数据保持一致,而保存/加载功能允许实体的状态在游戏存档中持久化。

注册实体到事件总线中

    private static final DeferredRegister<EntityType<?>> ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, "example_mod");

    public static final RegistryObject<EntityType<ExampleEntity>> EXAMPLE_ENTITY = ENTITY_TYPES.register("example_entity", () ->
            EntityType.Builder.<ExampleEntity>of(ExampleEntity::new, MobCategory.MISC)
                    .sized(0.6F, 1.8F).clientTrackingRange(8).updateInterval(3).build(new ResourceLocation("example_mod:example_entity").toString()));

    public static void init() {
        IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
        ENTITY_TYPES.register(modEventBus);
    }

这段代码是Minecraft Forge模组开发中用于注册自定义实体类型的一部分。下面是对这段代码的逐步分解和详细解释:

  1. 实体类型注册器的创建:

    private static final DeferredRegister<EntityType<?>> ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, "example_mod");
    

    这行代码创建了一个DeferredRegister对象,专门用于注册实体类型。DeferredRegister是Forge提供的一个类,允许在游戏生命周期的适当阶段(如数据加载阶段)注册内容,而不是在构造函数中立即注册。ForgeRegistries.ENTITY_TYPES表示要注册的是实体类型,而"example_mod"是模组的ID。

  2. 注册自定义实体类型:

    public static final RegistryObject<EntityType<ExampleEntity>> EXAMPLE_ENTITY = ENTITY_TYPES.register("example_entity", () ->
            EntityType.Builder.<ExampleEntity>of(ExampleEntity::new, MobCategory.MISC)
                    .sized(0.6F, 1.8F).clientTrackingRange(8).updateInterval(3).build(new ResourceLocation("example_mod:example_entity").toString()));
    

    这段代码通过DeferredRegisterregister方法注册了一个自定义实体类型ExampleEntityRegistryObject<EntityType<ExampleEntity>>是一个引用对象,它将在实体类型实际注册后引用该实体类型。EntityType.Builder用于构建实体类型实例,其中ExampleEntity::new是一个构造函数引用,表示如何实例化实体。MobCategory.MISC表示该实体的分类,这里使用的是“杂项”分类。sized(0.6F, 1.8F)设置了实体的大小,宽度为0.6,高度为1.8。clientTrackingRange(8)定义了客户端跟踪此实体的最大距离为8个方块。updateInterval(3)定义了客户端和服务器之间同步实体状态的频率,这里每3个游戏刻同步一次。最后,build(new ResourceLocation("example_mod:example_entity").toString())构建了实体类型,并使用ResourceLocation来唯一标识该实体类型。

  3. 初始化实体注册:

    public static void init() {
        IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
        ENTITY_TYPES.register(modEventBus);
    }
    

    init方法用于将之前创建的实体类型注册器ENTITY_TYPES与模组的事件总线modEventBus关联起来。FMLJavaModLoadingContext.get().getModEventBus()获取了模组的事件总线,然后通过ENTITY_TYPES.register(modEventBus)在模组加载时注册所有通过ENTITY_TYPES注册的实体类型。

总结:

 这段代码的主要功能是创建并注册一个名为ExampleEntity的自定义实体类型。通过使用DeferredRegister,确保了实体类型在正确的生命周期阶段被注册。此外,还设置了该实体的基本属性,如大小、跟踪范围和更新频率,并为其实体类型创建了一个ResourceLocation,以确保它是唯一且可以被Minecraft识别的。

三、EntityRenderersEvent事件

EntityRenderersEvent 是 Forge 模组开发框架中用于处理实体渲染器注册的事件类,它属于 Forge 事件总线系统的一部分。在 Minecraft 模组开发里,当你需要为自定义实体指定渲染器时,就可以借助 EntityRenderersEvent 来达成这一目的。
EntityRenderersEvent 的用途
EntityRenderersEvent 有多个子类,每个子类对应不同的渲染器注册阶段,不过最常用的是 EntityRenderersEvent.RegisterRenderers。这个子类事件允许你在模组初始化期间注册自定义实体的渲染器。
如何使用 EntityRenderersEvent 渲染实体
步骤 1:定义自定义实体
首先,你得定义一个自定义实体类,并且在 EntityRegistry 里注册这个实体。
步骤 2:创建实体渲染器
接着,你需要创建一个渲染器类,这个类要继承自合适的渲染器基类,例如 ThrownItemRenderer 或者 LivingEntityRenderer。
步骤 3:注册实体渲染器
最后,利用 EntityRenderersEvent.RegisterRenderers 事件来注册自定义实体的渲染器。

示例代码

创建实体渲染器:

import net.minecraft.client.renderer.entity.ThrownItemRenderer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

@OnlyIn(Dist.CLIENT)
public class CustomFireballRenderer extends ThrownItemRenderer<CustomFireball> {
    public CustomFireballRenderer(EntityRendererProvider.Context context) {
        super(context);
    }
}

注册实体渲染器:

import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ClientRegistry {
    @SubscribeEvent
    public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
        event.registerEntityRenderer(
                EntityRegistry.CUSTOM_FIREBALL.get(),
                CustomFireballRenderer::new
        );
    }
}

代码解释:
EntityRegistry:这个类负责注册自定义实体 CustomFireball。
CustomFireballRenderer:这是自定义实体的渲染器类,继承自 ThrownItemRenderer。
ClientRegistry:该类包含一个静态方法 registerRenderers,用于处理 EntityRenderersEvent.RegisterRenderers 事件。在这个方法里,我们使用 event.registerEntityRenderer 方法来注册自定义实体的渲染器。
通过以上步骤,你就可以使用 EntityRenderersEvent 为自定义实体注册渲染器,进而在 Minecraft 中渲染这些实体了。

四、自定义火球实体

讲这么多,到底怎么新建一个自定义火球呢?可以参考如下代码,代码就不解释了

public class CustomFireball extends Fireball {
    // 主构造方法需匹配父类参数
    public CustomFireball(EntityType<? extends Fireball> type, Level level) {
        super(type, level);
    }

    // 带实体的构造方法
    public CustomFireball(Level level, LivingEntity shooter, double dX, double dY, double dZ) {
        super(EntityRegistry.CUSTOM_FIREBALL.get(), shooter, dX, dY, dZ, level);
        this.setNoGravity(true);
    }

    @Override
    protected void onHit(@NotNull HitResult result) {
        if (!this.level().isClientSide) {
            // 创建影响范围
            AABB area = this.getBoundingBox().inflate(2.0D);
            List<LivingEntity> entities = this.level().getEntitiesOfClass(
                    LivingEntity.class,
                    area,
                    e -> e != this.getOwner()
            );

            // 对范围内所有生物应用燃烧效果
            for (LivingEntity entity : entities) {
                entity.setSecondsOnFire(999); // 30秒燃烧
            }

            // 生成火焰粒子
            ServerLevel level = (ServerLevel) this.level();
            level.sendParticles(
                    ParticleTypes.FLAME,
                    this.getX(),
                    this.getY(),
                    this.getZ(),
                    50,
                    0.5D, 0.5D, 0.5D,
                    0.2D
            );

            this.discard();
        }
    }
}

五、HUD显示

HUD 是什么

HUD 即 Heads - Up Display,直译为平视显示器,在游戏领域,它指的是游戏界面上显示的信息,如生命值、分数、武器状态等。这些信息通常直接叠加在游戏画面上,让玩家无需转移视线就能获取关键信息。
在 Minecraft Forge 中绘制 HUD 显示信息
步骤
监听渲染事件:借助 RenderGuiOverlayEvent.Post 事件,在游戏界面渲染完成后绘制 HUD。
绘制信息:运用 GuiGraphics 对象来绘制背景和文字。

public class ClientHandler {
    @SubscribeEvent
    public static void onRenderGui(RenderGuiOverlayEvent.Post event) {
        if (event.getOverlay() != VanillaGuiOverlay.HOTBAR.type()) return;

        Minecraft mc = Minecraft.getInstance();
        Player player = mc.player;
        if (player == null) return;

        GuiGraphics gui = event.getGuiGraphics();
        ItemStack mainHand = player.getMainHandItem();

        if (mainHand.getItem() instanceof FlameSwordItem) {
            // 获取精确剩余时间
            float cooldown = player.getCooldowns().getCooldownPercent(
                    MyMod.FLAME_SWORD.get(),
                    mc.getFrameTime()
            );

            if (cooldown > 0) {
                // 计算剩余秒数(保留1位小数)
                String text = String.format("冷却: %.1fs", cooldown * 3);
                int x = mc.getWindow().getGuiScaledWidth() / 2 - 25;
                int y = mc.getWindow().getGuiScaledHeight() - 25;

                // 绘制背景
                gui.fill(x - 2, y - 2, x + 50, y + 10, 0x80000000);
                // 绘制文字
                gui.drawString(
                        mc.font,
                        text,
                        x, y,
                        0xFFAA00, // 橙色文字
                        false
                );
            }
        }
    }
}

这段代码是一个Minecraft模组中的客户端事件处理器,主要功能是在玩家手持特定物品(名为FlameSwordItem的火焰剑)时,在游戏界面的热键栏下方显示一个冷却倒计时。

让我们逐步分解并详细解释这段代码:

  1. 类定义:

    public class ClientHandler {
    

    这里定义了一个名为ClientHandler的公共类。这个类将用于处理客户端事件。

  2. 事件订阅:

    @SubscribeEvent
    public static void onRenderGui(RenderGuiOverlayEvent.Post event) {
    

    这段代码使用了Forge的@SubscribeEvent注解,表示这个方法将订阅RenderGuiOverlayEvent.Post事件。这意味着每当Minecraft的GUI覆盖层渲染完毕之后,这个方法都会被调用。该事件处理方法名为onRenderGui,它接收一个RenderGuiOverlayEvent.Post类型的事件对象。

  3. 事件类型判断:

    if (event.getOverlay() != VanillaGuiOverlay.HOTBAR.type()) return;
    

    这段代码首先检查当前渲染的GUI覆盖层是否是热键栏(VanillaGuiOverlay.HOTBAR.type())。如果不是热键栏,则直接返回,不执行后续的代码逻辑。这一步是为了确保后续的代码仅在热键栏渲染后执行。

  4. 获取Minecraft实例和玩家对象:

    Minecraft mc = Minecraft.getInstance();
    Player player = mc.player;
    if (player == null) return;
    

    这里通过Minecraft.getInstance()获取Minecraft的游戏实例,然后从实例中获取当前玩家对象。接着检查玩家对象是否为空,如果为空(例如游戏中没有玩家时),则直接返回,不执行后续逻辑。

  5. 获取当前渲染的GUI图形对象:

    GuiGraphics gui = event.getGuiGraphics();
    

    从事件对象中获取GuiGraphics对象,这个对象可以用来在游戏界面绘制图形。

  6. 获取玩家主手物品:

    ItemStack mainHand = player.getMainHandItem();
    

    从玩家对象中获取玩家主手上的物品,并将其存储在ItemStack对象mainHand中。

  7. 判断主手物品是否为火焰剑:

    if (mainHand.getItem() instanceof FlameSwordItem) {
    

    这段代码检查玩家主手上的物品是否是FlameSwordItem类型的物品。如果是火焰剑,则执行后续逻辑;如果不是,则直接返回。

  8. 获取冷却时间百分比:

    float cooldown = player.getCooldowns().getCooldownPercent(
            MyMod.FLAME_SWORD.get(),
            mc.getFrameTime()
    );
    

    如果玩家手持的是火焰剑,这里会计算火焰剑的冷却时间百分比。player.getCooldowns().getCooldownPercent()方法需要两个参数:要检查的物品和当前帧的时间。MyMod.FLAME_SWORD.get()返回的是火焰剑物品的实例。

  9. 冷却时间大于0的判断:

    if (cooldown > 0) {
    

    这段代码判断冷却时间的百分比是否大于0,如果大于0,说明火焰剑还在冷却中,则执行后续的冷却时间显示逻辑。

  10. 计算冷却剩余秒数:

    String text = String.format("冷却: %.1fs", cooldown * 3);
    

    这里通过String.format()方法将冷却时间的百分比转换为秒数,并格式化为保留一位小数的字符串。注意到这里将百分比乘以3,可能是因为模组中设定火焰剑的冷却时间为3秒。

  11. 计算文本绘制的位置坐标:

    int x = mc.getWindow().getGuiScaledWidth() / 2 - 25;
    int y = mc.getWindow().getGuiScaledHeight() - 25;
    

    这段代码计算了冷却时间文本在游戏界面中的绘制位置。x坐标是窗口宽度的一半再减去25像素,y坐标是窗口高度减去25像素,这样文本就会出现在屏幕中央下方的位置。

  12. 绘制背景:

    gui.fill(x - 2, y - 2, x + 50, y + 10, 0x80000000);
    

    这段代码使用GuiGraphics对象gui在文本绘制的位置前绘制一个背景矩形。矩形的左上角坐标为(x - 2, y - 2),右下角坐标为(x + 50, y + 10)。矩形颜色为半透明的黑色(0x80000000),其中前两位十六进制数80表示透明度。

  13. 绘制冷却时间文本:

    gui.drawString(
            mc.font,
            text,
            x, y,
            0xFFAA00, // 橙色文字
            false
    );
    

    这段代码通过GuiGraphics对象gui在计算好的位置绘制冷却时间文本。mc.font是Minecraft中用于绘制文本的字体对象。文本颜色设置为橙色(0xFFAA00),并且不使用阴影效果(false)。

总结:

这段代码的主要功能是在Minecraft客户端渲染热键栏之后,检查玩家主手是否持有火焰剑。如果持有火焰剑且其处于冷却状态,则在屏幕中央下方显示剩余冷却时间。这为玩家提供了一个直观的冷却时间反馈,使得玩家更容易掌握火焰剑的使用时机。

怎么设置技能的冷却时间?(深入了解)

可以参考下面的代码:

player.getCooldowns().addCooldown(MyMod.FLAME_SWORD.get(), 60); // 设置冷却时间为 60 个游戏刻(游戏刻约为 1/20 秒)

为了更好的理解冷却时间,可以研究一下源码

 ItemCooldowns 相关方法

    protected ItemCooldowns createItemCooldowns() {
        return new ItemCooldowns();
    }

    public ItemCooldowns getCooldowns() {
        return this.cooldowns;
    }
  • createItemCooldowns() 创建一个 ItemCooldowns 对象,用于管理物品冷却时间。
  • getCooldowns() 返回 ItemCooldowns 对象,可以用来获取或设置物品的冷却时间。

技能冷却时间的计算与设置

计算冷却时间:

ItemCooldowns 类提供了方法来管理物品的冷却时间。在上述代码中,Player 类通过 ItemCooldowns 实例来管理冷却时间。

  • getCooldownPercent(Item item, float frameTime) 方法返回指定物品的冷却时间百分比。
    • item 是要查询冷却状态的物品。
    • frameTime 是当前帧的时间,通常用于动画和时间相关的计算。
  • 在 ClientHandler 类中,冷却时间的百分比被获取并乘以 3 来转换为秒数。这个转换可能是因为 getCooldownPercent 返回的是一个 0 到 1 的百分比值,而乘以 3 可能是某个特定物品的冷却时间长度(假设总共 3 秒)。

设置冷却时间:

  • 冷却时间设置通常在使用物品时调用。例如,当玩家使用某个物品后,可以通过 ItemCooldowns.addCooldown(Item item, int ticks) 来设置冷却时间。
    • item 是要设置冷却时间的物品。
    • ticks 是冷却时间的持续时长(以游戏刻为单位)。
  • 在 Player 类中,使用物品后会调用 disableShield 方法来设置冷却时间。这个方法中,调用了 this.getCooldowns().addCooldown(this.getUseItem().getItem(), 100); 来设置使用物品后的冷却时间为 100 个游戏刻。

总结

这段代码定义了 Player 类,它是 Minecraft 中玩家实体的抽象类。代码中涉及到了玩家的基本属性、物品栏、经验和冷却系统的管理。特别地,ItemCooldowns 类用于管理物品的冷却时间,玩家在使用某些物品后,系统会通过这个类来设置和获取冷却状态。在 ClientHandler 类中,当玩家使用特定物品(如火焰剑)时,代码会获取该物品的冷却时间并将其显示在游戏界面的热巴条上方。

ItemCooldowns类

package net.minecraft.world.item;

import com.google.common.collect.Maps; // 引入用于创建哈希表的Maps类
import java.util.Iterator; // 引入迭代器接口
import java.util.Map; // 引入映射接口
import net.minecraft.util.Mth; // 引入数学工具类

// ItemCooldowns 类用于管理物品的冷却时间
public class ItemCooldowns {
    private final Map<Item, CooldownInstance> cooldowns = Maps.newHashMap(); // 存储物品与其冷却实例之间的映射关系
    private int tickCount; // 计时器,用于跟踪冷却时间的滴答

    // 构造函数
    public ItemCooldowns() {
    }

    // 检查指定的物品是否在冷却中
    public boolean isOnCooldown(Item p_41520_) {
        return this.getCooldownPercent(p_41520_, 0.0F) > 0.0F; // 如果冷却百分比大于0,表示在冷却中
    }

    // 获取指定物品的冷却百分比
    public float getCooldownPercent(Item p_41522_, float p_41523_) {
        CooldownInstance $2 = (CooldownInstance)this.cooldowns.get(p_41522_); // 获取物品的冷却实例
        if ($2 != null) { // 如果存在该物品的冷却实例
            float $3 = (float)($2.endTime - $2.startTime); // 计算冷却时间间隔
            float $4 = (float)$2.endTime - ((float)this.tickCount + p_41523_); // 计算剩余冷却时间
            return Mth.clamp($4 / $3, 0.0F, 1.0F); // 将冷却百分比限制在0到1之间
        } else {
            return 0.0F; // 如果没有冷却实例,则返回0
        }
    }

    // 更新冷却计时器
    public void tick() {
        ++this.tickCount; // 增加计时器
        if (!this.cooldowns.isEmpty()) { // 如果冷却列表不为空
            Iterator<Map.Entry<Item, CooldownInstance>> $0 = this.cooldowns.entrySet().iterator(); // 获取冷却的迭代器

            // 遍历所有冷却实例
            while($0.hasNext()) {
                Map.Entry<Item, CooldownInstance> $1 = (Map.Entry)$0.next(); // 获取下一个条目
                if (((CooldownInstance)$1.getValue()).endTime <= this.tickCount) { // 如果当前时间超过冷却结束时间
                    $0.remove(); // 从冷却列表中移除
                    this.onCooldownEnded((Item)$1.getKey()); // 触发冷却结束事件
                }
            }
        }
    }

    // 为指定的物品添加冷却时间
    public void addCooldown(Item p_41525_, int p_41526_) {
        this.cooldowns.put(p_41525_, new CooldownInstance(this.tickCount, this.tickCount + p_41526_)); // 添加物品的冷却实例
        this.onCooldownStarted(p_41525_, p_41526_); // 触发冷却开始事件
    }

    // 移除指定物品的冷却时间
    public void removeCooldown(Item p_41528_) {
        this.cooldowns.remove(p_41528_); // 从冷却列表中移除物品
        this.onCooldownEnded(p_41528_); // 触发冷却结束事件
    }

    // 冷却开始时触发的事件,通常留空供子类重写
    protected void onCooldownStarted(Item p_41529_, int p_41530_) {
    }

    // 冷却结束时触发的事件,通常留空供子类重写
    protected void onCooldownEnded(Item p_41531_) {
    }

    // 内部类用于表示冷却实例
    static class CooldownInstance {
        final int startTime; // 冷却开始时间
        final int endTime; // 冷却结束时间

        // 构造函数
        CooldownInstance(int p_186358_, int p_186359_) {
            this.startTime = p_186358_; // 设置开始时间
            this.endTime = p_186359_; // 设置结束时间
        }
    }
}

代码解释总结:

  1. 类功能ItemCooldowns 是一个用于管理物品冷却时间的类。它追踪每个物品的冷却状态,并提供方法来查询物品是否在冷却中以及其剩余冷却时间。

  2. 数据结构:使用 Map<Item, CooldownInstance> 来存储物品和其冷却实例的映射。CooldownInstance 内部类用于表示冷却的开始和结束时间。

  3. 主方法

    • isOnCooldown(Item):检查指定物品是否在冷却中。
    • getCooldownPercent(Item, float):计算物品当前的冷却百分比。
    • tick():更新冷却计时器,并移除过期的冷却实例。
    • addCooldown(Item, int):为指定物品添加冷却。
    • removeCooldown(Item):移除指定物品的冷却。
  4. 事件处理:定义了 onCooldownStarted 和 onCooldownEnded 方法,这些方法在冷却开始或结束时被触发,允许子类自定义行为。

好了,本期就讲到这里,如果对你有帮助,请一定要点赞关注,感谢支持 

在《我的世界》(Minecraft)1.20.1版本中使用Forge模组开发时,自定义一本模型书以及添加Geckolib动画通常需要一定的编程知识,特别是对 Forge Modding API 和 Minecraft 的Item、Block系统有深入理解。 首先,你需要创建一个新的Book Item: ```java // 定义自定义书籍类 public class CustomBook extends Book { private String openAnimation = "your_open_animation"; private String closeAnimation = "your_close_animation"; // 构造函数可以包含动画数据 public CustomBook(Material material, int damage) { super(material, new ItemStack(this, 1, damage)); } @Override protected void registerModel() { // 注册模型 ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(this), 0, new ModelResourceLocation(Registry.ITEMS.get登记ID(this), "inventory")); } // 定义方法播放动画 public void playAnimation(String animationName) { if (animationName.equals(openAnimation)) { // 使用Geckolib或者其他支持的API播放open动画 Geckolib.playAnimation(animationName); } else if (animationName.equals(closeAnimation)) { // 播放close动画 Geckolib.playAnimation(animationName); } } } ``` 为了实现在打开书本时播放`openAnimation`,关闭书本时播放`closeAnimation`,你可以在相关的事件处理器如右键点击或特定交互触发时调用`playAnimation`方法: ```java @SubscribeEvent public void onPlayerInteract(PlayerInteractEvent event) { if (event.getAction().equals(Action.RIGHT_CLICK_AIR) && event.getItemStack().getItem() instanceof CustomBook) { CustomBook book = (CustomBook) event.getItemStack().getItem(); book.playAnimation(event.getAction() == Action.RIGHT_CLICK_BLOCK ? openAnimation : closeAnimation); } } ``` 请注意,以上代码示例并未实际提供Geckolib的详细导入和使用,因为这取决于你是否已经在项目中集成并正确配置了它。Geckolib是一个用于在游戏中添加动画的库,你需要按照其官方文档来引入和使用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lemon_sjdk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值