언리얼엔진에서는 PlayerController에서
위젯의 인스턴스를 가져AddToViewport로 추가를 했다.
Character에 해도 다른 엑터에 해도 똑같은 기능을 할텐데..
왜 위젯을 모두 PlayerController에 생성하는 걸까?
나름의 암묵적인 규칙이 존재하는 걸까??
PlayerController의 코드,
위젯을 생성하고 AddToViewport를 한다.
그리고 UI에 접근하기 위해서 Character에서 PlayerController를 참고해야만 했다.
예외도 존재하긴 한다. 바로 Character의 Component로 부착되는 것이다.
이 경우, 부착되어있기 때문에 Character와 같이 움직인다.
아래의 글들을 참고하였다.
- PlayerController 전용 멤버변수, 함수
https://wergia.tistory.com/139
게임을 구성하는 게임의 프레임워크에 있어서
크게 4가지로 나누어볼 수 있다.
쉽게말해 게임중에 같이 볼 수 있는 UI를 의미
ex) 각 팀의 점수, 접속된 플레이어의 목록
ex) 플레이어 이름, 개인 점수, 레벨, 인벤토리
게임플레이 프레임워크 설명에 없지만
자주 쓰였던 클래스들을 가져와보았다.
정확히 어떤 기능을 구현하기 위해 제작된 클래스인지 본다.
아래의 글들을 참고하였다.
- GameInstacne와 CSV
https://www.slideshare.net/TonyCms/gameinstance
ex) 게임 설정, 플레이어 통계 등 게임모드에서 엑세스해야하는 데이터
GameInstance에 CSV파일 테이블을 저장하기도 함
( 생성자 - PostInitializeComponents - BeginPlay - PossessedBy 순으로 실행)
1) 가장먼저 생성자에서 에셋으로부터
Class의 정보를 담는다.
2) Class를 바탕으로 위젯 인스턴스를 생성한다.
3) 그리고 해당 인스턴스를 뷰포트에 띄운다.
if (bIsPlayer)
{
DisableInput(NewPlayerController);
NewPlayerController->GetHUDWidget()->BindCharacterStat(CharacterStat);
//언리얼 5는 Pawn이 아니라 Pawn의 Controller에서 PlayerState를 가져와야만 한다.
auto NewPlayerState = Cast<ANewPlayerState>(NewPlayerController->PlayerState);
//auto NewPlayerState = Cast<ANewPlayerState>(PlayerState);
ABCHECK(nullptr != NewPlayerState);
CharacterStat->SetNewLevel(NewPlayerState->GetCharacterLevel());
// 추가한 코드
auto GameState = GetWorld()->GetGameState();
auto NewGameStateBase = Cast<ANewGameStateBase>(GameState);
NewGameStateBase->SetTotalScore(NewPlayerState->GetGameScore());
}
else
{
auto NewGameMode = Cast<ANewGameMode>(GetWorld()->GetAuthGameMode());
ABCHECK(nullptr != NewGameMode);
int32 TargetLevel = FMath::CeilToInt(((float)NewGameMode->GetScore() * 0.08f));
YU_LOG_FORMAT(Error, TEXT("*** TargetLevel : %f"), ((float)NewGameMode->GetScore() * 0.08f));
int32 FinalLevel = FMath::Clamp<int32>(TargetLevel, 1, 20);
YU_LOG_FORMAT(Error, TEXT("*** New NPC Level : %d"), FinalLevel);
CharacterStat->SetNewLevel(FinalLevel);
}
이유를 알아냈다..
Character를 NPC와 플레이어가 공동으로 사용하기 떄문에,
AIPlayerController는 PlayerState에 접근할 수 없고,
따라서 Player는 PlayerState를 참고하는 방식으로
차별화하여 구현한 것으로 보인다.
CharacterStatComponent는 위젯을 관리한다.
SetNewLevel
에 레벨을 설정하면
GameInstance로부터 받아와 해당 값으로 데이터가 지정이되고,
때릴때의 공격력 스탯과 맞을때의 HP변환을 진행하고,
HP값이 변경되면, SetHP를 호출하여 위젯을 수정한다.
(데이터중에 CurrentHP나 MaxHP같은 HP값을 위주로 사용한다)
PlayerState는 현재 플레이어의 정보를 Save하고 Load할 수 있다.
(hp를 제외한 데이터 모든 멤버변수를 사용한다)
(사실 사용해야하는게 맞다 (로드하려면 현재 체력도 알아야하므로))
따라서 PlayerState의 값들은 모두 Save값을 바탕으로 만들어진 것이고,
CharacterStateComponent는 위의 코드를 통해,
PlayerState의 Level으로 SetNewLevel
을 진행한다.
(레벨업도 관리한다.)
이렇게 하여 캐릭터 생성시,
PlayerState의 값과 CharacterStateComponent의 값을 동기화한다.
그렇다면 게임 중간에 레벨업을 한 경우에 HP값을 어떻게 동기화할까?
AddExp가 호출되고 정상적으로 위젯을 갱신하는 함수가 broadcast될 것이다.
레벨업을 한다면, SetCharacterLevel을 사용하여 레벨을 설정한다.
SetCharacterLevel은 위처럼 GameInstance에서
해당 레벨의 테이블 Data를 받아오고,
해당 값으로 CharacterLevel을 지정한다.
그러면 PlayerState의 레벨정보는 수정이 될텐데..
CharacterStateComponent는..?
흠.. 아무리봐도 갱신할 수 있는 코드가 보이지 않는다. update되지 않을 것이다.
한 번 테스트 해본다.
역시 6렙으로 레벨업 했음에도 HP는 동기화되지 않아 차지않는 모습.
하지만 공격력은 증가했을 것이다.맞으면 죽는다.
UI는 제대로 초기화 되는 중이었다.
결론
예제가 문제가 있었지만 찾아내었고,
PlayerState는 Save파일을 받아오고,
CharacterStatComponent를 초기화하는 형태로 쓰이고
PlayerState에서 레벨업시에, CharacterStatComponent의 델리게이트를 호출한다면
해결될 것으로 보인다.
길게 적을 글이 아니었는데.. 문제가 보였고, 단지 더 잘 알고 싶었다
- Character에서 PlayerController = GetOwner()
- PlayerController에서 Character = GetPawn()
- PlayerController에서 GameState = GetGameState()
- GameMode에서 GameInstance = GetGameInstance()
- 대부분의 UObject에서 GetWorld()를 사용하여 접근가능
- 관련없는 다른 엑터와 접근해야할 때 world를 매개체로 사용
따라서 어떤 UObject든 아래의 기능을 수행할 수 있게 된다.
- World에서 GameMode = GetAuthGameMode()
- World에서 특정 캐릭터 찾기 =
TActorIterator<ACharacter>UWorld* World = GetWorld(); if (World) { for (TActorIterator<ACharacter> It(World); It; ++It) { ACharacter* Character = *It; if (Character->IsA(CharacterClass)) { OutCharacters.Add(Character); } } } //TSubclassOf<ACharacter> CharacterClass 매개변수에 //가져오고자 하는 캐릭터 클래스를 전달
- World에서 특정 PlayerController 찾기 =
TActorIterator<APlayerController>
위의 character를 찾고 getowner도 가능
for (TActorIterator<APlayerController> It(World); It; ++It)
- World에서 특정 Actor 찾기 =
World->FindObjectByClass(ActorClassToFind())UWorld* World = GetWorld(); if (World) { // AYourActor 클래스로부터 생성된 액터를 검색합니다. TSubclassOf<AYourActor> ActorClassToFind; AYourActor* SpecificActor = Cast<AYourActor>(World->FindObjectByClass(ActorClassToFind)); // return SpecificActor; }
- Character에서 특정 Actor찾기
- GetOverlappingActors
- GetAllActorsNearby
// 1. GetOverlappingActors //TArray<AActor*> OverlappingActors; //GetOverlappingActors(OverlappingActors, AYourActor::StaticClass()); // // 2. GetAllActorsNearby TArray<AActor*> NearbyActors; UGameplayStatics::GetAllActorsNearLocation(GetWorld(), CenterLocation, Radius, TArray<TSubclassOf<AYourActor>>(), NearbyActors); for (AActor* Actor : NearbyActors) { AYourActor* SpecificActor = Cast<AYourActor>(Actor); if (SpecificActor) { // 원하는 조건을 만족하는 특정 액터를 찾았습니다. // 이제 SpecificActor를 사용하여 원하는 작업을 수행할 수 있습니다. } }
- 대부분의 UObject에서 GetGameInstance()를 사용하여 접근가능
좋은 정보 얻어갑니다, 감사합니다.