Flandre923
1402 words
7 minutes
41 饥渴值例子

参考#

给玩家添加口渴度#

这次我们使用之前讲解的capabilitiy能力系统,HUD,网络发包,热键注册等功能给玩家添加一个口渴度的系统,并且借助HUD功能将这个数值渲染到屏幕上。通过按键O增加口渴度,随时间口渴度逐渐减少

偷懒了。没写能力的接口,直接写了实现类。


public class PlayerThirst {
    // 玩家当前的口渴值
    private int thirst;
     // 最小和最大口渴值的常量
    private final int MIN_THIRST = 0;
    private final int MAX_THIRST = 10;
    // 获取当前的口渴值
    public int getThirst() {
        return thirst;
    }

 // 增加口渴值,但不能超过最大值
    public void addThirst(int add) {
        this.thirst = Math.min(thirst + add, MAX_THIRST);
    }

    // 减少口渴值,但不能低于最小值
    public void subThirst(int sub) {
        this.thirst = Math.max(thirst - sub, MIN_THIRST);
    }

    // 从另一个 PlayerThirst 对象复制口渴值
    public void copyFrom(PlayerThirst source) {
        this.thirst = source.thirst;
    }

    // 将口渴值保存到 NBT 数据中
    public void saveNBTData(CompoundTag nbt) {
        nbt.putInt("thirst", thirst);
    }

    // 从 NBT 数据中加载口渴值
    public void loadNBTData(CompoundTag nbt) {
        thirst = nbt.getInt("thirst");
    }
}

能力的提供者capabilityProvider,以及数据的持久化



public class PlayerThirstProvider implements ICapabilityProvider<Player,Void, PlayerThirst>, INBTSerializable<CompoundTag> {
// 存储玩家口渴值的实例
    private PlayerThirst thirst = null;

    // 创建 PlayerThirst 实例的私有方法
    private PlayerThirst createPlayerThirst() {
        if (this.thirst == null) {
            this.thirst = new PlayerThirst();
        }
        return this.thirst;
    }

    // 实现 INBTSerializable 接口的 serializeNBT 方法,用于将口渴值保存到 NBT 数据中
    @Override
    public CompoundTag serializeNBT() {
        CompoundTag nbt = new CompoundTag();
        createPlayerThirst().saveNBTData(nbt);
        return nbt;
    }

    // 实现 INBTSerializable 接口的 deserializeNBT 方法,用于从 NBT 数据中加载口渴值
    @Override
    public void deserializeNBT(CompoundTag nbt) {
        createPlayerThirst().loadNBTData(nbt);
    }

    // 实现 ICapabilityProvider 接口的 getCapability 方法,用于获取玩家的口渴值实例
    @Override
    public @Nullable PlayerThirst getCapability(Player player, Void context) {
        return this.createPlayerThirst();
    }
}

能力的创建


public class ModCapabilities {

    public static final EntityCapability<PlayerThirst,Void> PLAYER_THIRST_HANDLER =
            EntityCapability.createVoid(new ResourceLocation(ExampleMod.MODID,"player_thirst_handler"),
                    PlayerThirst.class);

}

给玩家实体添加能力。


    @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD)
    public static class ModEventBus{
        @SubscribeEvent
        private static void registerCapabilities(RegisterCapabilitiesEvent event) {
            event.registerEntity(ModCapabilities.PLAYER_THIRST_HANDLER,
                    EntityType.PLAYER,
                    new PlayerThirstProvider());
        }
    }

由于按键只能在客户端的上,所以喝水的增加口渴值的逻辑需要发包处理,这里添加一个数据包。

// 需要同步的数据只有thirst
public record ThirstData(int thirst) implements CustomPacketPayload {
    public static final ResourceLocation ID = new ResourceLocation(ExampleMod.MODID,"thirst_data");


    public ThirstData(final FriendlyByteBuf buf){
        this(buf.readInt());
    }

    @Override
    public void write(FriendlyByteBuf pBuffer) {
        pBuffer.writeInt(thirst());
    }

    @Override
    public ResourceLocation id() {
        return ID;
    }
}

如果是客户端收到了发包怎么处理数据包


public class ClientPayloadHandler {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler();

    public static ClientPayloadHandler getInstance() {
        return INSTANCE;
    }
    // 给我们的在客户端存储thirst的地方赋值
    public void handleThirstData(final ThirstData data,final PlayPayloadContext context){
        context.workHandler().submitAsync(()->{
            ClientPlayerThirstData.set(data.thirst());
        }).exceptionally(e->{
            context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage()));
            return null;
        });
    }

}

服务端接受到数据包怎么处理


public class ServerPayloadHandler {
    private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler();
    private static final Logger LOGGER = LogUtils.getLogger();

    public static ServerPayloadHandler getInstance() {
        return INSTANCE;
    }

    public void handleThirstData(final ThirstData data, final PlayPayloadContext context){
        context.workHandler().submitAsync(()->{
            context.player().ifPresent(player -> {
                // 玩家应该是服务端的玩家
                if (player instanceof ServerPlayer serverPlayer){
                    // 服务端的level
                    ServerLevel level = (ServerLevel) player.level();
                    // 判断玩家附近是否有水
                    if(hasWaterAroundThem(player,level,2)){
                        // 播放喝水的声音
                        level.playSound(null, player.getOnPos(), SoundEvents.GENERIC_DRINK, SoundSource.PLAYERS,
                                0.5F, level.random.nextFloat() * 0.1F + 0.9F);
                        // 从玩家身上获得口渴的能力,如果存在就增加数值,并发包同步数据
                        Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst->{
                            thirst.addThirst(1);

                            player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst())
                                    .withStyle(ChatFormatting.AQUA));

                            PacketDistributor.PLAYER.with(serverPlayer).send(new ThirstData(thirst.getThirst()));
                        });

                    }else{
                        // 同理
                        Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER)).ifPresent(thirst -> {
                            player.sendSystemMessage(Component.literal("Current Thirst " + thirst.getThirst())
                                    .withStyle(ChatFormatting.AQUA));
                            PacketDistributor.PLAYER.with(serverPlayer).send(new ThirstData(thirst.getThirst()));
                        });
                    }
                }
            });
        }).exceptionally(e->{
            context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage()));
            return null;
        });
    }
    // 判断玩家附近是否有水方块
    private boolean hasWaterAroundThem(Player player, ServerLevel level, int size) {
        return level.getBlockStates(player.getBoundingBox().inflate(size))
                .filter(state -> state.is(Blocks.WATER)).toArray().length > 0;
    }
}

网络包的注册,以及对网络包的处理。

@Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.MOD)
public class Networking {
    @SubscribeEvent
    public static void register(final RegisterPayloadHandlerEvent event) {
        final IPayloadRegistrar registrar = event.registrar(ExampleMod.MODID);
        registrar.play(ThirstData.ID,ThirstData::new, handler ->
                handler.client(ClientPayloadHandler.getInstance()::handleThirstData)
                        .server(ServerPayloadHandler.getInstance()::handleThirstData));
    }

}

我们客户端上的辅助的类,用于存储playerThirst数值,并给HUD提供渲染支持。

@OnlyIn(Dist.CLIENT)
public class ClientPlayerThirstData {
    private static int playerThirst;

    public static void set(int thirst) {
        ClientPlayerThirstData.playerThirst = thirst;
    }

    public static int getPlayerThirst() {
        return playerThirst;
    }
}

玩家安下O键时候,给服务器发包,服务器处理数据包完成喝水的过程。

@Mod.EventBusSubscriber(modid = ExampleMod.MODID,value = Dist.CLIENT)
public class ForgeClientEventHandler {

    @SubscribeEvent
    public static void onKeyInput(InputEvent.Key event) {
        if(KeyBinding.DRINKING_KEY.consumeClick()){
//            Minecraft.getInstance().player.sendSystemMessage(Component.literal("Press the key!"));
            PacketDistributor.SERVER.noArg().send(new ThirstData(ClientPlayerThirstData.getPlayerThirst()));
        }
    }
}

玩家每隔一段时间就数值-1,并发包同步数据

    @Mod.EventBusSubscriber(modid = ExampleMod.MODID,bus = Mod.EventBusSubscriber.Bus.FORGE)
    public static class ForgeEvents{

        @SubscribeEvent
        public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
            if(event.side == LogicalSide.SERVER) {
                Optional<PlayerThirst> optionalPlayerThirst = Optional.ofNullable(event.player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER));
                optionalPlayerThirst .ifPresent(thirst -> {
                    if(thirst.getThirst() > 0 && event.player.getRandom().nextFloat() < 0.005f) { // Once Every 10 Seconds on Avg
                        thirst.subThirst(1);
                        PacketDistributor.PLAYER.with((ServerPlayer) event.player).send(new ThirstData(thirst.getThirst()));
                        event.player.sendSystemMessage(Component.literal("Subtracted Thirst"));
                    }
                });
            }
        }
    // 当玩家刚刚加入世界时候同步数据
        @SubscribeEvent
        public static void onPlayerJoinWorld(EntityJoinLevelEvent event) {
            if(!event.getLevel().isClientSide()) {
                if(event.getEntity() instanceof ServerPlayer player) {
                    Optional<PlayerThirst> optionalPlayerThirst = Optional.ofNullable(player.getCapability(ModCapabilities.PLAYER_THIRST_HANDLER));
                    optionalPlayerThirst.ifPresent(thirst -> {
                        PacketDistributor.PLAYER.with((ServerPlayer) player).send(new ThirstData(thirst.getThirst()));
                    });
                }
            }
        }

    }

使用HUD绘制水瓶


public class ThirstHud {
    // 有水和无水的水瓶
    private static final ResourceLocation FILLED_THIRST = new ResourceLocation(ExampleMod.MODID,
            "textures/gui/filled_thirst.png");
    private static final ResourceLocation EMPTY_THIRST = new ResourceLocation(ExampleMod.MODID,
            "textures/gui/empty_thirst.png");
    public static final IGuiOverlay HUD_THIRST = (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> {
        int x = screenWidth / 2;
        int y = screenHeight;
        // 绘制无水的水瓶
        for(int i = 0; i < 10; i++) {
            guiGraphics.blit(EMPTY_THIRST,x - 94 + (i * 9), y - 54,90,0,0,12,12,
                    12,12);
        }
        // 绘制有水的水瓶
        // ClientPlayerThirstData我们提供的client辅助的类存储数值
        for(int i = 0; i < 10; i++) {
            if(ClientPlayerThirstData.getPlayerThirst() > i) {
                guiGraphics.blit(FILLED_THIRST,x - 94 + (i * 9),y - 54,90,0,0,12,12,
                        12,12);
            } else {
                break;
            }
        }
    };
}

注册HUD。

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD,value = Dist.CLIENT)
public class ModClientEventHandler {
    @SubscribeEvent
    public static void registerGuiOverlays(RegisterGuiOverlaysEvent event) {
        event.registerAboveAll(new ResourceLocation(ExampleMod.MODID,"thirst_hud"), ThirstHud.HUD_THIRST);
    }
}

好了进入游戏看看逻辑把,应该绘制出来了无水的水瓶,在水方块附近按下o,看看是不是增加了一个有水的水瓶,推出游戏在进入看看是不是数据是不是可以持久化。以及等一段时间看看瓶子会不会减少,对应了tick事件的方法。

41 饥渴值例子
https://fuwari.vercel.app/posts/minecraft1_20_4/out_41-饥渴值例子/
Author
Flandre923
Published at
2024-04-14