====== 自定义事件 ======
Fabric API 提供一个允许模组对游戏内事件作出反应的系统。事件(events)是满足常见用例的钩子(hooks),同时也能在钩到代码相同区域的模组之间增强兼容性、改善性能。使用事件通常就能代替 mixin。Fabric API 为 Minecraft 代码库中的重要区域提供了事件,模组作者就会需要钩到这些钩子。有些地方是没有钩子的,所以你可以选择使用 mixin,或创建你自己的事件。
在本教程中,我们将着眼于创建一个在剪羊毛时触发的事件。创建事件的过程是:
* 创建事件回调接口
* 从 mixin 触发事件
* 创建一个测试实现
==== 创建回调接口 ====
回调接口是最需要由监听事件的事件监听器实现的,事件也会从 mixin 中通过回调接口被调用。在回调接口中,必须放置一个 Event 对象作为字段,以识别实际事件。
对于事件实现,我们选择使用由数组支持的事件。这个数组会包含所有监听事件的事件监听器。我们的实现会依次调用事件监听器,直到其中一个不再返回 ''ActionResult.PASS'',这意味着,监听器可能会使用其返回值来要求“退出这个”、“批准这个”或者“不用在意,留给下一个事件监听器”。这种返回一个 ActionResult 值的方法,通常能够使得事件监听器能够合作。
你需要创建一个包含 ''Event'' 实例的接口,接口需包含用于响应实现的方法。剪羊毛事件回调的基本步骤为:
public interface SheepShearCallback {
Event EVENT = EventFactory.createArrayBacked(SheepShearCallback.class,
(listeners) -> (player, sheep) -> {
for (SheepShearCallback listener : listeners) {
ActionResult result = listener.interact(player, sheep);
if(result != ActionResult.PASS) {
return result;
}
}
return ActionResult.PASS;
});
ActionResult interact(PlayerEntity player, SheepEntity sheep);
}
现在再深入一点,调用了这个调用器(invoker)时,迭代所有的监听器:
(listeners) -> (player, sheep) -> {
for (SheepShearCallback listener : listeners) {
然后在监听器中调用我们的方法(本例中,为 ''interact'')以获得响应:
ActionResult result = listener.interact(player, sheep);
如果监听器告知我们需要退出(''ActionResult.FAIL'')或者彻底完成(''ActionResult.SUCCESS''),回调则会返回结果并完成循环。''ActionResult.PASS'' 会移到下一个监听器,大多数情况下,如果没有再注册更多监听器,则一般会以成功结束。
// ....
if(result != ActionResult.PASS) {
return result;
}
}
return ActionResult.PASS;
在 [[https://github.com/FabricMC/fabric|Fabric API]] 中,我们在回调类的顶部添加 Javadoc 注释,以阐明每个 ActionResult 所做了什么。在我们的例子中,可能是:
/**
* 剪羊毛时的回调。
* 剪羊毛并掉落物品、物品被损害之前调用。
* 会返回:
* - SUCCESS 退出后续处理过程,然后继续进行正常剪羊毛行为。
* - PASS 回落到后续处理过程,如果没有其他的监听器了,则默认为 SUCCESS。
* - FAIL 退出后续处理过程,羊毛不会被剪掉。
/**
==== 从 Mixin 中触发事件 ====
我们有了基本的事件框架,但我们还需要触发它。由于我们需要让事件在玩家尝试剪羊毛时调用,我们在 ''SheepEntity#interactMod'' 中,当 ''dropItems'' 调用的时候(即羊可以被剪且玩家拿着剪刀),调用事件调用器。
@Mixin(SheepEntity.class)
public class SheepShearMixin {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/passive/SheepEntity;dropItems()V"), method = "interactMob", cancellable = true)
private void onShear(final PlayerEntity player, final Hand hand, final CallbackInfoReturnable info) {
ActionResult result = SheepShearCallback.EVENT.invoker().interact(player, (SheepEntity) (Object) this);
if(result == ActionResult.FAIL) {
info.cancel();
}
}
}
在这个简单的 mixin 中,我们调用了事件的调用器(''SheepShearCallback.EVENT.invoker().[...]''),它会调用所有活动的监听器来看看应该做什么,然后根据这个招架一个 ActionResult。如果结果为 FAIL,我们不剪羊毛、掉落物品或者损害玩家的物品(''info.cancel();'')。**确保你在 mixins.json 文件中注册的这个 mixin!**
==== 使用监听器测试事件 ====
我们需要测试我们的事件。你可以在你的初始化方法(或者你需要的其他任何地方)注册一个监听器并在这里添加自定义的逻辑。这里是一个示例,会在绵羊的腿的位置掉落钻石而非羊毛:
SheepShearCallback.EVENT.register((player, sheep) -> {
sheep.setSheared(true);
// 在绵羊的位置妴钻石
ItemStack stack = new ItemStack(Items.DIAMOND);
ItemEntity itemEntity = new ItemEntity(player.world, sheep.x, sheep.y, sheep.z, stack);
player.world.spawnEntity(itemEntity);
return ActionResult.FAIL;
});
注意这个事件还需要设置羊被手动剪,就像在返回 FAIL 的时候自动取消。我们你//不需要//取消这个事件,确保你返回了 ''PASS'' 从而其他的监听器也可以操作。如果不遵守这个不成文规则,可能影响其他模组开发者对此模组的兼容性。
如果你进入游戏并给绵羊剪羊毛,掉落的就会是钻石而不是羊毛。