zh_cn:tutorial:persistent_states
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
zh_cn:tutorial:persistent_states [2023/12/17 02:52] – Player Specific Persistent dreamuniverse | zh_cn:tutorial:persistent_states [2024/07/10 03:31] (current) – several corrections to the title and specified words dreamuniverse | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== | + | ====== 持久状态 ====== |
+ | |||
+ | ===== 引言 | ||
通常情况下,我们的模组中带有与玩家相关的信息,或是在玩家死亡、服务器重启时我们希望保留的世界信息。\\ | 通常情况下,我们的模组中带有与玩家相关的信息,或是在玩家死亡、服务器重启时我们希望保留的世界信息。\\ | ||
Line 16: | Line 18: | ||
那么,我们如何能让 Fabric 保存这些信息,或者其他我们想保存的模组或游戏数据,以供下次玩家加载世界或重新登录时能读取到这些信息? | 那么,我们如何能让 Fabric 保存这些信息,或者其他我们想保存的模组或游戏数据,以供下次玩家加载世界或重新登录时能读取到这些信息? | ||
- | ====== 简单的消息发送——数据包 | + | ===== 简单的消息发送——数据包 ===== |
首先,既然我们的游戏保存在逻辑服务器上,那么我们就让服务器在检测到玩家挖掘泥土方块时向玩家发送一个数据包,并将其显示在玩家的聊天区内。\\ | 首先,既然我们的游戏保存在逻辑服务器上,那么我们就让服务器在检测到玩家挖掘泥土方块时向玩家发送一个数据包,并将其显示在玩家的聊天区内。\\ | ||
Line 105: | Line 107: | ||
想要达到这一目的,方法千千万,不过我们在这里使用 Minecraft 提供给我们的方法:实现一个继承了 '' | 想要达到这一目的,方法千千万,不过我们在这里使用 Minecraft 提供给我们的方法:实现一个继承了 '' | ||
- | ====== 状态持久化 | + | ===== 状态持久化详述 |
首先,我们在项目目录新建一个名为 '' | 首先,我们在项目目录新建一个名为 '' | ||
Line 128: | Line 130: | ||
</ | </ | ||
- | 注:在继承 '' | + | 注:在继承 '' |
* '' | * '' | ||
* 因此,如果您希望存储一个自定义类,那么您应当新建一个 '' | * 因此,如果您希望存储一个自定义类,那么您应当新建一个 '' | ||
- | 接下来,将这个函数添加到同一个文件中: | + | 接下来,将这个方法添加到同一个文件中: |
<code java> | <code java> | ||
Line 148: | Line 150: | ||
</ | </ | ||
- | 这个函数的作用与 '' | + | 这个方法的作用与 '' |
* 注:与 '' | * 注:与 '' | ||
- | 现在我们再添加一个工具类函数,这个函数需要导入 '' | + | 现在我们再添加一个工具类方法,这个方法需要导入 '' |
当创建时,其调用我们刚刚写到的 '' | 当创建时,其调用我们刚刚写到的 '' | ||
Line 281: | Line 283: | ||
如果您现在运行游戏,您会发现计数器正常递增,但现在即使您关闭游戏并重新启动,计数器也会从退出时的位置继续递增。 | 如果您现在运行游戏,您会发现计数器正常递增,但现在即使您关闭游戏并重新启动,计数器也会从退出时的位置继续递增。 | ||
- | 有一点您有可能会忽略,'' | + | 有一点您有可能会忽略,'' |
- | 这对我们模组中的特定类型数据是很好的,表明它们能正常工作。但更多时候,我们期望数据是玩家限定(因人而异)的。 | + | 这对我们模组中的特定类型数据是很好的,表明它们能正常工作。但更多时候,我们期望数据是因玩家而异的。 |
正如我们开篇所提,如果我们想要保留**任意玩家**的**特定方块**的挖掘数据,这时我们要怎么做? | 正如我们开篇所提,如果我们想要保留**任意玩家**的**特定方块**的挖掘数据,这时我们要怎么做? | ||
- | ====== Player Specific Persistent State ====== | + | ===== 因玩家而异的持久化状态 |
- | We can store player-specific data by extending what we already wrote. | + | 我们可以将我们所写的代码再改进一下,这样就可以存储每个玩家的特定数据了。 |
- | First write a new class '' | + | 首先,新建一个名为 |
- | * Extremely important note: Since we'll be creating a HashMap | + | * 特别提示: 我们会创建 |
+ | 如果您期望在客户端侧获知部分或全部的 ''**PlayerData**'' | ||
<code java> | <code java> | ||
Line 299: | 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 314: | 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 340: | Line 345: | ||
- | * If our '' | + | * 若类 |
- | Now update the class which '' | + | 接下来将实现了 |
<code java> | <code java> | ||
Line 358: | 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 364: | 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 373: | Line 379: | ||
playerState.dirtBlocksBroken += 1; | playerState.dirtBlocksBroken += 1; | ||
- | // Send a packet to the client | + | // 向客户端发送数据包 |
MinecraftServer server = world.getServer(); | MinecraftServer server = world.getServer(); | ||
Line 390: | Line 396: | ||
</ | </ | ||
- | You'll also have to modify the class which '' | + | 您同时需要将实现了 |
<code java> | <code java> | ||
Line 406: | 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 414: | 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 457: | Line 463: | ||
} | } | ||
- | // ... (Rest of code) | + | // ... (代码的剩余部分) |
} | } | ||
</ | </ | ||
- | The final '' | + | 最终的 |
<code java> | <code java> | ||
Line 516: | 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 543: | 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 551: | 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 | + | 那么,当玩家加入服务器时,他们应当收到与他们相关的部分或全部玩家数据(''**PlayerData**'' |
+ | 对于这一点,当玩家加入世界时,我们会向玩家发送一个用于内联同步('' | ||
- | Modify your class which '' | + | 现在,我们将实现了'' |
<code java> | <code java> | ||
Line 577: | Line 582: | ||
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 597: | Line 602: | ||
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 603: | Line 608: | ||
playerState.dirtBlocksBroken += 1; | playerState.dirtBlocksBroken += 1; | ||
- | // Send a packet to the client | + | // 向客户端发送数据包 |
MinecraftServer server = world.getServer(); | MinecraftServer server = world.getServer(); | ||
Line 620: | Line 625: | ||
</ | </ | ||
- | Then modify your class which '' | + | 随后,再将实现了 |
<code java> | <code java> | ||
Line 638: | Line 643: | ||
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 647: | Line 652: | ||
client.execute(() -> { | client.execute(() -> { | ||
- | client.player.sendMessage(Text.literal(" | + | client.player.sendMessage(Text.literal(" |
}); | }); | ||
}); | }); | ||
Line 654: | Line 659: | ||
</ | </ | ||
- | As soon as you join the world/ | + | 这样一来,当您进入本地世界或服务器时,您就会看到有一条消息提示您已经挖掘了多少泥土方块。 |
- | * Note: The '' | + | * 注①:我们在客户端侧所创建的 |
- | * 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 调试启动客户端时,您所分配到的 |
- | ====== More Involved Player Data ====== | + | ===== 更复杂的玩家数据 |
- | And just for good measure, let's see an example of how our '' | + | 现在我们已经不能满足单一方块的统计需求了,我们来看另一个例子:如果我们的 |
- | Let's say this is our '' | + | 假设我们的 |
<code java> | <code java> | ||
Line 680: | Line 685: | ||
</ | </ | ||
- | This would be our '' | + | 我们的 |
<code java> | <code java> | ||
Line 751: | Line 756: | ||
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(); | ||
+ | |||
return state; | return state; | ||
} | } | ||
Line 778: | Line 781: | ||
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 786: | Line 789: | ||
</ | </ | ||
- | Our classes which implement | + | 对已经实现了 |
<code java> | <code java> | ||
Line 804: | Line 807: | ||
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 813: | Line 816: | ||
client.execute(() -> { | client.execute(() -> { | ||
- | client.player.sendMessage(Text.literal(" | + | client.player.sendMessage(Text.literal(" |
}); | }); | ||
}); | }); | ||
Line 835: | Line 838: | ||
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 855: | Line 858: | ||
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 861: | Line 864: | ||
playerState.dirtBlocksBroken += 1; | playerState.dirtBlocksBroken += 1; | ||
- | // Send a packet to the client | + | // 向客户端发送数据包 |
MinecraftServer server = world.getServer(); | MinecraftServer server = world.getServer(); | ||
zh_cn/tutorial/persistent_states.1702781538.txt.gz · Last modified: 2023/12/17 02:52 by dreamuniverse