├── README.md ├── PersistentStore.h └── PersistentStore.cpp /README.md: -------------------------------------------------------------------------------- 1 | UE4ActorSaveLoad 2 | ================ 3 | 4 | A generic Actor Save/Load system that uses UE4's built in Actor Serialization 5 | -------------------------------------------------------------------------------- /PersistentStore.h: -------------------------------------------------------------------------------- 1 | // jweinhart 2 | 3 | #pragma once 4 | #include "PersistentStore.generated.h" 5 | 6 | /* We need to set ArIsSaveGame and use the ObjectAndNameAsString Proxy */ 7 | struct GALAXY_API FSaveGameArchive : public FObjectAndNameAsStringProxyArchive 8 | { 9 | /** 10 | * Creates and initializes a new instance. 11 | * 12 | * @param InInnerArchive - The inner archive to proxy. 13 | */ 14 | FSaveGameArchive(FArchive& InInnerArchive) 15 | : FObjectAndNameAsStringProxyArchive(InInnerArchive, true) 16 | { 17 | ArIsSaveGame = true; 18 | } 19 | }; 20 | 21 | 22 | /* Our Serialized Actor Data struct - ActorData is populated from the result of Actor->Serialize */ 23 | USTRUCT() 24 | struct FActorSaveData 25 | { 26 | GENERATED_USTRUCT_BODY() 27 | FTransform ActorTransform; 28 | FString ActorClass; 29 | TArray ActorData; 30 | 31 | friend FArchive& operator<<(FArchive& Ar, FActorSaveData& ActorData); 32 | }; 33 | 34 | /* Struct for the "Root" save file */ 35 | USTRUCT() 36 | struct FSaveGameData 37 | { 38 | GENERATED_USTRUCT_BODY() 39 | FName GameID; 40 | FDateTime Timestamp; 41 | TArray SavedActors; 42 | 43 | friend FArchive& operator<<(FArchive& Ar, FSaveGameData& GameData); 44 | 45 | }; 46 | 47 | class GALAXY_API PersistentStore 48 | { 49 | public: 50 | static void SaveGame(); 51 | static AActor* GetDebugTarget(); 52 | static TArray GetEligibleActorsToSave(); 53 | static void LoadGame(); 54 | }; 55 | -------------------------------------------------------------------------------- /PersistentStore.cpp: -------------------------------------------------------------------------------- 1 | // jweinhart 2 | 3 | #include "Galaxy.h" 4 | #include "PersistentStore.h" 5 | #include "Engine/World.h" 6 | 7 | #define SAVE_FILE "Test.sav" 8 | #define GAME_ID "TestGame" 9 | 10 | FArchive& operator<<(FArchive& Ar, FSaveGameData& GameData) 11 | { 12 | Ar << GameData.GameID; 13 | Ar << GameData.Timestamp; 14 | Ar << GameData.SavedActors; 15 | 16 | return Ar; 17 | } 18 | 19 | FArchive& operator<<(FArchive& Ar, FActorSaveData& ActorData) 20 | { 21 | Ar << ActorData.ActorTransform; 22 | Ar << ActorData.ActorClass; 23 | Ar << ActorData.ActorData; 24 | 25 | return Ar; 26 | } 27 | 28 | void PersistentStore::LoadGame() 29 | { 30 | TArray LoadArray; 31 | if (!FFileHelper::LoadFileToArray(LoadArray, *FString(TEXT(SAVE_FILE)))) 32 | { 33 | UE_LOG(LogTemp, Warning, TEXT("Load Failed!")); 34 | return; 35 | } 36 | else 37 | { 38 | UE_LOG(LogTemp, Warning, TEXT("Load Succeeded!")); 39 | } 40 | 41 | if (LoadArray.Num() <= 0) 42 | { 43 | UE_LOG(LogTemp, Warning, TEXT("Loaded file empty!")); 44 | return; 45 | } 46 | 47 | UE_LOG(LogTemp, Warning, TEXT("Save File Size: %d"), LoadArray.Num()); 48 | 49 | FMemoryReader FromBinary = FMemoryReader(LoadArray, true); 50 | FromBinary.Seek(0); 51 | 52 | FSaveGameData SaveData; 53 | 54 | FromBinary << SaveData; 55 | 56 | UE_LOG(LogTemp, Warning, TEXT("Loaded data with Timestamp: %s"), *SaveData.Timestamp.ToString()); 57 | FromBinary.FlushCache(); 58 | LoadArray.Empty(); 59 | FromBinary.Close(); 60 | 61 | for (FActorSaveData ActorRecord : SaveData.SavedActors) 62 | { 63 | FVector SpawnPos = ActorRecord.ActorTransform.GetLocation(); 64 | FRotator SpawnRot = ActorRecord.ActorTransform.Rotator(); 65 | UClass* Result = FindObject(ANY_PACKAGE, *ActorRecord.ActorClass); 66 | if (Result) 67 | { 68 | AActor* NewActor = GWorld->SpawnActor(Result, &SpawnPos, &SpawnRot); 69 | FMemoryReader MemoryReader(ActorRecord.ActorData, true); 70 | FSaveGameArchive Ar(MemoryReader); 71 | NewActor->Serialize(Ar); 72 | NewActor->SetActorTransform(ActorRecord.ActorTransform); 73 | } 74 | } 75 | } 76 | 77 | void PersistentStore::SaveGame() 78 | { 79 | TArray SavedActors; 80 | 81 | TArray ActorsToSave = GetEligibleActorsToSave(); 82 | 83 | for (AActor* Actor : ActorsToSave) 84 | { 85 | if (Actor == nullptr) 86 | { 87 | continue; 88 | } 89 | 90 | FActorSaveData ActorRecord; 91 | ActorRecord.ActorClass = Actor->GetClass()->GetPathName(); 92 | ActorRecord.ActorTransform = Actor->GetTransform(); 93 | 94 | FMemoryWriter MemoryWriter(ActorRecord.ActorData, true); 95 | FSaveGameArchive Ar(MemoryWriter); 96 | 97 | Actor->Serialize(Ar); 98 | SavedActors.Add(ActorRecord); 99 | Actor->Destroy(); 100 | } 101 | 102 | FSaveGameData SaveData; 103 | 104 | SaveData.GameID = TEXT(GAME_ID); 105 | SaveData.Timestamp = FDateTime::Now(); 106 | SaveData.SavedActors = SavedActors; 107 | 108 | FBufferArchive ToBinary; 109 | 110 | ToBinary << SaveData; 111 | 112 | if (ToBinary.Num() <= 0) 113 | { 114 | return; 115 | } 116 | 117 | if (FFileHelper::SaveArrayToFile(ToBinary, *FString(TEXT(SAVE_FILE)))) 118 | { 119 | UE_LOG(LogTemp, Warning, TEXT("Save Success!")); 120 | } 121 | else 122 | { 123 | UE_LOG(LogTemp, Warning, TEXT("Save Failed!")); 124 | } 125 | 126 | ToBinary.FlushCache(); 127 | ToBinary.Empty(); 128 | 129 | LoadGame(); 130 | return; 131 | } 132 | 133 | TArray PersistentStore::GetEligibleActorsToSave() 134 | { 135 | TArray Actors; 136 | 137 | //Hook up your actual actor aggregation here (find actors of a specific interface, etc) 138 | Actors.Add(GetDebugTarget()); 139 | 140 | return Actors; 141 | } 142 | 143 | AActor* PersistentStore::GetDebugTarget() 144 | { 145 | /* Stupid debug function to just get the actor we're looking at */ 146 | APlayerController* PlayerController = GWorld->GetFirstPlayerController(); 147 | FCollisionQueryParams QueryParams; 148 | QueryParams.AddIgnoredActor(PlayerController); 149 | QueryParams.AddIgnoredActor(PlayerController->GetPawn()); 150 | QueryParams.bTraceComplex = true; 151 | QueryParams.TraceTag = TEXT("GetDebugTarget"); 152 | 153 | FCollisionObjectQueryParams ObjectQueryParams; 154 | ObjectQueryParams.AddObjectTypesToQuery(ECC_PhysicsBody); 155 | ObjectQueryParams.AddObjectTypesToQuery(ECC_Pawn); 156 | ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic); 157 | 158 | FVector CameraLoc = PlayerController->PlayerCameraManager->GetCameraLocation(); 159 | FVector CameraRot = PlayerController->PlayerCameraManager->GetCameraRotation().Vector(); 160 | FVector CheckEndPos = CameraLoc + CameraRot * 500.0f; 161 | 162 | FHitResult HitResult; 163 | 164 | AActor* DebugTarget = nullptr; 165 | 166 | if (GWorld->LineTraceSingle(HitResult, CameraLoc, CheckEndPos, QueryParams, ObjectQueryParams)) 167 | { 168 | DebugTarget = HitResult.Actor.Get(); 169 | } 170 | 171 | return DebugTarget; 172 | } 173 | 174 | --------------------------------------------------------------------------------