• LoadingScreenWidget
• 타입: FSoftClassPath
• 설명: 로딩 스크린에 표시할 위젯 클래스(블루프린트 또는 C++ UserWidget)를 지정합니다.
이 위젯이 실제 로딩 화면에 표시됩니다.
• LoadingScreenZOrder
• 타입: int32
• 설명: 로딩 스크린 위젯이 뷰포트에 추가될 때의 Z-Order(레이어 우선순위)입니다.
값이 높을수록 다른 UI 위에 표시됩니다. 기본값은 10000입니다.
• HoldLoadingScreenAdditionalSecs
• 타입: float
• 설명: 로딩이 끝난 후에도 추가로 로딩 스크린을 유지할 시간(초)입니다.
텍스처 스트리밍 등으로 인한 블러 현상을 방지하기 위해 사용합니다.
• LoadingScreenHeartbeatHangDuration
• 타입: float
• 설명: 이 값(초)보다 더 오래 로딩 스크린이 유지되면 "로딩이 멈췄다"고 간주합니다.
0이면 비활성화됩니다.
• LogLoadingScreenHeartbeatInterval
• 타입: float
• 설명: 로딩 스크린이 유지되는 동안, 이 간격(초)마다 "무엇이 로딩 스크린을 붙잡고 있는지" 로그를 남깁니다.
0이면 비활성화됩니다.
• LogLoadingScreenReasonEveryFrame
• 타입: bool
• 설명: true로 설정하면, 매 프레임마다 로딩 스크린이 표시/숨겨지는 이유를 로그로 출력합니다.
• ForceLoadingScreenVisible
• 타입: bool
• 설명: true로 설정하면, 항상 로딩 스크린을 강제로 표시합니다.
디버깅 용도로 사용합니다.
• HoldLoadingScreenAdditionalSecsEvenInEditor
• 타입: bool
• 설명: true로 설정하면, 에디터 환경에서도 HoldLoadingScreenAdditionalSecs 지연을 적용합니다.
로딩 스크린을 반복적으로 테스트할 때 유용합니다.
• ForceTickLoadingScreenEvenInEditor
• 타입: bool
• 설명: true로 설정하면, 에디터 환경에서도 로딩 스크린의 틱(Tick)을 강제로 한번 호출하여 로딩 스크린이 즉시 표시 되도록 합니다. 기본값은 true입니다.
“지금 로딩이 필요한가?”를 판단하는 주체가 ILoadingProcessInterface(로딩 프로세서) 구현체
로딩 매니저가 매 프레임 이 구현체들을 체크해서 필요하면 지정된 로딩 위젯을 띄웁니다.
아래 코드 구문처럼 인터페이스를 상속받고 있는 리스트를 체크 해서 필요 여부 체크 GameState or Gamestate안에 구성하고있는 각 모듈의 컴포넌트(UGameStateComponent) or 외부에서 등록된 것들
// Ask the game state if it needs a loading screen
if (ILoadingProcessInterface::ShouldShowLoadingScreen(GameState, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
// Ask any game state components if they need a loading screen
for (UActorComponent* TestComponent : GameState->GetComponents())
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(TestComponent, /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
// Ask any of the external loading processors that may have been registered. These might be actors or components
// that were registered by game code to tell us to keep the loading screen up while perhaps something finishes
// streaming in.
for (const TWeakInterfacePtr<ILoadingProcessInterface>& Processor : ExternalLoadingProcessors)
{
if (ILoadingProcessInterface::ShouldShowLoadingScreen(Processor.GetObject(), /*out*/ DebugReasonForShowingOrHidingLoadingScreen))
{
return true;
}
}
- ‘어떤 배경을 쓸지’ 결정하는 데이터 소스 준비
- 라이라처럼 모드/맵 선택 데이터(Primary Asset)를 만들고, TSoftClassPtr<UUserWidget>로 로딩 배경 위젯을 지정.
- 레벨 전환 트리거에서 서브시스템에 저장
- 트래블 직전에 LoadingScreenSubsystem->SetLoadingWidgetClass(ChosenClass) 같은 식으로 “교체할 클래스” 전달.
- Common Loading Screen 세팅에서 Host 위젯만 지정
- Host는 항상 동일한 틀, “배경”은 Construct에서 교체되므로 Host만 플러그인 세팅에 등록.
- Host 위젯 Construct에서 Subsystem 조회 → SetContent
- 서브시스템에서 클래스를 꺼내 새 배경 위젯 생성 → 네임드 슬롯/패널에 SetContent.
- 해제 조건 신호 정리
- 라이라처럼 Experience 시스템을 쓰지 않는다면, 맵 로드 완료 + 게임플레이 준비 완료를 알리는 별도 이벤트/플래그를 만들어 ShouldShowLoadingScreen() 로직(혹은 등가의 판정)에서 참고하도록 해야 “무한 로딩”을 피합니다. 커뮤니티에서도 라이라 신호를 그대로 기대해 붙였다가 로딩이 안 사라지는 이슈가 보고됩니다.
이뿐만이 아니라 조건 체크 종류에는 맵 로딩중인지, 월드가 시작이 아직 안됬는지. 로컬플레이어의 플레이어 컨트롤러에 해당 인터페이스가 있다면 로컬플레이어마다 체크를 한다던지 체크를 하는 조건이 다양하게 설정되어있다.
만약 멀티 게임이라면 지금 드는 생각은 AGameState가 모든 PlayerState가 Ready인지 집계하고, ILoadingProcessInterface 를 구현하여 “모두 Ready 될 때까지 true(계속 보여 줘)”를 반환하게 하면, 로딩 화면이 자연스럽게 전원 준비 완료까지 유지가 될거 같다.
RPC를 이용해서 각 클라에서 Ready상태값을 서버에 요청해서 갱신해주고 서버에서 모든 플레이어들의 Ready상태값을 확인한다 식의?...
아래 코드 느낌처럼.. 이거 재대로 돌아가는 코드 아니다.. 그냥 생각나는대로 정리만 해본것
//PlayerState
.h
UCLASS()
class AMyPlayerState : public APlayerState
{
GENERATED_BODY()
public:
AMyPlayerState();
UPROPERTY(ReplicatedUsing=OnRep_IsReady, BlueprintReadOnly, Category="Ready")
bool bIsReady = false;
}
.cpp
AMyPlayerState::AMyPlayerState()
{
bReplicates = true;
}
void AMyPlayerState::SetReady(bool bInReady)
{
if (!HasAuthority())
{
return; // 서버에서만 상태 확정
}
const bool bChanged = (bIsReady != bInReady);
bIsReady = bInReady;
if (bChanged)
{
OnRep_IsReady();
// 서버에서 집계 갱신
if (AGameStateBase* GS = GetWorld()->GetGameState())
{
if (AMyGameState* MGS = Cast<AMyGameState>(GS))
{
MGS->RecalcAllPlayersReady();
}
}
}
}
void AMyPlayerState::OnRep_IsReady()
{
// 클라에서 UI 갱신이 필요하면 여기서 브로드캐스트 등
}
void AMyPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
Super::GetLifetimeReplicatedProps(Out);
DOREPLIFETIME(AMyPlayerState, bIsReady);
}
//PlayerController
.h
UCLASS()
class AMyPlayerController : public APlayerController
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="Ready")
void MarkClientReady(bool bReady = true);
protected:
UFUNCTION(Server, Reliable)
void ServerSetClientReady(bool bReady);
void ServerSetClientReady_Implementation(bool bReady);
};
.cpp
void AMyPlayerController::MarkClientReady(bool bReady)
{
if (IsLocalController())
{
ServerSetClientReady(bReady);
}
}
void AMyPlayerController::ServerSetClientReady_Implementation(bool bReady)
{
if (AMyPlayerState* PS = GetPlayerState<AMyPlayerState>())
{
PS->SetReady(bReady); // 서버에서 복제 + 집계 트리거
}
}
//GameState
.h
UCLASS()
class AMyGameState : public AGameState, public ILoadingProcessInterface
{
GENERATED_BODY()
public:
AMyGameState();
// 집계 결과(복제): 클라에서도 UI가 참고할 수 있게끔
UPROPERTY(ReplicatedUsing=OnRep_AllPlayersReady, BlueprintReadOnly, Category="Ready")
bool bAllPlayersReady = false;
// (선택) 기대 인원. 매치메이킹/세션 정보에서 세팅하면
// "예상 인원 다 찰 때까지"도 함께 기다릴 수 있음.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Ready")
int32 ExpectedNumPlayers = 0; // 0이면 무시
// 서버에서 호출: PlayerState 추가/변경/제거 시 집계
void RecalcAllPlayersReady();
// AGameState overrides
virtual void AddPlayerState(APlayerState* PlayerState) override;
virtual void RemovePlayerState(APlayerState* PlayerState) override;
// ILoadingProcessInterface
virtual bool ShouldShowLoadingScreen(FString& OutReason) const override;
protected:
UFUNCTION()
void OnRep_AllPlayersReady();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const override;
};
.cpp
AMyGameState::AMyGameState()
{
bReplicates = true;
}
void AMyGameState::AddPlayerState(APlayerState* PlayerState)
{
Super::AddPlayerState(PlayerState);
if (HasAuthority())
{
RecalcAllPlayersReady();
}
}
void AMyGameState::RemovePlayerState(APlayerState* PlayerState)
{
Super::RemovePlayerState(PlayerState);
if (HasAuthority())
{
RecalcAllPlayersReady();
}
}
void AMyGameState::RecalcAllPlayersReady()
{
if (!HasAuthority())
{
return;
}
const int32 NumPlayers = PlayerArray.Num();
// 1) 인원수 조건: 기대 인원이 있으면 그 수만큼 모일 때까지 대기
if (ExpectedNumPlayers > 0 && NumPlayers < ExpectedNumPlayers)
{
if (bAllPlayersReady != false)
{
bAllPlayersReady = false;
OnRep_AllPlayersReady();
}
return;
}
// 2) 각 PlayerState의 Ready 플래그 확인
bool bEveryoneReady = (NumPlayers > 0); // 0명이면 false로(원하면 정책 조정)
for (APlayerState* PS : PlayerArray)
{
const AMyPlayerState* MyPS = Cast<AMyPlayerState>(PS);
if (!MyPS || !MyPS->bIsReady)
{
bEveryoneReady = false;
break;
}
}
if (bAllPlayersReady != bEveryoneReady)
{
bAllPlayersReady = bEveryoneReady;
OnRep_AllPlayersReady();
}
}
bool AMyGameState::ShouldShowLoadingScreen(FString& OutReason) const
{
// 로딩 화면 매니저가 매 프레임 호출
if (!bAllPlayersReady)
{
OutReason = TEXT("Waiting for all players to be ready");
return true; // 아직 붙잡아 둔다
}
return false; // 모두 준비됨 → 해제
}
void AMyGameState::OnRep_AllPlayersReady()
{
// 클라 UI에 브로드캐스트하거나 로그 남기기 등
}
void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
Super::GetLifetimeReplicatedProps(Out);
DOREPLIFETIME(AMyGameState, bAllPlayersReady);
DOREPLIFETIME(AMyGameState, ExpectedNumPlayers);
}
만약 로딩화면이 떠야할 시점 전에 외부에서 로딩화면의 조건을 설정해야할 경우가 있다면 ULoadingProcessTask::CreateLoadingScreenProcessTask을 사용해서 외부 task를 등록하면 된다.
CreateLoadingScreenProcessTask()로 만드는 객체(ULoadingProcessTask)가 이미 ILoadingProcessInterface를 구현하고 있어서, 함수를 호출하는 쪽 클래스는 인터페이스를 구현할 필요가 없다. 태스크를 만들어 두고, 일이 끝났을 때 Unregister()만 호출하면 된다.
인자로 UObject* 타입의 WorldContextObject자리에 this가 들어가고 있는건데 당연하게도 UWorld(월드) 객체를 얻으려 하는거기때문에 대부분은 this면 될것이다.
ULoadingProcessTask* LoadTask =
ULoadingProcessTask::CreateLoadingScreenProcessTask(this, TEXT("Travel to Arena"));
// 2) 실제 작업 시작 (예: 맵 전환)
UGameplayStatics::OpenLevel(this, FName(TEXT("L_Arena")));
// 3) 작업이 정말 끝났을 때 Unregister
if (LoadTask)
{
LoadTask->Unregister(); // <- 해제 신호 (이 호출 전까지는 로딩 화면 유지)
}
'Unreal' 카테고리의 다른 글
| 플레이어 컨트롤러의 입력 키 복구 작업중 발생한 문제 정리. (0) | 2026.08.09 |
|---|---|
| 언리얼 TArray, Delegate 소소한 팁 정리 (0) | 2026.08.09 |
| (브러시 모드) 펜툴을 이용한 지오메트리 생성시 충돌 주의점 (0) | 2024.12.10 |
| 언리얼 리플렉션 TFieldIterator (0) | 2024.06.22 |
| 언리얼 엔진에서 제공하는 싱글톤 클래스 종류 (0) | 2024.02.13 |

