Post

UE - 레벨 이동, UI

UnrealEngine

UE - 레벨 이동, UI

GameMode, GameState 버그

새로 만든 GameState를 적용하면 입력이 제대로 작동하지 않는 것을 확인했다.

GameStateBase를 적용하면 정상 작동하길래 GameState로도 테스트 해보니, GameState에서도 입력을 받지 못하는 것을 확인했다.

찾아보니 언리얼 4.14버전 이후로 GameModeGameModeBase로 나뉘면서 GameModeMatchStateMachine이라는 기능이 생겼다.
또한 GameStateMatchStateMachine을 지원하게 되었고, 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를 생성해줬다.

내부는 이렇게 생겼다.

여기서 한 화면의 UI를 만들어준다.

팔레트에서 원하는 것들을 가져와 꾸미면 된다.

이렇게 계층 구조로 넣어주기도 가능하다.

메인 화면은 이렇게 3개의 텍스트를 넣어줬다.

저 텍스트는 바인딩이 가능하기에 실시간으로 데이터 변경이 가능하다.

텍스트 데이터 수정

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 에 사용하는 클래스들을 넣어주면 된다.

This post is licensed under CC BY 4.0 by the author.