Arzie100 发表于 2022-5-31 21:37

Fungus Save System 个人简单总结

官方文档

只是简单的总结。
不包括事件处理内容。
可能存在错误。

Fungus 的存档系统和 Galgame 常见的存档有一定区别。
与其说是存档系统,更像是一个线性记录点管理功能。
存储的并不是“一个存档”,而是多个“存档点”组成的列表。
重要概念


这里是从使用概念的角度出发,不是类的功能。
Save Point: 存档点,记录游戏进度的信息。Save History: 保存历史,维护存档点列表。Save Point Command: 存档点命令,创建存档点。Save Menu: 保存菜单,提供用户和存档系统交互的功能。Save Data: 持有当前场景的流程图列表,能够获得流程图数据用于存储。

在 Fungus 示例场景内, Unity 的层级窗口中,只存在 Save Menu 和 Save Data 两个与存档系统有关的物体。
Save Data 挂载在空物体上,只有一个 Flowchart 的列表字段,是否会自动添加暂不清楚。
Save Menu 是一组 UI 控件,最重要的就是根物体上挂载的 Save Menu 组件,能配置一些存档系统的参数。
Save Point Command


Block 中的每一个 Save Point Command 都会创建一个 Save Point.
当加载 Save Point 时,游戏从生成该存档点的 Block 的 Save Point Command 开始执行。
也就是说,如果 Save Point Command 不是 Block 的第一条命令,则之前的命令都不会执行。

Save Point Command 的参数介绍:
Is Start Point: 是否为开始点。启用后,游戏将会从该 Command 开始执行。在使用存档系统的情况下,一个场景应该存在且只存在一个启用了该属性的 Save Point Command.Key Mode: 键模式。用来标识 Save Point 的字符串,默认使用 Block 的名字,但如果一个 Block 中使用多条命令,则会重复导致只有其中一个 Save Point 有用。Description Mode: 描述模式。默认采用时间戳作为描述,也可自定义。Fire Event: 是否激活事件。一般勾选,不勾选则不会触发 Save Point Loaded 事件,即不会触发设置为该事件的 Block.Resume On Load: 是否在加载后恢复执行 Blcok. 一般勾选,不勾选在不写代码的情况下应该无法继续游戏。
Save Menu


Save Menu 不止是一个 UI 组件,也配置一些存档系统的重要参数。

示例场景中的 Save Menu 有六个按钮和一个文本控件:
文本控件:显示叙事日志,即剧情文本记录。菜单按钮:显示其它五个按钮和文本日志。Save 按钮:将当前的 Save History 持久化,即存储到硬盘上,内容为 Json 文本。会覆盖上一个文件,即默认只支持一个存档。(路径为:C:\Users\用户名\AppData\LocalLow\公司名(DefaultCompany)\项目名\FungusSaves)Load 按钮:加载持久化的 Save History 数据,并根据最新的 Save Point 恢复游戏执行。Restart 按钮:回到游戏开始,即启用了 Is Start Point 的 Save Point Command 处。默认情况下会同时删除存档文件。后退/前进按钮:切换到上一个或下一个 Save Point 处执行游戏。每一个 Save Point Command 都会创建一个 Save Point, 交由 Save History 维护,不点击 Save 按钮就不会持久化,但依然存在于内存中,因此可以进行切换。

Save Menu 组件的参数介绍:
Save Data Key: 一个存档的唯一标识符,也是生成的存档文件的名字。Load On Start: 是否在游戏运行时就加载存档。Auto Save: 自动保存,即每个 Save Point Command 不仅生成了 Save Point 还会同时执行持久化。Restart Deletes Save: 是否在重新开始游戏时删除存档。
大致工作流程

Block 执行到 Save Point Command, 创建一个 Save Point.Save Point 被添加进 Save History.以上会多次执行。当出现多个 Save Point 时,玩家就可以通过 Save Menu 后退或前进。此时 Save History 还只是存在于内存中,因此停止游戏会丢失所有 Save Point.点击 Save Menu 的 Save 按钮,整个 Save History 就会被持久化到硬盘上。下次启动游戏,点击 Load 按钮,就能加载 Save History 的数据,游戏会移动到最新的 Save Point 处执行,同样也能进行后退和前进。
注意事项

支持的变量类型


默认支持的 Flowchart 内的 Variables 类型仅有:Boolean, Integer, Float, String.

如果有其它类型需要支持,能继承 VariableBase<T> 类实现,可能还需要其它处理,这方面暂时没有具体研究过。
或者在 Fungus 存档系统之外处理。
存档点并不是一开始就持久化


之前在 Save Menu 小节中提到了。
每一条 Save Point Command 都会创建一个 Save Point. 这些存档点都会在 Save History 中持有。
Save History 提供 Save Point 的后退和前进功能。
只有点击 Save 按钮,或者说执行 SaveManager 的 Save 方法才会持久化。

要注意的是,存档并不是只存储某个 Save Point, 而是整个 Save History.
关于 Is Start Point 和 Game Started 事件


结论是:勾选了 Is Start Point 的 Save Point Command 的优先级要高于 Block 的 Execute On Event 设置为 Game Started.

在不使用存档功能,且不编写代码控制的情况下,一般应该是将某个 Block 的 Execute On Event 设置为 Game Started. 从而开始 Flowchart 的执行。

而根据官方文档,如果使用存档功能,则应该确保每个场景有一个勾选了 Is Start Point 的 Save Point Command. 游戏会从当前场景的该条命令开始执行。
不过根据测试,即便没有这样的命令,依旧以传统的 Block 事件的方式开始游戏,Save Point Command 依旧能正常工作。因此 Is Start Point 应该不是必要的。

根据 FungusManager 类的实现,SaveManager 是必定会被添加的组件,因此存档功能应该是无法“开启或关闭”的,只是使用与否的问题。
不过 SaveMenu 和 SaveData 是不会自动添加的,而这两个都是存档系统的重要组件。
回退后再次执行 Save Point Command 会导致之后的 Save Point 被清空


Save History 是通过两个 List 持有 Save Point 的。
回退是,会将 Save Point 从一般的 List 中弹出,加入到另一个回退列表中。
而每次添加新的 Save Point 都会清空回退列表,因此回退后,再次执行 Save Point Command 添加存档点,就会导致之后的存档点丢失。
避免使用 Game Started event handler


Game Started event handler 在新游戏和加载游戏时都会激活,这通常不会是你所期望的,因此避免在支持保存的游戏中使用它。

在官方文档中有所提及,但暂时没有具体研究过。
<hr>相关类


以下内容可能不全面或存在错误,请仅作为参考。
SaveManager: 管理并提供存档系统相关功能。并非单例模式,但应该交由 FungusManager 创建。SaveManagerSignals: 提供并持有存档系统需要使用的事件。SavePointLoaded: 存档点加载事件,用于设置 Block 的 Execute On Event 字段。SaveDataItem: 有两个 string 成员,持有数据类型标识符和数据内容,并且不提供编码解码功能。在使用中被用来存储 Json, 是相对底层的一个数据类。SaveData: 持有一个场景的 Flowchart 链表,并提供对 Flowchart 内需要保存的内容生成 SaveDataItem 的功能。即生成 Flowchart 中的 SaveDataItem 链表。SavePoint: 该类实际上继承自 Command, 负责描述命令参数,并不持有或处理存档数据,仅仅只是提供命令参数,以及开启生成 Save Point 的过程。SavePointData: 实际持有 Save Point 需要保存的相关数据,以 SaveDataItem 链表的形式持有,并且通过转换成 Json 文本的方式提供数据。SaveHistory: 持有 SavePointData 列表,包括标识符和时间戳描述,通过维护一个 SavePoints 链表和一个 RewoundSavePoints 链表,来实现回退和前进功能。每次添加新存档点都会清空 RewoundSavePoints 链表。SaveMenu: 提供用户与存档系统交互的 UI 功能,以及配置部分重要参数。
简化的工作流程


仅包括保存 Save Point 的流程。

SavePoint --> SaveHistory --> SavePointData --> SaveData --> SaveDataItem
Block 运行到 Save Point Command.SavePoint 对象向 SaveHistory 对象传递描述信息。SaveHistory 对象将信息传递给 SavePointData 类。SavePointData 类从 SaveData 对象获取需要保存的信息。SaveData 对象从自己持有的 Flowchart 获取数据,生成 SaveDataItem, 并返回给 SavePointData.SavePointData 类将 SaveHistory 对象给予的描述信息,和 SaveData 对象提供的 SaveDataItem 数据,组合生成一条 Json 字符串,并返回给 SaveHistory.SaveHistory 获得一个存档点所需的存储数据的 Json 字符串。至此,一个 Save Point 就被创建并交由 Save History 进行维护。
关键函数代码

Block 执行到 Save Point Command.
// in SavePointpublic override void OnEnter(){    // 获取 SaveManager 添加 SP 描述信息    var saveManager = FungusManager.Instance.SaveManager;    saveManager.AddSavePoint(SavePointKey, SavePointDescription);    if (fireEvent) {      // 通知事件处理器,即寻找匹配的 Block 并执行      SavePointLoaded.NotifyEventHandlers(SavePointKey);    }    // 继续执行之后的 Command    Continue();}向 SaveHistory 添加 SP.
// in SaveManagerpublic virtual void AddSavePoint(string savePointKey, string savePointDescription){    // 向自己持有的 SaveHistory 对象添加 SP 描述信息    saveHistory.AddSavePoint(savePointKey, savePointDescription);    // 调用事件,暂时不清楚添加了哪些事件    SaveManagerSignals.DoSavePointAdded(savePointKey, savePointDescription);}// in SaveHistorypublic void AddSavePoint(string savePointKey, string savePointDescription){    // 清空回退列表    ClearRewoundSavePoints();    // 获取场景信息,与 SP 描述信息一起编码为 SavePointData    string sceneName = SceneManager.GetActiveScene().name;    var savePointData = SavePointData.Encode(savePointKey, savePointDescription, sceneName);    // 将 SavePointData 生成的 Json 字符串存入 List    savePoints.Add(savePointData);}生成 SaveDataItem 的 Json 字符串,即最终持久化的内容。
// in SavePointDatapublic static string Encode(string _savePointKey, string _savePointDescription, string _sceneName){    var savePointData = Create(_savePointKey, _savePointDescription, _sceneName);    // 寻找挂载在场景中的 SaveData 对象,该对象持有 Flowchart 列表,并能生成 Flowachart 的 SaveDataItem    var saveData = GameObject.FindObjectOfType<SaveData>();    if (saveData != null)    {      // 将 SaveData 对象生成的 SaveDataItem 存入刚刚创建的 SavePointData 对象的列表中      saveData.Encode(savePointData.SaveDataItems);    }    // 将 SavePointData 转换成 Json 字符串返回给 SaveHistory    return JsonUtility.ToJson(savePointData, true);}
SaveData 从 Flowchart 生成了哪些 SaveDataItem.
// in SaveDatapublic virtual void Encode(List<SaveDataItem> saveDataItems){    for (int i = 0; i < flowcharts.Count; i++)    {      var flowchart = flowcharts;      // 内容包括:Flowchart 名字;StringVar, IntVar, FloatVar, BoolVar 的列表      var flowchartData = FlowchartData.Encode(flowchart);      // SaveDataItem 1 : FlowchartData 类型标识符 和 数据内容      var saveDataItem = SaveDataItem.Create(FlowchartDataKey, JsonUtility.ToJson(flowchartData));      saveDataItems.Add(saveDataItem);      // SaveDataItem 2 : 叙事日志      var narrativeLogItem = SaveDataItem.Create(NarrativeLogKey, FungusManager.Instance.NarrativeLog.GetJsonHistory());      saveDataItems.Add(narrativeLogItem);    }}
现在一个 SP 的数据,以 Json 字符串的方式,已经存入了 SaveHistory.
本质上,一条 SP 的 Json 内容包括:SP 标识符、SP 时间戳等描述信息;Flowchart 数据内容;叙事日志。
但还没有存入硬盘,只是存在于内存中。

点击保存按钮持久化,SaveMenu 会调用 SaveManager.
// in SaveManagerpublic virtual void Save(string saveDataKey){    WriteSaveHistory(saveDataKey);}protected virtual bool WriteSaveHistory(string saveDataKey){    // 将 SaveHistory 持有的所有 SP 数据,生成 Json 字符串    var historyData = JsonUtility.ToJson(saveHistory, true);    if (!string.IsNullOrEmpty(historyData))    {      // 文件操作,创建并写入文件      var fileLoc = GetFullFilePath(saveDataKey);      System.IO.FileInfo file = new System.IO.FileInfo(fileLoc);      file.Directory.Create();      System.IO.File.WriteAllText(fileLoc, historyData);      return true;    }    return false;}
页: [1]
查看完整版本: Fungus Save System 个人简单总结