Skip to main content
Version: 1.21.4

功能标志(Feature Flags)

功能标志(Feature flags)是一套允许开发者通过一组标志来控制某些功能开放与否的系统。这些受控内容可以是已注册的元素、游戏机制、数据包条目,或者你的模组中特有的其他系统。

一个常见的用例是将实验性功能或元素放在一个实验标志(experimental flag)后面,这样用户就可以方便地开启这些内容并进行尝试,而无需等到功能最终定稿。

tip

你并不是必须自己添加新的标志。如果你发现原版已有的标志正好适合你的需求,可以直接用该标志为你的方块、物品、生物等进行标记。

比如在 1.21.3 版本中,如果你想扩展 Pale Oak 木材方块系列,通常只希望这些方块在 WINTER_DROP 标志被启用时才出现。

创建功能标志(Feature Flag)

要创建新的功能标志,需要新建一个 JSON 文件,并在你的 neoforge.mods.toml 文件的 [[mods]] 块中,通过 featureFlags 条目进行引用。指定的路径必须是相对于 resources 目录的:

# 在 neoforge.mods.toml 文件中:
[[mods]]
# 文件路径相对于资源输出目录,或编译后 jar 包内的根路径
# 'resources' 目录即资源文件的根输出目录
featureFlags="META-INF/feature_flags.json"

该条目的定义包括一个功能标志名称的列表,这些标志会在游戏初始化时加载并注册。

{
"flags": [
// 要注册的功能标志的标识符
"examplemod:experimental"
]
}

获取功能标志(Feature Flag)

已注册的功能标志可以通过 FeatureFlagRegistry.getFlag(ResourceLocation) 获取。你可以在模组初始化的任意阶段进行获取,建议获取后存储在某处以便后续使用,而不是每次需要标志时都去查询注册表(Registry)。

// 查找 'examplemod:experimental' 功能标志
public static final FeatureFlag EXPERIMENTAL = FeatureFlags.REGISTRY.getFlag(ResourceLocation.fromNamespaceAndPath("examplemod", "experimental"));

功能元素(Feature Elements)

FeatureElement 是指可以被赋予一组所需标志(required flags)的注册值(registry value)。只有当关卡中启用的标志与这些元素所需的标志相匹配时,这些元素才会对玩家可用。

当某个功能元素被禁用时,它会完全从玩家视野中隐藏,所有相关交互都会被跳过。需要注意的是,这些被禁用的元素依然存在于注册表(Registry)中,只是无法被实际使用。

下列注册表是直接实现了 FeatureElement 系统的完整列表:

  • Item(物品)
  • Block(方块)
  • EntityType(实体类型)
  • MenuType(菜单类型)
  • Potion(药水)
  • MobEffect(生物效果)

标记元素(Flagging Elements)

要将某个 FeatureElement 标记为需要你的功能标志,只需在对应的注册方法中,将该标志及其他所需标志一同传入即可:

  • ItemItem.Properties#requiredFeatures
  • BlockBlockBehaviour.Properties#requiredFeatures
  • EntityTypeEntityType.Builder#requiredFeatures
  • MenuTypeMenuType#new
  • PotionPotion#requiredFeatures
  • MobEffectMobEffect#requiredFeatures
// 这些元素只有在启用 'EXPERIMENTAL'(实验性)标志后才会可用

// 物品(Item)
DeferredRegister.Items ITEMS = DeferredRegister.createItems("examplemod");
DeferredItem<Item> EXPERIMENTAL_ITEM = ITEMS.registerSimpleItem("experimental", new Item.Properties()
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);

// 方块(Block)
DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("examplemod");
// 请注意,BlockBehaviour.Properties#ofFullCopy 和 BlockBehaviour.Properties#ofLegacyCopy 会复制所需的特性标志。
// 这意味着在 1.21.3 版本中,使用 BlockBehaviour.Properties.ofFullCopy(Blocks.PALE_OAK_WOOD) 会让你的方块需要 'WINTER_DROP'(冬季掉落)标志。
DeferredBlock<Block> EXPERIMENTAL_BLOCK = BLOCKS.registerSimpleBlock("experimental", BlockBehaviour.Properties.of()
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);

// BlockItem 有一个特殊之处,它所需的特性标志会从其对应的 Block 继承。
// 生成蛋(spawn eggs)和它们对应的 EntityType 也是如此。
DeferredItem<BlockItem> EXPERIMENTAL_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(EXPERIMENTAL_BLOCK);

// 实体类型(EntityType)
DeferredRegister<EntityType<?>> ENTITY_TYPES = DeferredRegister.create(Registries.ENTITY_TYPE, "examplemod");
DeferredHolder<EntityType<?>, EntityType<ExperimentalEntity>> EXPERIMENTAL_ENTITY = ENTITY_TYPES.register("experimental", registryName -> EntityType.Builder.of(ExperimentalEntity::new, MobCategory.AMBIENT)
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
.build(ResourceKey.create(Registries.ENTITY_TYPE, registryName))
);

// 菜单类型(MenuType)
DeferredRegister<MenuType<?>> MENU_TYPES = DeferredRegister.create(Registries.MENU, "examplemod");
DeferredHolder<MenuType<?>, MenuType<ExperimentalMenu>> EXPERIMENTAL_MENU = MENU_TYPES.register("experimental", () -> new MenuType<>(
// 使用原版的 MenuSupplier:
// 当你的菜单在 `player.openMenu` 期间没有编码复杂数据时使用。例如:
// (windowId, inventory) -> new ExperimentalMenu(windowId, inventory),

// 使用 NeoForge 的 IContainerFactory:
// 当你希望在 `player.openMenu` 期间读取编码的复杂数据时使用。
// 这里的类型转换很重要,因为 `MenuType` 明确要求一个 `MenuSupplier`。
(IContainerFactory<ExperimentalMenu>) (windowId, inventory, buffer) -> new ExperimentalMenu(windowId, inventory, buffer),

FeatureFlagSet.of(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
));

// 生物效果(MobEffect)
DeferredRegister<MobEffect> MOB_EFFECTS = DeferredRegister.create(Registries.MOB_EFFECT, "examplemod");
DeferredHolder<MobEffect, ExperimentalMobEffect> EXPERIMENTAL_MOB_EEFECT = MOB_EFFECTS.register("experimental", registryName -> new ExperimentalMobEffect(MobEffectCategory.NEUTRAL, CommonColors.WHITE)
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);

// 药水(Potion)
DeferredRegister<Potion> POTIONS = DeferredRegister.create(Registries.POTION, "examplemod");
DeferredHolder<Potion, ExperimentalPotion> EXPERIMENTAL_POTION = POTIONS.register("experimental", registryName -> new ExperimentalPotion(registryName.toString(), new MobEffectInstance(EXPERIMENTAL_MOB_EEFECT))
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);

验证启用状态(Validating Enabled Status)

要判断某些功能(features)是否应该被启用,首先需要获取已启用功能的集合。获取方式有多种,但最常用且推荐的方法是通过 LevelReader#enabledFeatures

level.enabledFeatures(); // 从一个 'LevelReader' 实例获取
entity.level().enabledFeatures(); // 从一个 'Entity' 实例获取

// 客户端
minecraft.getConnection().enabledFeatures();

// 服务端
server.getWorldData().enabledFeatures();

如果需要判断某个 FeatureFlagSet 是否被启用,可以将已启用功能集传递给 FeatureFlagSet#isSubsetOf。若要判断具体的某个 FeatureElement 是否启用,可以调用 FeatureElement#isEnabled

note

ItemStack 提供了一个特殊的 isItemEnabled(FeatureFlagSet) 方法。这样即使空的物品堆(stack)所需的功能与已启用功能不匹配,也会被视为已启用。建议尽可能优先使用此方法,而不是 Item#isEnabled

requiredFeatures.isSubsetOf(enabledFeatures); // 判断所需功能集合是否为已启用集合的子集
featureElement.isEnabled(enabledFeatures); // 判断某个功能元素是否被启用
itemStack.isItemEnabled(enabledFeatures); // 判断物品堆是否被启用

功能包(Feature Packs)

另见:资源包数据包Pack.mcmeta

功能包(Feature Packs)是一种特殊的数据包,不仅可以加载资源和/或数据,还可以切换一组功能标志(feature flags)的启用状态。这些标志在功能包根目录下的 pack.mcmeta JSON 文件中定义,格式如下:

note

此文件与模组 resources/ 目录下的文件不同。它定义的是一个全新的功能包,因此必须放在独立的文件夹中。

{
"features": {
"enabled": [
// 要启用的功能标志的标识符
// 必须是已注册的有效标志
"examplemod:experimental"
]
},
"pack": { /*...*/ }
}

用户获取功能包的方式主要有两种:一是作为数据包(datapack)从外部来源安装,二是下载带有内置功能包的模组。两种方式的安装方法会根据 物理端 的不同而有所区别。

内置(Built-In)

内置包(Built-in packs)会随你的模组一起打包,并通过 AddPackFindersEvent 事件向游戏注册。

@SubscribeEvent
public static void addFeaturePacks(final AddPackFindersEvent event) {
event.addPackFinders(
// 路径是相对于你的模组 'resources' 目录,指向此数据包
// 注意,这也定义了你的数据包 id,格式如下
// mod/<namespace>:<path>,例如:mod/examplemod:data/examplemod/datapacks/experimental
ResourceLocation.fromNamespaceAndPath("examplemod", "data/examplemod/datapacks/experimental"),

// 此数据包包含的资源类型
// 'CLIENT_RESOURCES' 用于包含客户端资源(资源包)
// 'SERVER_DATA' 用于包含服务端数据(数据包)
PackType.SERVER_DATA,

// 在实验界面中显示的名称
Component.literal("ExampleMod: Experiments"),

// 为了让此数据包加载并启用功能标志,必须设置为 'FEATURE',
// 其他任何 PackSource 类型在这里都是无效的
PackSource.FEATURE,

// 如果为 true,则该数据包始终激活且无法禁用,功能包应始终为 false
false,

// 加载此数据包资源的优先级
// 'TOP' 表示此数据包优先于其他数据包加载
// 'BOTTOM' 表示其他数据包优先于此数据包加载
Pack.Position.TOP
);
}

单人模式下的启用方法(Enabling in Singleplayer)

  1. 创建一个新世界。
  2. 进入实验(Experiments)界面。
  3. 打开你想启用的数据包。
  4. 点击 完成(Done)以确认更改。

多人模式下的启用方法(Enabling in Multiplayer)

  1. 打开你的服务器的 server.properties 文件。
  2. 将功能包 id 添加到 initial-enabled-packs,多个包用 , 分隔。数据包 id 在注册 pack finder 时定义,如上所示。

外部功能包(External)

外部功能包以数据包(datapack)的形式提供给你的用户。

单人模式下的安装方法(Installation in Singleplayer)

  1. 创建一个新世界。
  2. 进入数据包选择界面。
  3. 将数据包 zip 文件拖放到游戏窗口中。
  4. 将新出现的数据包移动到 已选中(Selected)数据包列表中。
  5. 点击 完成(Done)以确认更改。

游戏现在会警告你新选择的实验性功能、潜在的 bug、问题及崩溃风险。你可以点击 继续(Proceed)确认更改,或点击 详情(Details)查看所有已选数据包及其将启用的功能的详细列表。

note

外部功能包不会出现在实验(Experiments)界面中。实验界面只会显示内置功能包。

要在启用后禁用外部功能包,请重新进入数据包界面,并将外部数据包从 已选中(Selected)移动回 可用(Available)。

多人模式下的安装方法(Installation in Multiplayer)

功能包只能在世界初次创建时启用,启用后无法禁用。

  1. 创建目录 ./world/datapacks
  2. 将数据包(datapack)压缩包上传到新建的目录中
  3. 打开你的服务器的 server.properties 文件
  4. 将数据包压缩包的名称(不包含 .zip 后缀)添加到 initial-enabled-packs 配置项中(多个数据包用 , 分隔)
    • 例如:如果压缩包名为 examplemod-experimental.zip,则应这样添加:initial-enabled-packs=vanilla,examplemod-experimental

数据生成(Data Generation)

另见:数据生成

特性包(feature pack)可以在常规的模组数据生成(datagen)过程中自动生成。这种方式最适合与内置数据包(built-in packs)结合使用,但你也可以将生成的内容打包成 zip 文件,作为外部数据包分发。两者任选其一即可,即不要同时作为外部数据包和内置数据包提供。

@SubscribeEvent
public static void gatherData(final GatherDataEvent.Client event) {
DataGenerator generator = event.getGenerator();

// 要生成特性包,首先需要为目标数据包获取一个 pack generator 实例。
// generator.getBuiltinDatapack(<shouldGenerate>, <namespace>, <path>);
// 这将在如下路径生成特性包:
// ./data/<namespace>/datapacks/<path>
PackGenerator featurePack = generator.getBuiltinDatapack(true, "examplemod", "experimental");

// 注册一个 provider 用于生成 `pack.mcmeta` 文件。
featurePack.addProvider(output -> PackMetadataGenerator.forFeaturePack(
output,

// 在“实验功能”界面中显示的描述文本
Component.literal("Enabled experimental features for ExampleMod"),

// 该数据包应启用的特性标志集合
FeatureFlagSet.of(EXPERIMENTAL)
));

// 可为 `featurePack` 注册更多 provider(如合成配方、战利品表等),
// 这样生成的资源会写入此特性包,而不是主数据包。
}