Fungus Save System 关于自行实现存档的分析

该篇简单分析 Fungus 是如何处理存档数据,并提供自行实现存档功能的思路。

如果想看自行实现存档功能的思路,可以直接看最后一节。

存档内容分析

以下取示例场景中第二个 Block 的 Save Point 内容。
为了方便查看,对内容有所修改,包括符号、空格、换行。

"{\"savePointKey\": \"Queens Chamber\",
   \"savePointDescription\": \"13:03 30 五月, 2022\",
   \"sceneName\": \"SaveGame\",
   \"saveDataItems\": [  {
      \"dataType\": \"FlowchartData\",
      \"data\": \"{
         \\\"flowchartName\\\":\\\"Flowchart\\\",
         \\\"stringVars\\\":[],
         \\\"intVars\\\":[],
         \\\"floatVars\\\":[],
         \\\"boolVars\\\":[
            {\\\"key\\\":\\\"has_umbrella\\\",
             \\\"value\\\":true},
            {\\\"key\\\":\\\"burnt_bottom\\\",
             \\\"value\\\":false}   ]
      }\"
   },
   {
      \"dataType\": \"NarrativeLogData\",
      \"data\": \"{
         \\\"entries\\\": [
            {\\\"name\\\": \\\"John\\\",
             \\\"text\\\": \\\"Phew, I can't believe I managed to escape from that horde of man-eating robotic ants.\\\"},
            {\\\"name\\\": \\\"John\\\",
             \\\"text\\\": \\\"Good thing I brought my trusty umbrella or those fiends would have been the end of me!\\\"},
            {\\n
             \\\"name\\\": \\\"John\\\",
             \\\"text\\\": \\\"Now where am I?\\\"}   ]
      }\"
   }   ]
}"

首先是 SavePoint 类提供的描述信息,之后是 SaveData 编码生成的 SaveDataItem 内容。
一般都是两项:"FlowchartData" 和 "NarrativeLogData".
前者记录 Flowchart 的数据,包括名称和变量集。
后者记录剧情日志。

生成 Json 的过程

以下文字描述可能不清晰,可以直接看结论。

  1. 数据一步步传递,直到 SavePointData 调用 SaveData.
  2. SavePointData 会传入一个 SaveDataItem 列表给 SaveData.
  3. SaveData 会从持有的 Flowchart 获取其 FlowchartData, 及名称和变量集。
  4. SaveData 会调用 JsonUtility 将 FlowchartData 生成 Json 并存入 SaveDataItem 列表。
  5. 然后从 FungusManager 获得 NarrativeLog, 该类提供自身的 Json 内容。
  6. 现在 SaveData 就给 SaveDataItem 列表就存入了两项内容,"FlowchartData" 和 "NarrativeLogData".
  7. SavePointData 现在持有 标识符,描述,场景名,以及装载过的数据列表。
  8. 于是 SavePointData 类调用 JsonUtility 将自己持有的数据全部生成 Json.
  9. 最终这条 Json 数据会传递给 SaveHistory 持有。

关键方法是 SaveData.Encode.
这对我们的意义在于,当我们希望另外实现存档功能时,可以自行获取 FlowchartData 进行 Json 化处理,即我们不用额外去获取 Fungus 变量了。
一般的存档系统,并不需要存储一个存档点列表,很多时候也不需要叙事日志。当然即便需要也可以使用 NarrativeLog 类。
我们能够跳脱出 Fungus 本身的调用路径,去自行实现额外的存储逻辑。当然,Fungus 本身的事件也就不太好触发了。

不过,还有问题没有解决,就是如何恢复执行,这就需要我们去查看加载过程了。

加载 Json 并恢复运行的过程

以下文字描述可能不清晰,可以直接看结论。

  1. SaveManager 读取文件,获得 Json 字符串,然后使用 JsonUtility 即可解码并赋值给 SaveHistory 对象。
  2. SaveHistory 加载最新的 SavePoint. 该过程首先获得相应的 Json字符串。(SaveHistory 本身就是持有 Json 字符串列表,所以这些 Json 当然不会被解码)
  3. 将 Json 交由 SavePointData 进行解码。
  4. SavePointData 会解码出一个自己的对象,包括 标识符、描述、场景名、SaveDataItem 列表(Json)。
  5. 将 SaveDataItem 列表交由 SaveData 解码,该过程会将 Json 进一步解析为 FlowchartData 和 NarrativeLog.
  6. 其中 FlowchartData 会交由其类本身提供的解码函数,该函数会将所有变量重新装填进去。
  7. 至此,数据内容都已经恢复。
  8. 在 SavePointData 中,恢复了数据后就会配置事件,加载场景后即可恢复 Block 执行。

重点方法是 SavePointData.DecodeSaveManager.ExecuteBlock, 下面还是结合代码分析。

// in SavePointData
public static void Decode(string saveDataJSON)
{
    // 解析出一个自己的对象
    var savePointData = JsonUtility.FromJson<SavePointData>(saveDataJSON);
    UnityAction<Scene, LoadSceneMode> onSceneLoadedAction = null;
    // 配置场景加载完成后的事件
    onSceneLoadedAction = (scene, mode) =>  {
        if (mode == LoadSceneMode.Additive ||
            scene.name != savePointData.SceneName) {
            return;
        }
        SceneManager.sceneLoaded -= onSceneLoadedAction;

        // 从场景中获取 SaveData, 目的是获得 Flowchart 对象以恢复变量数据
        var saveData = GameObject.FindObjectOfType<SaveData>();
        if (saveData != null) {
            // SaveData 会负责解析 SaveDataItem 内容,并配置变量和日志
            saveData.Decode(savePointData.SaveDataItems);
        }

        // 实际上执行的就是 SaveManager 中的 ExecuteBlock 方法
        SaveManagerSignals.DoSavePointLoaded(savePointData.savePointKey);
    };
    
    // 添加事件加载完成后的事件,注意,此时还未执行!
    SceneManager.sceneLoaded += onSceneLoadedAction;
    // 通过场景名称,加载场景
    SceneManager.LoadScene(savePointData.SceneName);
    // 场景加载完成后,才开始恢复数据和 Block
}     

这个方法对于我们的意义在于,如何恢复 Flowchart 数据。
如果是自行实现存档系统,那么只需要读取持久化的 FlowchartData 内容,将其 Json 传递给 SaveData 对象的 Decode 方法,就会完成解码和装配的工作。
注意,这些过程应该在场景加载之后进行!

// in SaveManager
protected virtual void ExecuteBlocks(string savePointKey)
{
    // 执行特定 Block
    SavePointLoaded.NotifyEventHandlers(savePointKey);

    // 获取 Save Point 对象,就是各个 Save Point Command
    var savePoints = UnityEngine.Object.FindObjectsOfType<SavePoint>();
    for (int i = 0; i < savePoints.Length; i++)
    {
        var savePoint = savePoints[i];
        // 比对 Save Point 标识符以确定是哪条命令
        if (savePoint.ResumeOnLoad &&
            string.Compare(savePoint.SavePointKey, savePointKey, true) == 0)
        {
            // 配置需要执行的 Block 及 Command 索引
            int index = savePoint.CommandIndex;
            var block = savePoint.ParentBlock;
            var flowchart = savePoint.GetFlowchart();
            // 执行 Block
            flowchart.ExecuteBlock(block, index + 1);

            break;
        }
    }
}

这个方法对于我们的意义在于,如何恢复 Block 执行。
该方法会获取场景中的所有 Save Point Command, 然后比对标识符。
比对成功后,配置好要执行的 Block 信息,开始执行。

自行实现存档功能的思路

不考虑存档点列表和叙事日志,以及事件触发,仅考虑恢复 Flowchart 的运行。
注意:暂未经过具体测试,以下仅为理论内容。

存储过程

我们需要存储的内容有三

  1. 场景标识。
  2. 用于恢复 Flowchart 的 FlowchartData.
  3. 用于恢复游戏运行的 Command 标识。

因此,我们需要一个类包含这些内容。
场景标识通过 SceneManager 可以获取。
FlowchartData 可以直接通过 FlowchartData.Encode 获取。
Command 标识根据需求会有所不同,一般来说可以记录当前运行到的 Command. 如果不在 Block 运行中,Fungus 通常是依靠变量或点击恢复到运行中,所以也不需要特殊处理。

然后通过 JsonUtility.ToJson 将该存档对象 Json 化,写入文件即可。

加载过程

  1. 读取文件,通过 JsonUtility.FromJson 实例化一个存档对象。
  2. 通过场景标识,加载场景。
  3. 从存档对象获取 FlowchartData 对象,可以直接通过 FlowchartData.Decode 装配所有变量。
  4. 如果需要,获取 Command 标识,执行 flowchart.ExecuteBlock 方法即可恢复执行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容