在这一期中,我们将继续探讨Forge的开发教程,着重介绍能力(Capability)的概念。Capability是一种可以为实体添加额外数据的方式,可以通过查阅Forge文档来了解更多信息。在Forge中,能力通常附加给阻挡实体(blocking entities),而实体(entity)指的是游戏中的角色或物体。
除了实体,能力还可以附加到世界级别(world levels)、存储容器(level trunks)等各种对象上。然而,我们通常将能力应用于实体,尤其是玩家实体。其他类型的实体较少使用。Forge提供了几种默认的能力,但在本次讲解中我们将先自己生成一个能力。
我们可以以一个例子来说明,比如之前提到的骨粉事件。现在想要升级该事件,给玩家附加一个特殊的能力,称为“农业经验”。这个农业经验可以在正常使用骨粉促使作物生长时消耗,并在收获其他作物(如小麦、土豆等)时增加。我们需要实现一个农业经验系统,这时就可以运用自定义的能力(Capability)了。
首先,在代码中创建一个新的软件包(package),取名为"capability",再建立一个子包,命名为"farmxp"。在其中创建一个名为"PlayerFarmXP"的类,用于表示玩家的农业经验能力。在这个类中,我们需要定义一个整数型变量"XP",并创建相应的getter和setter方法。
接下来,我们需要将数据写入到NBT标签中。对于整数类型的数据,我们可以使用putInt方法来存储到NBT标签中。还需要实现loadNBTData方法,参数仍然是CompoundTag类型,在这里我们使用getInt方法从NBT标签中获取数据,然后赋值给我们定义的XP变量。
构造函数可以设置默认值为零,并且不需要参数。每收获一个作物时,农业经验(XP)增加一个单位。在减少经验时,需要确保经验不能为负数,因此可以返回布尔类型进行判断经验是否足够。
接着,我们新建一个Java类,命名为PlayerFarmXPProvider,实现AbilityProvider接口和INBTSerializable接口。需要实现对应的方法才能注册。在该类中,我们持有一个PlayerFarmXP对象,并实现serializeNBT和deserializeNBT方法来进行NBT序列化和反序列化操作。
在构造函数中,需要创建一个Capability的实例,并实现getCapability方法。在getCapability方法中,如果判断到是我们的PlayerFarmXP能力时,就返回LazyOptional对象。
接下来,让我们来创建一个私有的LazyOptional<PlayerFarmXP>对象,命名为loFarmXP,通过LazyOptional.of方法创建,使用Lambda表达式将其初始化为this::getPlayerFarmXP。在方法中,如果是PlayerFarmXP的实例,则返回LazyOptional.of,否则返回LazyOptional.empty()。
关于LazyOptional,它是一种在需要时才创建的动态对象。简单来说,只有在需要时才会进行实例化,具体用法可以按需使用,不必特别深究。
完成上述操作后,我们需要将这个能力注册到游戏中。在ModEventListener中的onRegisterCapabilitiesEvent方法中,使用event.register来注册我们的PlayerFarmXPProvider类。这样,我们就成功地注册了一个能力。
接着,我们需要将这个能力附加到玩家身上。在事件监听器中,可以编写一个方法attachCapability来处理AttachCapabilitiesEvent<Entity>事件。判断实体是否为玩家,并检查该实体是否具有我们定义的能力,然后向事件中添加能力。最后,在游戏启动时,可根据日志来确认是否成功添加了能力。
首先,我们需要创建一个新的包命名为command,在其中新建一个Java类,取名为GetFarmXPCommand。我们可以参考原版命令的写法,比如获取世界种子的命令,了解参数和逻辑结构。在GetFarmXPCommand中,我们定义了一个命令,命名为DESC,并注册了该命令。
命令通常包含权限检查以确定命令是否可执行,可以使用原版hasPermission方法或自定义检查逻辑。在execute方法中,可以通过getSource和getLevel获取命令来源和世界实例,然后使用sendFeedback将信息发送给玩家。
接着,我们需要在DISPATCH中注册这个命令,并定义其执行逻辑。在execute方法中,可以通过getContext获取CommandSourceStack和CommandContext,并进一步操作。例如,通过getPlayer获取执行命令的玩家,再用getCapability获取PlayerFarmXPProvider,然后判断其是否存在(若不为空则执行相应操作)。
首先,我们继续使用Lambda表达式,在GetFarmXPCommand中的execute方法中,可以通过context.getSource().sendSuccess发送消息给命令的执行者。在这里,我们可以使用ComponentUtils.copyOnClickText来创建一个可点击复制的文本,用Literal表示纯文本,然后以XP: 开头,再加上getXP()获取到的经验值。最后,将false替换为0,表示默认为0。
接着,我们需要将这个命令注册到我们的mod中。通常在Forge事件监听器中,在registerCommands方法中注册我们的命令,使用CommandDispatcher的register方法。通过ForgeEventBus中的register方法将其与相关事件关联起来,实现命令的注册。
另外,我们还需要考虑增加经验值的逻辑。在处理方块破坏事件时,可以使用BlockEvent.BreakEvent来判断是否摧毁了作物等特定方块,如果是作物,则可以通过以下代码
来增加经验值或做其他操作。
event.getPlayer().getCapability(PlayerFarmXPProvider.FARM_XP_CAPABILITY).ifPresent
在这里,我们需要确保对经验值进行适当的判断。可以在PlayerFarmXP类中的对应方法中添加逻辑,确保经验值足够时返回true,否则返回false。在处理经验减少事件时,可以使用xp.decreaseEvent().setCancelled()来取消事件,以防止经验值不足。
通过合适的逻辑控制,当经验值不足时取消事件的发生,保证了稳健和正确的行为。在游戏中测试这个逻辑是否有效,确保经验值随着操作的合理增减。同时,通过种植、收成、使用骨粉等交互验证经验值的变化是否符合预期。
最后提醒,有些情况下需要进一步完善代码,比如对于不同作物或不同状态的处理逻辑、对于已成熟植物的特殊处理等;在下期教程我们可以进一步讨论和解决这些问题,以提高功能的完整性。