Conversation
у меня не компилилось иначе
|
изменения, НЕ касающиеся системы сохранений:
|
ba25940 to
306cded
Compare
306cded to
442542d
Compare
There was a problem hiding this comment.
- Предложение добавить функцию переключения на конкретного персонажа, а не на следующего. Код в комментарии приложила
- А точно нужен интерфейс для GameInstance?
Возможно, можно вынести систему сохранений в GameInstanceSubsystem (так UIManager выносила), чтобы логчески отделить от game instance. И вместо gameinstance с нужным интерфейсом вызывать GameInstance->GetSubsystem<НужныйКласс>(); - И второй интерфейс не удобнее компонентой (возможно, с наследниками) сделать, чтобы не копировать логику функций?
- Немного вопросов по написанию синтаксису
- Об изменениях в слайдере в общий чат напиши, плиз
- Ну и часть вопросов классических по nullptr и ensur-ов для error-логов. Чтоб меньше проверок на nullptr делать, можно в функции передавать ссылки, а не пойнтеры
| return OnUnPossessedDelegate; | ||
| } | ||
|
|
||
| void AG2ICharacterDaughter::SaveData_Implementation(UG2IGameplaySaveGame* SaveGameRef) |
There was a problem hiding this comment.
Либо проверить SaveGameRef на nullptr
Либо передавать в функцию ссылку, а не указатель
There was a problem hiding this comment.
GameInstance не будет вызывать эти функции, если SaveGame == nullptr.
но могу поменять на ссылку
There was a problem hiding this comment.
Лучше проверку всё же сделать, или на ссылку поменять
|
|
||
| void AG2ICharacterDaughter::LoadData_Implementation(const UG2IGameplaySaveGame* SaveGameRef) | ||
| { | ||
| if (IsPlayerControlled() && !this->IsA(SaveGameRef->PlayersSaveData.CurrentCharacter)) |
There was a problem hiding this comment.
хороший вопрос. в моменте так почувствовала
| { | ||
| if (auto* G2IPlayerState = Cast<AG2IPlayerState>(GetPlayerState())) | ||
| { | ||
| G2IPlayerState->SelectNextCharacter(); |
There was a problem hiding this comment.
В идеале
можно было бы в PlayerState добавить функцию,
SetCharacterByClass(SaveGameRef->PlayersSaveData.CurrentCharacter)
В которой в идеале выбирать персонажа с нужным классом или писать, что нет такого персонажа
Примерно так:
void AG2IPlayerState::SetCharacterByClass(const TSubclassOf<ACharacter> TargetClass)
{
if (!ensure(TargetClass))
{
UE_LOG(LogG2I, Warning, TEXT("An attempt to set character with null class"));
return;
}
if (!ensure(!PlayableCharactersRowNames.IsEmpty()))
{
UE_LOG(LogG2I, Warning, TEXT("An attempt to set character, when array of playable characters is empty"));
return;
}
for (int32 OffsetRowName = 1; OffsetRowName <= PlayableCharactersRowNames.Num(); ++OffsetRowName)
{
const int32 NewCharacterIndex = (NumberCurrentCharacter + OffsetRowName) % PlayableCharactersRowNames.Num();
if (NewCharacterIndex == NumberCurrentCharacter)
{
UE_LOG(LogG2I, Warning, TEXT("Character with class %s doesn't founded"), *TargetClass->GetName());
return;
}
if (const FG2IItemCharacter *Row = PlayableCharactersDataTable->FindRow<FG2IItemCharacter>(
PlayableCharactersRowNames[NewCharacterIndex], TEXT("Playable Character Context")))
{
if (Row->CharacterClass == TargetClass)
{
// Строки из SelectNextCharacter, в принципе их можно вынеститож в отдельную функцию
const int32 OldCharacterNumber = NumberCurrentCharacter;
NumberCurrentCharacter = NewCharacterIndex;
if (SetupControllerForPawn(OldCharacterNumber))
{
if (SetupControllerForPawn(NumberCurrentCharacter))
{
OnNewControllerPossessDelegate.Broadcast(GetPawn(OldCharacterNumber));
OnNewControllerPossessDelegate.Broadcast(GetPawn(NumberCurrentCharacter));
break;
}
else
{
UE_LOG(LogG2I, Warning, TEXT("Character %i doesn't switched on player controller"),
NumberCurrentCharacter);
NumberCurrentCharacter = OldCharacterNumber;
SetupControllerForPawn(NumberCurrentCharacter);
continue;
}
}
else
{
UE_LOG(LogG2I, Warning, TEXT("Character %i doesn't switched on ai controller"),
OldCharacterNumber);
NumberCurrentCharacter = OldCharacterNumber;
continue;
}
}
}
else
{
UE_LOG(LogG2I, Error, TEXT("In %s array of row names doesn't sync with %s"), *GetName(),
*PlayableCharactersDataTable.GetName());
continue;
}
}
}
There was a problem hiding this comment.
ооо фига, сделаем, спасибо!
(в своем решении я основывалась на том, что у нас уже точно больше двух персонажей не будет)
| SaveGameRef->PlayersSaveData.CharactersTransform.Add(GetClass(), GetTransform()); | ||
| } | ||
|
|
||
| void AG2ICharacterDaughter::LoadData_Implementation(const UG2IGameplaySaveGame* SaveGameRef) |
There was a problem hiding this comment.
Проверка SaveGameRef на nullptr или передавать ссылку в функцию
| #include "CoreMinimal.h" | ||
| #include "G2ICharacterInterface.h" | ||
| #include "GameFramework/Character.h" | ||
| #include "Interfaces/SavingSystem/G2ISaveGameplayInterface.h" |
| #include "G2ICharacterInterface.h" | ||
| #include "GameFramework/Character.h" | ||
| #include "Interfaces/SavingSystem/G2ISaveGameplayInterface.h" | ||
| #include "Interfaces/SavingSystem/G2ISavableInterface.h" |
There was a problem hiding this comment.
Можно кстати без "Interfaces/" этот инклуд писать
В Build.cs путь прописан
| #include "CoreMinimal.h" | ||
| #include "Engine/GameInstance.h" | ||
| #include "Kismet/GameplayStatics.h" | ||
| #include "GameFramework/SaveGame.h" |
There was a problem hiding this comment.
Тут инклуд не используемые убрать можно
| TObjectPtr<UG2ISaveGameplayDelegates> SaveGameplayDelegates; | ||
|
|
||
| private: | ||
| // SaveGame w/ gameplay info |
There was a problem hiding this comment.
Имхо, лучше в комментариях не использовать сокращения, чтобы у всех, кто посмотрит код, было однозначное понимание
|
|
||
| void CreateNewGameplaySaveGameObject_Implementation(); | ||
|
|
||
| void SaveGameplay_Implementation(bool bAsync); |
There was a problem hiding this comment.
Лучше для функций, реализующих функции интерфейса писать virtual и override
There was a problem hiding this comment.
для Implementation это надо делать? мне казалось что это анриловская тема и она не коннектится с этими специальными обозначениями
There was a problem hiding this comment.
Код стайл а-дя
_Implementation говорит компилятору, что это виртуальная функция, но в коде лучше помечать дополнительно virtual
А без override - в принципе любые функции могут и без него работать, но для читаемости и чтоб компилятор проверял, что всё ок написано, лучше ставить
Close #149
Итак, система сохранений.
Этим занимается GameInstance через интерфейс - сейчас есть все для сохранения состояния игры, и чисто база для сохранения настроек (ее нужно будет потом дописать, но работать она будет почти так же).
Работает это так: в GameInstance лежат два объекта SaveGame - один для игры, второй для настроек. Отовсюду обращение к ним идет через интерфейс (SaveGameplayInterface для игры, SaveSettingsInterface для настроек). Сначала данные сохраняются в SaveGame объект, потом этот SaveGame анрильскими функциями уже сохраняется в файлик на компьютере.
Этот файлик находится в G2I-Game -> Saved -> SaveGames. Если хотите затереть сохранение, просто удалите оттуда файлик.
Данные сохраняются либо для отдельного объекта (SaveRequestedData) либо для всех акторов (AActor) с интерфейсом сохранения (SavableInterface) на данном уровне (SaveAllData).
С загрузкой то же самое, хотя загрузку лучше всего будет применять все-таки индивидуально для актора по его запросу. Почему? Потому что для разных акторов это будет необходимо в разное время. Они не одновременно спавнятся на уровне, какие-то будут спавниться во время игры, так что в основном будет использоваться LoadRequestedData. Я вижу LoadAllData полезным только в двух случаях: при дебаге и при начальном запуске игры, и то второе не факт.
Так, как этим пользоваться на практике?
Допустим, вам надо, чтобы TestActor сохранял свою позицию и переменную bool.
// Ссылки и имена (GetName) использовать нельзя - используйте для идентификации то, что останется неизменным от запуска к запуску.
// ОБЯЗАТЕЛЬНО в спецификаторе UPROPERTY указать SaveGame.
// Если данных для объекта много, оберните их в структуру. См. пример, который есть сейчас для игрока.
// Проверять в них ссылку на SaveGame не надо - эта проверка уже есть во всех функциях GameInstance, где Save и Load вызываются. Этот референс никогда не будет nullptr.
// В этих функциях вы оперируете данными, которые мы добавили к сейв гейму в первом пункте.
То есть, для нашего примера, это будет:
SaveGameRef->TestActorSaveData.FTransform = GetActorTransform();
SaveGameRef->TestActorSaveData.bool = bool;
Вот и все.
Сохраняются только AActor!!
Чтобы подписаться на делегаты сохранения/загрузки, возьмите их из GameInstance - они хранятся в классе UG2ISaveGameplayDelegates - через функцию GetGameplaySaveDelegates. Полезно для виджетов.
Теперь про чекпоинт.
Одноразовый чекпоинт говорит GameInstance сохранить данные всех акторов, что есть на текущем уровне, и записать это в ячейку. Чтобы это работало правильно, тк у нас будет несколько уровней, при перемещении между ними надо также просить GameInstance сохранить все данные (без сохранения в ячейку, это разные вещи). Ну и выбрать момент, когда их загружать (в BeginPlay акторов или в другом месте).
Дебаг
Я добавила две дебажные кнопки для тестирования системы сохранений:
На тестовом уровне есть два чекпоинта-триггер бокса.
И сейчас сохраняются/загружаются следующие данные: