3 minute read

몬스터 스폰

맵에 배치해놓고 시작하는 방식 말고 스폰하는 방식으로 변경.

이미지

시작하면 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));
		}
	}
}




다음 할 것 : 새 몹 배치 및 애니메이션 등 세팅!

Updated: