保存游戏系统的总览 (Save Game System Overview)
总览 (Overview)
"保存游戏系统"中包含的游戏保存和加载功能,是专门为使用这个插件而设计的。你可以对“保存游戏系统”进行扩展、修改或忽略它,或者完全替换它。
目录
[TOC]
保存游戏的文件位置 (Saved Game File Location)
“保存游戏的文件”也就是“存档”。
在默认情况下,“存档”文件位于“Application.persistDataPath”属性指定的文件夹中。有关“Application.persistDataPath”的详细信息,请参见Unity文档。
例如,在Windows 7系统上,这个文件夹的路径为:
C:\Users\[USERNAME]\AppData\LocalLow\While Fun Games\First Person Exploration Kit Demo v2\
请注意,“While Fun Games”文件夹和“First Person Exploration Kit Demo v2”文件夹的路径地址,是与Unity内部的“Player Settings”中的某些设置相关联的。
现在“Player Settings”中的设置是用于本插件中示例场景的默认设置 。要更改这些设置,请打开上方菜单栏中的File -> Build Settings -> Player Settings,然后更改Company Name选项和Product Name选项,以满足你的需求。
当你修改完毕后,“保存游戏系统”将继续工作,无需任何进一步的更改。
“保存游戏系统”使用了几个关键的文件/组:
文件名 | 文件名的中文翻译———————— | 说明 |
---|---|---|
options.dat | 选项 | 这个文件中储存了:与游戏选项(比如:鼠标的灵敏度)相关的所有数据。 |
core.dat | 核心 | 这个文件中储存了:一小组核心的数据。(例如:last level saved等) |
inventory.dat | 库存 | 这个文件中储存了:有关玩家库存的详细信息。例如:物品、数量、笔记、日志等。 |
player.dat | 玩家 | 这个文件中储存了:玩家的核心数据。例如:玩家的位置、玩家视线方向等。 |
level_N.dat | 关卡_N | 这个文件中储存了:每个level(Level应该是场景的意思吧)中的对象数据。(其中N是场景的索引,例如“level_1.dat”)。 请注意,只有这类文件同时具有“full(全部)”和“auto(自动)”保存的功能。 |
结构 (Architecture)
下面是一个高级架构图,显示了系统的结构。
在默认情况下,“保存游戏系统”使用的是C#对象序列化和二进制格式化,将数据写入文件。你也可以将保存数据的方式,替换为JSON、XML、纯文本或您最熟悉的任何内容。
使用C#原生的序列化的原因是,它是C#原生的功能,可以直接在Unity中使用。还有一个原因是,虽然有很多JSON库也可以用,但由于这些库都是第三方的库,考虑到许可证和维护的问题,因此没有把这些库包含在本插件中。所以才使用了C#原生的序列化。
如果你要替换"保存游戏系统"的核心,你只需要更改系统中,将数据写入文件部分的代码。这个部分的代码都包含在“FPESaveLoadManager”类中。
在“FPESaveLoadManager”类中,有一个“FPESaveLoadLogic”类型的变量。这个“FPESaveLoadLogic”类型的变量,用于与“FPESaveDataTypes”脚本进行通信(也就是说,让游戏对象和文件之间进行通信)。
例如,如果你想要将保存文件的方式替换为JSON,那么只需要在“FPESaveLoadLogic”脚本中,更改各种数据类型写入JSON的方式。其余的逻辑应该能够“正常工作”,因为它不关心怎样从文件创建类型,只需要数据有效就行。
最后,这个“保存游戏系统”只在Windows系统的电脑上进行了测试。如果你要使用到其他平台(例如移动设备、控制台等),可能需要你进行额外的设计和测试。
注意:
这个系统仅作为一个“让你为你的整个游戏创建更大的保存系统”的指南。
这个系统可以让你保存和加载First Person Exploration Kit插件中的核心数据和功能。
并提供了指导和示例的代码,以帮助你保存/读取:不在此插件范围内的自定义类型和对象。
公开的接口 (Public Interfaces)
在这里,“公开的接口”应该是“公开的函数”的意思。
"游戏的保存和加载"功能,由FPESaveLoadManager预制体进行处理。如果你的场景里有一个FPECore预制体,那么将会在场景运行时,自动为你创建一个FPESaveLoadManager预制体。
用于保存和加载的所有公开函数,都位于FPESaveLoadManager.cs脚本的“public_interface”的代码区域中。
这些公开函数是:
函数名 | 函数名的中文翻译———————— | 说明 |
---|---|---|
SaveGame() | 保存游戏 | 调用这个方法,将执行“全部保存(full save)”的功能。 这个方法与“Save Game(保存游戏)”菜单选项,一起使用。 (这个方法可以很方便的让“保存游戏”功能,与“保存游戏(Save Game)”菜单中的选项,关联在一起。) |
LoadGame() | 读取游戏 | 调用这个方法,将执行“全部加载(full load)”的功能。 这个方法与“Load Game(加载游戏)”菜单选项,一起使用。 |
ChangeSceneToAndAutoSave() | 更改场景并自动保存 | 更改场景(跳转场景),并自动保存当前场景的状态。 |
ChangeSceneToNoSave() | 更改场景但不保存 | 更改场景,并且不会自动保存当前场景的状态。 |
SavedGameExists() | 是否存在游戏存档 | 检查是否存在已保存的游戏存档。(玩家是否拥有一个游戏存档?) 然后我们可以刷新菜单的UI(例如,如果玩家还没有游戏存档,则应该禁用(或者隐藏)“加载游戏”的按钮)。 |
StartANewGame() | 开始一个新游戏 | 删除现有的“游戏存档”文件,并重置基础数据。 |
SaveGameOptions() | 保存游戏的选项 | 根据FPEInputManager脚本的当前状态,保存游戏的选项。 |
LoadGameOptions() | 读取游戏的选项 | 加载游戏选项的数据,并更新FPEInputManager的状态(这样可以把读取到的选项数据,反映出来)。 |
这些函数可以根据你的需要,在你的游戏菜单或快捷键中进行调用。
保存的对象数据 (Saved Object Data)
本插件中,核心的“可交互物”、玩家的库存、玩家的状态,以及游戏的选项数据,都可以被保存和加载。
如果你要保存和加载自定义的数据类型,可以这样做:将你自定义的类型添加到“FPESaveDataTypes”脚本文件,然后再添加一些逻辑,让系统可以“收集”和“恢复”这个数据类型的数据到场景中。在FPESaveDataTypes脚本中,有一个叫做“CUSTOM_TYPES”的代码区域,就是用于实现这个功能的。
本插件自带的基本类型,可以允许你保存和加载的Unity的数据类型,例如:Vector2、Vector3、Transform和Quaternions等。你可以组合这些“本插件自带的基本类型”,来保存基本的Unity数据,或者其他你自定义的类型数据。
这个插件中自带的“可被保存”的类型(比如:“可交互物”),可以作为你创建自定义的“可保存的数据类型”的参考。
除了下面列出的“这个插件中自带的类型”之外,你还可以创建自己的custom data types(自定义的数据类型)。
玩家库存 (Player Inventory)
库存物品类型的对象 (Inventory Item Type Objects)
所有的“库存物品”都必须位于Resources/RegioryItems文件夹中。
世界对象 (World Objects)
可拾取道具类型的对象 (Pickup Type Objects)
场景中的所有“库存物品”对象和“可拾取道具”对象,都必须是预制体。如果这些对象不是预制体,那么“保存游戏系统”将无法从Resources(资源)文件夹中,加载这些对象。
命名规范 (Naming Conventions)
保存“可拾取道具”类型的对象时,对象名字中第一个空格(或左括号之前)的文字,将会被认为是预制体的名称。
例如,场景中名为“demoSoup”、“demoSoup (1)”、“demoSoup(Clone)”和“demoSoup special2_FINAL”的“可拾取道具”对象,都将被视为是来自“demoSoup”预制体的。但是,名为“demoSoupIntro”的对象,将被认为是来自叫做“demoSoupIntro”的预制体。
注意:
要将这中“分隔字符”自定义为空格(或者左括号)以外的其他字符,
请参阅叫做FPEObjectTypeLookup脚本中的“PickupPrefabDelimiter”变量(char类型的数组)。
所有“可拾取道具”类型的对象,必须位于“Resources/Pickups”文件夹中。例如,场景中的“demoSoup”对象的预制体,位于“Resources/Pickups/demoSoup.prefab”中。
所有“库存”类型的对象,必须位于“Resources/InventoryItems”下的路径中。 例如,场景中的“demoApple”对象的预制体,位于'Resources/InventoryItems/demoApple.prefab'中。
注意:
要自定义资源(Resource)的路径,请参阅FPEObjectTypeLookup类。
所有“库存”类型的预制体,必须在FPEObjectTypeLookup脚本中注册查找条目(lookup entry)。如果未提供任何条目,则不能保存和加载这个类型的对象,并会打印错误。
“注册查找条目”就是“新注册一个库存物品”的意思。
(比如,你想做一个新的库存物品,这个可交互物是一瓶可乐。
那么你需要把这个可乐注册到系统中,才能让这瓶可乐可以被放入库存中。)
下面是一个“添加查找条目”的示例:
inventoryItemsLookup.Add(FPEInventoryManagerScript.eInventoryItems.APPLE, "demoApple");
这行代码可以添加一个查找条目:我们在系统中新注册一个APPLE类型的“可交互物”,APPLE类型的对象的预制体名字叫做“demoApple”。
这意味着当加载游戏后,并且遇到类型为“APPLE”的“可交互物”时,将实例化“Resources/InventoryItems/demoApple.prefab”预制体。
你必须对每个“库存物品”类型都注册查找条目,才能使“保存游戏系统”正常工作。
FPEInteractableDockScript类型 (FPEInteractableDockScript Type)
玩家停靠的状态,会按照你的预期进行保存和加载。
但是,如果“停靠点”类型的对象具有复杂的状态机(state machine)(应该是说如果具有动画状态机的话),那么必须单独保存这些状态机的状态。
例如,如果玩家坐在电视机前面,并且玩家保存了游戏。那么当玩家加载游戏时,玩家会仍然坐在电视机前。但是,如果你的停靠点涉及到一些复杂的动画状态,比如电视机上会播放几个短片,那么你必须保存这些动画状态。
这里有3种简单的方法,帮你实现这个功能:
- 将复杂的状态机设置为保存游戏逻辑(save game logic)的一部分,并在加载游戏时恢复它。
- 不要保存它,在玩家重新加载游戏时,让玩家自己重新去激活它。
- 添加一些逻辑,不允许在与复杂的状态机进行交互时保存游戏
FPEDoor类型 (FPEDoor Type)
注意:
FPEDoor类对象的示例,
请参阅\Prefabs\DemoPrefabs\DoorsAndDrawers\文件夹中“门”的预制体。
“门”要保存和读取的数据有:“内部的锁”的状态、“外部的锁”的状态,以及“门把手的交互字符串”。这些数据都是在内部进行管理的。只要每扇门都有唯一的名字,你就不用操心了,“门”就会正常的被保存和读取。
这个插件中,配有“滑动门”和两种类型的“摆动门”。如果你想创建一个其他类型的门,你可以扩展FPEDoor类。
FPEDrawer类型 (FPEDrawer Type)
注意:
有关FPEDrawer类对象的示例,
请参阅\Prefabs\DemoPrefabs\DoorsAndDrawers\文件夹中的“抽屉”的预置体。
(例如demoBasicDesk.prefab)。
“抽屉”要保存和读取的数据有:“内部的锁”的状态、“外部的锁”的状态,以及“抽屉把手的交互字符串”。这些数据都是在内部进行管理的。只要每个抽屉都有唯一的名字,你就不用操心了,“抽屉”就能被正常的保存和读取。
如果你想创建一个其他类型的抽屉,你可以扩展FPEDrawer 类。
FPEInteractableActivateScript类型 (FPEInteractableActivateScript Type)
激活事件 (Activate Events)
对于每个“可激活物”类型的对象,它们身上的FPEInteractableActivateScript脚本中,都有一个“Fire Toggle Events On Load Game(在加载游戏时,触发Toggle事件)”选项。如果将此选项设置为true,则在加载游戏时触发Toggle事件,从而恢复保存时的开关状态。例如:玩家打开电灯的开关,然后保存游戏。如果这个选项的值为true,那么会在加载游戏时,再次触发“Toggle事件”,从而自动打开电灯的开关。
对于其他的“可激活物”类型的对象(比如:门),可以将这个选项设置为false。这将允许在保存游戏时打开的门,在加载游戏时仍处于打开状态。
FPEEventTrigger类型 (FPEEventTrigger Type)
触发器的事件 (Trigger Events)
“事件触发器”是特殊的,因为它们几乎可以做任何事情。也许最特别的是它们可以包含逻辑循环。
例如,事件触发器A可以撤销自身的“部署”,而事件触发器B可以撤销自身和事件触发器A的的“部署”。如果你想在保存状态时,保存“事件触发器的事件”的顺序,这实际上是不可行的。
因此,这些“事件触发器”的状态被保存,但它们的事件的后果却没有。也就是说,“事件触发器”的“部署(Armed)”和“撤销(Tripped)”状态将被保存。但对对象进行的启用、禁用、销毁等操作,是不会被保存的。
对于复杂的“事件触发器的事件”(例如:序列化脚本、过场动画、大型组合等),建议将其作为较大的事件,来单独进行跟踪。
例如:
- 事件触发器A被激活时:设置事件触发器A为“撤销”状态,并且设置事件触发器B为“部署”状态
- 事件触发器B被激活时:设置为事件触发器为“撤销”状态,并升起一面墙
- “保存系统”将跟踪事件触发器A和事件触发器B的状态,现在A和B都被激活了,而且都是“撤销”状态
- 现在,你需要额外做一些工作,以保存在“玩家保存游戏”时,升起的那面墙的状态
注意:
对于特殊物品的实例化(生成)或其他的内容,建议将这些需要生成的物品,做成是可保存的类型。
例如,如果有一个事件触发器在激活时,生成了一个demoApple,
因为这个demoApple是一个Pickup类型的对象,所以会被保存系统自动的保存和加载。
音频日志和附加笔记 (Audio Diaries and Attached Notes)
当玩家播放音频日志或阅读笔记时,相应的信息(例如:文本、声音)将作为音频日志和笔记条目,添加到库存中。
当游戏被保存时(使用完全保存(full)或自动保存(auto)),将会保存这些日志和笔记条目的对象的“收集(collected)”状态。
例如,如果玩家“读取/收集”场景1中的“笔记A”,然后玩家保存了游戏。再读取游戏时,他们就不能再次收集该笔记了,因为这个笔记已经在库存中了。
FPEGenericSaveableGameObject类型 (FPEGenericSaveableGameObject Type)
这个插件中还包含了一个叫做“FPEGenericSaveableGameObject”的脚本,我们称之为“通用的保存数据的类型”。这个脚本可以用于“其他的一般的”对象(这个脚本非常的通用),让这些对象可以支持数据的保存和加载。这个脚本会用到“FPEGenericSaveableInterface”接口和“FPEGenericObjectSaveData”数据类型。