-
블루아카이브 루트슈터 팬게임 프로젝트(18) - 적 스폰 시스템 & 웨이브 게임모드(2)루트슈터 프로젝트 2024. 1. 19. 01:55
안녕하세요. 오늘은 적 스폰 시스템을 좀 더 다듬어 보겠습니다. 그 이전에 적을 스폰하여 AI를 빙의시킬때 오류가 발생하는 경우가 있었는데요. AI를 프로그래밍 할 때 주의할 점이 있습니다.
AEnemyAiController.cpp
void AEnemyAiController::BeginPlay() { Super::BeginPlay(); playerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); //SetFocus(playerPawn); if(AIBehavior && playerPawn) { RunBehaviorTree(AIBehavior); GetBlackboardComponent()->SetValueAsVector(TEXT("PlayerLocation"), playerPawn->GetActorLocation()); if(GetPawn())GetBlackboardComponent()->SetValueAsVector(TEXT("StartLocation"), GetPawn()->GetActorLocation()); } }
그것은 AI컨트롤러 cpp에서 BeginPlay를 정의할 때 폰을 불러올 수 없다는 것입니다. 이 뜻이 뭔지 더 자세히 설명하자면
적이 월드에 이미 존재하는 경우와 적이 월드에 없었다가 스폰되는 경우 이 두가지에서 코드가 실행되는 순서가 다르다는 뜻 입니다.
적이 월드에 이미 존재하는 경우 적들이 소환되어 있는 상태에서 AI컨트롤러 코드를 실행하기 때문에 폰을 불러와도(GetPawn 함수를 실행해도) 폰에 대한 정보를 받아올 수 있습니다. 하지만 스폰되는 적들의 경우 적들이 스폰되기 전에 컨트롤러의 코드들이 먼저 실행되기 때문에 폰에 대한 정보를 받아올 수 없습니다. 따라서 GetPawn 함수를 실행하면 폰에 대한 포인터 값을 받아오지 못해 오류가 발생하는 것입니다.
따라서 BeginPlay에서는 폰 포인터를 부르지 않거나 if문으로 null 체크를 해두는 것이 오류를 막는 방법입니다. 사실 null체크는 무조건 하는것이 당연합니다만 저는 그러지 않아서 어떤 오류인지 한참 헤맸습니다..
GameModeBase.h
public: virtual void ActorDied(AActor* DeadActor);
아무튼 이제 적의 스폰시스템을 좀 더 다듬어 보도록 하겠습니다. 제가 만들고 싶은것은 웨이브 형식의 게임이기 때문에 소환된 적들이 모두 죽으면 다음 웨이브로 넘어가면서 다시 적들이 리젠됐으면 좋겠습니다. 제가 게임모드 베이스를 코딩하면서 액터들의 죽음을 관리하는 함수를 하나 만들어 두었던 적이 있습니다. 이 함수를 저는 오버라이딩 하여 웨이브 게임모드에서도 사용할 수 있게 하고 싶습니다. 따라서 virtual 키워드를 앞에 붙여 가상함수로 선언합니다.
WaveGameMode.h
public: int32 waveRound = 0; int32 enemyLeft; void ActorDied(AActor* DeadActor) override;
그리고 이 함수를 웨이브 게임모드에서 오버라이딩 하여 선언해주면 됩니다.
WaveGameMode.cpp
void ABluelandsGameWaveMode::ActorDied(AActor *DeadActor) { Super::ActorDied(DeadActor); //잊지말고 호출 if(Cast<AEnemyCharacter>(DeadActor)) { enemyLeft--; UE_LOG(LogTemp, Error, TEXT("%d enemy left!"), enemyLeft); } if(enemyLeft == 0) { UE_LOG(LogTemp, Error, TEXT("Next Wave!!")); waveRound++; SpawnEnemy(3, 1, 1); } }
부모 클래스의 함수를 오버라이드 하여 사용할 때에는 꼭 맨 앞에 Super::함수명; 호출을 해야 부모 클래스에 있는 함수 코드들 까지 제대로 정상 작동합니다. 적 액터가 죽었다면 남은 적 변수를 1씩 빼주고 0이 됐다면 다음 라운드가 되어서 다시 적들을 스폰합니다.
void ABluelandsGameWaveMode::SpawnEnemy(int normal, int middle, int boss) //각 라운드 시작시 발동됨 { enemyLeft = 0; TArray<AActor*> spawners; UGameplayStatics::GetAllActorsOfClass(GetWorld(), spawnerClass, spawners); for(int i=0; i<(normal + middle + boss); i++) { AEnemySpawner* spawner = Cast<AEnemySpawner>(spawners[i]); if(spawner && player) { FEnemyTableRow* enemyRow = nullptr; if(i < normal) { enemyRow = arNormalEnemyDataTable->FindRow<FEnemyTableRow>(FName(*(FString::FormatAsNumber(player->GetCharacterLevel()))), FString("")); } else if(i < normal + middle) enemyRow = arMiddleEnemyDataTable->FindRow<FEnemyTableRow>(FName(*(FString::FormatAsNumber(player->GetCharacterLevel()))), FString("")); else enemyRow = arBossEnemyDataTable->FindRow<FEnemyTableRow>(FName(*(FString::FormatAsNumber(player->GetCharacterLevel()))), FString("")); if(enemyRow == nullptr) continue; spawner->SpawnEnemy((*enemyRow).enemyLevel, (*enemyRow).itemDropRate, (*enemyRow).legendDropRate, (*enemyRow).rareDropRate, (*enemyRow).uncommonDropRate, (*enemyRow).enemyHP, (*enemyRow).enemyDamage, (*enemyRow).enemyEXP); enemyLeft++; } } }
적 스폰 함수를 보면 전편에 비해 바뀐 부분이 존재합니다. 저는 적들의 등급을 노말, 중간보스, 보스로 나누어 놓았기 때문에 각 웨이브마다 각 등급의 적이 몇명씩 소환될 것인지 변수로 받습니다. 참고로 중간보스와 보스의 스탯들도 전부 데이터 테이블로 준비해 두었습니다.
결과물을 확인해보면 마지막 적을 처치했을때 다음 웨이브의 적들이 새로 스폰되는 것을 확인할 수 있습니다. 너무 바로 소환되서 5초정도의 시간 텀을 둬야겠습니다. 이것은 다음에 해보도록 하겠습니다. 지금 웨이브가 몇웨이브인지, 현재 웨이브에서 남은 적들은 몇명 정도인지 표시하는 UI도 만들어야겠습니다.
'루트슈터 프로젝트' 카테고리의 다른 글
블루아카이브 루트슈터 팬게임 프로젝트(19) - 체력 회복 시스템 (1) 2024.01.21 블루아카이브 루트슈터 팬게임 프로젝트(18) - 적 스폰 시스템 & 웨이브 게임모드(3) (0) 2024.01.20 블루아카이브 루트슈터 팬게임 프로젝트(17) - 체력 컴포넌트 교체 (0) 2024.01.17 블루아카이브 루트슈터 팬게임 프로젝트(16) - 플레이어 캐릭터 모델링 교체 & 리타겟팅 (0) 2024.01.14 블루아카이브 루트슈터 팬게임 프로젝트(15) - 적 스폰 시스템 & 웨이브 게임모드(1) (1) 2024.01.14