UE - 레벨 이동, UI
UnrealEngine
GameMode, GameState 버그
새로 만든 GameState
를 적용하면 입력이 제대로 작동하지 않는 것을 확인했다.
GameStateBase
를 적용하면 정상 작동하길래 GameState
로도 테스트 해보니, GameState
에서도 입력을 받지 못하는 것을 확인했다.
찾아보니 언리얼 4.14버전 이후로 GameMode
와 GameModeBase
로 나뉘면서 GameMode
에 MatchStateMachine
이라는 기능이 생겼다.
또한 GameState
도 MatchStateMachine
을 지원하게 되었고, GameStateBase
가 추가로 생성되었다.
저 Match State
의 상태에 따라 입력을 받지 않는 상태가 있다고 하는데, 아마 이걸 GameModeBase
가 변경하지 못해서 입력을 받지 못하는 것으로 예상한다.
이번엔 레벨 변경과 UI에 대해 공부했다.
레벨 변경
동적으로 레벨을 변경하기 위해 사용하는 함수는
1
UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
이 함수다.
1
2
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", AdvancedDisplay = "2", DisplayName = "Open Level (by Name)"), Category="Game")
static ENGINE_API void OpenLevel(const UObject* WorldContextObject, FName LevelName, bool bAbsolute = true, FString Options = FString(TEXT("")));
이렇게 레벨의 이름을 받아서 레벨을 변경하는 함수다.
레벨이 변경되면 GameMode
, GameState
둘 다 새롭게 생성되므로 전 레벨에서 사용하던 전역적인 데이터인 Score
가 저장되지 않는다.
때문에 레벨의 생명 주기와 독립적인 GameInstance
에서 Score
를 가지도록 변경했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UCLASS()
class OPENWORLDRPG_API UC_GameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UC_GameInstance();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GameData")
int32 TotalScore;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GameData")
int32 CurrentLevelIndex;
UFUNCTION(BlueprintCallable, Category = "GameData")
void AddToScore(int32 Amount);
};
아직은 현재 레벨 인덱스와 점수만 저장한다.
1
2
3
4
5
6
auto GameInstance = Cast<UC_GameInstance>(GetGameInstance());
if (GameInstance != nullptr)
{
CurrentLevelIndex = GameInstance->CurrentLevelIndex;
}
사용법은 위와 같다.
또한 에디터에서 세팅 해줘야한다.
프로젝트 세팅 -> 맵 & 모드
하단에 GameInstance
에서 내가 만든 게임 인스턴스로 세팅하면 된다.
UI
UI는 유저 인터페이스의 위젯 블루프린트로 생성했다.
메인 게임의 UI와 첫 시작 화면의 UI를 생성해줬다.
저 텍스트는 바인딩이 가능하기에 실시간으로 데이터 변경이 가능하다.
텍스트 데이터 수정
2가지 방법에 대해 배웠다.
에디터에서 바인딩
텍스트에 데이터 바인딩을 하려면 디테일 창에서 콘텐츠->텍스트 부분을 찾아주면 된다.
텍스트 입력 칸 오른쪽에 사슬 모양 이미지를 누르면 바인딩 생성을 할 수 있다.
넘어가면 이 화면이다.
여기서 데이터를 가져와 텍스트에 넣어주면 된다.
C++에서 텍스트 변경
에디터에서 바인딩하는 방법은 간단하지만 매 틱마다 업데이트 해야한다는 단점이 있다.
때문에 성능을 개선하기 위해 C++에서 수정 해주기도 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
AC_PlayerController* SpartaPlayerController = Cast<AC_PlayerController>(PlayerController);
{
if (UUserWidget* HUDWidget = SpartaPlayerController->GetHUDWidget())
{
if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
{
float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time: %.1f"), RemainingTime)));
}
if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
{
if (UGameInstance* GameInstance = GetGameInstance())
{
UC_GameInstance* C_GameInstance = Cast<UC_GameInstance>(GameInstance);
if (C_GameInstance)
{
ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score: %d"), C_GameInstance->TotalScore)));
}
}
}
if (UTextBlock* LevelIndexText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
{
LevelIndexText->SetText(FText::FromString(FString::Printf(TEXT("Level: %d"), CurrentLevelIndex + 1)));
}
}
}
}
UI를 뷰포트에 적용하는 방법
UI 는 플레이어 컨트롤러에서 관리한다고 한다.
1
2
3
4
5
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HUD")
TSubclassOf<UUserWidget> MainMenuWidgetClass;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "HUD")
UUserWidget* MainMenuWidget;
여기서 사용하는 변수들
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (MainMenuWidgetClass)
{
MainMenuWidget = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
if (MainMenuWidget)
{
MainMenuWidget->AddToViewport();
bShowMouseCursor = true;
SetInputMode(FInputModeUIOnly());
}
if (UTextBlock* ButtonText = Cast<UTextBlock>(MainMenuWidget->GetWidgetFromName(TEXT("StartButton"))))
{
if (bIsRestart)
{
ButtonText->SetText(FText::FromString(TEXT("Restart")));
}
else
{
ButtonText->SetText(FText::FromString(TEXT("Start")));
}
}
}
이렇게 구현해줬다.
저 AddToViewport
함수가 뷰포트에 등록되는 부분이다.
이제 TSubclassOf
에 사용하는 클래스들을 넣어주면 된다.