保存和加载游戏

保存和加载游戏方式的概述

Choose your operating system:

Windows

macOS

Linux

选择实现方法:

Blueprints

C++

在不同游戏中,"保存游戏"的含义可能会有很大的不同,但多数现代游戏均会支持玩家退出游戏后可在之后从离开位置继续游戏。基于游戏的类型,可能只需些许基本信息,例如玩家到达的最后检查点及其找到的物品等。当然,可能仍需更多详细信息,例如玩家与游戏其他角色的社交互动列表,或各种任务、任务目标或支线情节的当前状态。虚幻引擎4(UE4)具有保存和加载系统,可以一个或多个用于满足游戏特定需求的自定义 SaveGame 类为中心进行运转,包括在多个游戏会话中保存时所需的全部信息。系统支持保存多个游戏文件,可将不同SaveGame类保存到此类文件。此功能有助于区分全局解锁功能与游戏进程特定游戏数据。

创建SaveGame对象

要新建SaveGame对象, 新建蓝图类 。弹出 选取父类(Pick Parent Class) 对话框时,展开 自定义类(Custom Classes) 下拉框,然后选择 SaveGame 。可用搜索框直接跳至SaveGame。将新蓝图命名为MySaveGame。

savegame.png

在新的SaveGame对象蓝图中,创建要保存所有信息的变量。

本例中还声明了部分将用于存储SaveSlotName和UserIndex默认值的变量,因此保存到该 SaveGame对象的各类无需单独设置此类变量。此为可选步骤,若未修改默认值,将覆盖一个保存插槽。

SaveGameVariables.png

编译蓝图后,可设置变量的默认值。

创建SaveGame对象

USaveGame 类将设置对象,可作为 Kismet/GameplayStatics.h 中声明的保存和加载函数的目标。

可基于 USaveGame 使用 C++类向导 新建类。

SaveGameCode.png

在本例中,新 USaveGame 类名为 UMySaveGame 。要进行使用,在游戏模块标头文件中,将以下行添加 #include 指令后:

MyProject.h

#include "MySaveGame.h"
#include "Kismet/GameplayStatics.h"

标头

SaveGame 对象的标头文件中,可声明 SaveGame 要存储的变量。

UPROPERTY(VisibleAnywhere, Category = Basic)
FString PlayerName;

本例中还声明了将用于存储 SaveSlotName UserIndex 默认值的变量,因此保存到此 SaveGame 对象的各类无需单独设置此类变量。此为可选步骤,若未修改默认值,将覆盖一个保存插槽。

MySaveGame.h

#pragma once

#include "GameFramework/SaveGame.h"
#include "MySaveGame.generated.h"

/**
 * 
 */
UCLASS()
class [PROJECTNAME]_API UMySaveGame : public USaveGame
{
    GENERATED_BODY()

    public:

    UPROPERTY(VisibleAnywhere, Category = Basic)
    FString PlayerName;

    UPROPERTY(VisibleAnywhere, Category = Basic)
    FString SaveSlotName;

    UPROPERTY(VisibleAnywhere, Category = Basic)
    uint32 UserIndex;

    UMySaveGame();
};

SaveGame 对象的源文件通常无需特定代码即可运行,在此处设置特定保存系统的其他功能时除外。

此例在类构造函数中定义了 SaveSlotName UserIndex 的值,以便其可被其他游戏类读出和使用。

MySaveGame.cpp

// 版权所有 1998-2018 Epic Games, Inc。保留所有权利。

#include "[ProjectName].h"
#include "MySaveGame.h"

UMySaveGame::UMySaveGame()
{
    SaveSlotName = TEXT("TestSaveSlot");
    UserIndex = 0;
}

保存游戏

创建SaveGame类后,可使用变量填充以存储游戏数据。例如,创建整数变量存储玩家分数,或创建字符串变量存储玩家名称。保存游戏时,需将当前游戏场景中的此类信息转移到SaveGame对象中,加载游戏时,再将此类信息从SaveGame对象复制到游戏对象中(如角色、玩家控制器或游戏模式)。

首先,使用 Create Save Game Object 节点,基于SaveGame类创建对象。确保将 Save Game Class 输入引脚的下拉框设为创建的类,本例中为 MySaveGame Create Save Game Object 节点将自动更改其输出引脚类型,以使指定类型与 Save Game Class 输入引脚匹配。利用此操作,可无需 Cast To 节点而直接进行使用。建议使用 提升为变量(Promote to Variable) 将结果对象保存至变量,以便之后重复使用刚创建的对象。

SaveGameBP_1.png

现在 保存游戏实例(Save Game Instance) 已包含自定义SaveGame对象,可向其发送信息。例如,可将 玩家名称(Player Name) 字段设为"PlayerOne"。继续设置SaveGame对象中的字段,直至包括要存储在保存游戏文件中的所有数据。

SaveGameBP_2.png

完全填充SaveGame对象后, 利用 ASync Save Game To Slot 节点完成保存游戏。还需提供文件名和用户ID。本例中的文件名和用户ID为之前创建的默认值。将从顶部引脚立即继续执行,savegame操作完成后将从第二引脚执行。执行第二引脚前输出引脚无效。

SaveGameBP_3.png

即便在保存大量数据时, Async Save Game To Slot 也能避免卡顿,因此推荐使用此方法保存数据。若savegame数据较小或在菜单或暂停画面中保存,则可使用 Save Game To Slot 节点保存游戏,如下所示。

SaveGameBP_4.png

以下屏幕截图显示了用MySaveGame类保存游戏的完成蓝图过程:

点击放大上图。

首先,(在 UGameplayStatics 库中)调用 CreateSaveGameObject ,以获取新 UMySaveGame 对象。获取该对象后,使用要保存的数据进行填充。最后,调用 SaveGameToSlot AsyncSaveGameToSlot 将数据写出到设备。

异步保存

推荐使用 AsyncSaveGameToSlot 保存游戏。使用异步可防止帧率突然卡帧,使玩家不易觉察,并避免某些平台上出现认证问题。完成保存流程后,将使用插槽命名、用户索引和表明成败的 布尔值 调用委托( FAsyncSaveGameToSlotDelegate 类型)。

if (UMySaveGame* SaveGameInstance = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass())))
{
    // 设置(可选)委托。
    FAsyncSaveGameToSlotDelegate SavedDelegate;
    // USomeUObjectClass::SaveGameDelegateFunction is a void function that takes the following parameters: const FString& SlotName, const int32 UserIndex, bool bSuccess
    SavedDelegate.BindUObject(SomeUObjectPointer, &USomeUObjectClass::SaveGameDelegateFunction);

    // 设置savegame对象上的数据。
    SaveGameInstance->PlayerName = TEXT("PlayerOne");

    // 启动异步保存进程。
    UGameplayStatics::AsyncSaveGameToSlot(SaveGameInstance, SlotNameString, UserIndexInt32, SavedDelegate);
}

同步保存

SaveGameToSlot 足以应对小型SaveGame格式,暂停时或在菜单中即可保存游戏。其简单易用,可即时保存游戏并返回表明成败的 布尔值 。对于较大数据量,或要在玩家与游戏世界积极互动时自动保存游戏,建议使用 AsyncSaveGameToSlot

if (UMySaveGame* SaveGameInstance = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass())))
{
    // 设置savegame对象上的数据。
    SaveGameInstance->PlayerName = TEXT("PlayerOne");

    // 即时保存游戏。
    if (UGameplayStatics::SaveGameToSlot(SaveGameInstance, SlotNameString, UserIndexInt32))
    {
        // 成功保存。
    }
}

二进制保存

可使用 SaveGameToMemory 函数将SaveGame对象传输到内存。此函数仅支持同步操作,但快于保存到驱动器。调用器将提供存储数据缓冲区( TArray<uint8>& )的引用。成功时,函数返回true。

TArray<uint8> OutSaveData;
if (UGameplayStatics::SaveGameToMemory(SaveGameObject, OutSaveData))
{
    // 操作成功,OutSaveData现在包含SaveGame对象的二进制代表。
}

使用缓冲区( const TArray<uint8>& )、插槽命名和用户ID信息以调用 SaveDataToSlot ,也可将二进制数据直接保存到文件,与 SaveGameToSlot 函数类似。与 SaveGameToMemory 相同,此函数仅支持同步操作,并返回 布尔值 表明成败。

if (UGameplayStatics::SaveDataToSlot(InSaveData, SlotNameString, UserIndexInt32))
{
    // 操作成功,已将InSaveData写入到由我们提供的插槽命名和用户ID定义的存档文件。
}

在开发平台上,保存游戏文件使用 .sav 扩展名,并在项目的 Saved\SaveGames 文件夹中显示。在其他平台上,尤其是主机平台上,此操作将视特定文件系统而有所不同。

加载游戏

要加载保存的游戏,须提供保存时使用的保存插槽命名和用户ID。若存在指定SaveGame,引擎将用其中数据填充SaveGame对象,并将其返回为SaveGame( USaveGame 类)基本对象。然后可将该对象投射回自定义SaveGame类并访问数据。根据SaveGame类型所含数据的类型,可能需保存副本,也可直接使用数据并废弃对象。

与保存相同,亦可同步或异步加载。若有大量数据,或希望在加载时使用加载画面或动画,建议采用异步方法。同步方法主要用于快速加载小型数据。

要同步加载,使用 Load Game From Slot 。该节点直接明了,提供的插槽命名和用户ID辨识出有效SaveGame文件,其将返回有效SaveGame对象。执行加载操作时,游戏将停止。

LoadGameBP.png

Async Load Game From Slot 的工作原理大致相同,但具有两个执行输出引脚。加载操作开始时将执行第一个引脚,加载操作完成时则执行第二个引脚。执行第二引脚前变量输出引脚无效。 成功(Success) 引脚可表示加载操作是否成功,但也可将返回对象传递到 Is Valid 节点,或将来自 Cast To 节点的失败视为加载过程中出现的错误总计。

LoadGameBPAsync.png

异步加载

使用 AsyncLoadGameFromSlot 执行异步加载时,必须提供回调委托,以接收系统加载的数据。

// 设置委托。
FAsyncLoadGameFromSlotDelegate LoadedDelegate;
// USomeUObjectClass::LoadGameDelegateFunction 是一个void类型的函数,它需要传入以下参数: const FString& SlotName, const int32 UserIndex, USaveGame* LoadedGameData
LoadedDelegate.BindUObject(SomeUObjectPointer, &USomeUObjectClass::LoadGameDelegateFunction);
UGameplayStatics::AsyncLoadGameFromSlot(SlotName, 0, LoadedDelegate);

同步加载

若成功, LoadGameFromSlot 函数将创建并返回 USaveGame 对象。

// 检索并将USaveGame对象投射到UMySaveGame。
if (UMySaveGame* LoadedGame = Cast<UMySaveGame>(UGameplayStatics::LoadGameFromSlot(SlotName, 0)))
{
    // 操作成功,LoadedGame现在将包含较早前保存的数据。
    UE_LOG(LogTemp, Warning, TEXT("LOADED:%s"), *LoadedGame->PlayerName);
}

二进制加载

可在文件中,使用 LoadDataFromSlot 并以原始二进制格式加载SaveGame数据。除开不创建SaveGame对象外,此函数与 LoadGameFromSlot 极为类似。同步操作尽可用于此类加载。

TArray<uint8> OutSaveData;
if (UGameplayStatics::LoadDataFromSlot(OutSaveData, SlotNameString, UserIndexInt32))
{
    // 操作成功,OutSaveData现在包含SaveGame对象的二进制代表。
}

还可通过调用 LoadGameFromMemory ,将此二进制数据转换为SaveGame对象。此才做为同步调用,成功时返回新的 USaveGame 对象,失败时返回空指针。

if (UMySaveGame* SaveGameInstance = Cast<UMySaveGame>(LoadGameFromMemory(InSaveData)))
{
    // 操作成功,SaveGameInstance将能投射到预期类型(UMySaveGame)。
}
欢迎帮助改进虚幻引擎文档!请告诉我们该如何更好地为您服务。
填写问卷调查
取消