Dev19 몬스터 스폰, 몬스터 상태 및 npc 위치 저장, 아이템 로드, 대화 조건 체크, 긴급탈출
몬스터 스폰
맵에 배치해놓고 시작하는 방식 말고 스폰하는 방식으로 변경.
시작하면 SpawnEenemy 호출
SpawnEenemy는 먼저 0부터 Number Of Enemies-1까지 for문을 돌리면서 액터를 스폰함.
그 다음 저장된 게임을 로드해서 DeadEnemyList(FString 배열)로 foreach문을 돌리는데 방금 스폰한 액터의 이름이 DeadEnemyList의 요소와 같으면 액터를 파괴함.
월드에 배치해놓은 모습. 변수들을 인스턴스 편집 가능하게 해서 디테일 패널에서 설정할 수 있음.
고블린 3마리를 스폰하는 스포너.
드디어 오랫동안 미뤄뒀던 저장 문제
유튜브 따라하려고 봤는데 음 결국 실패함.
그래서 어쩌다보니 야매처럼 해결함;
Main.h
UPROPERTY(VisibleAnyWhere, BlueprintReadWrite)
TArray<FString> Enemies;
원래는 Enemy 클래스 배열이었는데 죽으니까 배열에서 없어져서;;
몬스터가 죽는 거랑 상관없이 넣을 수 있는 정보가 필요함.
그래서 몬스터 이름으로 이루어진 배열을 만들기로 함.
Enemy.cpp
in Die()
Main->Enemies.Add(this->GetName());
죽을 때 Main의 Enemies 배열에 본인 이름 추가.
저장할 정보를 추가하자.
YaroSaveGame.h
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Basic)
TArray<FString> DeadEnemyList; // 죽은 적들 이름 리스트 (=데스노트..?)
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Basic)
FNpcStats NpcInfo; // npc 정보
USTRUCT(BlueprintType)
struct FNpcStats
{
GENERATED_BODY()
//UPROPERTY(VisibleAnywhere, Category = "SaveGameData")
//float HP;
// npc들 위치 정보
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SaveGameData")
FVector MomoLocation;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SaveGameData")
FVector LukoLocation;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SaveGameData")
FVector VovoLocation;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SaveGameData")
FVector ViviLocation;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SaveGameData")
FVector ZiziLocation;
// 던전 1에서 보보,비비,지지는 인덱스에 따라 이동하므로 인덱스도 저장
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SaveGameData")
int TeamMoveIndex;
};
Main.cpp
void AMain::SaveGame()
{
// 대화 중 or 떨어진 상태 or 공중에 떠있는 상태에서는 저장 불가
if (MainPlayerController->bDialogueUIVisible || MainPlayerController->bFallenPlayer || MainAnimInstance->bIsInAir)
{
//3초 뒤 다시 저장 시도
GetWorld()->GetTimerManager().SetTimer(SaveTimer, this, &AMain::SaveGame, 3.f, false);
return;
}
UE_LOG(LogTemp, Log, TEXT("SaveGame"));
UYaroSaveGame* SaveGameInstance = Cast<UYaroSaveGame>(UGameplayStatics::CreateSaveGameObject(UYaroSaveGame::StaticClass()));
// 플레이어 스탯 저장 부분은 생략
// 대화 넘버와 떨어진 횟수 저장
SaveGameInstance->DialogueNum = MainPlayerController->DialogueNum;
SaveGameInstance->CharacterStats.FallingCount = MainPlayerController->FallingCount;
// npc들 위치 저장
SaveGameInstance->NpcInfo.MomoLocation = Momo->GetActorLocation();
SaveGameInstance->NpcInfo.LukoLocation = Luko->GetActorLocation();
SaveGameInstance->NpcInfo.VovoLocation = Vovo->GetActorLocation();
SaveGameInstance->NpcInfo.ViviLocation = Vivi->GetActorLocation();
SaveGameInstance->NpcInfo.ZiziLocation = Zizi->GetActorLocation();
if (MainPlayerController->DialogueNum < 4) // 골렘 해치우기 전까진 인덱스 저장
SaveGameInstance->NpcInfo.TeamMoveIndex = Vivi->index;
SaveGameInstance->DeadEnemyList = Enemies; // 몬스터 이름 배열 저장
if (ItemInHand) // 아이템 들고 있었으면
{
SaveGameInstance->CharacterStats.ItemName = ItemInHand->GetName(); // 아이템 이름 저장
}
UGameplayStatics::SaveGameToSlot(SaveGameInstance, SaveGameInstance->SaveName, SaveGameInstance->UserIndex);
GetWorld()->GetTimerManager().SetTimer(SaveTimer, this, &AMain::SaveGame, 3.f, false);
// 3초마다 저장
}
void AMain::LoadGame()
{
UYaroSaveGame* LoadGameInstance = Cast<UYaroSaveGame>(UGameplayStatics::CreateSaveGameObject(UYaroSaveGame::StaticClass()));
LoadGameInstance = Cast<UYaroSaveGame>(UGameplayStatics::LoadGameFromSlot(LoadGameInstance->SaveName, LoadGameInstance->UserIndex));
MainPlayerController = Cast<AMainPlayerController>(GetController());
MainPlayerController->DialogueNum = LoadGameInstance->DialogueNum;
MainPlayerController->FallingCount = LoadGameInstance->CharacterStats.FallingCount;
// 플레이어 스탯 로드 부분 생략
Enemies = LoadGameInstance->DeadEnemyList;
if (MainPlayerController->DialogueNum != 5) // 5는 배타고 이동하는 상태 or 두번째 던전 첫 입장
{
SetActorLocation(LoadGameInstance->CharacterStats.Location);
SetActorRotation(LoadGameInstance->CharacterStats.Rotation);
Momo->SetActorLocation(LoadGameInstance->NpcInfo.MomoLocation);
Luko->SetActorLocation(LoadGameInstance->NpcInfo.LukoLocation);
Vovo->SetActorLocation(LoadGameInstance->NpcInfo.VovoLocation);
Vivi->SetActorLocation(LoadGameInstance->NpcInfo.ViviLocation);
Zizi->SetActorLocation(LoadGameInstance->NpcInfo.ZiziLocation);
}
if (MainPlayerController->DialogueNum < 4) // 골렘 처리 전이면
{
Vovo->index = LoadGameInstance->NpcInfo.TeamMoveIndex;
Vivi->index = LoadGameInstance->NpcInfo.TeamMoveIndex;
Zizi->index = LoadGameInstance->NpcInfo.TeamMoveIndex;
}
if (SP < MaxSP && !recoverySP)
{
recoverySP = true;
GetWorldTimerManager().SetTimer(SPTimer, this, &AMain::RecoverySP, SPDelay, true);
}
if (HP < MaxHP)
GetWorldTimerManager().SetTimer(HPTimer, this, &AMain::RecoveryHP, HPDelay, true);
if (MP < MaxMP)
GetWorldTimerManager().SetTimer(MPTimer, this, &AMain::RecoveryMP, MPDelay, true);
if (ObjectStorage)
{
if (Storage)
{
FString ItemName = LoadGameInstance->CharacterStats.ItemName;
if (ItemName.Contains("Yellow") && Storage->ItemMap.Contains("YellowStone")) // 저장할 때 손에 돌을 집은 상태였으면
{
AItem* Item = GetWorld()->SpawnActor<AItem>(Storage->ItemMap["YellowStone"]);
Item->PickUp(this); // 픽업 실행, 플레이어 손에 장착
}
}
}
}
ItemStorage 블루프린트
옐로 스톤을 추가해줌.
대화 조건 체크
예를 들면 대화 중에 종료했을 경우, 게임 이어하기를 했을 때 다시 대화가 자동으로 시작되어야함.
그리고 같은 상황이어야 함.
Main에 함수를 만듬.
Main.cpp
void AMain::CheckDialogueRequirement()
{
switch (MainPlayerController->DialogueNum)
{
case 3: // after golem died
for (int i = 0; i < Enemies.Num(); i++) // (죽은)적 이름 배열에 골렘이 있으면
{
if (Enemies[i].Contains("Golem"))
{
MainPlayerController->DisplayDialogueUI(); // 대화 시작
return;
}
}
NpcGo = true;
break;
case 4: // npc move to boat and wait player
Momo->SetActorRotation(FRotator(0.f, 85.f, 0.f));
Luko->SetActorRotation(FRotator(0.f, 103.f, 0.f));
Vivi->SetActorRotation(FRotator(0.f, 97.f, 0.f));
Zizi->SetActorRotation(FRotator(0.f, 94.f, 0.f));
Vovo->SetActorRotation(FRotator(0.f, 91.f, 0.f));
Momo->AIController->MoveToLocation(FVector(660.f, 1035.f, 1840.f));
Luko->AIController->MoveToLocation(FVector(598.f, 1030.f, 1840.f));
Vivi->AIController->MoveToLocation(FVector(710.f, 995.f, 1840.f));
Zizi->AIController->MoveToLocation(FVector(690.f, 930.f, 1840.f));
Vovo->AIController->MoveToLocation(FVector(630.f, 970.f, 1840.f));
break;
case 9: //if ItemInHand is null, the stone have to put on the floor (this is check in blueprint)
MainPlayerController->SystemMessageNum = 10;
MainPlayerController->SetSystemMessage();
MainPlayerController->DialogueUI->AllNpcLookAtPlayer();
for (int i = 0; i < NPCList.Num(); i++)
{
NPCList[i]->AIController->StopMovement();
GetWorld()->GetTimerManager().ClearTimer(NPCList[i]->MoveTimer);
}
Momo->AIController->MoveToLocation(FVector(5320.f, -3702.f, -2122.f));
Luko->AIController->MoveToLocation(FVector(5249.f, -3685.f, -2117.f));
Vovo->AIController->MoveToLocation(FVector(5462.f, -3725.f, -2117.f));
Vivi->AIController->MoveToLocation(FVector(5392.f, -3686.f, -2117.f));
Zizi->AIController->MoveToLocation(FVector(5538.f, -3696.f, -2115.f));
break;
}
}
블루프린트에서 LoadGame() 실행 후 CheckDialogueRequirement() 실행.
긴급 탈출 기능
트리거 박스로 어느정도 막아두긴 했지만 귀찮아서 트리거 박스 안 둔 곳이라거나 플레이어가 어디에 끼었을 경우 이 기능으로 안전한 위치로 순간이동
E키로 설정.
조작 매뉴얼 아래에 텍스트로 둠. (기본 Visibility : Hidden)
두번째 던전에서만 사용할 것이므로 던전 이름 확인 후 두번째 던전이면 보이게 함.
이거 때문에 함수 만들기 귀찮아서 레벨 함수에 함ㅋ
Main.h
void Escape(); // press E key, spawn player at the other location
Main.cpp
in SetupPlayerInputComponent(~~)
PlayerInputComponent->BindAction("Escape", IE_Pressed, this, &AMain::Escape);
void AMain::Escape()
{
if (MainPlayerController->DialogueNum >= 6) // 두번째 던전 입장 후
{
if (MainPlayerController->DialogueNum <= 8) // 플레이어가 건너편 건너가기 전
{
SetActorLocation(FVector(4620.f, -3975.f, -2117.f));
}
else if (MainPlayerController->DialogueNum <= 12)
{
SetActorLocation(FVector(5165.f, -2307.f, -2117.f));
}
}
}