플러그인 UI - 2
개발 일지2024. 12. 2. 22:50
Custom Delegate 제작
- Subsystem 플러그인에서 커스텀 델리게이트를 만들어 플러그인을 사용하는 user widget에 콜백 함수를 바인딩한다.
- 이 방법으로 MultiplayerSessionSubsystem에서 플러그인을 사용하는 클래스로 연결
- UserWidget -> Plugin -> SessoinInterface 클래스로 이어지는 One-Way dependency 설계를 델리게이트-콜백함수로 연결하며 플러그인의 호환성 유지
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiplayerOnCreateSessionComplete, bool, bWasSuccessful);
// Own Custom delegates for the menu class to bind callbacks
FMultiplayerOnCreateSessionComplete MultiplayerOnCreateSessionComplete;
- 이를위해 MultiplayerSessionSubsystem에 다이나믹 델리게이트 선언
- UserWidget 클래스인 MenuUI의 MenuSetup 함수에 콜백함수 바인딩
if (MultiplayerSessionsSubsystem) {
MultiplayerSessionsSubsystem->MultiplayerOnCreateSessionComplete.AddDynamic(this, &ThisClass::OnCreateSession);
}
- UserWidget 클래스에 OnCreateSession 함수 구현
void UMenuUI::OnCreateSession(bool bWasSuccessful)
{
if (bWasSuccessful) {
if (GEngine) {
GEngine->AddOnScreenDebugMessage(
-1,
15.f,
FColor::Yellow,
FString(TEXT("Session created successfully!"))
);
}
}
}
- MultiplayerSessionSubsystem에 콜백함수 구현. 여기서 델리게이트 리스트 제거 및 MenuUI의 델리게이트 broadcast
void UMultiplayerSessionsSubsystem::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (SessionInterface) {
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);
}
MultiplayerOnCreateSessionComplete.Broadcast(bWasSuccessful);
}
- 그리고 HostButtonClicked 함수에 있던 ServerTravel 로직을 OnCreateSession 콜백 함수로 이동
- 버튼을 누르자마자 바로 레벨이동이 발생하지 않고 세션이 생성될때까지 기다릴 수 있도록 하기위함
void UMenuUI::OnCreateSession(bool bWasSuccessful)
{
if (bWasSuccessful) {
if (GEngine) {
GEngine->AddOnScreenDebugMessage(
-1,
15.f,
FColor::Yellow,
FString(TEXT("Session created successfully!"))
);
}
UWorld* World = GetWorld();
if (World) {
World->ServerTravel("/Game/FirstPerson/Maps/Lobby?listen");
}
}
}
Find & JoinSession 구현
- Character 클래스에서 했던것과 크게 다르지는 않다. 델리게이트와 콜백함수를 사용해 의존성을 줄인 방식으로 구현된다.
- 먼저 MultiplayerSessionsSubsystem에서 SessionSearch 관련 로직을 짠다.
- 마지막에 MenuUI 클래스에서 델리게이트를 통해 찾은 함수와 성공 여부를 보낼 수 있도록 한다.
void UMultiplayerSessionsSubsystem::FindSessions(int32 MaxSearchResults)
{
if (!SessionInterface.IsValid()) {
return;
}
FindSessionsCompleteDelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);
LastSessionSearch = MakeShareable(new FOnlineSessionSearch());
LastSessionSearch->MaxSearchResults = MaxSearchResults;
LastSessionSearch->bIsLanQuery = IOnlineSubsystem::Get()->GetSubsystemName() == "NULL" ? true : false;
LastSessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
if (!SessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), LastSessionSearch.ToSharedRef())) {
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);
MultiplayerOnFindSessionsComplete.Broadcast(TArray<FOnlineSessionSearchResult>(), false);
}
}
void UMultiplayerSessionsSubsystem::OnFindSessionsComplete(bool bWasSuccessful)
{
if (SessionInterface) {
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);
}
if (LastSessionSearch->SearchResults.Num() <= 0) {
// Fail to find any Sessions
MultiplayerOnFindSessionsComplete.Broadcast(TArray<FOnlineSessionSearchResult>(), false);
return;
}
MultiplayerOnFindSessionsComplete.Broadcast(LastSessionSearch->SearchResults, bWasSuccessful);
}
- 그러면 MenuUI에서 받은 결과를 바탕으로 반복문을 돌려 결과를 확인하고 우리가 세팅한 조건과 일치하다면 MultiplayerSessionsSubsystem의 JoinSession을 다시 호출한다.
void UMenuUI::OnFindSessions(const TArray<FOnlineSessionSearchResult>& SessionResults, bool bWasSuccessful)
{
if (MultiplayerSessionsSubsystem == nullptr) {
return;
}
for (auto Result : SessionResults) {
FString SettingsValue;
Result.Session.SessionSettings.Get(FName("MatchType"), SettingsValue);
if (SettingsValue == MatchType) {
MultiplayerSessionsSubsystem->JoinSession(Result);
return;
}
}
}
- 그러면 다시 MultiplayerSessionsSubsystem의 JoinSession에서 MenuUI의 JoinSession으로 넘어가도록 호출한다.
void UMultiplayerSessionsSubsystem::JoinSession(const FOnlineSessionSearchResult& SessionResult)
{
if (!SessionInterface.IsValid()) {
MultiplayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError);
return;
}
JoinSessionCompleteDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate);
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
if (!SessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionResult)) {
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);
MultiplayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError);
}
}
void UMultiplayerSessionsSubsystem::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionInterface) {
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);
}
MultiplayerOnJoinSessionComplete.Broadcast(Result);
}
- 최종적으로 MenuUI는 EOnJoinSessionCompleteResult 타입의 결과값을 받아 ClientTravel에 필요한 값들을 저장하고 호출하여 세션에 유저가 참가하도록 한다.
void UMenuUI::OnJoinSession(EOnJoinSessionCompleteResult::Type Result)
{
IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
if (Subsystem) {
IOnlineSessionPtr SessionInterface = Subsystem->GetSessionInterface();
if (SessionInterface.IsValid()) {
FString Address;
SessionInterface->GetResolvedConnectString(NAME_GameSession, Address);
APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController();
if (PlayerController) {
PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute);
}
}
}
}
이렇게 보면 과정이 복잡한것 같지만, 플러그인을 통해 대부분의 로직이 돌아가면서 여러 프로젝트에서 돌려가며 쓸 수 있는 의존성이 최소화된 구조가 완성되었다.
'개발 일지' 카테고리의 다른 글
플러그인 다듬기 (0) | 2024.12.03 |
---|---|
Tracking Player (0) | 2024.12.03 |
플러그인 UI - 1 (0) | 2024.12.01 |
플러그인 등록 (0) | 2024.11.30 |
Join Session (0) | 2024.11.29 |
댓글()