Fungus Save System 個(gè)人簡(jiǎn)單總結(jié)

官方文檔

只是簡(jiǎn)單的總結(jié)。
不包括事件處理內(nèi)容钠怯。
可能存在錯(cuò)誤佳魔。

Fungus 的存檔系統(tǒng)和 Galgame 常見(jiàn)的存檔有一定區(qū)別。
與其說(shuō)是存檔系統(tǒng)晦炊,更像是一個(gè)線性記錄點(diǎn)管理功能鞠鲜。
存儲(chǔ)的并不是“一個(gè)存檔”宁脊,而是多個(gè)“存檔點(diǎn)”組成的列表。

重要概念

這里是從使用概念的角度出發(fā)贤姆,不是類的功能榆苞。

  1. Save Point: 存檔點(diǎn),記錄游戲進(jìn)度的信息霞捡。
  2. Save History: 保存歷史坐漏,維護(hù)存檔點(diǎn)列表。
  3. Save Point Command: 存檔點(diǎn)命令弄砍,創(chuàng)建存檔點(diǎn)仙畦。
  4. Save Menu: 保存菜單,提供用戶和存檔系統(tǒng)交互的功能音婶。
  5. Save Data: 持有當(dāng)前場(chǎng)景的流程圖列表慨畸,能夠獲得流程圖數(shù)據(jù)用于存儲(chǔ)。

在 Fungus 示例場(chǎng)景內(nèi)衣式, Unity 的層級(jí)窗口中寸士,只存在 Save Menu 和 Save Data 兩個(gè)與存檔系統(tǒng)有關(guān)的物體。
Save Data 掛載在空物體上碴卧,只有一個(gè) Flowchart 的列表字段弱卡,是否會(huì)自動(dòng)添加暫不清楚。
Save Menu 是一組 UI 控件住册,最重要的就是根物體上掛載的 Save Menu 組件婶博,能配置一些存檔系統(tǒng)的參數(shù)。

Save Point Command

Block 中的每一個(gè) Save Point Command 都會(huì)創(chuàng)建一個(gè) Save Point.
當(dāng)加載 Save Point 時(shí)荧飞,游戲從生成該存檔點(diǎn)的 Block 的 Save Point Command 開(kāi)始執(zhí)行凡人。
也就是說(shuō),如果 Save Point Command 不是 Block 的第一條命令叹阔,則之前的命令都不會(huì)執(zhí)行挠轴。

Save Point Command 的參數(shù)介紹:

  • Is Start Point: 是否為開(kāi)始點(diǎn)。啟用后耳幢,游戲?qū)?huì)從該 Command 開(kāi)始執(zhí)行岸晦。在使用存檔系統(tǒng)的情況下,一個(gè)場(chǎng)景應(yīng)該存在且只存在一個(gè)啟用了該屬性的 Save Point Command.
  • Key Mode: 鍵模式睛藻。用來(lái)標(biāo)識(shí) Save Point 的字符串启上,默認(rèn)使用 Block 的名字,但如果一個(gè) Block 中使用多條命令修档,則會(huì)重復(fù)導(dǎo)致只有其中一個(gè) Save Point 有用碧绞。
  • Description Mode: 描述模式。默認(rèn)采用時(shí)間戳作為描述吱窝,也可自定義讥邻。
  • Fire Event: 是否激活事件。一般勾選院峡,不勾選則不會(huì)觸發(fā) Save Point Loaded 事件兴使,即不會(huì)觸發(fā)設(shè)置為該事件的 Block.
  • Resume On Load: 是否在加載后恢復(fù)執(zhí)行 Blcok. 一般勾選,不勾選在不寫代碼的情況下應(yīng)該無(wú)法繼續(xù)游戲照激。

Save Menu

Save Menu 不止是一個(gè) UI 組件发魄,也配置一些存檔系統(tǒng)的重要參數(shù)。

示例場(chǎng)景中的 Save Menu 有六個(gè)按鈕和一個(gè)文本控件:

  • 文本控件:顯示敘事日志俩垃,即劇情文本記錄励幼。
  • 菜單按鈕:顯示其它五個(gè)按鈕和文本日志。
  • Save 按鈕:將當(dāng)前的 Save History 持久化口柳,即存儲(chǔ)到硬盤上苹粟,內(nèi)容為 Json 文本。會(huì)覆蓋上一個(gè)文件跃闹,即默認(rèn)只支持一個(gè)存檔嵌削。(路徑為:C:\Users\用戶名\AppData\LocalLow\公司名(DefaultCompany)\項(xiàng)目名\FungusSaves)
  • Load 按鈕:加載持久化的 Save History 數(shù)據(jù),并根據(jù)最新的 Save Point 恢復(fù)游戲執(zhí)行望艺。
  • Restart 按鈕:回到游戲開(kāi)始文捶,即啟用了 Is Start Point 的 Save Point Command 處飒箭。默認(rèn)情況下會(huì)同時(shí)刪除存檔文件。
  • 后退/前進(jìn)按鈕:切換到上一個(gè)或下一個(gè) Save Point 處執(zhí)行游戲。每一個(gè) Save Point Command 都會(huì)創(chuàng)建一個(gè) Save Point, 交由 Save History 維護(hù)膝迎,不點(diǎn)擊 Save 按鈕就不會(huì)持久化,但依然存在于內(nèi)存中咙轩,因此可以進(jìn)行切換呢堰。

Save Menu 組件的參數(shù)介紹:

  • Save Data Key: 一個(gè)存檔的唯一標(biāo)識(shí)符,也是生成的存檔文件的名字咧欣。
  • Load On Start: 是否在游戲運(yùn)行時(shí)就加載存檔浅缸。
  • Auto Save: 自動(dòng)保存,即每個(gè) Save Point Command 不僅生成了 Save Point 還會(huì)同時(shí)執(zhí)行持久化魄咕。
  • Restart Deletes Save: 是否在重新開(kāi)始游戲時(shí)刪除存檔衩椒。

大致工作流程

  1. Block 執(zhí)行到 Save Point Command, 創(chuàng)建一個(gè) Save Point.
  2. Save Point 被添加進(jìn) Save History.
  3. 以上會(huì)多次執(zhí)行。
  4. 當(dāng)出現(xiàn)多個(gè) Save Point 時(shí)哮兰,玩家就可以通過(guò) Save Menu 后退或前進(jìn)毛萌。
  5. 此時(shí) Save History 還只是存在于內(nèi)存中,因此停止游戲會(huì)丟失所有 Save Point.
  6. 點(diǎn)擊 Save Menu 的 Save 按鈕喝滞,整個(gè) Save History 就會(huì)被持久化到硬盤上阁将。
  7. 下次啟動(dòng)游戲,點(diǎn)擊 Load 按鈕右遭,就能加載 Save History 的數(shù)據(jù)做盅,游戲會(huì)移動(dòng)到最新的 Save Point 處執(zhí)行缤削,同樣也能進(jìn)行后退和前進(jìn)。

注意事項(xiàng)

支持的變量類型

默認(rèn)支持的 Flowchart 內(nèi)的 Variables 類型僅有:Boolean, Integer, Float, String.

如果有其它類型需要支持吹榴,能繼承 VariableBase<T> 類實(shí)現(xiàn)亭敢,可能還需要其它處理,這方面暫時(shí)沒(méi)有具體研究過(guò)图筹。
或者在 Fungus 存檔系統(tǒng)之外處理帅刀。

存檔點(diǎn)并不是一開(kāi)始就持久化

之前在 Save Menu 小節(jié)中提到了。
每一條 Save Point Command 都會(huì)創(chuàng)建一個(gè) Save Point. 這些存檔點(diǎn)都會(huì)在 Save History 中持有远剩。
Save History 提供 Save Point 的后退和前進(jìn)功能扣溺。
只有點(diǎn)擊 Save 按鈕,或者說(shuō)執(zhí)行 SaveManager 的 Save 方法才會(huì)持久化瓜晤。

要注意的是锥余,存檔并不是只存儲(chǔ)某個(gè) Save Point, 而是整個(gè) Save History.

關(guān)于 Is Start Point 和 Game Started 事件

結(jié)論是:勾選了 Is Start Point 的 Save Point Command 的優(yōu)先級(jí)要高于 Block 的 Execute On Event 設(shè)置為 Game Started.

在不使用存檔功能,且不編寫代碼控制的情況下活鹰,一般應(yīng)該是將某個(gè) Block 的 Execute On Event 設(shè)置為 Game Started. 從而開(kāi)始 Flowchart 的執(zhí)行哈恰。

而根據(jù)官方文檔,如果使用存檔功能志群,則應(yīng)該確保每個(gè)場(chǎng)景有一個(gè)勾選了 Is Start Point 的 Save Point Command. 游戲會(huì)從當(dāng)前場(chǎng)景的該條命令開(kāi)始執(zhí)行着绷。
不過(guò)根據(jù)測(cè)試,即便沒(méi)有這樣的命令锌云,依舊以傳統(tǒng)的 Block 事件的方式開(kāi)始游戲荠医,Save Point Command 依舊能正常工作。因此 Is Start Point 應(yīng)該不是必要的桑涎。

根據(jù) FungusManager 類的實(shí)現(xiàn)彬向,SaveManager 是必定會(huì)被添加的組件,因此存檔功能應(yīng)該是無(wú)法“開(kāi)啟或關(guān)閉”的攻冷,只是使用與否的問(wèn)題娃胆。
不過(guò) SaveMenu 和 SaveData 是不會(huì)自動(dòng)添加的,而這兩個(gè)都是存檔系統(tǒng)的重要組件等曼。

回退后再次執(zhí)行 Save Point Command 會(huì)導(dǎo)致之后的 Save Point 被清空

Save History 是通過(guò)兩個(gè) List 持有 Save Point 的里烦。
回退是,會(huì)將 Save Point 從一般的 List 中彈出禁谦,加入到另一個(gè)回退列表中胁黑。
而每次添加新的 Save Point 都會(huì)清空回退列表,因此回退后州泊,再次執(zhí)行 Save Point Command 添加存檔點(diǎn)丧蘸,就會(huì)導(dǎo)致之后的存檔點(diǎn)丟失。

避免使用 Game Started event handler

Game Started event handler 在新游戲和加載游戲時(shí)都會(huì)激活遥皂,這通常不會(huì)是你所期望的力喷,因此避免在支持保存的游戲中使用它刽漂。

在官方文檔中有所提及,但暫時(shí)沒(méi)有具體研究過(guò)冗懦。


相關(guān)類

以下內(nèi)容可能不全面或存在錯(cuò)誤爽冕,請(qǐng)僅作為參考仇祭。

  • SaveManager: 管理并提供存檔系統(tǒng)相關(guān)功能披蕉。并非單例模式,但應(yīng)該交由 FungusManager 創(chuàng)建乌奇。
  • SaveManagerSignals: 提供并持有存檔系統(tǒng)需要使用的事件没讲。
  • SavePointLoaded: 存檔點(diǎn)加載事件,用于設(shè)置 Block 的 Execute On Event 字段礁苗。
  • SaveDataItem: 有兩個(gè) string 成員爬凑,持有數(shù)據(jù)類型標(biāo)識(shí)符和數(shù)據(jù)內(nèi)容,并且不提供編碼解碼功能试伙。在使用中被用來(lái)存儲(chǔ) Json, 是相對(duì)底層的一個(gè)數(shù)據(jù)類嘁信。
  • SaveData: 持有一個(gè)場(chǎng)景的 Flowchart 鏈表,并提供對(duì) Flowchart 內(nèi)需要保存的內(nèi)容生成 SaveDataItem 的功能疏叨。即生成 Flowchart 中的 SaveDataItem 鏈表潘靖。
  • SavePoint: 該類實(shí)際上繼承自 Command, 負(fù)責(zé)描述命令參數(shù),并不持有或處理存檔數(shù)據(jù)蚤蔓,僅僅只是提供命令參數(shù)卦溢,以及開(kāi)啟生成 Save Point 的過(guò)程。
  • SavePointData: 實(shí)際持有 Save Point 需要保存的相關(guān)數(shù)據(jù)秀又,以 SaveDataItem 鏈表的形式持有单寂,并且通過(guò)轉(zhuǎn)換成 Json 文本的方式提供數(shù)據(jù)。
  • SaveHistory: 持有 SavePointData 列表吐辙,包括標(biāo)識(shí)符和時(shí)間戳描述宣决,通過(guò)維護(hù)一個(gè) SavePoints 鏈表和一個(gè) RewoundSavePoints 鏈表,來(lái)實(shí)現(xiàn)回退和前進(jìn)功能昏苏。每次添加新存檔點(diǎn)都會(huì)清空 RewoundSavePoints 鏈表尊沸。
  • SaveMenu: 提供用戶與存檔系統(tǒng)交互的 UI 功能,以及配置部分重要參數(shù)捷雕。

簡(jiǎn)化的工作流程

僅包括保存 Save Point 的流程椒丧。

SavePoint --> SaveHistory --> SavePointData --> SaveData --> SaveDataItem

  1. Block 運(yùn)行到 Save Point Command.
  2. SavePoint 對(duì)象向 SaveHistory 對(duì)象傳遞描述信息。
  3. SaveHistory 對(duì)象將信息傳遞給 SavePointData 類救巷。
  4. SavePointData 類從 SaveData 對(duì)象獲取需要保存的信息壶熏。
  5. SaveData 對(duì)象從自己持有的 Flowchart 獲取數(shù)據(jù),生成 SaveDataItem, 并返回給 SavePointData.
  6. SavePointData 類將 SaveHistory 對(duì)象給予的描述信息浦译,和 SaveData 對(duì)象提供的 SaveDataItem 數(shù)據(jù)棒假,組合生成一條 Json 字符串溯职,并返回給 SaveHistory.
  7. SaveHistory 獲得一個(gè)存檔點(diǎn)所需的存儲(chǔ)數(shù)據(jù)的 Json 字符串。
  8. 至此帽哑,一個(gè) Save Point 就被創(chuàng)建并交由 Save History 進(jìn)行維護(hù)谜酒。

關(guān)鍵函數(shù)代碼

  1. Block 執(zhí)行到 Save Point Command.
// in SavePoint
public override void OnEnter()
{
    // 獲取 SaveManager 添加 SP 描述信息
    var saveManager = FungusManager.Instance.SaveManager;
    saveManager.AddSavePoint(SavePointKey, SavePointDescription);
    if (fireEvent) {
        // 通知事件處理器,即尋找匹配的 Block 并執(zhí)行
        SavePointLoaded.NotifyEventHandlers(SavePointKey);
    }
    // 繼續(xù)執(zhí)行之后的 Command
    Continue();
}
  1. 向 SaveHistory 添加 SP.
// in SaveManager
public virtual void AddSavePoint(string savePointKey, string savePointDescription)
{
    // 向自己持有的 SaveHistory 對(duì)象添加 SP 描述信息
    saveHistory.AddSavePoint(savePointKey, savePointDescription);
    // 調(diào)用事件妻枕,暫時(shí)不清楚添加了哪些事件
    SaveManagerSignals.DoSavePointAdded(savePointKey, savePointDescription);
}

// in SaveHistory
public void AddSavePoint(string savePointKey, string savePointDescription)
{
    // 清空回退列表
    ClearRewoundSavePoints();
    // 獲取場(chǎng)景信息僻族,與 SP 描述信息一起編碼為 SavePointData
    string sceneName = SceneManager.GetActiveScene().name;
    var savePointData = SavePointData.Encode(savePointKey, savePointDescription, sceneName);
    // 將 SavePointData 生成的 Json 字符串存入 List
    savePoints.Add(savePointData);
}
  1. 生成 SaveDataItem 的 Json 字符串,即最終持久化的內(nèi)容屡谐。
// in SavePointData
public static string Encode(string _savePointKey, string _savePointDescription, string _sceneName)
{
    var savePointData = Create(_savePointKey, _savePointDescription, _sceneName);
    // 尋找掛載在場(chǎng)景中的 SaveData 對(duì)象述么,該對(duì)象持有 Flowchart 列表,并能生成 Flowachart 的 SaveDataItem
    var saveData = GameObject.FindObjectOfType<SaveData>();
    if (saveData != null)
    {
        // 將 SaveData 對(duì)象生成的 SaveDataItem 存入剛剛創(chuàng)建的 SavePointData 對(duì)象的列表中
        saveData.Encode(savePointData.SaveDataItems);
    }
    // 將 SavePointData 轉(zhuǎn)換成 Json 字符串返回給 SaveHistory
    return JsonUtility.ToJson(savePointData, true);
}

SaveData 從 Flowchart 生成了哪些 SaveDataItem.

// in SaveData
public virtual void Encode(List<SaveDataItem> saveDataItems)
{
    for (int i = 0; i < flowcharts.Count; i++)
    {
        var flowchart = flowcharts[i];
        // 內(nèi)容包括:Flowchart 名字愕掏;StringVar, IntVar, FloatVar, BoolVar 的列表
        var flowchartData = FlowchartData.Encode(flowchart);

        // SaveDataItem 1 : FlowchartData 類型標(biāo)識(shí)符 和 數(shù)據(jù)內(nèi)容
        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);
    }
}

現(xiàn)在一個(gè) SP 的數(shù)據(jù)度秘,以 Json 字符串的方式,已經(jīng)存入了 SaveHistory.
本質(zhì)上饵撑,一條 SP 的 Json 內(nèi)容包括:SP 標(biāo)識(shí)符剑梳、SP 時(shí)間戳等描述信息;Flowchart 數(shù)據(jù)內(nèi)容滑潘;敘事日志垢乙。
但還沒(méi)有存入硬盤,只是存在于內(nèi)存中众羡。

點(diǎn)擊保存按鈕持久化侨赡,SaveMenu 會(huì)調(diào)用 SaveManager.

// in SaveManager
public virtual void Save(string saveDataKey)
{
    WriteSaveHistory(saveDataKey);
}
protected virtual bool WriteSaveHistory(string saveDataKey)
{
    // 將 SaveHistory 持有的所有 SP 數(shù)據(jù),生成 Json 字符串
    var historyData = JsonUtility.ToJson(saveHistory, true);
    if (!string.IsNullOrEmpty(historyData))
    {
        // 文件操作粱侣,創(chuàng)建并寫入文件
        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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羊壹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子齐婴,更是在濱河造成了極大的恐慌油猫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柠偶,死亡現(xiàn)場(chǎng)離奇詭異情妖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)诱担,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門毡证,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蔫仙,你說(shuō)我怎么就攤上這事料睛。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵恤煞,是天一觀的道長(zhǎng)屎勘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)居扒,這世上最難降的妖魔是什么概漱? 我笑而不...
    開(kāi)封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮喜喂,結(jié)果婚禮上瓤摧,老公的妹妹穿的比我還像新娘。我一直安慰自己夜惭,他們只是感情好姻灶,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著诈茧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捂掰。 梳的紋絲不亂的頭發(fā)上敢会,一...
    開(kāi)封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音这嚣,去河邊找鬼鸥昏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛姐帚,可吹牛的內(nèi)容都是我干的吏垮。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼罐旗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼膳汪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起九秀,我...
    開(kāi)封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤遗嗽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鼓蜒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痹换,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年都弹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娇豫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畅厢,死狀恐怖冯痢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤系羞,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布郭计,位于F島的核電站,受9級(jí)特大地震影響椒振,放射性物質(zhì)發(fā)生泄漏昭伸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一澎迎、第九天 我趴在偏房一處隱蔽的房頂上張望庐杨。 院中可真熱鬧,春花似錦夹供、人聲如沸灵份。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)填渠。三九已至,卻和暖如春鸟辅,著一層夾襖步出監(jiān)牢的瞬間氛什,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工匪凉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枪眉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓再层,卻偏偏與公主長(zhǎng)得像贸铜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子聂受,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容