[BeginPlay] ─▶ Controller가 Title 위젯 생성 & 마우스 커서 보임
└▶ CineCameraActor로 뷰 전환
[Player 입력]
- Start 버튼 클릭 or Enter 키 입력
└▶ Controller에 닉네임 전달
├─ GameInstance에 저장
├─ (서버 권한 있으면) ServerTravel("WaitingLevel")
└─ (클라 단독 실행/테스트) OpenLevel("WaitingLevel")
ATitleGameModeATitlePlayerControllerUTitleLevelWidget (닉네임 입력/Start & Enter키)UDCGameInstance (닉네임 보관해서 레벨 전환 후에도 유지)// DCGameInstance.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "DCGameInstance.generated.h"
/**
*
*/
UCLASS()
class DC_API UDCGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
// 김여울
// 닉네임 저장/조회
UFUNCTION(BlueprintCallable, Category = "Profile")
void SetNickname(const FString& InNickname)
{
Nickname = InNickname;
}
UFUNCTION(BlueprintCallable, Category = "Profile")
FString GetNickname() const
{
return Nickname;
}
private:
// 클라 로컬에 보관 (레벨 넘어가도 유지)
UPROPERTY()
FString Nickname;
};
블루프린트 클래스로 만들고
Project Settings → Maps & Modes → Game Instance Class 를 BP_DCGameInstance로 지정하기
큰 로직 없으니까 GameModeBase로 만들기
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "TitleGameMode.generated.h"
/**
* TitleLevel의 GameModeBase
* 김여울
*/
UCLASS()
class DC_API ATitleGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
ATitleGameMode();
protected:
virtual void BeginPlay() override;
};
#include "TitleLevelGameMode.h"
ATitleLevelGameMode::ATitleLevelGameMode()
{
// 필요 시 DefaultPawnClass = nullptr; 등으로 입력 막기 가능
}
void ATitleLevelGameMode::BeginPlay()
{
Super::BeginPlay();
// 규칙/세팅이 있으면 여기에. (현재는 컨트롤러에서 UI 처리)
}
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "TitlePlayerController.generated.h"
/**
* TitleLevel의 PlayerController
* 김여울
*/
UCLASS()
class DC_API ATitlePlayerController : public APlayerController
{
GENERATED_BODY()
public:
ATitlePlayerController();
protected:
virtual void BeginPlay() override;
public:
// 위젯에서 호출 : 닉네임으로 대기실 입장 요청
UFUNCTION(BlueprintCallable, Category = "Flow")
void RequestEnterWaitingLevel(const FString& InNickname);
protected:
// 서버에서 맵 이동 수행(전용 서버/리스닝 서버 대응)
UFUNCTION(Server, Reliable)
void ServerGoToWaitingLevel(const FString& InNickname);
void ServerGoToWaitingLevel_Implementation(const FString& InNickname);
// 테스트/싱글 실행 대비: 클라에서 로컬로 열어줌
void ClientOpenWaitingLevelLocal();
// UI 스폰
void ShowTitleWidget();
private:
// 위젯 블루프린트 할당용
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<class UUserWidget> TitleWidgetClass;
// 생성된 위젯 참조
UPROPERTY()
class UTitleLevelWidget* TitleWidgetRef;
};
#include "Player/TitlePlayerController.h"
#include "Blueprint/UserWidget.h"
#include "UI/TitleLevelWidget.h"
#include "Kismet/GameplayStatics.h"
#include "CineCameraActor.h"
#include "Game/DCGameInstance.h"
#include "Game/DCGameInstance.h"
ATitlePlayerController::ATitlePlayerController()
{
static ConstructorHelpers::FClassFinder<UUserWidget> WBP(TEXT("/Game/DC/UI/OutGame/WBP_TitleLevel.WBP_TitleLevel_C"));
if (WBP.Succeeded())
{
TitleWidgetClass = WBP.Class;
}
bShowMouseCursor = true;
}
void ATitlePlayerController::BeginPlay()
{
Super::BeginPlay();
// 타이틀 UI 표시
ShowTitleWidget();
// 시네 카메라로 뷰 전환 (레벨에 1개 배치)
TArray<AActor*> Cameras;
UGameplayStatics::GetAllActorsOfClass(this, ACineCameraActor::StaticClass(), Cameras);
if (Cameras.Num() > 0)
{
SetViewTargetWithBlend(Cameras[0], 0.f);
}
// 입력모드 : Game & UI
FInputModeGameAndUI InputMode;
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
SetInputMode(InputMode);
}
void ATitlePlayerController::ShowTitleWidget()
{
if (!TitleWidgetClass)
{
UE_LOG(LogTemp, Warning, TEXT("TitleWidgetClass 미지정: 에디터에서 ATitlePlayerController의 TitleWidgetClass 설정"));
return;
}
if (!TitleWidgetRef)
{
UUserWidget* Created = CreateWidget<UUserWidget>(this, TitleWidgetClass);
TitleWidgetRef = Cast<UTitleLevelWidget>(Created);
if (TitleWidgetRef)
{
TitleWidgetRef->AddToViewport();
UE_LOG(LogTemp, Log, TEXT("Title Widget Added To Viewport"));
}
}
}
void ATitlePlayerController::RequestEnterWaitingLevel(const FString& InNickname)
{
UE_LOG(LogTemp, Warning, TEXT("▶ RequestEnterWaitingLevel called: %s"), *InNickname);
// 닉네임 저장 (레벨 넘어가도 유지)
if (UDCGameInstance* GI = GetGameInstance<UDCGameInstance>())
{
GI->SetNickname(InNickname);
}
// 전용/리스닝 서버라면 서버가 맵 이동
if (HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("▶ Server travel by HasAuthority"));
ServerGoToWaitingLevel(InNickname); // 서버 자신 호출 (Authority)
return;
}
// 클라에서 서버에서 RPC 요청
ServerGoToWaitingLevel(InNickname);
// 전용 서버에 아직 연결 안 된 로컬 테스트 환경 대비
// 에디터 플레이(클라 단독)에서는 로컬 OpenLevel로도 열어줌
if (!IsNetMode(NM_DedicatedServer))
{
UE_LOG(LogTemp, Warning, TEXT("▶ Local ClientOpenWaitingLevel"));
ClientOpenWaitingLevelLocal();
}
}
void ATitlePlayerController::ServerGoToWaitingLevel_Implementation(const FString& InNickname)
{
UE_LOG(LogTemp, Warning, TEXT("▶ ServerGoToWaitingLevel_Implementation: %s"), *InNickname);
// 서버에서만 실행되도록 보장
if (!HasAuthority())
{
return; // 클라에서는 실행 안 함
}
UWorld* World = GetWorld();
if (!World)
{
return;
}
// WaitingLevel로 맵 이동
// Dedicated Server일 경우: 클라이언트는 이미 IP:Port로 접속해 있으므로
// ServerTravel만 호출하면 접속 클라들이 자동으로 맵 이동
World->ServerTravel(TEXT("/Game/DC/Maps/WaitingLevel.WaitingLevel"), true);
}
void ATitlePlayerController::ClientOpenWaitingLevelLocal()
{
// 싱글/PIE 단독 실행시 편의용
UGameplayStatics::OpenLevel(this, FName("WaitingLevel"));
}
Build.cs 파일에 “CinematicCamera”추가
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/EditableTextBox.h"
#include "Components/Button.h"
#include "TitleLevelWidget.generated.h"
/**
* 닉네임 입력 & Start/Enter 키 바인딩
* 김여울
*/
UCLASS()
class DC_API UTitleLevelWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
// 위젯 구성 요소
UPROPERTY(meta=(BindWidget), BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
UEditableTextBox* EditableTextBox_Nickname;
UPROPERTY(meta=(BindWidget), BlueprintReadOnly, meta=(AllowPrivateAccess="true"))
UButton* StartButton;
protected:
// 버튼 클릭
UFUNCTION()
void OnStartClicked();
// Enter Key - 텍스트 커밋
UFUNCTION()
void OnNameCommitted(const FText& Text, ETextCommit::Type CommitType);
// 컨트롤러 헬퍼
class ATitlePlayerController* GetTitlePC() const;
};
#include "UI/TitleLevelWidget.h"
#include "Components/EditableTextBox.h"
#include "Components/Button.h"
#include "Player/TitlePlayerController.h"
void UTitleLevelWidget::NativeConstruct()
{
Super::NativeConstruct();
// 버튼 클릭 바인딩
if (StartButton)
{
StartButton->OnClicked.AddDynamic(this, &UTitleLevelWidget::OnStartClicked);
}
// Enter Key (텍스트 커밋) 바인딩
if (EditableTextBox_Nickname)
{
// 입력 전에는 비워두고
EditableTextBox_Nickname->SetText(FText::GetEmpty());
EditableTextBox_Nickname->OnTextCommitted.AddDynamic(this, &UTitleLevelWidget::OnNameCommitted);
EditableTextBox_Nickname->SetKeyboardFocus();
}
}
void UTitleLevelWidget::OnStartClicked()
{
FString Nickname = EditableTextBox_Nickname
? EditableTextBox_Nickname->GetText().ToString()
: TEXT("");
FString FinalName = Nickname.IsEmpty() ? TEXT("Player") : Nickname;
if (ATitlePlayerController* PC = GetTitlePC())
{
UE_LOG(LogTemp, Warning, TEXT("▶ RequestEnterWaitingLevel with %s"), *Nickname);
PC->RequestEnterWaitingLevel(Nickname);
}
}
void UTitleLevelWidget::OnNameCommitted(const FText& Text, ETextCommit::Type CommitType)
{
if (CommitType == ETextCommit::OnEnter)
{
OnStartClicked(); // 엔터 == 시작
}
}
class ATitlePlayerController* UTitleLevelWidget::GetTitlePC() const
{
return GetOwningPlayer<ATitlePlayerController>();
}
이후 WaitingLevel에서는 최소인원/Ready버튼 → 모두 Ready 시 게임 시작으로 이어가기