只是簡(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ā)贤姆,不是類的功能榆苞。
- Save Point: 存檔點(diǎn),記錄游戲進(jìn)度的信息霞捡。
- Save History: 保存歷史坐漏,維護(hù)存檔點(diǎn)列表。
- Save Point Command: 存檔點(diǎn)命令弄砍,創(chuàng)建存檔點(diǎn)仙畦。
- Save Menu: 保存菜單,提供用戶和存檔系統(tǒng)交互的功能音婶。
- 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í)刪除存檔衩椒。
大致工作流程
- Block 執(zhí)行到 Save Point Command, 創(chuàng)建一個(gè) Save Point.
- Save Point 被添加進(jìn) Save History.
- 以上會(huì)多次執(zhí)行。
- 當(dāng)出現(xiàn)多個(gè) Save Point 時(shí)哮兰,玩家就可以通過(guò) Save Menu 后退或前進(jìn)毛萌。
- 此時(shí) Save History 還只是存在于內(nèi)存中,因此停止游戲會(huì)丟失所有 Save Point.
- 點(diǎn)擊 Save Menu 的 Save 按鈕喝滞,整個(gè) Save History 就會(huì)被持久化到硬盤上阁将。
- 下次啟動(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
- Block 運(yùn)行到 Save Point Command.
- SavePoint 對(duì)象向 SaveHistory 對(duì)象傳遞描述信息。
- SaveHistory 對(duì)象將信息傳遞給 SavePointData 類救巷。
- SavePointData 類從 SaveData 對(duì)象獲取需要保存的信息壶熏。
- SaveData 對(duì)象從自己持有的 Flowchart 獲取數(shù)據(jù),生成 SaveDataItem, 并返回給 SavePointData.
- SavePointData 類將 SaveHistory 對(duì)象給予的描述信息浦译,和 SaveData 對(duì)象提供的 SaveDataItem 數(shù)據(jù)棒假,組合生成一條 Json 字符串溯职,并返回給 SaveHistory.
- SaveHistory 獲得一個(gè)存檔點(diǎn)所需的存儲(chǔ)數(shù)據(jù)的 Json 字符串。
- 至此帽哑,一個(gè) Save Point 就被創(chuàng)建并交由 Save History 進(jìn)行維護(hù)谜酒。
關(guān)鍵函數(shù)代碼
- 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();
}
- 向 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);
}
- 生成 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;
}