zh_cn:tutorial:persistent_states
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionNext revisionBoth sides next revision | ||
zh_cn:tutorial:persistent_states [2023/12/15 08:10] – created & translation started dreamuniverse | zh_cn:tutorial:persistent_states [2023/12/21 07:51] – translating player specified persistent states dreamuniverse | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== 状态持久化 ====== | ====== 状态持久化 ====== | ||
+ | |||
+ | ===== 引言 ===== | ||
通常情况下,我们的模组中带有与玩家相关的信息,或是在玩家死亡、服务器重启时我们希望保留的世界信息。\\ | 通常情况下,我们的模组中带有与玩家相关的信息,或是在玩家死亡、服务器重启时我们希望保留的世界信息。\\ | ||
Line 16: | Line 18: | ||
那么,我们如何能让 Fabric 保存这些信息,或者其他我们想保存的模组或游戏数据,以供下次玩家加载世界或重新登录时能读取到这些信息? | 那么,我们如何能让 Fabric 保存这些信息,或者其他我们想保存的模组或游戏数据,以供下次玩家加载世界或重新登录时能读取到这些信息? | ||
- | ====== 简单的消息发送——数据包 | + | ===== 简单的消息发送——数据包 ===== |
首先,既然我们的游戏保存在逻辑服务器上,那么我们就让服务器在检测到玩家挖掘泥土方块时向玩家发送一个数据包,并将其显示在玩家的聊天区内。\\ | 首先,既然我们的游戏保存在逻辑服务器上,那么我们就让服务器在检测到玩家挖掘泥土方块时向玩家发送一个数据包,并将其显示在玩家的聊天区内。\\ | ||
Line 103: | Line 105: | ||
那么,如果我们把上次游戏结束时的计数写入到了本地(如硬盘某处),我们就可以在游戏启动时读取并加载它,作为计数器的初始值。这样一来我们就可以继续统计下去。\\ | 那么,如果我们把上次游戏结束时的计数写入到了本地(如硬盘某处),我们就可以在游戏启动时读取并加载它,作为计数器的初始值。这样一来我们就可以继续统计下去。\\ | ||
但是,我们**必须**要做到这一点:数值要在游戏关闭时保存,在游戏启动时读取并加载。\\ | 但是,我们**必须**要做到这一点:数值要在游戏关闭时保存,在游戏启动时读取并加载。\\ | ||
- | 想要达到这一目的,方法千千万,不过我们在这里使用 Minecraft 提供给我们的方法:实现一个扩展了 ' | + | 想要达到这一目的,方法千千万,不过我们在这里使用 Minecraft 提供给我们的方法:实现一个继承了 '' |
- | ====== 状态持久化 | + | ===== 状态持久化详述 |
首先,我们在项目目录新建一个名为 '' | 首先,我们在项目目录新建一个名为 '' | ||
Line 128: | Line 130: | ||
</ | </ | ||
- | 注:在扩展 | + | 注:在继承 |
- | * '' | + | * '' |
+ | | ||
- | Next add the following function to that same file: | + | 接下来,将这个方法添加到同一个文件中: |
<code java> | <code java> | ||
public class StateSaverAndLoader extends PersistentState { | public class StateSaverAndLoader extends PersistentState { | ||
- | // ... (Previously written code) | + | // ... (先前写好的代码部分) |
public static StateSaverAndLoader createFromNbt(NbtCompound tag) { | public static StateSaverAndLoader createFromNbt(NbtCompound tag) { | ||
Line 147: | Line 150: | ||
</ | </ | ||
- | This function does the opposite of '' | + | 这个方法的作用与 |
- | * Note: how we pull out the int we stored earlier with '' | + | * 注:与 |
- | Now we just need to add one more utility function which hooks everything up together. This function will take a '' | + | 现在我们再添加一个工具类方法,这个方法需要导入 |
+ | 当创建时,其调用我们刚刚写到的 | ||
<code java> | <code java> | ||
public class StateSaverAndLoader extends PersistentState { | public class StateSaverAndLoader extends PersistentState { | ||
- | // ... (Previously written code) | + | // ... (先前写好的代码部分) |
private static Type< | private static Type< | ||
- | StateSaverAndLoader:: | + | StateSaverAndLoader:: |
- | StateSaverAndLoader:: | + | StateSaverAndLoader:: |
- | null // Supposed to be an ' | + | null // 此处理论上应为 |
); | ); | ||
public static StateSaverAndLoader getServerState(MinecraftServer server) { | public static StateSaverAndLoader getServerState(MinecraftServer server) { | ||
- | // (Note: arbitrary choice to use ' | + | // (注:如需在任意维度生效,请使用 |
PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager(); | PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager(); | ||
- | // The first time the following | + | // 当第一次调用了方法 |
- | // stores it inside the ' | + | // ' |
- | // ' | + | |
StateSaverAndLoader state = persistentStateManager.getOrCreate(type, | StateSaverAndLoader state = persistentStateManager.getOrCreate(type, | ||
- | // If state is not marked | + | // 若状态未标记为脏(dirty),当 |
- | // Technically it's ' | + | // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 |
- | // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to ' | + | // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 |
- | // Besides, it's literally just setting a bool to true, and the only time there' | + | // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见) |
- | // there were no actual change to any of the mods state (INCREDIBLY RARE). | + | |
state.markDirty(); | state.markDirty(); | ||
Line 185: | Line 187: | ||
</ | </ | ||
- | Your '' | + | 现在,您的 |
<code java> | <code java> | ||
Line 211: | Line 213: | ||
private static Type< | private static Type< | ||
- | StateSaverAndLoader:: | + | StateSaverAndLoader:: |
- | StateSaverAndLoader:: | + | StateSaverAndLoader:: |
- | null // Supposed to be an ' | + | null // 此处理论上应为 |
); | ); | ||
public static StateSaverAndLoader getServerState(MinecraftServer server) { | public static StateSaverAndLoader getServerState(MinecraftServer server) { | ||
- | // (Note: arbitrary choice to use ' | + | // (注:如需在任意维度生效,请使用 |
PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager(); | PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager(); | ||
- | // The first time the following | + | // 当第一次调用了方法 |
- | // stores it inside the ' | + | // ' |
- | // ' | + | |
StateSaverAndLoader state = persistentStateManager.getOrCreate(type, | StateSaverAndLoader state = persistentStateManager.getOrCreate(type, | ||
- | // If state is not marked | + | // 若状态未标记为脏(dirty),当 |
- | // Technically it's ' | + | // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 |
- | // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to ' | + | // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 |
- | // Besides, it's literally just setting a bool to true, and the only time there' | + | // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见) |
- | // there were no actual change to any of the mods state (INCREDIBLY RARE). | + | |
state.markDirty(); | state.markDirty(); | ||
Line 237: | Line 237: | ||
</ | </ | ||
- | You'll also have to update your class which '' | + | 相应地,您也需要将实现了 |
<code java> | <code java> | ||
Line 253: | Line 253: | ||
public class ExampleMod implements ModInitializer { | public class ExampleMod implements ModInitializer { | ||
- | public static final String MOD_ID = "your_unique_mod_id_change_me_please"; | + | public static final String MOD_ID = "您的MOD_ID"; |
public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, | public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, | ||
Line 262: | Line 262: | ||
if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) { | if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) { | ||
StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer()); | StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer()); | ||
- | // Increment the amount of dirt blocks that have been broken | + | // 当泥土方块被挖掘时增加计数 |
serverState.totalDirtBlocksBroken += 1; | serverState.totalDirtBlocksBroken += 1; | ||
- | // Send a packet to the client | + | // 向客户端发送数据包 |
MinecraftServer server = world.getServer(); | MinecraftServer server = world.getServer(); | ||
Line 281: | Line 281: | ||
</ | </ | ||
- | If you run your game now, you should see the counter going up, but now, if you fully close Minecraft, and open it again, you should see the number keeps increasing from where it left off. | + | 如果您现在运行游戏,您会发现计数器正常递增,但现在即使您关闭游戏并重新启动,计数器也会从退出时的位置继续递增。 |
- | What you might or might not expect is that '' | + | 有一点您有可能会忽略,'' |
- | ====== Player Specific Persistent State ====== | + | 这对我们模组中的特定类型数据是很好的,表明它们能正常工作。但更多时候,我们期望数据是玩家限定(因人而异)的。 |
+ | 正如我们开篇所提,如果我们想要保留**任意玩家**的**特定方块**的挖掘数据,这时我们要怎么做? | ||
- | We can store player-specific data by extending what we already wrote. | + | ===== 因玩家而异的持久化状态 ===== |
- | First write a new class '' | + | 我们可以将我们所写的代码延伸一下,这样就可以存储每个玩家的特定数据了。 |
- | | + | 首先,新建一个名为 '' |
+ | |||
+ | * 特别提示: | ||
+ | 如果您期望在客户端侧获知部分或全部的 ''**PlayerData**'' | ||
<code java> | <code java> | ||
Line 298: | Line 302: | ||
</ | </ | ||
- | To simplify, we're just continuing with our simple example, but you could put any fields you'd like in the '' | + | 为尽可能简明地说明问题,在示例代码中我们依然使用这个简单的例子,但您在实际应用中可以向 |
- | Next, we'll modify the top of our '' | + | 接下来,将 |
<code java> | <code java> | ||
- | // ... (Previous imports) | + | // ... (其他的引用包) |
import java.util.HashMap; | import java.util.HashMap; | ||
import java.util.UUID; | import java.util.UUID; | ||
Line 313: | Line 317: | ||
public HashMap< | public HashMap< | ||
- | // ... (Rest of the code) | + | // ... (代码的剩余部分) |
} | } | ||
</ | </ | ||
- | Note: We create a '' | + | 注:我们创建了一个关于 |
+ | Hashmap 即哈希表,简单而言,在本例中,您向表中给出一个特定的“键值”(key),表从 | ||
+ | 我们使用 | ||
- | Let's add a utility function to '' | + | 接下来我们向 |
<code java> | <code java> | ||
public class StateSaverAndLoader extends PersistentState { | public class StateSaverAndLoader extends PersistentState { | ||
- | // ... (Previously written code) | + | // ... (先前写好的代码部分) |
public static PlayerData getPlayerState(LivingEntity player) { | public static PlayerData getPlayerState(LivingEntity player) { | ||
StateSaverAndLoader serverState = getServerState(player.getWorld().getServer()); | StateSaverAndLoader serverState = getServerState(player.getWorld().getServer()); | ||
- | // Either get the player by the uuid, or we don't have data for him yet, make a new player state | + | // 根据 UUID 获取对应玩家的状态,如果没有该玩家的数据,就创建一个新的玩家状态。 |
PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), | PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), | ||
Line 339: | Line 345: | ||
- | * If our '' | + | * 若类 |
- | Now update the class which '' | + | 接下来将实现了 |
<code java> | <code java> | ||
Line 357: | Line 363: | ||
public class ExampleMod implements ModInitializer { | public class ExampleMod implements ModInitializer { | ||
- | public static final String MOD_ID = "your_unique_mod_id_change_me_please"; | + | public static final String MOD_ID = "您的MOD_ID"; |
public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, | public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, | ||
Line 363: | Line 369: | ||
@Override | @Override | ||
public void onInitialize() { | public void onInitialize() { | ||
+ | // 注册一个方块挖掘事件监听器 | ||
PlayerBlockBreakEvents.AFTER.register((world, | PlayerBlockBreakEvents.AFTER.register((world, | ||
if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) { | if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) { | ||
StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer()); | StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer()); | ||
- | // Increment the amount of dirt blocks that have been broken | + | // 当泥土方块被挖掘时增加计数 |
serverState.totalDirtBlocksBroken += 1; | serverState.totalDirtBlocksBroken += 1; | ||
Line 372: | Line 379: | ||
playerState.dirtBlocksBroken += 1; | playerState.dirtBlocksBroken += 1; | ||
- | // Send a packet to the client | + | // 向客户端发送数据包 |
MinecraftServer server = world.getServer(); | MinecraftServer server = world.getServer(); | ||
Line 389: | Line 396: | ||
</ | </ | ||
- | You'll also have to modify the class which '' | + | 您同时需要将实现了 |
<code java> | <code java> | ||
Line 405: | Line 412: | ||
client.execute(() -> { | client.execute(() -> { | ||
- | client.player.sendMessage(Text.literal(" | + | client.player.sendMessage(Text.literal(" |
- | client.player.sendMessage(Text.literal(" | + | client.player.sendMessage(Text.literal(" |
}); | }); | ||
}); | }); | ||
Line 413: | Line 420: | ||
</ | </ | ||
- | If you ran the client now, it would seem as if everything is working, but we are forgetting a crucial step: We haven' | + | 如果您现在运行客户端,看上去一切工作正常,但我们忘记了非常关键的一点:我们没有使用 |
- | The updated functions are as follows: | + | 改动后的方法代码如下所示: |
<code java> | <code java> | ||
public class StateSaverAndLoader extends PersistentState { | public class StateSaverAndLoader extends PersistentState { | ||
- | // ... (Rest of code) | + | // ... (代码的剩余部分) |
@Override | @Override | ||
Line 456: | Line 463: | ||
} | } | ||
- | // ... (Rest of code) | + | // ... (代码的剩余部分) |
} | } | ||
</ | </ | ||
- | The final '' | + | 最终的 |
<code java> | <code java> | ||
Line 515: | Line 522: | ||
private static Type< | private static Type< | ||
- | StateSaverAndLoader:: | + | StateSaverAndLoader:: |
- | StateSaverAndLoader:: | + | StateSaverAndLoader:: |
- | null // Supposed to be an ' | + | null // 此处理论上应为 |
); | ); | ||
public static StateSaverAndLoader getServerState(MinecraftServer server) { | public static StateSaverAndLoader getServerState(MinecraftServer server) { | ||
- | // (Note: arbitrary choice to use ' | + | // (注:如需在任意维度生效,请使用 |
PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager(); | PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager(); | ||
- | // The first time the following | + | // 当第一次调用了方法 |
- | // stores it inside the ' | + | // ' |
- | // ' | + | |
StateSaverAndLoader state = persistentStateManager.getOrCreate(type, | StateSaverAndLoader state = persistentStateManager.getOrCreate(type, | ||
- | // If state is not marked | + | // 若状态未标记为脏(dirty),当 |
- | // Technically it's ' | + | // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 |
- | // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to ' | + | // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 |
- | // Besides, it's literally just setting a bool to true, and the only time there' | + | // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见) |
- | // there were no actual change to any of the mods state (INCREDIBLY RARE). | + | |
state.markDirty(); | state.markDirty(); | ||
Line 542: | Line 547: | ||
StateSaverAndLoader serverState = getServerState(player.getWorld().getServer()); | StateSaverAndLoader serverState = getServerState(player.getWorld().getServer()); | ||
- | // Either get the player by the uuid, or we don't have data for him yet, make a new player state | + | // 根据 UUID 获取对应玩家的状态,如果没有该玩家的数据,就创建一个新的玩家状态。 |
PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), | PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), | ||
Line 550: | Line 555: | ||
</ | </ | ||
- | Running the client now, all our player-specific data is correctly saved. | + | 接下来运行客户端,玩家特定的数据现在都可以被正确地保存了。 |
- | * Note: each time you restart the minecraft client with fabric, you're assigned a new UUID, so it may seem like it's not working, but that's just because of the developer environment. | + | * 注:每次您使用 Fabric 调试启动客户端时,您所分配到的 |
- | Just remember if you add new fields to '' | + | 请谨记,只要您向 |
- | ====== Initial Sync ====== | + | ===== 内联同步 |
What if it's important for our mod that as soon as a player joins they receive some or all the PlayerData associated with them? For this, we will crate a new packet '' | What if it's important for our mod that as soon as a player joins they receive some or all the PlayerData associated with them? For this, we will crate a new packet '' | ||
Line 659: | Line 664: | ||
* Note: each time you restart the minecraft client with fabric, you're assigned a new UUID, so it may seem like it's not working, but that's just because of the developer environment. (If you run the fabric multiplayer server, ' | * Note: each time you restart the minecraft client with fabric, you're assigned a new UUID, so it may seem like it's not working, but that's just because of the developer environment. (If you run the fabric multiplayer server, ' | ||
- | ====== More Involved Player Data ====== | + | ===== 更复杂的玩家数据 |
And just for good measure, let's see an example of how our '' | And just for good measure, let's see an example of how our '' |
zh_cn/tutorial/persistent_states.txt · Last modified: 2024/07/10 03:31 by dreamuniverse