我的世界1.20.1forge模组开发进阶教程——序列化(1)
mc的序列化
在《Minecraft》(MC)中,序列化指将游戏数据(如方块、实体、玩家状态等)转换为可存储或传输的格式。这是游戏运行、存档保存和网络通信的关键技术。以下是Minecraft中常见的序列化方式及其用途:
一、序列化在Minecraft中的作用
- 存档数据持久化
将玩家建筑、地图、物品栏等数据保存到硬盘(如.minecraft/saves
中的区域文件)。 - 网络传输
服务器与客户端同步方块更新、实体移动等实时数据。 - NBT数据存储
存储复杂结构数据(如箱子内的物品、附魔属性)。
二、Minecraft中的序列化方式
1. NBT(Named Binary Tag)
• 特点:Minecraft专用的二进制序列化格式,高效压缩且支持嵌套结构。
• 用途:
◦ 存储物品、实体、方块的附加数据(如箱子内的物品、生物属性)。
◦ 存档文件(如level.dat
)和结构文件(.nbt
)的核心格式。
• 示例:
通过命令生成一个带NBT数据的钻石剑:
mcfunction give @p diamond_sword{Enchantments:[{id:sharpness,lvl:5}]}
2. Region文件(Anvil格式)
• 特点:将地图划分为32x32区块的区域文件(.mca
),使用压缩算法(如GZip)存储方块数据。
• 用途:游戏存档(如world/region
目录)的核心存储格式。
3. JSON序列化
• 特点:文本格式,可读性强,但体积较大。
• 用途:
◦ 数据包(datapacks
)和资源包的配置(如战利品表、进度条件)。
◦ 部分Mod的配置文件(如模组物品的生成规则)。
4. Protocol Buffers(Protobuf)
• 特点:二进制协议,高效压缩,用于网络通信。
• 用途:Minecraft多人服务器的数据包传输(如方块更新、实体移动)。
5. Base64编码
• 特点:将二进制数据转换为文本,便于嵌入其他格式。
• 用途:
◦ 在命令中存储自定义物品的NBT数据(如告示牌文本)。
◦ 部分Mod的跨存档数据交换。
三、如何操作Minecraft的序列化数据?
- 游戏内工具:
• 使用/data
命令读取或修改NBT数据。
• 通过数据包(datapacks
)自定义JSON规则。 - 外部工具:
• NBTExplorer:可视化编辑存档的NBT数据。
• MCEdit:直接修改Region文件中的方块。
• 调试屏幕:按F3 + H显示物品的NBT标签。
四、不同场景下的选择建议
• 存档编辑:优先使用NBTExplorer或命令修改NBT。
• 模组开发:JSON配置 + NBT数据操作。
• 服务器优化:Protobuf传输减少带宽占用。
如果需要更具体的操作示例(如自定义NBT结构或数据包配置),可以进一步补充说明! 🎮
Holder接口
代码解释与 Biome
类的联系
1. Holder<T>
接口的核心功能
Holder<T>
是 Minecraft 中用于 管理注册表对象引用 的核心接口,主要用于以下场景:
• 资源键(ResourceKey)绑定:将对象(如 Biome
)与唯一的 ResourceKey
关联,例如 ResourceKey<Biome> FOREST = ResourceKey.create(Registries.BIOME, new ResourceLocation("forest"))
。
• 标签(Tag)支持:通过 is(TagKey<T>)
和 tags()
方法,判断对象是否属于某个标签集合(如生物群系标签 is_forest
),并遍历所有关联标签。
• 动态加载与序列化:通过 canSerializeIn
和 unwrap
方法,支持跨数据包的资源引用和序列化。
2. Direct
与 Reference
的实现差异
• Direct
实现
直接包装一个对象实例,不依赖注册表系统。例如:
Holder<Biome> directBiome = Holder.direct(new Biome(...)); // 临时生物群系实例
特点:
• 不可绑定资源键或标签(is(ResourceKey)
始终返回 false
)。
• 适用于未注册的临时对象或测试场景。
• Reference
实现
通过 ResourceKey
间接引用注册表中的对象,支持动态绑定。例如:
// 创建未绑定的引用
Holder.Reference<Biome> biomeRef = Holder.Reference.createStandAlone(registryOwner, FOREST_KEY);
// 注册时绑定键和值
biomeRef.bindKey(FOREST_KEY);
biomeRef.bindValue(forestBiomeInstance);
特点:
• 绑定后可通过 key()
获取资源键,value()
获取实例。
• 支持标签系统(通过 bindTags
关联标签集合)。
3. 与 Biome
类的直接联系
在 Minecraft 生物群系系统中,Holder<Biome>
用于:
• 注册表管理
每个生物群系实例(如 Biome.FOREST
)在注册时会被包装为 Holder.Reference<Biome>
,绑定到唯一的 ResourceKey
:
// 伪代码示例:生物群系注册流程
Registry<Biome> biomeRegistry = ...;
Holder.Reference<Biome> holder = biomeRegistry.register(FOREST_KEY, forestBiome);
holder.bindTags(Set.of(BiomeTags.IS_FOREST)); // 关联标签
• 标签查询
游戏逻辑通过 holder.is(BiomeTags.IS_FOREST)
判断生物群系属性,影响生成规则(如树木生成)。
• 数据包支持
Holder
的序列化能力(canSerializeIn
)允许生物群系配置通过数据包动态修改,无需重启游戏。
4. 关键方法在 Biome
中的应用场景
• value()
获取实际的 Biome
实例,用于访问气候、生成设置等属性:
Biome biome = holder.value();
float temperature = biome.getBaseTemperature();
• tags()
遍历生物群系的所有标签,用于世界生成时的条件判断:
holder.tags().forEach(tag -> {
if (tag.equals(BiomeTags.IS_OVERWORLD)) {
// 生成主世界特有结构
}
});
• unwrapKey()
在保存世界数据时,通过资源键(而非实例)持久化生物群系引用,避免序列化整个对象:
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
key.ifPresent(k -> nbt.putString("biome", k.location().toString()));
5. 设计意义与性能优化
• 懒加载与动态绑定
Reference
允许生物群系实例在注册表加载完成前暂不绑定,减少启动时的内存压力。
• 标签缓存
bindTags
将标签集合存储为不可变 Set
,加速 is(TagKey)
的查询速度。
• 安全性
IllegalStateException
确保未绑定的 Holder
不会被误用(如尝试访问未初始化的 value()
)。
总结
Holder<T>
是 Minecraft 注册表系统的核心抽象,为 Biome
等注册对象提供 资源键管理、标签系统集成、跨数据包序列化 等功能。通过 Direct
和 Reference
两种实现,既支持临时对象的快速访问,又满足正式注册资源的动态加载与高效查询需求。在生物群系生成、世界保存、数据包编辑等场景中,Holder<Biome>
是连接代码逻辑与配置数据的关键桥梁。
Codec
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.mojang.serialization;
import com.mojang.datafixers.DataFixUtils;
import com.mojang.datafixers.util.Either;
import com.mojang.datafixers.util.Pair;
import com.mojang.datafixers.util.Unit;
import com.mojang.serialization.codecs.CompoundListCodec;
import com.mojang.serialization.codecs.EitherCodec;
import com.mojang.serialization.codecs.EitherMapCodec;
import com.mojang.serialization.codecs.KeyDispatchCodec;
import com.mojang.serialization.codecs.ListCodec;
import com.mojang.serialization.codecs.OptionalFieldCodec;
import com.mojang.serialization.codecs.PairCodec;
import com.mojang.serialization.codecs.PairMapCodec;
import com.mojang.serialization.codecs.PrimitiveCodec;
import com.mojang.serialization.codecs.SimpleMapCodec;
import com.mojang.serialization.codecs.UnboundedMapCodec;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public interface Codec<A> extends Encoder<A>, Decoder<A> {
PrimitiveCodec<Boolean> BOOL = new PrimitiveCodec<Boolean>() {
public <T> DataResult<Boolean> read(DynamicOps<T> ops, T input) {
return ops.getBooleanValue(input);
}
public <T> T write(DynamicOps<T> ops, Boolean value) {
return (T)ops.createBoolean(value);
}
public String toString() {
return "Bool";
}
};
PrimitiveCodec<Byte> BYTE = new PrimitiveCodec<Byte>() {
public <T> DataResult<Byte> read(DynamicOps<T> ops, T input) {
return ops.getNumberValue(input).map(Number::byteValue);
}
public <T> T write(DynamicOps<T> ops, Byte value) {
return (T)ops.createByte(value);
}
public String toString() {
return "Byte";
}
};
PrimitiveCodec<Short> SHORT = new PrimitiveCodec<Short>() {
public <T> DataResult<Short> read(DynamicOps<T> ops, T input) {
return ops.getNumberValue(input).map(Number::shortValue);
}
public <T> T write(DynamicOps<T> ops, Short value) {
return (T)ops.createShort(value);
}
public String toString() {
return "Short";
}
};
PrimitiveCodec<Integer> INT = new PrimitiveCodec<Integer>() {
public <T> DataResult<Integer> read(DynamicOps<T> ops, T input) {
return ops.getNumberValue(input).map(Number::intValue);
}
public <T> T write(DynamicOps<T> ops, Integer value) {
return (T)ops.createInt(value);
}
public String toString() {
return "Int";
}
};
PrimitiveCodec<Long> LONG = new PrimitiveCodec<Long>() {
public <T> DataResult<Long> read(DynamicOps<T> ops, T input) {
return ops.getNumberValue(input).map(Number::longValue);
}
public <T> T write(DynamicOps<T> ops, Long value) {
return (T)ops.createLong(value);
}
public String toString() {
return "Long";
}
};
PrimitiveCodec<Float> FLOAT = new PrimitiveCodec<Float>() {
public <T> DataResult<Float> read(DynamicOps<T> ops, T input) {
return ops.getNumberValue(input).map(Number::floatValue);
}
public <T> T write(DynamicOps<T> ops, Float value) {
return (T)ops.createFloat(value);
}
public String toString() {
return "Float";
}
};
PrimitiveCodec<Double> DOUBLE = new PrimitiveCodec<Double>() {
public <T> DataResult<Double> read(DynamicOps<T> ops, T input) {
return ops.getNumberValue(input).map(Number::doubleValue);
}
public <T> T write(DynamicOps<T> ops, Double value) {
return (T)ops.createDouble(value);
}
public String toString() {
return "Double";
}
};
PrimitiveCodec<String> STRING = new PrimitiveCodec<String>() {
public <T> DataResult<String> read(DynamicOps<T> ops, T input) {
return ops.getStringValue(input);
}
public <T> T write(DynamicOps<T> ops, String value) {
return (T)ops.createString(value);
}
public String toString() {
return "String";
}
};
PrimitiveCodec<ByteBuffer> BYTE_BUFFER = new PrimitiveCodec<ByteBuffer>() {
public <T> DataResult<ByteBuffer> read(DynamicOps<T> ops, T input) {
return ops.getByteBuffer(input);
}
public <T> T write(DynamicOps<T> ops, ByteBuffer value) {
return (T)ops.createByteList(value);
}
public String toString() {
return "ByteBuffer";
}
};
PrimitiveCodec<IntStream> INT_STREAM = new PrimitiveCodec<IntStream>() {
public <T> DataResult<IntStream> read(