开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 1416|回复: 2
收起左侧

[技术专题] 【Minecraft Fabric】世界数据存储

[复制链接]
结帖率:67% (2/3)
发表于 2024-6-16 11:32:04 | 显示全部楼层 |阅读模式   山西省晋城市
世界数据存储   
若问题欢迎讨论,感谢阅读!

官方教程(教程:global_data [面料百科] (fabricmc.net))并没讲具体如何存储数据数据,不过看了原版后也容易写出,在此将教你保存世界数据(注意,这些操作几乎是在服务端完成的)。

PersistentState
游戏每个世界中的数据是通过PersistentState保存的,该世界存档中保存的数据(*.dat)可以在游戏目录下saves\[world]\data文件夹看到,其中的文件都对应了一个PersistentState对象(如计分板的ScoreboardState类),这些类会实现“fromTag”和“toTag”方法来完成数据的解析与生成。接下来让我们编写自己的PersistentState类吧!
样例:

[Java] 纯文本查看 复制代码
public class ExampleState extends PersistentState {  
         private static final Logger LOGGER = LogManager.getLogger();  
         private ExampleObject object;  
   
         public ExampleState() {  
                 super("example");  
         }  
   
         public void setObject(ExampleObject object) {  
                 this.object = object;  
         }  
   
         public ExampleObject getObject() {  
                 return object;  
         }  
   
         public void fromTag(CompoundTag tag) {  
                 object.fromTag(tag.getCompound("ExampleData"));  
         }  
   
         public CompoundTag toTag(CompoundTag tag) {  
                 if(this.handler == null) {  
                         LOGGER.warn("无法保存未创建的存储对象。");  
                 } else {  
                         tag.put("ExampleData", object.toTag());  
                 }  
                 return tag;  
         }  
}
  
在对应功能的包下创建一个类并继承 net.minecraft.world.PersistentState 类,类名为 记录对象+“State”(如“ScoreboardState”);
添加构造器,调用父构造器传入字符串 key,以小写驼峰式命名(如“scoreboard”),该参数将用作数据文件的文件名;
添加被存储对象成员,并实现“get”与“set”方法;(其实原版 ScoreboardState 里并无“get”方法,是通过服务器的成员方法获取计分板的,这里只是为了获取方便)
实现“public void fromTag(CompoundTag tag)”和“public CompoundTag toTag(CompoundTag tag)”方法,这是该类的核心方法,分别对应数据的读入和写出,一般会间接调用存储对象的“fromTag”和“toTag”方法;
如果需要,可以创建一个 Logger 记录错误。
完成后,存储对象还需要注意下,为减少不必要的负担,防止该对象未更新数据而再次保存,需要实现void addUpdateListener(Runnable listener)并加入一个Synchronizer监听数据更改,稍等你将会用到它们,此外实现void runUpdateListeners()并在更新数据后调用。
样例:

[Java] 纯文本查看 复制代码
//存储对象类  
private Runnable[] updateListeners = new Runnable[0];  
   
public void addUpdateListener(Runnable listener) {  
         this.updateListeners = Arrays.copyOf(this.updateListeners, this.updateListeners.length + );  
         this.updateListeners[this.updateListeners.length - 1] = listener;  
}  
   
protected void runUpdateListeners() {  
         Runnable[] var1 = this.updateListeners;  
         for(Runnable runnable : var1) {  
                 runnable.run();  
         }  
}
public class ExampleSynchronizer implements Runnable {  
         private final PersistentState compound;  
   
         public ExampleSynchronizer(PersistentState compound) {  
                 this.compound = compound;  
         }  
   
         public void run() {  
                 this.compound.markDirty();  
         }  
}
  
addUpdateListener():向存储对象添加数据更新监听器;
runUpdateListeners():数据更新后调用,将执行已添加的监听器动作;
Synchronizer:以 存储对象+“Synchronizer” 命名,用来标记已更改的数据(原理:每个 PersistentState 都有dirty成员标记数据是否更改,在保存时会判断该dirty是否为ture)。
接下来将教你将该 PersistentState 类加入到服务器中。

PersistentStateManager
官方教程中讲到数据是按维度(代码中是World,使用DimensionType辨别维度)保存的,即每个维度都有自己的数据管理器——PersistentStateManager,我们可以使用serverWorld.getPersistentStateManager()获得 ,其中使用一个 map 存储着各个PersistentState,会在创建与保存时分别调用PersistentState的“fromTag”和“toTag”方法,我们可以在创建维度时使用persistentStateManager.getOrCreate(Supplier factory, String id)创建自己的PersistentState。接下来我们将使用 Mixin 向MinecraftServer中注入我们的代码。
样例:

[Java] 纯文本查看 复制代码
@Mixin(MinecraftServer.class)  
public abstract class MinecraftServerMixin {  
         private ExampleObject object;  
   
         @Inject(method = "(Ljava/io/File;Ljava/net/Proxy;Lcom/mojang/datafixers/DataFixer;Lnet/minecraft/server/command/CommandManager;Lcom/mojang/authlib/yggdrasil/YggdrasilAuthenticationService;Lcom/mojang/authlib/minecraft/MinecraftSessionService;Lcom/mojang/authlib/GameProfileRepository;Lnet/minecraft/util/UserCache;Lnet/minecraft/server/WorldGenerationProgressListenerFactory;Ljava/lang/String;)V", at = @At(value = "RETURN"))  
         private void initExampleObject(File gameDir, Proxy proxy, DataFixer dataFixer, CommandManager commandManager, YggdrasilAuthenticationService authService, MinecraftSessionService sessionService, GameProfileRepository gameProfileRepository, UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory, String levelName, CallbackInfo ci) {  
                 this.object = new ExampleObject();  
         }  
   
         private void initExampleObject(PersistentStateManager persistentStateManager) {  
                 ExampleState exampleState = persistentStateManager.getOrCreate(ExampleState::new, "example");  
                 exampleState.setObject(object);  
                 object.addUpdateListener(new ExampleSynchronizer(exampleState));  
         }  
   
         @Inject(method = "createWorlds(Lnet/minecraft/world/WorldSaveHandler;Lnet/minecraft/world/level/LevelProperties;Lnet/minecraft/world/level/LevelInfo;Lnet/minecraft/server/WorldGenerationProgressListener;)V", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE\_FAILHARD)  
         private void createTemperatureState(WorldSaveHandler worldSaveHandler, LevelProperties properties, LevelInfo levelInfo, WorldGenerationProgressListener worldGenerationProgressListener, CallbackInfo ci, ServerWorld serverWorld, ersistentStateManager persistentStateManager) {  
                 this.initExampleObject(persistentStateManager);  
         }  
}
  
在服务器被创建时,初始化存储对象;
在创建世界时,向某维度中的 persistentStateManager 创建你的 PersistentState,设置存储对象并对存储对象添加数据更新监听器(Synchronizer),注意,这里注入时捕获其中的局部变量 persistentStateManager 是主世界的,若要在其他维度创建,请使用 this.getWorld(dimensionType).getPersistentStateManager() 获取对应维度的 persistentStateManager。
完成后,我们将学习如何在其它地方获取存储对象对其修改。

获取存储对象
向 persistentStateManager 添加 PersistentState 后,我们应如何获取我们的存储对象?前面说到为了方便获取存储对象,在我们的 PersistentState 类中加入的“get”方法来获取存储对象,而已创建的 PersistentState 对象可以通过 persistentStateManager 的 get(Supplier factory, String id) 方法获取,因此可以写出以下代码:

[Java] 纯文本查看 复制代码
server().getWorld(DimensionType.OVERWORLD).getPersistentStateManager().get(ExampleState::new, "example").getObject()

通过 MinecraftClient 类的静态方法 getInstance() 获取客户Duan的游戏实例;
使用 getServer() 方法获取服务端对象;
使用 getWorld(DimensionType) 获取对应维度,若已有 ServerWorld 对象此前步骤可以跳过;
使用 getPersistentStateManager() 获取该维度的 persistentStateManager 并获取我们创建的 PersistentState 对象;
通过 PersistentState 中的“get”方法获取被存储对象,即可对之修改。
完成后,赶快进入游戏测试吧!若成功你将在游戏目录(或开发环境的“run”目录)下saves\[world]\data文件夹看到你的数据文件,如果在 IDEA 安装的 MinecraftDev 插件,你可以方便地查看或编写这些数据文件。编写不易,你的支持是我最大的动力!


结帖率:100% (1/1)

签到天数: 8 天

发表于 2024-8-9 21:33:55 | 显示全部楼层   香港特别行政区*
这个解决了我的燃眉之急。
回复 支持 反对

使用道具 举报

结帖率:0% (0/5)
发表于 2024-7-19 17:04:28 | 显示全部楼层   辽宁省丹东市
感谢分享,学习了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表