ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Godot存档设计:用字典构建可持久化的世界状态

Godot存档设计:用字典构建可持久化的世界状态 如果你认真做过哪怕一个很小的 Godot 小游戏一定遇到过这种尴尬角色动了树砍了道具捡了结果一关游戏再打开时全部恢复原样。之前几课里我们学会让玩家移动、让场景切换、让对象响应输入但最终都会碰到同一个问题——游戏怎么记住世界之前的样子。这一节是“Ep 2.5”听起来像补充包其实它是很多初学者卡住的地方世界存档、类型化字典、右键移除。三件事看起来分散实际是一条线先想清楚世界状态用什么数据结构表达再把状态变成文件最后让玩家通过右键操作改变世界并让这个改变真正被记住。我直接给出判断存档不是“写文件”问题而是“数据结构设计”问题。文件写入只是最后一步真正决定存档质量的是你用字典把世界拆成了什么样子。读完本节你会得到一个完整可运行的最小存档项目一棵小树可以右键移除重新启动游戏后它不会再出现。1. 世界存档到底要存什么很多初学者第一次写存档第一反应是“把玩家位置存下来”。位置当然重要但如果你的世界里存在可采集、可破坏、可移除的物体事情就没这么简单。假设我们做了一个小场景地上有三棵树、一块石头玩家可以走到树旁边右键把这棵树“移除”。这个行为会带来两个结果树从当前场景中消失下次启动游戏时这棵树仍然应该保持消失状态如果未来加入背包移除树还应给玩家增加木材。所以“世界存档”真正存储的不是一张截图而是一系列对象的状态集合。对树来说状态可以是“没有被移除”或“已经被移除”。对石头来说还需要记录它是否被采集过。对玩家来说除了位置还要记录背包中每种物品的数量。如果用一个数据模型来概括它大概长这样玩家数据出生位置、背包内容世界物体数据物体ID、物体类型、是否仍然存在版本数据这个存档用哪个版本的结构写的方便以后升级迁移在这个阶段不推荐一上来就学数据库、SQLite 或者复杂的序列化库。Godot 自带的能力已经足够支撑中小型游戏用 Dictionary 描述状态用 JSON 转成文本用 FileAccess 写入文件。下面先看一个最典型的存档 JSON 长什么样{ version: 1, player: { position: [0.0, 0.0] }, inventory: { wood: 0 }, world: { items: { tree_01: { name: 小树, pos: [80.0, 20.0], removed: false }, tree_02: { name: 小树, pos: [180.0, -60.0], removed: false }, rock_01: { name: 石头, pos: [-90.0, 100.0], removed: false } } } }请注意这里没有直接用 Godot 的Vector2而是用了[x, y]这种数组。原因很简单JSON 不认识Vector2。直接塞进去JSON 会把向量序列化成一串非常难恢复的文本之后加载就麻烦了。正确做法是保存时拆成数组读取时再拼回Vector2。这一小步会让很多人困惑但它是“世界存档”最容易踩的坑之一后面我会在完整代码里演示。2. 用字典表达世界字典不只是“键值对”GDScript 里最常用的复合结构是数组和字典。数组适合存“一组同类型元素”比如敌人列表字典适合存“一件事对应的属性”比如一个树ID对应它的完整状态。我经常用一个比喻字典就是一个档案柜。每个抽屉外面贴了标签抽屉里放东西。你不需要记住东西在第几个抽屉只需要知道标签叫什么。在存档场景中字典尤其擅长表达两种关系“物品ID”对应“物体属性”“物品类型”对应“物品数量”回到我们的小世界world.items就是一个字典key 是tree_01、tree_02这样的 IDvalue 是包含name、pos、removed的字典。var items : { tree_01: { name: 小树, pos: [80.0, 20.0], removed: false }, tree_02: { name: 小树, pos: [180.0, -60.0], removed: false } }字典还有一个好处它天然是“可序列化”的。所谓序列化就是把内存里的数据结构变成一段能存储、能传输的文本。JSON 本身就是从字典这种结构演化出来的所以 Godot 的JSON.stringify()可以直接处理字典不需要额外开发转换器。不过字典也不是没有缺点。普通字典对键和值没有约束你可以往“物品数量”字典里塞进一个字符串写代码的人未必能立刻发现。这就是为什么接下来要介绍类型化字典。2.1 类型化字典给字典加上规则Godot 的数组可以声明类型比如Array[int]表示这个数组里只能放整数。类型化字典的思路与之相似声明“key 是什么类型value 又是什么类型”。# 背包key 是物品名字符串value 是物品数量整数 var inventory: Dictionary[String, int] {} inventory[wood] 3 inventory[stone] 5这里最大的好处是“提前发现错误”。比如后面不小心写成了inventory[wood] 很多如果inventory是类型化字典编辑器或编译器会在更早阶段提示类型不匹配。对于小学阶段的教程可能觉得这是小事但当游戏数据量变大之后这种约束能省下大量排查时间。需要提醒一点类型化字典泛型语法的支持情况取决于你使用的 Godot 版本。如果你手头版本较旧写Dictionary[String, int]报错只需要退回普通字典var inventory: Dictionary {}本节的存档例子不依赖这个尖括号
返回列表