So I’ve got it working locally. I don’t actually need the ability to break the FSaviorRecord.
That’s nice.
I am adding these nodes that will help to have more control over individual records as well:
Nice, good stuff! Can you let me know when these changes go out? Also a few questions:
- Does the FindRecord (by name) node expect the full path ID?
- How do the P0_, P1_, P2_, ID prefixes work?
No, it’s checked on screenshot above to illustrate, but record ID != full object path.
Full path is actually a member of a record.
Prefixes = Owning Player Connection.
Player 0 = P0_ = Local Player.
I just bought and downloaded Savior. Do I have to read all 39 pages of the forum here to find a Getting Started tutorial? I downloaded the demo but trying to open the project says “VS 2017” required. I’m already using 2019. So I open it in 2019 and it wants to install “Desktop Development with C++” but I’m already using C++ and don’t want to risk corrupting my IDE by installing spurious components. I’ve spent the latest couple of months trying to get state persistence working and gave up so I bought this and can’t even get to stage 1. I don’t want to implement Savior in blueprints. Just simple character data is all I want to persist at this stage - name and class. Please throw me a bone someone.
@Grrrover there’s a small guide on page 1 of this forum post, but you can also check a list of nodes introduced by the plugin here:
https://brunoxavierleite.github.io/Savior2API.github.io
A good place for you to start seems to be simply using the Async “Save Game Mode” and “Load Game Mode” nodes, the ones that have a little clock icon on top-right corner.
Thanks for the reply Bruno. Here’s what I’ve been trying to achieve. It’s very simple and can’t be too far off the mark, surely. CharacterName is empty at BeginPlay().
//-------- Base Character ---------
//Header (.h)
UCLASS()
class TEST_API ACharacterBase : public ACharacter, public ISAVIOR_Procedural
{
public:
UPROPERTY(SaveGame)
FGuid SGUID;
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame)
FText CharacterName;
}
//Implementation (.cpp)
ACharacterBase::ACharacterBase()
{
SGUID = USavior3::CreateSGUID(this);
}
void ACharacterBase::OnFinishRespawn(const FSaviorRecord& Data)
{
APC_Lobby* pc = Cast<APC_Lobby>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
ESaviorResult newSlotResult;
USavior3* si = USavior3::NewSlotInstance(this, pc->Savior3Slot, newSlotResult);
ESaviorResult loadActorResult;
si->LoadActorWithGUID(this, SGUID, loadActorResult);
}
void ACharacterBase::BeginPlay()
{
Super::BeginPlay();
GetCharacterNameRenderer()->SetText(CharacterName);
}
// ---------- Player Controller ------------
//Header (.h)
USavior3* Savior3Slot;
//Implementation (.cpp)
static ConstructorHelpers::FObjectFinder<USavior3> Savior3Asset(TEXT(“Savior3’/Game/NewSavior3.NewSavior3’”));
if (Savior3Asset.Object != nullptr)
{
Savior3Slot = Savior3Asset.Object;
}
void APC_Lobby::RespawnPlayer_Implementation()
{
GetPawn()->Destroy();
spawnedCharacter = GetWorld()->SpawnActor(characterClass, &Location, spawnParameters);
}
OnFinishRespawn and any member of ISAVIOR_Procedural interface is only relevant for the “Load Game World” algorithm.
For doing things manually like that, you should instead implement the _Serializable interface.
“OnFinishRespawn” in this sample code will never execute.
Do I need a new slot instance? Because that fails. SaveActor/Hierarchy doesn’t fail but neither does it call the Serializable interface’s OnSaved event. Are there any examples of this “manual” approach? Later in the game I have plenty of things to save (using the other algorithms) but at this stage in the game I simply want the character name to be available after the “spawn” transition without having to write it to disk. I’ve tried so many permutations now. I grow weary.
Regards.
@Grrrover A character + controller of what you’re trying to do would be something like these:
Character.h:
#pragma once
#include "Savior3.h"
#include "CoreMinimal.h"
#include "Engine/StaticMesh.h"
#include "GameBaseCharacter.generated.h"
UCLASS()
class MYGAME_API ACharacterBase : public ACharacter, public ISAVIOR_Serializable
{
GENERATED_BODY()
public:
ACharacterBase();
public:
UPROPERTY(SaveGame) FGuid SGUID;
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame)
FText CharacterName;
public:
virtual void BeginPlay() override;
virtual void OnLoaded_Implementation(const FSlotMetaData &MetaData) override;
};
Character.cpp:
#include "GameBaseCharacter.h"
ACharacterBase::ACharacterBase()
{
SGUID = USavior3::CreateSGUID(this);
}
void ACharacterBase::BeginPlay()
{
Super::BeginPlay();
//GetCharacterNameRenderer()->SetText(CharacterName);
LOG_SV(true,ESeverity::Warning,CharacterName.ToString());
}
void ACharacterBase::OnLoaded_Implementation(const FSlotMetaData &MetaData)
{
//GetCharacterNameRenderer()->SetText(CharacterName);
LOG_SV(true,ESeverity::Warning,
FString::Printf(TEXT("OnLoaded() ==>> %s :: %s"),
*MetaData.SaveLocation,
*CharacterName.ToString()
)
);
}
[HR][/HR]
Controller.h:
#pragma once
#include "Savior3.h"
#include "CoreMinimal.h"
#include "Engine/StaticMesh.h"
#include "UObject/ConstructorHelpers.h"
#include "GameFramework/PlayerController.h"
#include "GameBaseController.generated.h"
UCLASS()
class MYGAME_API AControllerBase : public APlayerController
{
GENERATED_BODY()
public:
AControllerBase();
public:
virtual void BeginPlay() override;
virtual void SetupInputComponent() override;
public:
void DoSaveGame();
private:
USavior3* SlotBaseType;
};
Controller.cpp:
#include "GameBaseController.h"
AControllerBase::AControllerBase()
{
ConstructorHelpers::FObjectFinder<USavior3>SlotClass(TEXT("/Game/SAVE_Slot_1.SAVE_Slot_1"));
if (SlotClass.Succeeded()) {
SlotBaseType = SlotClass.Object;
}
}
void AControllerBase::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("SaveAction",IE_Pressed,this,&AControllerBase::DoSaveGame);
}
void AControllerBase::BeginPlay()
{
Super::BeginPlay();
ESaviorResult Results;
if (SlotBaseType != nullptr) {
LOG_SV(
true, ESeverity::Warning,
FString::Printf(TEXT("DoLoadGame() ==>> %s"),*SlotBaseType->GetSlotName())
);
auto SaveSlot = USavior3::NewSlotInstance(this,SlotBaseType,Results);
SaveSlot->LoadGameMode(this,Results);
switch(Results)
{
case ESaviorResult::Failed:
{
LOG_SV(true,ESeverity::Error,TEXT("Something went wrong while loading progress!"));
} break;
case ESaviorResult::Success:
{
LOG_SV(true,ESeverity::Warning,TEXT("Progress loaded successfully!"));
} default:break;
}
}
}
void AControllerBase::DoSaveGame()
{
ESaviorResult Results;
if (SlotBaseType != nullptr) {
LOG_SV(
true, ESeverity::Warning,
FString::Printf(TEXT("DoSaveGame() ==>> %s"),*SlotBaseType->GetSlotName())
);
auto SaveSlot = USavior3::NewSlotInstance(this,SlotBaseType,Results);
SaveSlot->SaveGameMode(this,Results);
switch(Results)
{
case ESaviorResult::Failed:
{
LOG_SV(true,ESeverity::Error,TEXT("Something went wrong while saving progress!"));
} break;
case ESaviorResult::Success:
{
LOG_SV(true,ESeverity::Warning,TEXT("Progress saved successfully!"));
} default:break;
}
}
}
Compiled and ran fine. Saved game (to disk) and loaded it without problem. However the OnLoaded() call-back for the base character (implementing ISAVIOR_Serializable) is never executed. I replaced Savior3::Load/SaveGame() with Savior3::Load/SaveActor() (and changed logic accordingly) but that failed to save to slot. I think I’ll just save player states in game mode instead, this isn’t working out for me.
SaveActor() generates the record on slot in memory.
You then have to call WriteSlotToFile() to write down your slot instance (containing all the actor records) to disk.
Hello Xavier, first of all thanks for the plugin.
I have almost exactly the useCase you describe above:
- At first play start the player creates a ResourceManager UObject and saves the ref in a variable
- The ResourceManager has itself an Array References to UObjects where amount of each resource is stored and so on.
I am not exactly sure how to restore my Resources in the ResourceManager. Everything is marked with SaveGame (as with Actors where it works perfectly). Also I tried adding the UObjects to Instance Scope and Respawn Scope which made no difference. The ResourceManager gets restored but the array is always empty on load.
I wanted to try your sample Project above but the Link is dead
That project link above was to show that the “SGUID” property must be implemented in your UObject class if you want the array to restore references to object instances on load.
There’s a post that explains SGUID here:
Thanks for the fast reply.
I understand implementing SGUIDs, all my Actors where Saving works have them. I also have them on my UOBjects. But for example you can’t even call “Create Once: SGUID” inside an UOBject. I don’t even know if I need to assign them in an UObject?
Can you give an Example on how to setup an UOBject to be saved? I can’t find that info anywhere other then your example project which is down unfortunately.
Also I’m on 4.23 unfortunately, because I have to use the Tensorflow-Plugin.
On UObject you should take each instance from the array and then manually generate the Guid value in a for each loop as soon as you instantiate them;
This is what was in that example.
The thing you have to plan is, SGUID values should be unique for each instance, not invalid and, always the same set across multiple application runs.
For important objects, some people create a data table of Guid values on Excel and always assign these guids to the same array of Object instances on their SGUID property, like MMOs do.
Unreal also implements a “Actor Guid” but that is only reliable for permanently spawned actors.
Hi Bruno,
Just updating from Savior 2 to Savior 3 at the moment. I’ve read through the update notes ([PLUGIN] Savior 3 - #9 by BrUnO_XaVIeR - Marketplace - Unreal Engine Forums) and have fixed up the Load Object nodes. I’m running into a few oddities when using the Save Game World (which was previously working).
I have some warnings when the engine loads saying: “failed to load ‘/Script/Savior2’: Can’t find file.” - not a lot of them, but some from files I wouldn’t have thought would be relevant. It’s only a warning, but still an odd one to see.
I’m also now getting these errors (quite a lot of them) in the debug output on save, where I wasn’t before:
SaviorLog: {S}:: Serialized Component :: ERROR:ID
SaviorLog: {S}:: SAVED DATA for ERROR:ID :: {}
I’m guessing this is a component I probably need to add !Save onto, but there isn’t any indication on what component, or even what actor it belongs to (aside from guessing based on the output order).
SaviorLog: {S}:: Serialized Actor :: ERROR:ID
SaviorLog: {S}:: SAVED DATA for ERROR:ID :: {"bCanBeDamaged":true}
This one I presume is some kind of actor it’s trying to save which doesn’t have or isn’t generating its SGUID properly, but there’s nothing to help track down which actor that might be.
Is there anything I can do to get additional log output to help track these down?
There are some other issues where it doesn’t seem to be loading the game mode & game state, but I want to make sure that those aren’t cascade problems from something else first.
Any help would be much appreciated!
SomeBlueprint.uasset: Failed to load ‘/Script/Savior2’: Can’t find file.
Open the blueprint causing this warning then:
File => Refresh All Nodes => Compile
[HR][/HR]
It’s trying to save from a soft reference to an Actor that might not exist anymore or the reference became invalid.
This I can only help you if you mail a zipped copy of the Unreal map that causes this warning, so I can step through with a debugger.
I will try to add more meaningful warning message there, which is hard because the target object actually doesn’t exist.
@Xandemon If you are on Unreal 4.26 Epic released a patch that should help you with that.
You should see an update available for the plugin on your launcher.
Thanks, that gives me plenty to track down just by itself - not all that many soft references that it *could *be being serialized. I’m using 4.25 - I don’t suppose the patch could go on that version as well? ^^;