数据附加 API 是 Fabric API 中一项近期推出的实验性功能, 允许开发者轻松地将任意数据附加到实体、方块实体、Level 和区块。 附加的数据可以通过 Codec 和流 Codec 进行存储和同步,因此在使用前你应该熟悉这些 Codec。
创建数据附加
首先,你需要调用 AttachmentRegistry.create 方法。 以下示例创建一个基本数据附加,不会在重启后同步或保留。
java
public static final AttachmentType<String> EXAMPLE_STRING_ATTACHMENT = AttachmentRegistry.create(
Identifier.fromNamespaceAndPath("example-mod", "example_string_attachment") // The ID of your Attachment
);1
2
3
2
3
AttachmentRegistry 包含一些用于创建基本数据附加的方法,包括:
AttachmentRegistry.create():创建一个数据附加。 重启游戏会清除附加。AttachmentRegistry.createPersistent():创建一个在游戏重启后仍然有效的数据附加。AttachmentRegistry.createDefaulted():创建一个带有默认值的数据附加,可以使用getAttachedOrCreate读取该默认值。 重启游戏会清除附加。
使用用方法链模式,使用 create 的 builder 参数来复制和进一步自定义每个方法的行为。
同步数据附加
如果需要数据附加在服务器和客户端之间既持久又同步,可以使用 create 方法设置此行为,允许通过 builder 链来配置。 例如:
java
public static final AttachmentType<BlockPos> EXAMPLE_BLOCK_POS_ATTACHMENT = AttachmentRegistry.create(
Identifier.fromNamespaceAndPath("example-mod", "example_block_pos_attachment"),
builder -> builder
.initializer(() -> new BlockPos(0, 0, 0)) // The default value of the Attachment, if one has not been set.
.syncWith(
BlockPos.STREAM_CODEC, // Dictates how to turn the data into a packet to send to clients.
AttachmentSyncPredicate.all() // Dictates who to send the data to.
)
);1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
上面的例子会同步给每位玩家,但可能不适用于你的使用例。 这是一些其他默认的谓词,但你也可以通过引用 AttachmentSyncPredicate 类来构建自己的。
AttachmentSyncPredicate.all():将附加同步给所有客户端。AttachmentSyncPredicate.targetOnly():仅将附加同步给附加到的目标。 注意只有当目标是玩家时,才会发生同步。AttachmentSyncPredicate.allButTarget():将数据同步给所有客户端,除了被附加到的目标。 注意只有当目标是玩家时,才会有此例外。
持久数据附加
数据附加也可以在游戏重启时保持,在 builder 链调用 persistent 方法即可, 此方法接收一个 Codec 这样游戏知道如何序列化数据。
还可以设置为即使是目标死后或转化后也维持,调用 copyOnDeath 方法即可。
java
public static final AttachmentType<BlockPos> EXAMPLE_PERSISTENT_ATTACHMENT = AttachmentRegistry.create(
Identifier.fromNamespaceAndPath("example-mod", "example_block_pos_attachment"),
builder -> builder
.initializer(() -> new BlockPos(0, 0, 0)) // The default value of the Attachment, if one has not been set.
.persistent(BlockPos.CODEC) // Dictates how this Attachment's data should be saved and loaded.
.copyOnDeath() // Dictates that this Attachment should persist even after the entity dies or converts.
);1
2
3
4
5
6
7
2
3
4
5
6
7
从数据附加中读取
用于从数据附加中读取的方法已被注入到 Entity、BlockEntity、ServerLevel 和 ChunkAccess 类。 就像调用其他常规方法一样,返回值是附加的数据。
java
// Checks if the given AttachmentType has attached data, returning a boolean.
entity.hasAttached(EXAMPLE_STRING_ATTACHMENT);
// Gets the data associated with the given AttachmentType, or `null` if it doesn't exist.
entity.getAttached(EXAMPLE_STRING_ATTACHMENT);
// Gets the data associated with the given AttachmentType, throwing a `NullPointerException` if it doesn't exist.
entity.getAttachedOrThrow(EXAMPLE_STRING_ATTACHMENT);
// Gets the data associated with the given AttachmentType, setting the value if it doesn't exist.
entity.getAttachedOrSet(EXAMPLE_STRING_ATTACHMENT, "basic");
entity.getAttachedOrSet(EXAMPLE_BLOCK_POS_ATTACHMENT, new BlockPos(0, 0, 0););
// Gets the data associated with the given AttachmentType, returning the provided value if it doesn't exist.
entity.getAttachedOrElse(EXAMPLE_STRING_ATTACHMENT, "basic");
entity.getAttachedOrElse(EXAMPLE_BLOCK_POS_ATTACHMENT, new BlockPos(0, 0, 0););1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
写入数据附加
用于向数据附加中写入的方法已被注入到 Entity、BlockEntity、ServerLevel 和 ChunkAccess 类。 调用以下方法之一会更新被附加的数据的值,返回此前的值(如果此前没有则为 null)。
java
// Sets the data associated with the given AttachmentType, returning the previous value.
entity.setAttached(EXAMPLE_STRING_ATTACHMENT, "new value");
// Modifies the data associated with the given AttachmentType in place, returning the currently attached value. Note that currentValue is null if there is no previously attached data.
entity.modifyAttached(EXAMPLE_STRING_ATTACHMENT, currentValue -> "The length was " + (currentValue == null ? 0 : currentValue.length()));
// Removes the data associated with the given AttachmentType, returning the previous value.
entity.removeAttached(EXAMPLE_STRING_ATTACHMENT);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
WARNING
数据附加始终都应使用不可变的类型,也仅应使用 API 的方法来更新, 否则可能会导致数据附加不能持续或恰当同步。
更大的附加
尽管数据附加可以以 codec 可写入的方式存储任何形式的数据,但只对同步的单个的数据时恰到好处, 这是因为数据附加是不可变的,修改其一部分值(例如对象的单个字段)意味着需要整个替换,并触发同步到每个追踪该数据附加的客户端。
不过,你可以将其拆分为多个字段,并使用一个辅助类来组织,实现复杂些的附加。 例如,如果需要两个有关玩家的体力值的字段,可以像这样:
java
public class Stamina {
private static final AttachmentType<Integer> CURRENT_STAMINA = AttachmentRegistry.create(
Identifier.fromNamespaceAndPath("example-mod", "current_stamina"),
builder -> builder.syncWith(ByteBufCodecs.INT, AttachmentSyncPredicate.all())
);
private static final AttachmentType<Integer> MAX_STAMINA = AttachmentRegistry.create(
Identifier.fromNamespaceAndPath("example-mod", "max_stamina"),
builder -> builder.syncWith(ByteBufCodecs.INT, AttachmentSyncPredicate.all())
);
public static StaminaData get(AttachmentTarget target) {
return new StaminaData(target);
}
public record StaminaData(AttachmentTarget target) {
public int getCurrentStamina() {
return target.getAttachedOrElse(CURRENT_STAMINA, 0);
}
public int decrementCurrentStamina() {
return target.modifyAttached(CURRENT_STAMINA, currentStamina -> currentStamina-1);
}
public void setCurrentStamina(int value) {
target.setAttached(CURRENT_STAMINA, value);
}
public int getMaxStamina() {
return target.getAttachedOrElse(MAX_STAMINA, 0);
}
public void setMaxStamina(int value) {
target.setAttached(MAX_STAMINA, value);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
辅助类可以这样使用:
java
Player player = getPlayer();
Stamina.get(player).getCurrentStamina();1
2
2


